fs-fixture 2.10.2 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -128,6 +128,10 @@ const files = await fixture.readdir('src')
128
128
  // Copy files into fixture
129
129
  await fixture.cp('/path/to/file.txt', 'copied-file.txt')
130
130
 
131
+ // Move or rename files
132
+ await fixture.mv('old-name.txt', 'new-name.txt')
133
+ await fixture.mv('file.txt', 'src/file.txt')
134
+
131
135
  // Check if path exists
132
136
  if (await fixture.exists('optional-file.txt')) {
133
137
  // ...
@@ -145,6 +149,19 @@ const fixture = await createFixture({
145
149
  })
146
150
  ```
147
151
 
152
+ **Symlinks:**
153
+ ```ts
154
+ const fixture = await createFixture({
155
+ 'index.js': 'import pkg from \'pkg\'',
156
+
157
+ // Symlink individual file or directory
158
+ 'node_modules/pkg': ({ symlink }) => symlink(process.cwd()),
159
+
160
+ // Symlink entire directory (useful for sharing node_modules)
161
+ node_modules: ({ symlink }) => symlink(path.resolve('node_modules'))
162
+ })
163
+ ```
164
+
148
165
  **Binary files:**
149
166
  ```ts
150
167
  const fixture = await createFixture({
@@ -203,6 +220,7 @@ const fixture = await createFixture({}, { tempDir: './custom-temp' })
203
220
  | `readdir(path, options?)` | List directory contents |
204
221
  | `mkdir(path)` | Create directory (recursive) |
205
222
  | `cp(source, dest?)` | Copy file/directory into fixture |
223
+ | `mv(source, dest)` | Move or rename file/directory |
206
224
 
207
225
  ### Types
208
226
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var g=Object.defineProperty;var n=(s,e)=>g(s,"name",{value:e,configurable:!0});var c=require("node:fs/promises"),f=require("node:path"),F=require("node:url"),v=require("node:fs"),P=require("node:os");typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class b{static{n(this,"FsFixture")}path;constructor(e){this.path=e}getPath(...e){return f.join(this.path,...e)}exists(e=""){return c.access(this.getPath(e)).then(()=>!0,()=>!1)}rm(e=""){return c.rm(this.getPath(e),{recursive:!0,force:!0})}cp(e,t,i){return t?t.endsWith(f.sep)&&(t+=f.basename(e)):t=f.basename(e),c.cp(e,this.getPath(t),i)}mkdir(e){return c.mkdir(this.getPath(e),{recursive:!0})}readFile=n(((e,t)=>c.readFile(this.getPath(e),t)),"readFile");readdir=n(((e,t)=>c.readdir(this.getPath(e||""),t)),"readdir");writeFile=n(((e,t,...i)=>c.writeFile(this.getPath(e),t,...i)),"writeFile");async readJson(e){const t=await this.readFile(e,"utf8");return JSON.parse(t)}writeJson(e,t,i=2){return this.writeFile(e,JSON.stringify(t,null,i))}async[Symbol.asyncDispose](){await this.rm()}}const D=v.realpathSync(P.tmpdir());class p{static{n(this,"PathBase")}constructor(e){this.path=e}}class m extends p{static{n(this,"Directory")}}class y extends p{static{n(this,"File")}constructor(e,t){super(e),this.content=t}}class u extends p{static{n(this,"Symlink")}constructor(e,t,i){super(i??""),this.target=e,this.type=t}}const d=n((s,e,t)=>{const i=[];for(const h in s){if(!Object.hasOwn(s,h))continue;const o=f.join(e,h);let a=s[h];if(typeof a=="function"){const r=Object.assign(Object.create(t),{filePath:o}),l=a(r);if(l instanceof u){const w=new u(l.target,l.type,o);i.push(w);continue}else a=l}if(typeof a=="string"||Buffer.isBuffer(a))i.push(new y(o,a));else if(a&&typeof a=="object"&&!Array.isArray(a))i.push(new m(o),...d(a,o,t));else throw new TypeError(`Invalid file content for path "${o}". Functions must return a string, Buffer, Symlink, or a nested FileTree object. Received: ${String(a)}`)}return i},"flattenFileTree"),j=n(async(s,e)=>{const t=e?.tempDir?f.resolve(typeof e.tempDir=="string"?e.tempDir:F.fileURLToPath(e.tempDir)):D;e?.tempDir&&await c.mkdir(t,{recursive:!0});const i=await c.mkdtemp(f.join(t,"fs-fixture-"));if(s){if(typeof s=="string")await c.cp(s,i,{recursive:!0,filter:e?.templateFilter});else if(typeof s=="object"){const o=d(s,i,{fixturePath:i,getPath:n((...r)=>f.join(i,...r),"getPath"),symlink:n((r,l)=>new u(r,l),"symlink")}),a=new Set;for(const r of o)r instanceof m?a.add(r.path):(r instanceof y||r instanceof u)&&a.add(f.dirname(r.path));await Promise.all(Array.from(a).map(r=>c.mkdir(r,{recursive:!0}))),await Promise.all(o.map(async r=>{r instanceof u?await c.symlink(r.target,r.path,r.type):r instanceof y&&await c.writeFile(r.path,r.content)}))}}return new b(i)},"createFixture");exports.createFixture=j;
1
+ "use strict";var g=Object.defineProperty;var c=(s,e)=>g(s,"name",{value:e,configurable:!0});var n=require("node:fs/promises"),f=require("node:path"),P=require("node:url"),v=require("node:fs"),F=require("node:os");typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class b{static{c(this,"FsFixture")}path;constructor(e){this.path=e}getPath(...e){return f.join(this.path,...e)}exists(e=""){return n.access(this.getPath(e)).then(()=>!0,()=>!1)}rm(e=""){return n.rm(this.getPath(e),{recursive:!0,force:!0})}cp(e,t,i){return t?(t.endsWith("/")||t.endsWith(f.sep))&&(t+=f.basename(e)):t=f.basename(e),n.cp(e,this.getPath(t),i)}mkdir(e){return n.mkdir(this.getPath(e),{recursive:!0})}mv(e,t){return n.rename(this.getPath(e),this.getPath(t))}readFile=c(((e,t)=>n.readFile(this.getPath(e),t)),"readFile");readdir=c(((e,t)=>n.readdir(this.getPath(e||""),t)),"readdir");writeFile=c(((e,t,...i)=>n.writeFile(this.getPath(e),t,...i)),"writeFile");async readJson(e){const t=await this.readFile(e,"utf8");return JSON.parse(t)}writeJson(e,t,i=2){return this.writeFile(e,JSON.stringify(t,null,i))}async[Symbol.asyncDispose](){await this.rm()}}const D=v.realpathSync(F.tmpdir());class p{static{c(this,"PathBase")}constructor(e){this.path=e}}class y extends p{static{c(this,"Directory")}}class m extends p{static{c(this,"File")}constructor(e,t){super(e),this.content=t}}class u extends p{static{c(this,"Symlink")}constructor(e,t,i){super(i??""),this.target=e,this.type=t}}const d=c((s,e,t)=>{const i=[];for(const h in s){if(!Object.hasOwn(s,h))continue;const o=f.join(e,h);let a=s[h];if(typeof a=="function"){const r=Object.assign(Object.create(t),{filePath:o}),l=a(r);if(l instanceof u){const w=new u(l.target,l.type,o);i.push(w);continue}else a=l}if(typeof a=="string"||Buffer.isBuffer(a))i.push(new m(o,a));else if(a&&typeof a=="object"&&!Array.isArray(a))i.push(new y(o),...d(a,o,t));else throw new TypeError(`Invalid file content for path "${o}". Functions must return a string, Buffer, Symlink, or a nested FileTree object. Received: ${String(a)}`)}return i},"flattenFileTree"),j=c(async(s,e)=>{const t=e?.tempDir?f.resolve(typeof e.tempDir=="string"?e.tempDir:P.fileURLToPath(e.tempDir)):D;e?.tempDir&&await n.mkdir(t,{recursive:!0});const i=await n.mkdtemp(f.join(t,"fs-fixture-"));if(s){if(typeof s=="string")await n.cp(s,i,{recursive:!0,filter:e?.templateFilter});else if(typeof s=="object"){const o=d(s,i,{fixturePath:i,getPath:c((...r)=>f.join(i,...r),"getPath"),symlink:c((r,l)=>new u(r,l),"symlink")}),a=new Set;for(const r of o)r instanceof y?a.add(r.path):(r instanceof m||r instanceof u)&&a.add(f.dirname(r.path));await Promise.all(Array.from(a).map(r=>n.mkdir(r,{recursive:!0}))),await Promise.all(o.map(async r=>{r instanceof u?await n.symlink(r.target,r.path,r.type):r instanceof m&&await n.writeFile(r.path,r.content)}))}}return new b(i)},"createFixture");exports.createFixture=j;
package/dist/index.d.cts CHANGED
@@ -58,6 +58,27 @@ declare class FsFixture {
58
58
  * @returns Promise that resolves when directory is created
59
59
  */
60
60
  mkdir(folderPath: string): Promise<string | undefined>;
61
+ /**
62
+ * Move or rename a file or directory within the fixture.
63
+ * This is a wrapper around `node:fs/promises.rename`.
64
+ *
65
+ * @param sourcePath - The source path relative to the fixture root
66
+ * @param destinationPath - The destination path relative to the fixture root
67
+ * @returns Promise that resolves when the move is complete
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * // Rename a file
72
+ * await fixture.mv('old-name.txt', 'new-name.txt')
73
+ *
74
+ * // Move a file into a directory
75
+ * await fixture.mv('file.txt', 'src/file.txt')
76
+ *
77
+ * // Rename a directory
78
+ * await fixture.mv('src', 'lib')
79
+ * ```
80
+ */
81
+ mv(sourcePath: string, destinationPath: string): Promise<void>;
61
82
  /**
62
83
  * Read a file from the fixture directory.
63
84
  *
package/dist/index.d.mts CHANGED
@@ -58,6 +58,27 @@ declare class FsFixture {
58
58
  * @returns Promise that resolves when directory is created
59
59
  */
60
60
  mkdir(folderPath: string): Promise<string | undefined>;
61
+ /**
62
+ * Move or rename a file or directory within the fixture.
63
+ * This is a wrapper around `node:fs/promises.rename`.
64
+ *
65
+ * @param sourcePath - The source path relative to the fixture root
66
+ * @param destinationPath - The destination path relative to the fixture root
67
+ * @returns Promise that resolves when the move is complete
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * // Rename a file
72
+ * await fixture.mv('old-name.txt', 'new-name.txt')
73
+ *
74
+ * // Move a file into a directory
75
+ * await fixture.mv('file.txt', 'src/file.txt')
76
+ *
77
+ * // Rename a directory
78
+ * await fixture.mv('src', 'lib')
79
+ * ```
80
+ */
81
+ mv(sourcePath: string, destinationPath: string): Promise<void>;
61
82
  /**
62
83
  * Read a file from the fixture directory.
63
84
  *
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var g=Object.defineProperty;var n=(s,e)=>g(s,"name",{value:e,configurable:!0});import o from"node:fs/promises";import f from"node:path";import{fileURLToPath as F}from"node:url";import P from"node:fs";import b from"node:os";typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class D{static{n(this,"FsFixture")}path;constructor(e){this.path=e}getPath(...e){return f.join(this.path,...e)}exists(e=""){return o.access(this.getPath(e)).then(()=>!0,()=>!1)}rm(e=""){return o.rm(this.getPath(e),{recursive:!0,force:!0})}cp(e,t,i){return t?t.endsWith(f.sep)&&(t+=f.basename(e)):t=f.basename(e),o.cp(e,this.getPath(t),i)}mkdir(e){return o.mkdir(this.getPath(e),{recursive:!0})}readFile=n(((e,t)=>o.readFile(this.getPath(e),t)),"readFile");readdir=n(((e,t)=>o.readdir(this.getPath(e||""),t)),"readdir");writeFile=n(((e,t,...i)=>o.writeFile(this.getPath(e),t,...i)),"writeFile");async readJson(e){const t=await this.readFile(e,"utf8");return JSON.parse(t)}writeJson(e,t,i=2){return this.writeFile(e,JSON.stringify(t,null,i))}async[Symbol.asyncDispose](){await this.rm()}}const j=P.realpathSync(b.tmpdir());class h{static{n(this,"PathBase")}constructor(e){this.path=e}}class y extends h{static{n(this,"Directory")}}class u extends h{static{n(this,"File")}constructor(e,t){super(e),this.content=t}}class m extends h{static{n(this,"Symlink")}constructor(e,t,i){super(i??""),this.target=e,this.type=t}}const d=n((s,e,t)=>{const i=[];for(const p in s){if(!Object.hasOwn(s,p))continue;const c=f.join(e,p);let a=s[p];if(typeof a=="function"){const r=Object.assign(Object.create(t),{filePath:c}),l=a(r);if(l instanceof m){const w=new m(l.target,l.type,c);i.push(w);continue}else a=l}if(typeof a=="string"||Buffer.isBuffer(a))i.push(new u(c,a));else if(a&&typeof a=="object"&&!Array.isArray(a))i.push(new y(c),...d(a,c,t));else throw new TypeError(`Invalid file content for path "${c}". Functions must return a string, Buffer, Symlink, or a nested FileTree object. Received: ${String(a)}`)}return i},"flattenFileTree"),k=n(async(s,e)=>{const t=e?.tempDir?f.resolve(typeof e.tempDir=="string"?e.tempDir:F(e.tempDir)):j;e?.tempDir&&await o.mkdir(t,{recursive:!0});const i=await o.mkdtemp(f.join(t,"fs-fixture-"));if(s){if(typeof s=="string")await o.cp(s,i,{recursive:!0,filter:e?.templateFilter});else if(typeof s=="object"){const c=d(s,i,{fixturePath:i,getPath:n((...r)=>f.join(i,...r),"getPath"),symlink:n((r,l)=>new m(r,l),"symlink")}),a=new Set;for(const r of c)r instanceof y?a.add(r.path):(r instanceof u||r instanceof m)&&a.add(f.dirname(r.path));await Promise.all(Array.from(a).map(r=>o.mkdir(r,{recursive:!0}))),await Promise.all(c.map(async r=>{r instanceof m?await o.symlink(r.target,r.path,r.type):r instanceof u&&await o.writeFile(r.path,r.content)}))}}return new D(i)},"createFixture");export{k as createFixture};
1
+ var g=Object.defineProperty;var o=(s,e)=>g(s,"name",{value:e,configurable:!0});import n from"node:fs/promises";import f from"node:path";import{fileURLToPath as P}from"node:url";import F from"node:fs";import b from"node:os";typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class D{static{o(this,"FsFixture")}path;constructor(e){this.path=e}getPath(...e){return f.join(this.path,...e)}exists(e=""){return n.access(this.getPath(e)).then(()=>!0,()=>!1)}rm(e=""){return n.rm(this.getPath(e),{recursive:!0,force:!0})}cp(e,t,i){return t?(t.endsWith("/")||t.endsWith(f.sep))&&(t+=f.basename(e)):t=f.basename(e),n.cp(e,this.getPath(t),i)}mkdir(e){return n.mkdir(this.getPath(e),{recursive:!0})}mv(e,t){return n.rename(this.getPath(e),this.getPath(t))}readFile=o(((e,t)=>n.readFile(this.getPath(e),t)),"readFile");readdir=o(((e,t)=>n.readdir(this.getPath(e||""),t)),"readdir");writeFile=o(((e,t,...i)=>n.writeFile(this.getPath(e),t,...i)),"writeFile");async readJson(e){const t=await this.readFile(e,"utf8");return JSON.parse(t)}writeJson(e,t,i=2){return this.writeFile(e,JSON.stringify(t,null,i))}async[Symbol.asyncDispose](){await this.rm()}}const j=F.realpathSync(b.tmpdir());class h{static{o(this,"PathBase")}constructor(e){this.path=e}}class y extends h{static{o(this,"Directory")}}class u extends h{static{o(this,"File")}constructor(e,t){super(e),this.content=t}}class m extends h{static{o(this,"Symlink")}constructor(e,t,i){super(i??""),this.target=e,this.type=t}}const d=o((s,e,t)=>{const i=[];for(const p in s){if(!Object.hasOwn(s,p))continue;const c=f.join(e,p);let a=s[p];if(typeof a=="function"){const r=Object.assign(Object.create(t),{filePath:c}),l=a(r);if(l instanceof m){const w=new m(l.target,l.type,c);i.push(w);continue}else a=l}if(typeof a=="string"||Buffer.isBuffer(a))i.push(new u(c,a));else if(a&&typeof a=="object"&&!Array.isArray(a))i.push(new y(c),...d(a,c,t));else throw new TypeError(`Invalid file content for path "${c}". Functions must return a string, Buffer, Symlink, or a nested FileTree object. Received: ${String(a)}`)}return i},"flattenFileTree"),v=o(async(s,e)=>{const t=e?.tempDir?f.resolve(typeof e.tempDir=="string"?e.tempDir:P(e.tempDir)):j;e?.tempDir&&await n.mkdir(t,{recursive:!0});const i=await n.mkdtemp(f.join(t,"fs-fixture-"));if(s){if(typeof s=="string")await n.cp(s,i,{recursive:!0,filter:e?.templateFilter});else if(typeof s=="object"){const c=d(s,i,{fixturePath:i,getPath:o((...r)=>f.join(i,...r),"getPath"),symlink:o((r,l)=>new m(r,l),"symlink")}),a=new Set;for(const r of c)r instanceof y?a.add(r.path):(r instanceof u||r instanceof m)&&a.add(f.dirname(r.path));await Promise.all(Array.from(a).map(r=>n.mkdir(r,{recursive:!0}))),await Promise.all(c.map(async r=>{r instanceof m?await n.symlink(r.target,r.path,r.type):r instanceof u&&await n.writeFile(r.path,r.content)}))}}return new D(i)},"createFixture");export{v as createFixture};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fs-fixture",
3
- "version": "2.10.2",
3
+ "version": "2.12.0",
4
4
  "description": "Easily create test fixtures at a temporary file-system path",
5
5
  "keywords": [
6
6
  "test",
@@ -20,7 +20,8 @@
20
20
  "email": "hiroki.osame@gmail.com"
21
21
  },
22
22
  "files": [
23
- "dist"
23
+ "dist",
24
+ "skills"
24
25
  ],
25
26
  "main": "./dist/index.cjs",
26
27
  "module": "./dist/index.mjs",
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: fs-fixture
3
+ description: Create disposable file system test fixtures from objects, templates, or empty directories with automatic cleanup. Use when writing tests that need temporary files, directories, symlinks, or JSON fixtures on disk. Keywords - fs, tmp, temp, fixture, testing utility, cleanup, dispose.
4
+ ---
5
+
6
+ ## Quick Start
7
+
8
+ ```ts
9
+ import { createFixture } from 'fs-fixture'
10
+
11
+ // From object — keys are paths, values are content
12
+ const fixture = await createFixture({
13
+ 'package.json': JSON.stringify({ name: 'test' }),
14
+ 'src/index.js': 'export default 42',
15
+ })
16
+
17
+ // Use fixture.path, fixture.readFile(), etc.
18
+ await fixture.rm() // Cleanup when done
19
+ ```
20
+
21
+ Auto-cleanup with `using` (TypeScript 5.2+):
22
+ ```ts
23
+ await using fixture = await createFixture({ 'file.txt': 'content' })
24
+ // Automatically cleaned up when scope exits
25
+ ```
26
+
27
+ ## createFixture(source?, options?)
28
+
29
+ | Source | Behavior |
30
+ |--------|----------|
31
+ | `{ path: content }` | Create files from object. Nested objects create directories. |
32
+ | `'./path'` | Copy template directory into fixture |
33
+ | _(omitted)_ | Empty temporary directory |
34
+
35
+ | Option | Type | Default | Description |
36
+ |--------|------|---------|-------------|
37
+ | `tempDir` | `string \| URL` | `os.tmpdir()` | Custom parent directory |
38
+ | `templateFilter` | `(src, dest) => boolean` | — | Filter files when copying template |
39
+
40
+ ## FsFixture Methods
41
+
42
+ | Method | Description |
43
+ |--------|-------------|
44
+ | `fixture.path` | Absolute path to fixture root |
45
+ | `getPath(...paths)` | Resolve path relative to fixture root |
46
+ | `exists(path?)` | Check existence (returns `boolean`) |
47
+ | `readFile(path, encoding?)` | Read file — `'utf8'` returns `string`, omit for `Buffer` |
48
+ | `writeFile(path, content)` | Write string or Buffer |
49
+ | `readJson<T>(path)` | Read and parse JSON with type parameter |
50
+ | `writeJson(path, data, space?)` | Write JSON (default: 2-space indent, `0` for minified) |
51
+ | `readdir(path, options?)` | List directory (`{ withFileTypes: true }` for Dirent[]) |
52
+ | `mkdir(path)` | Create directory recursively |
53
+ | `cp(source, dest?)` | Copy external file/directory into fixture |
54
+ | `mv(source, dest)` | Move or rename within fixture |
55
+ | `rm(path?)` | Delete path, or entire fixture if omitted |
56
+
57
+ ## FileTree Values
58
+
59
+ | Type | Example |
60
+ |------|---------|
61
+ | `string` | `'file content'` |
62
+ | `Buffer` | `Buffer.from([0x89, 0x50])` |
63
+ | Nested object | `{ dir: { 'file.txt': 'content' } }` |
64
+ | Function | `({ fixturePath, filePath, getPath, symlink }) => symlink('./target')` |
65
+
66
+ Path syntax — these are equivalent:
67
+ ```ts
68
+ // Nested objects
69
+ { src: { utils: { 'helper.js': 'code' } } }
70
+
71
+ // Slash-separated keys
72
+ { 'src/utils/helper.js': 'code' }
73
+ ```
74
+
75
+ ## Patterns
76
+
77
+ ### Symlinks
78
+ ```ts
79
+ const fixture = await createFixture({
80
+ 'target.txt': 'real file',
81
+ 'link.txt': ({ symlink }) => symlink('./target.txt'),
82
+ 'node_modules/pkg': ({ symlink }) => symlink(process.cwd()),
83
+ })
84
+ ```
85
+
86
+ ### Dynamic content
87
+ ```ts
88
+ const fixture = await createFixture({
89
+ 'info.txt': ({ fixturePath }) => `Root: ${fixturePath}`,
90
+ })
91
+ ```
92
+
93
+ ## Related
94
+
95
+ Commonly used with `manten` test framework.