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 +18 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
- package/skills/fs-fixture/SKILL.md +95 -0
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
|
|
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
|
|
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.
|
|
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.
|