From 07b30fe184273a369b2d94c24d68de9c5c779bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aran=C4=91el=20=C5=A0arenac?= Date: Sat, 31 Aug 2019 23:47:32 +0200 Subject: [PATCH] feat: implement context and contextSync Add nyc for coverage --- .gitignore | 3 ++ .travis.yml | 1 + index.d.ts | 63 +++++++++++++++++++++++++++++++++++++- index.js | 37 ++++++++++++++++++++++ index.test-d.ts | 11 +++++++ package.json | 8 +++-- readme.md | 65 +++++++++++++++++++++++++++++++++++++-- test.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 263 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 239ecff..8ef4c7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules yarn.lock +wallaby.js +coverage +.nyc_output diff --git a/.travis.yml b/.travis.yml index f3fa8cd..f98fed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js node_js: + - '12' - '10' - '8' diff --git a/index.d.ts b/index.d.ts index 1b78c30..fccf2c2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,5 @@ -import {MergeExclusive} from 'type-fest'; +import {MergeExclusive, Merge} from 'type-fest'; +import {Options as DelOptions} from 'del'; declare namespace tempy { type Options = MergeExclusive< @@ -19,6 +20,28 @@ declare namespace tempy { readonly name?: string; } >; + type ContextOptions = Merge< + /** + ContextOptions + */ + { + /** + keep temp directory after leaving context + @default false + */ + keepDir?: boolean; + }, + { + /** + * context call will pass `delOptions` opts key to del package when called + * like this: + * "tempy.context({ delOptions: { force: false, dryrun: true ... } }, directory => { ... }) + * @default "{ force: true }" + * default will force del to delete tempy context unless you pass different opts to it + */ + readonly delOptions?: DelOptions; + } + > } declare const tempy: { @@ -58,6 +81,44 @@ declare const tempy: { directory(): string; /** + * + * @param options + * @returns tempy context dirname + */ + + contextSync(options?: tempy.ContextOptions): string; + /** + * + * @param options + * @param callback + */ + + contextSync(options?: tempy.ContextOptions, callback?: Function): void; + /** + * + * @param callback + */ + + contextSync(callback?: Function): void; + + + /** + * + * @param options + * @see tempy.ContextOptions + * @returns a temp dir location and a function to call when you need to clean it up + */ + + context(options?: tempy.ContextOptions): Promise<[string, Function]>; + /** + * + * @param options + * @returns a temp dir location and a function to call when you need to clean it up + */ + + context(): Promise<[string, Function]>; + + /** Get the root temporary directory path. For example: `/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T`. */ readonly root: string; diff --git a/index.js b/index.js index 99159c6..559853f 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,8 @@ const fs = require('fs'); const path = require('path'); const uniqueString = require('unique-string'); const tempDir = require('temp-dir'); +const del = require('del'); +const once = require('once'); const getPath = () => path.join(tempDir, uniqueString()); @@ -29,6 +31,41 @@ module.exports.directory = () => { return directory; }; +module.exports.contextSync = (opts, callback) => { + const defaultDelOpts = {force: true}; + let _cb = callback; + let _opts = opts; + + if (typeof opts === 'function') { + _cb = opts; + _opts = {keepDir: false, delOpts: defaultDelOpts}; + } + + const directory = module.exports.directory(); + if (!_cb) { + return directory; + } + + _cb(directory); + + if (_opts.keepDir !== true) { + del.sync(directory, _opts.delOpts || defaultDelOpts); + } +}; + +module.exports.context = async (opts = {keepDir: false, delOpts: {force: true}}) => { + const directory = module.exports.directory(); + return [directory, once(done)]; + + function done() { + if (opts.keepDir !== true) { + return del(directory, opts.delOpts || {force: true}); + } + + return Promise.resolve([]); + } +}; + Object.defineProperty(module.exports, 'root', { get() { return tempDir; diff --git a/index.test-d.ts b/index.test-d.ts index 5f7fc25..3c37635 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -8,3 +8,14 @@ expectType(tempy.file({extension: 'png'})); expectType(tempy.file({name: 'afile.txt'})); expectError(tempy.file({extension: 'png', name: 'afile.txt'})); expectType(tempy.root); + +expectType(tempy.contextSync()); +expectType(tempy.contextSync((dir: string) => { + expectType(dir); +})); +expectType(tempy.contextSync({}, (dir: string) => { + expectType(dir); +})); + +expectType>(tempy.context()); +expectType>(tempy.context({ keepDir: true })); \ No newline at end of file diff --git a/package.json b/package.json index 90c6927..2416c0b 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,14 @@ "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" + "url": "https://sindresorhus.com" }, "engines": { "node": ">=8" }, "scripts": { - "test": "xo && ava && tsd" + "test": "xo && ava && tsd", + "coverage": "nyc ava && nyc report --reporter=html" }, "files": [ "index.js", @@ -35,12 +36,15 @@ "uniq" ], "dependencies": { + "del": "^5.1.0", + "once": "^1.4.0", "temp-dir": "^1.0.0", "type-fest": "^0.3.1", "unique-string": "^1.0.0" }, "devDependencies": { "ava": "^1.4.1", + "nyc": "^14.1.1", "tsd": "^0.7.2", "xo": "^0.24.0" } diff --git a/readme.md b/readme.md index 452043e..57448c1 100644 --- a/readme.md +++ b/readme.md @@ -61,12 +61,71 @@ Get a temporary directory path. The directory is created for you. Get the root temporary directory path. For example: `/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T` +### tempy.contextSync(cb) -## FAQ +Get the sync context + +```js +tempy.contextSync((directoryPath) => { + // do something with temp directory +}) +// directory is then deleted here +``` + +you can also preserve the dir after the context by explicitly setting `keepDir` to `true`: +```js +const directory = tempy.contextSync({ keepDir: true }) +// it's basically equivalent to // tempy.directory() + +// or + +tempy.contextSync({ keepDir: true }, directory => { + // directory is preserved here +}) +// AND also here + +``` + +Context: Promise version + +```js +let myDir +let onDone + +tempy.context() + .then(([tempDirectory, done]) => { + // assign to local vars, we need it for later + myDir = tempDirectory + onDone = done + }) + .then(() => { + // a long task using myDir... + }) + .then(() => { + // another long task using myDir... + }) + .then(() => { + // call done when you are done with it! + onDone() + }) + .then(() => { + // myDir is deleted now + }) +``` + +Context: Using async/await api + +```js + +const [tempDir, cleanUp] = await tempy.context() +// do something with tempDir here +await cleanUp() +// call cleanUp and tempDir is gone forever + +``` -### 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 [`rimraf`](https://github.com/isaacs/rimraf) for cleanup. +Note: deletion is made with [del](https://github.com/sindresorhus/del) ## Related diff --git a/test.js b/test.js index 50bc247..cdf71d3 100644 --- a/test.js +++ b/test.js @@ -1,3 +1,4 @@ +import {existsSync, rmdirSync} from 'fs'; import path from 'path'; import {tmpdir} from 'os'; import test from 'ava'; @@ -9,6 +10,11 @@ test('.file()', t => { t.true(tempy.file({extension: '.png'}).endsWith('.png')); t.false(tempy.file({extension: '.png'}).endsWith('..png')); t.true(tempy.file({name: 'custom-name.md'}).endsWith('custom-name.md')); + t.throws( + () => tempy.file({name: 'fiat', extension: 'lux'}), + /The `name` and `extension` options are mutually exclusive/, + 'expect error message match on mutually exclusive opts' + ); }); test('.directory()', t => { @@ -22,3 +28,78 @@ test('.root', t => { tempy.root = 'foo'; }); }); + +test('.contextSync()', t => { + t.plan(2); + const directory = tempy.contextSync(); + t.true(existsSync(directory)); + + rmdirSync(directory); + t.false(existsSync(directory), 'expected test cleanup'); +}); + +test('.contextSync((opts, (directory) => ...))', t => { + t.plan(2); + let tmp; + tempy.contextSync({keepDir: false}, directory => { + // Do something with tmp directory + tmp = directory; + t.true(existsSync(tmp)); + }); + t.false(existsSync(tmp)); +}); + +test('.contextSync((directory) => ...)', t => { + t.plan(2); + let tmp; + tempy.contextSync(directory => { + // Do something with tmp directory + tmp = directory; + t.true(existsSync(tmp)); + }); + t.false(existsSync(tmp)); +}); + +test('.contextSync(({ keepDir = "true" }, (directory) => ...))', t => { + t.plan(3); + let tmp; + tempy.contextSync({keepDir: true}, directory => { + // Do something with tmp directory + tmp = directory; + t.true(existsSync(tmp)); + }); + t.true(existsSync(tmp)); + + rmdirSync(tmp); + t.false(existsSync(tmp), 'expected test cleanup'); +}); + +test('.context().then((dir, done) => ...)', async t => { + t.plan(3); + const [dir, done] = await tempy.context(); + t.true(existsSync(dir)); + const deleted = await done(); + t.false(existsSync(dir)); + t.deepEqual(dir, deleted[0]); +}); + +test('.context({}).then((dir, done) => ...)', async t => { + t.plan(3); + const [dir, done] = await tempy.context({}); + t.true(existsSync(dir)); + const deleted = await done(); + t.false(existsSync(dir)); + t.deepEqual(dir, deleted[0]); +}); + +test('.context({ keepDir: "true" }).then((dir, done) => ...', async t => { + t.plan(4); + const [dir, done] = await tempy.context({keepDir: true}); + t.true(existsSync(dir)); + const deleted = await done(); + t.true(existsSync(dir)); + t.deepEqual(deleted, []); + + rmdirSync(dir); + t.false(existsSync(dir), 'expected test cleanup'); +});