fs-fixture 2.11.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 +69 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +72 -4
- package/dist/index.d.mts +72 -4
- package/dist/index.mjs +1 -1
- package/package.json +4 -2
- package/skills/fs-fixture/SKILL.md +95 -0
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
|
|
|
@@ -149,6 +150,19 @@ const fixture = await createFixture({
|
|
|
149
150
|
})
|
|
150
151
|
```
|
|
151
152
|
|
|
153
|
+
**Symlinks:**
|
|
154
|
+
```ts
|
|
155
|
+
const fixture = await createFixture({
|
|
156
|
+
'index.js': 'import pkg from \'pkg\'',
|
|
157
|
+
|
|
158
|
+
// Symlink individual file or directory
|
|
159
|
+
'node_modules/pkg': ({ symlink }) => symlink(process.cwd()),
|
|
160
|
+
|
|
161
|
+
// Symlink entire directory (useful for sharing node_modules)
|
|
162
|
+
node_modules: ({ symlink }) => symlink(path.resolve('node_modules'))
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
152
166
|
**Binary files:**
|
|
153
167
|
```ts
|
|
154
168
|
const fixture = await createFixture({
|
|
@@ -172,6 +186,31 @@ const fixture = await createFixture({
|
|
|
172
186
|
})
|
|
173
187
|
```
|
|
174
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
|
+
|
|
175
214
|
## API
|
|
176
215
|
|
|
177
216
|
### `createFixture(source?, options?)`
|
|
@@ -182,6 +221,7 @@ Creates a temporary fixture directory and returns a `FsFixture` instance.
|
|
|
182
221
|
- `source` (optional): String path to template directory, or `FileTree` object defining the structure
|
|
183
222
|
- `options.tempDir` (optional): Custom temp directory. Defaults to `os.tmpdir()`
|
|
184
223
|
- `options.templateFilter` (optional): Filter function when copying from template directory
|
|
224
|
+
- `options.fs` (optional): Custom `fs/promises`-compatible API for virtual filesystem support
|
|
185
225
|
|
|
186
226
|
**Returns:** `Promise<FsFixture>`
|
|
187
227
|
|
|
@@ -197,6 +237,7 @@ const fixture = await createFixture({}, { tempDir: './custom-temp' })
|
|
|
197
237
|
| Method | Description |
|
|
198
238
|
|--------|-------------|
|
|
199
239
|
| `fixture.path` | Absolute path to the fixture directory |
|
|
240
|
+
| `fixture.fs` | The underlying `fs/promises` API used by the fixture |
|
|
200
241
|
| `getPath(...paths)` | Get absolute path to file/directory in fixture |
|
|
201
242
|
| `exists(path?)` | Check if file/directory exists |
|
|
202
243
|
| `rm(path?)` | Delete file/directory (or entire fixture if no path) |
|
|
@@ -228,6 +269,34 @@ type Api = {
|
|
|
228
269
|
```
|
|
229
270
|
</details>
|
|
230
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
|
+
|
|
231
300
|
## Related
|
|
232
301
|
|
|
233
302
|
### [manten](https://github.com/privatenumber/manten)
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
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
|
|
164
|
-
constructor(target: string, type?: SymlinkType
|
|
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
|
|
164
|
-
constructor(target: string, type?: SymlinkType
|
|
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
|
|
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.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Easily create test fixtures at a temporary file-system path",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"test",
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
"email": "hiroki.osame@gmail.com"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
-
"dist"
|
|
23
|
+
"dist",
|
|
24
|
+
"skills"
|
|
24
25
|
],
|
|
26
|
+
"type": "module",
|
|
25
27
|
"main": "./dist/index.cjs",
|
|
26
28
|
"module": "./dist/index.mjs",
|
|
27
29
|
"types": "./dist/index.d.cts",
|
|
@@ -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.
|