diff --git a/index.d.ts b/index.d.ts index 180601c..39b0ae9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -35,59 +35,119 @@ declare namespace tempy { */ readonly prefix?: string; }; + + /** + The temporary path created by the function. Can be asynchronous. + */ + type TaskCallback = (tempPath: string) => Promise | void; } declare const tempy: { - /** - Get a temporary file path you can write to. + file: { + /** + The `callback` resolves with a temporary file path you can write to. The file is automatically cleaned up after the callback is executed. - @example - ``` - import tempy = require('tempy'); + @returns A promise that resolves after the callback is executed and the file is cleaned up. - tempy.file(); - //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/4f504b9edb5ba0e89451617bf9f971dd' + @example + ``` + import tempy = require('tempy'); - tempy.file({extension: 'png'}); - //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/a9fb0decd08179eb6cf4691568aa2018.png' + await tempy.file.task(tempFile => { + console.log(tempFile); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/4f504b9edb5ba0e89451617bf9f971dd' + }); + ``` + */ + task: (callback: tempy.TaskCallback, options?: tempy.FileOptions) => Promise; - tempy.file({name: 'unicorn.png'}); - //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/f7f62bfd4e2a05f1589947647ed3f9ec/unicorn.png' + /** + Get a temporary file path you can write to. - tempy.directory(); - //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/2f3d094aec2cb1b93bb0f4cffce5ebd6' - ``` - */ - file: (options?: tempy.FileOptions) => string; + @example + ``` + import tempy = require('tempy'); - /** - Get a temporary directory path. The directory is created for you. + tempy.file(); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/4f504b9edb5ba0e89451617bf9f971dd' - @example - ``` - import tempy = require('tempy'); + tempy.file({extension: 'png'}); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/a9fb0decd08179eb6cf4691568aa2018.png' - tempy.directory(); - //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/2f3d094aec2cb1b93bb0f4cffce5ebd6' + tempy.file({name: 'unicorn.png'}); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/f7f62bfd4e2a05f1589947647ed3f9ec/unicorn.png' - tempy.directory({prefix: 'a'}); - //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/name_3c085674ad31223b9653c88f725d6b41' - ``` - */ - directory: (options?: tempy.DirectoryOptions) => string; + tempy.directory(); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/2f3d094aec2cb1b93bb0f4cffce5ebd6' + ``` + */ + (options?: tempy.FileOptions): string; + }; - /** - Write data to a random temp file. + directory: { + /** + The `callback` resolves with a temporary directory path you can write to. The directory is automatically cleaned up after the callback is executed. - @example - ``` - import tempy = require('tempy'); + @returns A promise that resolves after the callback is executed and the directory is cleaned up. - await tempy.write('🦄'); - //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/2f3d094aec2cb1b93bb0f4cffce5ebd6' - ``` - */ - write: (fileContent: string | Buffer | TypedArray | DataView | NodeJS.ReadableStream, options?: tempy.FileOptions) => Promise; + @example + ``` + import tempy = require('tempy'); + + await tempy.directory.task(tempDirectory => { + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/2f3d094aec2cb1b93bb0f4cffce5ebd6' + }) + ``` + */ + task: (callback: tempy.TaskCallback, options?: tempy.DirectoryOptions) => Promise; + + /** + Get a temporary directory path. The directory is created for you. + + @example + ``` + import tempy = require('tempy'); + + tempy.directory(); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/2f3d094aec2cb1b93bb0f4cffce5ebd6' + + tempy.directory({prefix: 'a'}); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/name_3c085674ad31223b9653c88f725d6b41' + ``` + */ + (options?: tempy.DirectoryOptions): string; + }; + + write: { + /** + Write data to a random temp file. The file is automatically cleaned up after the callback is executed. + + @returns A promise that resolves after the callback is executed and the file is cleaned up. + + @example + ``` + import tempy = require('tempy'); + + await tempy.write.task('🦄', tempFile => { + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/4f504b9edb5ba0e89451617bf9f971dd' + }); + ``` + */ + task: (fileContent: string | Buffer | TypedArray | DataView | NodeJS.ReadableStream, callback: tempy.TaskCallback, options?: tempy.FileOptions) => Promise; + + /** + Write data to a random temp file. + + @example + ``` + import tempy = require('tempy'); + + await tempy.write('🦄'); + //=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/2f3d094aec2cb1b93bb0f4cffce5ebd6' + ``` + */ + (fileContent: string | Buffer | TypedArray | DataView | NodeJS.ReadableStream, options?: tempy.FileOptions): Promise; + }; /** Synchronously write data to a random temp file. diff --git a/index.js b/index.js index 1960918..8dcbf41 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const path = require('path'); const uniqueString = require('unique-string'); const tempDir = require('temp-dir'); const isStream = require('is-stream'); +const del = require('del'); const stream = require('stream'); const {promisify} = require('util'); @@ -14,6 +15,13 @@ const getPath = (prefix = '') => path.join(tempDir, prefix + uniqueString()); const writeStream = async (filePath, data) => pipeline(data, fs.createWriteStream(filePath)); +const createTask = (tempyFunction, {extraArguments = 0} = {}) => async (...arguments_) => { + const [callback, options] = arguments_.slice(extraArguments); + const result = await tempyFunction(...arguments_.slice(0, extraArguments), options); + await callback(result); + await del(result, {force: true}); +}; + module.exports.file = options => { options = { ...options @@ -30,12 +38,16 @@ module.exports.file = options => { return getPath() + (options.extension === undefined || options.extension === null ? '' : '.' + options.extension.replace(/^\./, '')); }; +module.exports.file.task = createTask(module.exports.file); + module.exports.directory = ({prefix = ''} = {}) => { const directory = getPath(prefix); fs.mkdirSync(directory); return directory; }; +module.exports.directory.task = createTask(module.exports.directory); + module.exports.write = async (data, options) => { const filename = module.exports.file(options); const write = isStream(data) ? writeStream : writeFile; @@ -43,6 +55,8 @@ module.exports.write = async (data, options) => { return filename; }; +module.exports.write.task = createTask(module.exports.write, {extraArguments: 1}); + module.exports.writeSync = (data, options) => { const filename = module.exports.file(options); fs.writeFileSync(filename, data); diff --git a/index.test-d.ts b/index.test-d.ts index d2c3b9a..5225377 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -5,6 +5,12 @@ const options: tempy.FileOptions = {}; // eslint-disable-line @typescript-eslint expectType(tempy.directory()); expectType(tempy.directory({prefix: 'name_'})); expectType(tempy.file()); +expectType>(tempy.file.task(temporaryFile => { + expectType(temporaryFile); +})); +expectType>(tempy.directory.task(temporaryDirectory => { + expectType(temporaryDirectory); +})); expectType(tempy.file({extension: 'png'})); expectType(tempy.file({name: 'afile.txt'})); expectError(tempy.file({extension: 'png', name: 'afile.txt'})); @@ -14,6 +20,9 @@ expectType>(tempy.write('unicorn')); expectType>(tempy.write('unicorn', {name: 'pony.png'})); expectType>(tempy.write(process.stdin, {name: 'pony.png'})); expectType>(tempy.write(Buffer.from('pony'), {name: 'pony.png'})); +expectType>(tempy.write.task('', temporaryFile => { + expectType(temporaryFile); +})); expectType(tempy.writeSync('unicorn')); expectType(tempy.writeSync(Buffer.from('unicorn'))); diff --git a/package.json b/package.json index d1fb16c..ff0d2fc 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "unique" ], "dependencies": { + "del": "^5.1.0", "is-stream": "^2.0.0", "temp-dir": "^2.0.0", "type-fest": "^0.16.0", @@ -42,6 +43,8 @@ }, "devDependencies": { "ava": "^2.4.0", + "path-exists": "^4.0.0", + "touch": "^3.1.0", "tsd": "^0.13.1", "xo": "^0.32.1" }, diff --git a/readme.md b/readme.md index ce3b1fc..1de8e8c 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,16 @@ tempy.directory({prefix: 'name'}); Get a temporary file path you can write to. +### tempy.file.task(callback, options?) + +The `callback` resolves with a temporary file path you can write to. The file is automatically cleaned up after the callback is executed. Returns a promise that resolves after the callback is executed and the file is cleaned up. + +#### callback + +Type: `(tempPath: string) => void` + +A callback that is executed with the temp file path. Can be asynchronous. + #### options Type: `object` @@ -57,13 +67,22 @@ Filename. Mutually exclusive with the `extension` option. Get a temporary directory path. The directory is created for you. +### tempy.directory.task(callback, options?) + +The `callback` resolves with a temporary directory path you can write to. The directory is automatically cleaned up after the callback is executed. Returns a promise that resolves after the callback is executed and the directory is cleaned up. + +##### callback + +Type: `(tempPath: string) => void` + +A callback that is executed with the temp directory path. Can be asynchronous. + #### options Type: `Object` ##### prefix - Type: `string` Directory prefix. @@ -76,12 +95,22 @@ Useful for testing by making it easier to identify cache directories that are cr Write data to a random temp file. +### tempy.write.task(fileContent, callback, options?) + +Write data to a random temp file. The file is automatically cleaned up after the callback is executed. Returns a promise that resolves after the callback is executed and the file is cleaned up. + ##### fileContent Type: `string | Buffer | TypedArray | DataView | stream.Readable` Data to write to the temp file. +##### callback + +Type: `(tempPath: string) => void` + +A callback that is executed with the temp file path. Can be asynchronous. + ##### options See [options](#options). @@ -103,9 +132,3 @@ See [options](#options). ### tempy.root Get the root temporary directory path. For example: `/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T` - -## FAQ - -#### Why doesn't it have a cleanup method? - -Temp files will be periodically cleaned up on macOS. Most Linux distros will clean up on reboot. If you're generating a lot of temp files, it's recommended to use a complementary module like [`del`](https://github.com/sindresorhus/del) for cleanup. diff --git a/test.js b/test.js index 6ad2715..659966e 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,7 @@ import path from 'path'; import tempDir from 'temp-dir'; +import pathExists from 'path-exists'; +import touch from 'touch'; import fs from 'fs'; import stream from 'stream'; import test from 'ava'; @@ -32,6 +34,15 @@ test('.file()', t => { }); }); +test('.file.task()', async t => { + let temporaryFilePath; + await tempy.file.task(async temporaryFile => { + await touch(temporaryFile); + temporaryFilePath = temporaryFile; + }); + t.false(await pathExists(temporaryFilePath)); +}); + test('.directory()', t => { const prefix = 'name_'; @@ -39,12 +50,28 @@ test('.directory()', t => { t.true(path.basename(tempy.directory({prefix})).startsWith(prefix)); }); +test('.directory.task()', async t => { + let temporaryDirectoryPath; + await tempy.directory.task(async temporaryDirectory => { + temporaryDirectoryPath = temporaryDirectory; + }); + t.false(await pathExists(temporaryDirectoryPath)); +}); + test('.write(string)', async t => { const filePath = await tempy.write('unicorn', {name: 'test.png'}); t.is(fs.readFileSync(filePath, 'utf8'), 'unicorn'); t.is(path.basename(filePath), 'test.png'); }); +test('.write.task(string)', async t => { + let temporaryFilePath; + await tempy.write.task('', async temporaryFile => { + temporaryFilePath = temporaryFile; + }); + t.false(await pathExists(temporaryFilePath)); +}); + test('.write(buffer)', async t => { const filePath = await tempy.write(Buffer.from('unicorn')); t.is(fs.readFileSync(filePath, 'utf8'), 'unicorn');