diff --git a/package-lock.json b/package-lock.json index 0351350..d9cad5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "glob": "^10.4.2", "is-unicode-supported": "^2.0.0", "merge-strategies": "^0.3.0", + "nanoid": "^5.0.7", "ora": "^8.0.1", "pipettes": "^0.1.3", "prefix-stream": "^1.0.1", @@ -5508,6 +5509,25 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/@riseup/utils/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -18608,10 +18628,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", + "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", "funding": [ { "type": "github", @@ -18620,10 +18639,10 @@ ], "license": "MIT", "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, "node_modules/natural-compare": { @@ -20469,6 +20488,26 @@ "node": ">=4" } }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/prefix-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prefix-stream/-/prefix-stream-1.0.1.tgz", diff --git a/package.json b/package.json index a0c4fc6..7ec7bf8 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "glob": "^10.4.2", "is-unicode-supported": "^2.0.0", "merge-strategies": "^0.3.0", + "nanoid": "^5.0.7", "ora": "^8.0.1", "pipettes": "^0.1.3", "prefix-stream": "^1.0.1", diff --git a/src/tasks/filesystem/tmp.ts b/src/tasks/filesystem/tmp.ts index 7231d58..c175916 100644 --- a/src/tasks/filesystem/tmp.ts +++ b/src/tasks/filesystem/tmp.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import os from 'node:os'; import type { Empty, MaybePromise, Serial } from 'type-core'; -import { shallow } from 'merge-strategies'; +import { nanoid } from 'nanoid'; import fs from 'fs-extra'; import type { Context, Task } from '../../definitions'; @@ -12,57 +12,46 @@ import { isCancelled, onCancel } from '../../utils/cancellation'; import { create } from '../creation/create'; import { series } from '../aggregate/series'; import { finalize } from '../exception/finalize'; -import { log } from '../stdio/log'; import { write } from './write'; +import { mkdir } from './mkdir'; -export interface TmpOptions { - /** Extension to use for the temporal file */ - ext?: string | null; +export interface TmpFile { + name: string; + content: Buffer | Serial.Type; } /** - * Creates a temporal file with the result of `content`; - * when it is an object, it will be stringified as JSON. - * Passes the temporal file path to a `callback`, which - * can return a `Task`. + * Creates a temporal `directory` and places there + * all `TmpFile`s returned by the `files` callback. + * When `TmpFile.content` is an object, it will be + * stringified as JSON. + * Passes the temporal `directory` to a `callback`, + * which can return a `Task`. * @returns Task */ export function tmp( - content: (context: Context) => MaybePromise, - callback: (path: string) => MaybePromise, - options?: TmpOptions + files: (context: Context) => MaybePromise, + callback: (directory: string) => MaybePromise ): Task.Async { return create(async (ctx) => { - const opts = shallow({ ext: null as string | null }, options || undefined); - - const buffer = await content(ctx); - if (isCancelled(ctx)) return; - - const tmpdir = path.resolve(os.tmpdir(), constants.name); - await fs.ensureDir(tmpdir); + const response = await files(ctx); if (isCancelled(ctx)) return; - const filename = - 'tmp-' + - String(Math.random()).substring(2) + - (opts.ext ? '.' + opts.ext.replace(/^\./, '') : ''); - const filepath = path.resolve(tmpdir, filename); - - const teardown = (): void => { - try { - fs.unlinkSync(filepath); - } catch (err: any) { - if (err.code !== 'ENOENT') throw err; - } - }; - + const tmpdir = path.join(os.tmpdir(), constants.name, 'tmp-' + nanoid()); + const teardown = (): void => fs.removeSync(tmpdir); const cleanup = onCancel(ctx, () => teardown()); return finalize( series( - log('debug', 'Tmp:', filename), - write(filepath, buffer, { exists: 'error' }), - create(() => callback(filepath)) + mkdir(tmpdir, { ensure: true }), + ...(Array.isArray(response) ? response : [response]) + .filter((file): file is TmpFile => Boolean(file)) + .map((file) => { + return write(path.join(tmpdir, file.name), file.content, { + exists: 'error' + }); + }), + create(() => callback(tmpdir)) ), () => { cleanup();