fs-fixture 2.12.0 → 2.13.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
@@ -17,6 +17,7 @@ Simple API to create disposable test fixtures on disk. Tiny (`1.1 kB` gzipped) w
17
17
  - 💾 Binary file support with Buffers
18
18
  - 🎯 TypeScript-first with full type safety
19
19
  - 🔄 File methods inherit types directly from Node.js `fs` module
20
+ - 🔌 Pluggable filesystem — use with @platformatic/vfs, memfs, or any `fs/promises`-compatible API
20
21
 
21
22
  ## Installation
22
23
 
@@ -185,6 +186,31 @@ const fixture = await createFixture({
185
186
  })
186
187
  ```
187
188
 
189
+ ### Custom filesystem
190
+
191
+ Pass any `fs/promises`-compatible API via the `fs` option to use a virtual filesystem instead of disk:
192
+
193
+ ```ts
194
+ import { create, MemoryProvider } from '@platformatic/vfs'
195
+ import { createFixture } from 'fs-fixture'
196
+
197
+ const fs = create(new MemoryProvider()).promises
198
+ const fixture = await createFixture({
199
+ 'package.json': JSON.stringify({ name: 'test' }),
200
+ 'src/index.js': 'export default 42'
201
+ }, { fs })
202
+
203
+ await fixture.readFile('src/index.js', 'utf8') // 'export default 42'
204
+ ```
205
+
206
+ Works with any library that implements the `fs/promises` API shape, including [@platformatic/vfs](https://github.com/platformatic/vfs), the future [`node:vfs`](https://github.com/nodejs/node/pull/61478), and [memfs](https://github.com/streamich/memfs).
207
+
208
+ > [!NOTE]
209
+ > With a custom fs, files only exist in that fs instance. Use `fixture.readFile()` or `fixture.fs` to access them — `fixture.path` is a virtual path that doesn't exist on the real disk.
210
+
211
+ > [!NOTE]
212
+ > Template directory sources (string paths) are not supported with custom filesystems because most virtual fs implementations lack recursive `cp`. Use a `FileTree` object instead.
213
+
188
214
  ## API
189
215
 
190
216
  ### `createFixture(source?, options?)`
@@ -195,6 +221,7 @@ Creates a temporary fixture directory and returns a `FsFixture` instance.
195
221
  - `source` (optional): String path to template directory, or `FileTree` object defining the structure
196
222
  - `options.tempDir` (optional): Custom temp directory. Defaults to `os.tmpdir()`
197
223
  - `options.templateFilter` (optional): Filter function when copying from template directory
224
+ - `options.fs` (optional): Custom `fs/promises`-compatible API for virtual filesystem support
198
225
 
199
226
  **Returns:** `Promise<FsFixture>`
200
227
 
@@ -210,6 +237,7 @@ const fixture = await createFixture({}, { tempDir: './custom-temp' })
210
237
  | Method | Description |
211
238
  |--------|-------------|
212
239
  | `fixture.path` | Absolute path to the fixture directory |
240
+ | `fixture.fs` | The underlying `fs/promises` API used by the fixture |
213
241
  | `getPath(...paths)` | Get absolute path to file/directory in fixture |
214
242
  | `exists(path?)` | Check if file/directory exists |
215
243
  | `rm(path?)` | Delete file/directory (or entire fixture if no path) |
@@ -241,6 +269,34 @@ type Api = {
241
269
  ```
242
270
  </details>
243
271
 
272
+ <details>
273
+ <summary><strong>FsPromises</strong></summary>
274
+
275
+ The subset of `fs/promises` methods that custom filesystem implementations must provide:
276
+
277
+ ```ts
278
+ type FsPromises = {
279
+ // Required
280
+ readFile(path: string, options?): Promise<Buffer | string>
281
+ writeFile(path: string, data: string | Buffer, options?): Promise<void>
282
+ readdir(path: string, options?): Promise<string[] | Dirent[]>
283
+ mkdir(path: string, options?): Promise<string | undefined>
284
+ rename(oldPath: string, newPath: string): Promise<void>
285
+ access(path: string, mode?: number): Promise<void>
286
+
287
+ // Optional
288
+ rm?(path: string, options?): Promise<void>
289
+ unlink?(path: string): Promise<void>
290
+ rmdir?(path: string): Promise<void>
291
+ symlink?(target: string, path: string, type?: string): Promise<void>
292
+ cp?(source: string, destination: string, options?): Promise<void>
293
+ mkdtemp?(prefix: string): Promise<string>
294
+ }
295
+ ```
296
+
297
+ If `rm` is not available, fs-fixture falls back to recursive removal using `readdir({ withFileTypes })` + `unlink` + `rmdir`. If `mkdtemp` is not available, fixture paths are generated with a counter.
298
+ </details>
299
+
244
300
  ## Related
245
301
 
246
302
  ### [manten](https://github.com/privatenumber/manten)
package/dist/index.cjs CHANGED
@@ -1 +1 @@
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;
1
+ "use strict";var v=Object.defineProperty;var o=(r,e)=>v(r,"name",{value:e,configurable:!0});var y=require("node:fs/promises"),c=require("node:path"),F=require("node:url"),b=require("node:fs"),j=require("node:os");const w=o(async(r,e)=>{try{await r.unlink(e);return}catch(i){if(D(i))return}const t=await r.readdir(e,{withFileTypes:!0});await Promise.all(t.map(i=>{const n=c.join(e,i.name);return i.isDirectory()?w(r,n):r.unlink(n)})),await r.rmdir(e)},"recursiveRm"),D=o(r=>r instanceof Error&&"code"in r&&r.code==="ENOENT","isEnoent");typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class x{static{o(this,"FsFixture")}path;fs;constructor(e,t){this.path=e,this.fs=t??y}getPath(...e){return c.join(this.path,...e)}exists(e=""){return this.fs.access(this.getPath(e)).then(()=>!0,()=>!1)}rm(e=""){const t=this.getPath(e);if(this.fs.rm)return this.fs.rm(t,{recursive:!0,force:!0});if(!this.fs.unlink||!this.fs.rmdir)throw new Error("rm() requires the fs API to support rm(), or unlink() + rmdir()");return w(this.fs,t)}cp(e,t,i){if(!this.fs.cp)throw new Error("cp() requires the fs API to support cp()");return t?(t.endsWith("/")||t.endsWith(c.sep))&&(t+=c.basename(e)):t=c.basename(e),this.fs.cp(e,this.getPath(t),i)}mkdir(e){return this.fs.mkdir(this.getPath(e),{recursive:!0})}mv(e,t){return this.fs.rename(this.getPath(e),this.getPath(t))}readFile=o(((e,t)=>this.fs.readFile(this.getPath(e),t)),"readFile");readdir=o(((e,t)=>this.fs.readdir(this.getPath(e||""),t)),"readdir");writeFile=o(((e,t,...i)=>this.fs.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 T=b.realpathSync(j.tmpdir());class m{static{o(this,"PathBase")}path;constructor(e){this.path=e}}class d extends m{static{o(this,"Directory")}}class p extends m{static{o(this,"File")}content;constructor(e,t){super(e),this.content=t}}class h extends m{static{o(this,"Symlink")}target;type;constructor(e,t,i){super(i??""),this.target=e,this.type=t}}const g=o((r,e,t)=>{const i=[];for(const n in r){if(!Object.hasOwn(r,n))continue;const f=c.join(e,n);let a=r[n];if(typeof a=="function"){const l=Object.assign(Object.create(t),{filePath:f}),u=a(l);if(u instanceof h){const s=new h(u.target,u.type,f);i.push(s);continue}else a=u}if(typeof a=="string"||Buffer.isBuffer(a))i.push(new p(f,a));else if(a&&typeof a=="object"&&!Array.isArray(a))i.push(new d(f),...g(a,f,t));else throw new TypeError(`Invalid file content for path "${f}". Functions must return a string, Buffer, Symlink, or a nested FileTree object. Received: ${String(a)}`)}return i},"flattenFileTree");let k=0;const q=o(async(r,e)=>{const t=e?.fs??y,i=e?.tempDir?c.resolve(typeof e.tempDir=="string"?e.tempDir:F.fileURLToPath(e.tempDir)):T;e?.tempDir&&await t.mkdir(i,{recursive:!0});let n;if(t.mkdtemp?n=await t.mkdtemp(c.join(i,"fs-fixture-")):(k+=1,n=c.join(i,`fs-fixture-${process.pid}-${k}`),await t.mkdir(n,{recursive:!0})),r){if(typeof r=="string"){if(!t.cp)throw new TypeError("Template directory sources require the fs API to support cp()");await t.cp(r,n,{recursive:!0,filter:e?.templateFilter})}else if(typeof r=="object"){const a=g(r,n,{fixturePath:n,getPath:o((...s)=>c.join(n,...s),"getPath"),symlink:o((s,P)=>new h(s,P),"symlink")}),l=new Set;for(const s of a)s instanceof d?l.add(s.path):(s instanceof p||s instanceof h)&&l.add(c.dirname(s.path));if(await Promise.all(Array.from(l).map(s=>t.mkdir(s,{recursive:!0}))),a.some(s=>s instanceof h)&&!t.symlink)throw new TypeError("Symlinks require the fs API to support symlink()");await Promise.all(a.map(async s=>{s instanceof h?await t.symlink(s.target,s.path,s.type):s instanceof p&&await t.writeFile(s.path,s.content)}))}}return new x(n,e?.fs)},"createFixture");exports.createFixture=q;
package/dist/index.d.cts CHANGED
@@ -1,17 +1,69 @@
1
1
  import { CopyOptions } from 'node:fs';
2
2
  import fs from 'node:fs/promises';
3
3
 
4
+ /**
5
+ * A subset of `fs/promises` methods used by FsFixture.
6
+ * Compatible with Node.js `fs/promises`, `@platformatic/vfs`,
7
+ * `memfs`, and other fs-compatible implementations.
8
+ *
9
+ * Pass a custom implementation to `createFixture({ fs })`
10
+ * to use a virtual filesystem.
11
+ */
12
+ type FsPromises = {
13
+ readFile: {
14
+ (path: string, options?: {
15
+ encoding?: null;
16
+ } | null): Promise<Buffer>;
17
+ (path: string, options: BufferEncoding | {
18
+ encoding: BufferEncoding;
19
+ }): Promise<string>;
20
+ };
21
+ writeFile(path: string, data: string | Buffer, options?: BufferEncoding | {
22
+ encoding?: BufferEncoding;
23
+ } | null): Promise<void>;
24
+ readdir: {
25
+ (path: string, options?: {
26
+ withFileTypes?: false;
27
+ }): Promise<string[]>;
28
+ (path: string, options: {
29
+ withFileTypes: true;
30
+ }): Promise<Array<{
31
+ name: string;
32
+ isFile(): boolean;
33
+ isDirectory(): boolean;
34
+ }>>;
35
+ };
36
+ mkdir(path: string, options?: {
37
+ recursive?: boolean;
38
+ }): Promise<string | undefined>;
39
+ rename(oldPath: string, newPath: string): Promise<void>;
40
+ access(path: string, mode?: number): Promise<void>;
41
+ rm?(path: string, options?: {
42
+ recursive?: boolean;
43
+ force?: boolean;
44
+ }): Promise<void>;
45
+ unlink?(path: string): Promise<void>;
46
+ rmdir?(path: string): Promise<void>;
47
+ symlink?(target: string, path: string, type?: string | null): Promise<void>;
48
+ cp?(source: string, destination: string, options?: {
49
+ recursive?: boolean;
50
+ }): Promise<void>;
51
+ mkdtemp?(prefix: string): Promise<string>;
52
+ };
53
+
4
54
  declare class FsFixture {
5
55
  /**
6
56
  * Path to the fixture directory.
7
57
  */
8
58
  readonly path: string;
59
+ readonly fs: FsPromises;
9
60
  /**
10
61
  * Create a Fixture instance from a path. Does not create the fixture directory.
11
62
  *
12
63
  * @param fixturePath - The path to the fixture directory
64
+ * @param fsApi - Optional fs/promises-compatible API. Defaults to real node:fs/promises.
13
65
  */
14
- constructor(fixturePath: string);
66
+ constructor(fixturePath: string, fsApi?: FsPromises);
15
67
  /**
16
68
  * Get the full path to a subpath in the fixture directory.
17
69
  *
@@ -160,8 +212,8 @@ declare class PathBase {
160
212
  type SymlinkType = 'file' | 'dir' | 'junction';
161
213
  declare class Symlink extends PathBase {
162
214
  readonly target: string;
163
- readonly type?: SymlinkType | undefined;
164
- constructor(target: string, type?: SymlinkType | undefined, filePath?: string);
215
+ readonly type?: SymlinkType;
216
+ constructor(target: string, type?: SymlinkType, filePath?: string);
165
217
  }
166
218
 
167
219
  type ApiBase = {
@@ -196,6 +248,22 @@ type CreateFixtureOptions = {
196
248
  * Return `true` to copy the item, `false` to ignore it.
197
249
  */
198
250
  templateFilter?: FilterFunction;
251
+ /**
252
+ * Custom fs/promises-compatible API for fixture operations.
253
+ * Use this to create fixtures in a virtual filesystem instead of on disk.
254
+ *
255
+ * Required: readFile, writeFile, readdir (with withFileTypes),
256
+ * mkdir, rename, access.
257
+ * Optional: rm (or unlink + rmdir as fallback), symlink, cp, mkdtemp.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * import { create, MemoryProvider } from '@platformatic/vfs'
262
+ * const vfs = create(new MemoryProvider())
263
+ * const fixture = await createFixture({ 'file.txt': 'hi' }, { fs: vfs.promises })
264
+ * ```
265
+ */
266
+ fs?: FsPromises;
199
267
  };
200
268
  /**
201
269
  * Create a temporary test fixture directory.
@@ -229,4 +297,4 @@ type CreateFixtureOptions = {
229
297
  declare const createFixture: (source?: string | FileTree, options?: CreateFixtureOptions) => Promise<FsFixture>;
230
298
 
231
299
  export { createFixture };
232
- export type { CreateFixtureOptions, FileTree, FsFixtureType as FsFixture };
300
+ export type { CreateFixtureOptions, FileTree, FsFixtureType as FsFixture, FsPromises };
package/dist/index.d.mts CHANGED
@@ -1,17 +1,69 @@
1
1
  import { CopyOptions } from 'node:fs';
2
2
  import fs from 'node:fs/promises';
3
3
 
4
+ /**
5
+ * A subset of `fs/promises` methods used by FsFixture.
6
+ * Compatible with Node.js `fs/promises`, `@platformatic/vfs`,
7
+ * `memfs`, and other fs-compatible implementations.
8
+ *
9
+ * Pass a custom implementation to `createFixture({ fs })`
10
+ * to use a virtual filesystem.
11
+ */
12
+ type FsPromises = {
13
+ readFile: {
14
+ (path: string, options?: {
15
+ encoding?: null;
16
+ } | null): Promise<Buffer>;
17
+ (path: string, options: BufferEncoding | {
18
+ encoding: BufferEncoding;
19
+ }): Promise<string>;
20
+ };
21
+ writeFile(path: string, data: string | Buffer, options?: BufferEncoding | {
22
+ encoding?: BufferEncoding;
23
+ } | null): Promise<void>;
24
+ readdir: {
25
+ (path: string, options?: {
26
+ withFileTypes?: false;
27
+ }): Promise<string[]>;
28
+ (path: string, options: {
29
+ withFileTypes: true;
30
+ }): Promise<Array<{
31
+ name: string;
32
+ isFile(): boolean;
33
+ isDirectory(): boolean;
34
+ }>>;
35
+ };
36
+ mkdir(path: string, options?: {
37
+ recursive?: boolean;
38
+ }): Promise<string | undefined>;
39
+ rename(oldPath: string, newPath: string): Promise<void>;
40
+ access(path: string, mode?: number): Promise<void>;
41
+ rm?(path: string, options?: {
42
+ recursive?: boolean;
43
+ force?: boolean;
44
+ }): Promise<void>;
45
+ unlink?(path: string): Promise<void>;
46
+ rmdir?(path: string): Promise<void>;
47
+ symlink?(target: string, path: string, type?: string | null): Promise<void>;
48
+ cp?(source: string, destination: string, options?: {
49
+ recursive?: boolean;
50
+ }): Promise<void>;
51
+ mkdtemp?(prefix: string): Promise<string>;
52
+ };
53
+
4
54
  declare class FsFixture {
5
55
  /**
6
56
  * Path to the fixture directory.
7
57
  */
8
58
  readonly path: string;
59
+ readonly fs: FsPromises;
9
60
  /**
10
61
  * Create a Fixture instance from a path. Does not create the fixture directory.
11
62
  *
12
63
  * @param fixturePath - The path to the fixture directory
64
+ * @param fsApi - Optional fs/promises-compatible API. Defaults to real node:fs/promises.
13
65
  */
14
- constructor(fixturePath: string);
66
+ constructor(fixturePath: string, fsApi?: FsPromises);
15
67
  /**
16
68
  * Get the full path to a subpath in the fixture directory.
17
69
  *
@@ -160,8 +212,8 @@ declare class PathBase {
160
212
  type SymlinkType = 'file' | 'dir' | 'junction';
161
213
  declare class Symlink extends PathBase {
162
214
  readonly target: string;
163
- readonly type?: SymlinkType | undefined;
164
- constructor(target: string, type?: SymlinkType | undefined, filePath?: string);
215
+ readonly type?: SymlinkType;
216
+ constructor(target: string, type?: SymlinkType, filePath?: string);
165
217
  }
166
218
 
167
219
  type ApiBase = {
@@ -196,6 +248,22 @@ type CreateFixtureOptions = {
196
248
  * Return `true` to copy the item, `false` to ignore it.
197
249
  */
198
250
  templateFilter?: FilterFunction;
251
+ /**
252
+ * Custom fs/promises-compatible API for fixture operations.
253
+ * Use this to create fixtures in a virtual filesystem instead of on disk.
254
+ *
255
+ * Required: readFile, writeFile, readdir (with withFileTypes),
256
+ * mkdir, rename, access.
257
+ * Optional: rm (or unlink + rmdir as fallback), symlink, cp, mkdtemp.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * import { create, MemoryProvider } from '@platformatic/vfs'
262
+ * const vfs = create(new MemoryProvider())
263
+ * const fixture = await createFixture({ 'file.txt': 'hi' }, { fs: vfs.promises })
264
+ * ```
265
+ */
266
+ fs?: FsPromises;
199
267
  };
200
268
  /**
201
269
  * Create a temporary test fixture directory.
@@ -229,4 +297,4 @@ type CreateFixtureOptions = {
229
297
  declare const createFixture: (source?: string | FileTree, options?: CreateFixtureOptions) => Promise<FsFixture>;
230
298
 
231
299
  export { createFixture };
232
- export type { CreateFixtureOptions, FileTree, FsFixtureType as FsFixture };
300
+ export type { CreateFixtureOptions, FileTree, FsFixtureType as FsFixture, FsPromises };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
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};
1
+ var F=Object.defineProperty;var o=(r,t)=>F(r,"name",{value:t,configurable:!0});import y from"node:fs/promises";import c from"node:path";import{fileURLToPath as b}from"node:url";import j from"node:fs";import v from"node:os";const w=o(async(r,t)=>{try{await r.unlink(t);return}catch(i){if(D(i))return}const e=await r.readdir(t,{withFileTypes:!0});await Promise.all(e.map(i=>{const n=c.join(t,i.name);return i.isDirectory()?w(r,n):r.unlink(n)})),await r.rmdir(t)},"recursiveRm"),D=o(r=>r instanceof Error&&"code"in r&&r.code==="ENOENT","isEnoent");typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class x{static{o(this,"FsFixture")}path;fs;constructor(t,e){this.path=t,this.fs=e??y}getPath(...t){return c.join(this.path,...t)}exists(t=""){return this.fs.access(this.getPath(t)).then(()=>!0,()=>!1)}rm(t=""){const e=this.getPath(t);if(this.fs.rm)return this.fs.rm(e,{recursive:!0,force:!0});if(!this.fs.unlink||!this.fs.rmdir)throw new Error("rm() requires the fs API to support rm(), or unlink() + rmdir()");return w(this.fs,e)}cp(t,e,i){if(!this.fs.cp)throw new Error("cp() requires the fs API to support cp()");return e?(e.endsWith("/")||e.endsWith(c.sep))&&(e+=c.basename(t)):e=c.basename(t),this.fs.cp(t,this.getPath(e),i)}mkdir(t){return this.fs.mkdir(this.getPath(t),{recursive:!0})}mv(t,e){return this.fs.rename(this.getPath(t),this.getPath(e))}readFile=o(((t,e)=>this.fs.readFile(this.getPath(t),e)),"readFile");readdir=o(((t,e)=>this.fs.readdir(this.getPath(t||""),e)),"readdir");writeFile=o(((t,e,...i)=>this.fs.writeFile(this.getPath(t),e,...i)),"writeFile");async readJson(t){const e=await this.readFile(t,"utf8");return JSON.parse(e)}writeJson(t,e,i=2){return this.writeFile(t,JSON.stringify(e,null,i))}async[Symbol.asyncDispose](){await this.rm()}}const T=j.realpathSync(v.tmpdir());class p{static{o(this,"PathBase")}path;constructor(t){this.path=t}}class d extends p{static{o(this,"Directory")}}class u extends p{static{o(this,"File")}content;constructor(t,e){super(t),this.content=e}}class h extends p{static{o(this,"Symlink")}target;type;constructor(t,e,i){super(i??""),this.target=t,this.type=e}}const g=o((r,t,e)=>{const i=[];for(const n in r){if(!Object.hasOwn(r,n))continue;const f=c.join(t,n);let a=r[n];if(typeof a=="function"){const l=Object.assign(Object.create(e),{filePath:f}),m=a(l);if(m instanceof h){const s=new h(m.target,m.type,f);i.push(s);continue}else a=m}if(typeof a=="string"||Buffer.isBuffer(a))i.push(new u(f,a));else if(a&&typeof a=="object"&&!Array.isArray(a))i.push(new d(f),...g(a,f,e));else throw new TypeError(`Invalid file content for path "${f}". Functions must return a string, Buffer, Symlink, or a nested FileTree object. Received: ${String(a)}`)}return i},"flattenFileTree");let k=0;const E=o(async(r,t)=>{const e=t?.fs??y,i=t?.tempDir?c.resolve(typeof t.tempDir=="string"?t.tempDir:b(t.tempDir)):T;t?.tempDir&&await e.mkdir(i,{recursive:!0});let n;if(e.mkdtemp?n=await e.mkdtemp(c.join(i,"fs-fixture-")):(k+=1,n=c.join(i,`fs-fixture-${process.pid}-${k}`),await e.mkdir(n,{recursive:!0})),r){if(typeof r=="string"){if(!e.cp)throw new TypeError("Template directory sources require the fs API to support cp()");await e.cp(r,n,{recursive:!0,filter:t?.templateFilter})}else if(typeof r=="object"){const a=g(r,n,{fixturePath:n,getPath:o((...s)=>c.join(n,...s),"getPath"),symlink:o((s,P)=>new h(s,P),"symlink")}),l=new Set;for(const s of a)s instanceof d?l.add(s.path):(s instanceof u||s instanceof h)&&l.add(c.dirname(s.path));if(await Promise.all(Array.from(l).map(s=>e.mkdir(s,{recursive:!0}))),a.some(s=>s instanceof h)&&!e.symlink)throw new TypeError("Symlinks require the fs API to support symlink()");await Promise.all(a.map(async s=>{s instanceof h?await e.symlink(s.target,s.path,s.type):s instanceof u&&await e.writeFile(s.path,s.content)}))}}return new x(n,t?.fs)},"createFixture");export{E as createFixture};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fs-fixture",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "Easily create test fixtures at a temporary file-system path",
5
5
  "keywords": [
6
6
  "test",
@@ -23,6 +23,7 @@
23
23
  "dist",
24
24
  "skills"
25
25
  ],
26
+ "type": "module",
26
27
  "main": "./dist/index.cjs",
27
28
  "module": "./dist/index.mjs",
28
29
  "types": "./dist/index.d.cts",