@soederpop/luca 0.0.6 → 0.0.8
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/CLAUDE.md +10 -1
- package/RUNME.md +56 -0
- package/bun.lock +1 -1
- package/commands/build-bootstrap.ts +78 -0
- package/commands/build-scaffolds.ts +24 -2
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/docs/README.md +52 -80
- package/docs/TABLE-OF-CONTENTS.md +82 -51
- package/docs/apis/clients/elevenlabs.md +232 -8
- package/docs/apis/clients/graph.md +59 -8
- package/docs/apis/clients/openai.md +362 -2
- package/docs/apis/clients/rest.md +122 -2
- package/docs/apis/clients/websocket.md +71 -17
- package/docs/apis/features/agi/assistant.md +9 -3
- package/docs/apis/features/agi/assistants-manager.md +2 -2
- package/docs/apis/features/agi/claude-code.md +153 -14
- package/docs/apis/features/agi/conversation-history.md +15 -3
- package/docs/apis/features/agi/conversation.md +133 -20
- package/docs/apis/features/agi/openai-codex.md +90 -12
- package/docs/apis/features/agi/skills-library.md +23 -5
- package/docs/apis/features/node/container-link.md +59 -0
- package/docs/apis/features/node/content-db.md +1 -1
- package/docs/apis/features/node/disk-cache.md +1 -1
- package/docs/apis/features/node/dns.md +1 -0
- package/docs/apis/features/node/docker.md +2 -1
- package/docs/apis/features/node/esbuild.md +4 -3
- package/docs/apis/features/node/file-manager.md +13 -4
- package/docs/apis/features/node/fs.md +726 -171
- package/docs/apis/features/node/git.md +1 -0
- package/docs/apis/features/node/google-auth.md +23 -4
- package/docs/apis/features/node/google-calendar.md +14 -2
- package/docs/apis/features/node/google-docs.md +15 -2
- package/docs/apis/features/node/google-drive.md +21 -3
- package/docs/apis/features/node/google-sheets.md +14 -2
- package/docs/apis/features/node/grep.md +2 -0
- package/docs/apis/features/node/helpers.md +29 -0
- package/docs/apis/features/node/ink.md +2 -2
- package/docs/apis/features/node/networking.md +39 -4
- package/docs/apis/features/node/os.md +28 -0
- package/docs/apis/features/node/postgres.md +26 -4
- package/docs/apis/features/node/proc.md +37 -28
- package/docs/apis/features/node/process-manager.md +33 -5
- package/docs/apis/features/node/repl.md +1 -1
- package/docs/apis/features/node/runpod.md +1 -0
- package/docs/apis/features/node/secure-shell.md +7 -0
- package/docs/apis/features/node/semantic-search.md +12 -5
- package/docs/apis/features/node/sqlite.md +26 -4
- package/docs/apis/features/node/telegram.md +30 -5
- package/docs/apis/features/node/tts.md +17 -2
- package/docs/apis/features/node/ui.md +1 -1
- package/docs/apis/features/node/vault.md +4 -9
- package/docs/apis/features/node/vm.md +3 -12
- package/docs/apis/features/node/window-manager.md +128 -20
- package/docs/apis/features/web/asset-loader.md +13 -1
- package/docs/apis/features/web/container-link.md +59 -0
- package/docs/apis/features/web/esbuild.md +4 -3
- package/docs/apis/features/web/helpers.md +29 -0
- package/docs/apis/features/web/network.md +16 -2
- package/docs/apis/features/web/speech.md +16 -2
- package/docs/apis/features/web/vault.md +4 -9
- package/docs/apis/features/web/vm.md +3 -12
- package/docs/apis/features/web/voice.md +18 -1
- package/docs/apis/servers/express.md +18 -2
- package/docs/apis/servers/mcp.md +29 -4
- package/docs/apis/servers/websocket.md +34 -6
- package/docs/bootstrap/CLAUDE.md +100 -0
- package/docs/bootstrap/SKILL.md +222 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +25 -0
- package/docs/bootstrap/templates/runme.md +54 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/window-manager-layouts.md +180 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/models.ts +13 -10
- package/docs/philosophy.md +4 -3
- package/docs/reports/console-hmr-design.md +170 -0
- package/docs/reports/helper-semantic-search.md +72 -0
- package/docs/scaffolds/client.md +29 -20
- package/docs/scaffolds/command.md +64 -50
- package/docs/scaffolds/endpoint.md +31 -36
- package/docs/scaffolds/feature.md +28 -18
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +18 -9
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +148 -0
- package/docs/tutorials/07-endpoints.md +7 -7
- package/docs/tutorials/08-commands.md +153 -72
- package/luca.cli.ts +3 -0
- package/package.json +6 -5
- package/public/index.html +1430 -0
- package/scripts/examples/using-ollama.ts +2 -1
- package/scripts/update-introspection-data.ts +2 -2
- package/src/agi/endpoints/experts.ts +1 -1
- package/src/agi/features/assistant.ts +7 -0
- package/src/agi/features/assistants-manager.ts +5 -5
- package/src/agi/features/claude-code.ts +263 -3
- package/src/agi/features/conversation-history.ts +7 -1
- package/src/agi/features/conversation.ts +26 -3
- package/src/agi/features/openai-codex.ts +26 -2
- package/src/agi/features/openapi.ts +6 -1
- package/src/agi/features/skills-library.ts +9 -1
- package/src/bootstrap/generated.ts +595 -0
- package/src/cli/cli.ts +64 -21
- package/src/client.ts +23 -357
- package/src/clients/civitai/index.ts +1 -1
- package/src/clients/client-template.ts +1 -1
- package/src/clients/comfyui/index.ts +13 -2
- package/src/clients/elevenlabs/index.ts +2 -1
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +10 -1
- package/src/clients/rest.ts +207 -0
- package/src/clients/websocket.ts +176 -0
- package/src/command.ts +281 -34
- package/src/commands/bootstrap.ts +185 -0
- package/src/commands/chat.ts +5 -4
- package/src/commands/describe.ts +341 -4
- package/src/commands/help.ts +35 -9
- package/src/commands/index.ts +3 -0
- package/src/commands/introspect.ts +92 -2
- package/src/commands/prompt.ts +5 -6
- package/src/commands/run.ts +75 -10
- package/src/commands/save-api-docs.ts +49 -0
- package/src/commands/scaffold.ts +169 -23
- package/src/commands/select.ts +94 -0
- package/src/commands/serve.ts +10 -1
- package/src/container.ts +15 -0
- package/src/endpoint.ts +19 -0
- package/src/graft.ts +181 -0
- package/src/introspection/generated.agi.ts +12458 -8968
- package/src/introspection/generated.node.ts +10573 -7145
- package/src/introspection/generated.web.ts +1 -1
- package/src/introspection/index.ts +26 -0
- package/src/node/container.ts +6 -7
- package/src/node/features/content-db.ts +49 -2
- package/src/node/features/disk-cache.ts +16 -9
- package/src/node/features/dns.ts +16 -3
- package/src/node/features/docker.ts +16 -4
- package/src/node/features/esbuild.ts +22 -2
- package/src/node/features/file-manager.ts +184 -29
- package/src/node/features/fs.ts +704 -248
- package/src/node/features/git.ts +21 -8
- package/src/node/features/grep.ts +23 -3
- package/src/node/features/helpers.ts +372 -43
- package/src/node/features/networking.ts +39 -4
- package/src/node/features/opener.ts +28 -15
- package/src/node/features/os.ts +76 -0
- package/src/node/features/port-exposer.ts +11 -1
- package/src/node/features/postgres.ts +17 -1
- package/src/node/features/proc.ts +4 -1
- package/src/node/features/python.ts +63 -14
- package/src/node/features/repl.ts +11 -7
- package/src/node/features/runpod.ts +16 -3
- package/src/node/features/secure-shell.ts +27 -2
- package/src/node/features/semantic-search.ts +12 -1
- package/src/node/features/ui.ts +5 -69
- package/src/node/features/vm.ts +17 -0
- package/src/node/features/window-manager.ts +68 -20
- package/src/node.ts +5 -0
- package/src/scaffolds/generated.ts +492 -290
- package/src/scaffolds/template.ts +9 -0
- package/src/schemas/base.ts +46 -5
- package/src/selector.ts +282 -0
- package/src/server.ts +11 -0
- package/src/servers/express.ts +27 -12
- package/src/servers/socket.ts +45 -11
- package/src/web/clients/socket.ts +4 -1
- package/src/web/container.ts +2 -1
- package/src/web/features/network.ts +7 -1
- package/src/web/features/voice-recognition.ts +16 -1
- package/test/clients-servers.test.ts +2 -1
- package/test/command.test.ts +267 -0
- package/test/vm-context.test.ts +146 -0
- package/test-integration/assistants-manager.test.ts +10 -20
- package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
- package/docs/examples/launcher-app-command-listener.md +0 -120
- package/docs/tasks/web-container-helper-discovery.md +0 -71
- package/docs/todos.md +0 -1
- package/scripts/test-command-listener.ts +0 -123
- package/src/node/features/launcher-app-command-listener.ts +0 -389
package/src/node/features/fs.ts
CHANGED
|
@@ -7,9 +7,13 @@ import {
|
|
|
7
7
|
readdirSync,
|
|
8
8
|
statSync,
|
|
9
9
|
readFileSync,
|
|
10
|
+
cpSync,
|
|
11
|
+
renameSync,
|
|
12
|
+
|
|
13
|
+
rmSync as nodeRmSync,
|
|
10
14
|
} from "fs";
|
|
11
|
-
import { join, resolve, dirname } from "path";
|
|
12
|
-
import { readFile, stat, unlink, mkdir, writeFile, appendFile, readdir } from "fs/promises";
|
|
15
|
+
import { join, resolve, dirname, relative } from "path";
|
|
16
|
+
import { readFile, stat, unlink, mkdir, writeFile, appendFile, readdir, cp, rename, rm as nodeRm } from "fs/promises";
|
|
13
17
|
import { native as rimraf } from 'rimraf'
|
|
14
18
|
|
|
15
19
|
type WalkOptions = {
|
|
@@ -17,8 +21,25 @@ type WalkOptions = {
|
|
|
17
21
|
files?: boolean;
|
|
18
22
|
exclude?: string | string[];
|
|
19
23
|
include?: string | string[];
|
|
24
|
+
/** When true, returned paths are relative to `baseDir` instead of absolute. */
|
|
25
|
+
relative?: boolean;
|
|
20
26
|
};
|
|
21
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Checks whether a path matches any of the given glob-like patterns.
|
|
30
|
+
* Supports simple wildcards: * matches anything except /, ** matches anything including /.
|
|
31
|
+
*/
|
|
32
|
+
function matchesPattern(filePath: string, patterns: string[]): boolean {
|
|
33
|
+
return patterns.some(pattern => {
|
|
34
|
+
const regex = pattern
|
|
35
|
+
.replace(/\./g, '\\.')
|
|
36
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}')
|
|
37
|
+
.replace(/\*/g, '[^/]*')
|
|
38
|
+
.replace(/\{\{GLOBSTAR\}\}/g, '.*')
|
|
39
|
+
return new RegExp(`(^|/)${regex}($|/)`).test(filePath)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
/**
|
|
23
44
|
* The FS feature provides methods for interacting with the file system, relative to the
|
|
24
45
|
* container's cwd.
|
|
@@ -30,7 +51,9 @@ type WalkOptions = {
|
|
|
30
51
|
* const fs = container.feature('fs')
|
|
31
52
|
* const content = fs.readFile('package.json')
|
|
32
53
|
* const exists = fs.exists('tsconfig.json')
|
|
33
|
-
* await fs.
|
|
54
|
+
* await fs.writeFileAsync('output.txt', 'Hello World')
|
|
55
|
+
* fs.writeFile('sync-output.txt', 'Hello Sync')
|
|
56
|
+
* fs.copy('src', 'backup/src')
|
|
34
57
|
* ```
|
|
35
58
|
*/
|
|
36
59
|
export class FS extends Feature {
|
|
@@ -39,189 +62,144 @@ export class FS extends Feature {
|
|
|
39
62
|
static override optionsSchema = FeatureOptionsSchema
|
|
40
63
|
static { Feature.register(this, 'fs') }
|
|
41
64
|
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Read
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
42
69
|
/**
|
|
43
|
-
*
|
|
70
|
+
* Synchronously reads a file and returns its contents as a string.
|
|
44
71
|
*
|
|
45
72
|
* @param {string} path - The file path relative to the container's working directory
|
|
46
|
-
* @
|
|
73
|
+
* @param {BufferEncoding | null} [encoding='utf-8'] - The encoding to use. Pass null to get a raw Buffer.
|
|
74
|
+
* @returns {string | Buffer} The file contents as a string (default) or Buffer if encoding is null
|
|
47
75
|
* @throws {Error} Throws an error if the file doesn't exist or cannot be read
|
|
48
76
|
*
|
|
49
77
|
* @example
|
|
50
78
|
* ```typescript
|
|
51
|
-
* const
|
|
52
|
-
* const buffer =
|
|
53
|
-
* console.log(buffer.toString())
|
|
79
|
+
* const content = fs.readFile('README.md')
|
|
80
|
+
* const buffer = fs.readFile('image.png', null)
|
|
54
81
|
* ```
|
|
55
82
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
83
|
+
readFile(path: string, encoding?: BufferEncoding | null): string | Buffer {
|
|
84
|
+
const filePath = this.container.paths.resolve(path);
|
|
85
|
+
if (encoding === null) {
|
|
86
|
+
return readFileSync(filePath)
|
|
87
|
+
}
|
|
88
|
+
return readFileSync(filePath, encoding ?? 'utf-8')
|
|
58
89
|
}
|
|
59
90
|
|
|
60
91
|
/**
|
|
61
|
-
* Asynchronously reads
|
|
92
|
+
* Asynchronously reads a file and returns its contents as a string.
|
|
62
93
|
*
|
|
63
|
-
* @param {string} path - The
|
|
64
|
-
* @
|
|
65
|
-
* @
|
|
94
|
+
* @param {string} path - The file path relative to the container's working directory
|
|
95
|
+
* @param {BufferEncoding | null} [encoding='utf-8'] - The encoding to use. Pass null to get a raw Buffer.
|
|
96
|
+
* @returns {Promise<string | Buffer>} A promise that resolves to the file contents as a string (default) or Buffer
|
|
97
|
+
* @throws {Error} Throws an error if the file doesn't exist or cannot be read
|
|
66
98
|
*
|
|
67
99
|
* @example
|
|
68
100
|
* ```typescript
|
|
69
|
-
* const
|
|
70
|
-
* const
|
|
71
|
-
* console.log(entries) // ['index.ts', 'utils.ts', 'components']
|
|
101
|
+
* const content = await fs.readFileAsync('data.txt')
|
|
102
|
+
* const buffer = await fs.readFileAsync('image.png', null)
|
|
72
103
|
* ```
|
|
73
104
|
*/
|
|
74
|
-
async
|
|
75
|
-
|
|
105
|
+
async readFileAsync(path: string, encoding?: BufferEncoding | null): Promise<string | Buffer> {
|
|
106
|
+
const filePath = this.container.paths.resolve(path);
|
|
107
|
+
if (encoding === null) {
|
|
108
|
+
return await readFile(filePath)
|
|
109
|
+
}
|
|
110
|
+
return await readFile(filePath, encoding ?? 'utf-8')
|
|
76
111
|
}
|
|
77
112
|
|
|
78
113
|
/**
|
|
79
|
-
*
|
|
114
|
+
* Synchronously reads and parses a JSON file.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} path - The path to the JSON file
|
|
117
|
+
* @returns {any} The parsed JSON content
|
|
118
|
+
* @throws {Error} Throws an error if the file doesn't exist, cannot be read, or contains invalid JSON
|
|
80
119
|
*
|
|
81
|
-
* @param {string} basePath - The base directory path to start walking from
|
|
82
|
-
* @param {WalkOptions} options - Options to configure the walk behavior
|
|
83
|
-
* @param {boolean} [options.directories=true] - Whether to include directories in results
|
|
84
|
-
* @param {boolean} [options.files=true] - Whether to include files in results
|
|
85
|
-
* @param {string | string[]} [options.exclude=[]] - Patterns to exclude from results
|
|
86
|
-
* @param {string | string[]} [options.include=[]] - Patterns to include in results
|
|
87
|
-
* @returns {{ directories: string[], files: string[] }} Object containing arrays of directory and file paths
|
|
88
|
-
*
|
|
89
120
|
* @example
|
|
90
121
|
* ```typescript
|
|
91
|
-
* const
|
|
92
|
-
* console.log(
|
|
122
|
+
* const config = fs.readJson('config.json')
|
|
123
|
+
* console.log(config.version)
|
|
93
124
|
* ```
|
|
94
125
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
directories = true,
|
|
98
|
-
files = true,
|
|
99
|
-
exclude = [],
|
|
100
|
-
include = [],
|
|
101
|
-
} = options;
|
|
102
|
-
|
|
103
|
-
const walk = (baseDir: string) => {
|
|
104
|
-
const results = {
|
|
105
|
-
directories: [] as string[],
|
|
106
|
-
files: [] as string[],
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
110
|
-
|
|
111
|
-
for (const entry of entries) {
|
|
112
|
-
const name = entry.name;
|
|
113
|
-
const path = join(baseDir, name);
|
|
114
|
-
const isDir = entry.isDirectory();
|
|
115
|
-
|
|
116
|
-
if (isDir && directories) {
|
|
117
|
-
results.directories.push(path);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!isDir && files) {
|
|
121
|
-
results.files.push(path);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (isDir) {
|
|
125
|
-
const subResults = walk(path);
|
|
126
|
-
results.files.push(...subResults.files);
|
|
127
|
-
results.directories.push(...subResults.directories);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return results;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return walk(this.container.paths.resolve(basePath));
|
|
126
|
+
readJson(path: string) {
|
|
127
|
+
return JSON.parse(this.readFile(path) as string)
|
|
135
128
|
}
|
|
136
129
|
|
|
137
130
|
/**
|
|
138
|
-
* Asynchronously and
|
|
131
|
+
* Asynchronously reads and parses a JSON file.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} path - The path to the JSON file
|
|
134
|
+
* @returns {Promise<any>} A promise that resolves to the parsed JSON content
|
|
135
|
+
* @throws {Error} Throws an error if the file doesn't exist, cannot be read, or contains invalid JSON
|
|
139
136
|
*
|
|
140
|
-
* @param {string} baseDir - The base directory path to start walking from
|
|
141
|
-
* @param {WalkOptions} options - Options to configure the walk behavior
|
|
142
|
-
* @param {boolean} [options.directories=true] - Whether to include directories in results
|
|
143
|
-
* @param {boolean} [options.files=true] - Whether to include files in results
|
|
144
|
-
* @param {string | string[]} [options.exclude=[]] - Patterns to exclude from results
|
|
145
|
-
* @param {string | string[]} [options.include=[]] - Patterns to include in results
|
|
146
|
-
* @returns {Promise<{ directories: string[], files: string[] }>} Promise resolving to object with directory and file paths
|
|
147
|
-
* @throws {Error} Throws an error if the directory cannot be accessed
|
|
148
|
-
*
|
|
149
137
|
* @example
|
|
150
138
|
* ```typescript
|
|
151
|
-
* const
|
|
152
|
-
* console.log(
|
|
139
|
+
* const config = await fs.readJsonAsync('config.json')
|
|
140
|
+
* console.log(config.version)
|
|
153
141
|
* ```
|
|
154
142
|
*/
|
|
155
|
-
async
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
exclude = [],
|
|
160
|
-
include = [],
|
|
161
|
-
} = options;
|
|
162
|
-
|
|
163
|
-
const walk = async (baseDir: string) => {
|
|
164
|
-
const results = {
|
|
165
|
-
directories: [] as string[],
|
|
166
|
-
files: [] as string[],
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
170
|
-
|
|
171
|
-
for (const entry of entries) {
|
|
172
|
-
const name = entry.name;
|
|
173
|
-
const path = join(baseDir, name);
|
|
174
|
-
const isDir = entry.isDirectory();
|
|
175
|
-
|
|
176
|
-
if (isDir && directories) {
|
|
177
|
-
results.directories.push(path);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!isDir && files) {
|
|
181
|
-
results.files.push(path);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (isDir) {
|
|
185
|
-
const subResults = await walk(path);
|
|
186
|
-
results.files.push(...subResults.files);
|
|
187
|
-
results.directories.push(...subResults.directories);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return results;
|
|
192
|
-
};
|
|
143
|
+
async readJsonAsync(path: string) {
|
|
144
|
+
const content = await this.readFileAsync(path)
|
|
145
|
+
return JSON.parse(content as string)
|
|
146
|
+
}
|
|
193
147
|
|
|
194
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Synchronously reads the contents of a directory.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} path - The directory path relative to the container's working directory
|
|
152
|
+
* @returns {string[]} An array of file and directory names
|
|
153
|
+
* @throws {Error} Throws an error if the directory doesn't exist or cannot be read
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const entries = fs.readdirSync('src')
|
|
158
|
+
* console.log(entries) // ['index.ts', 'utils.ts', 'components']
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
readdirSync(path: string) {
|
|
162
|
+
return readdirSync(this.container.paths.resolve(path))
|
|
195
163
|
}
|
|
196
164
|
|
|
197
165
|
/**
|
|
198
|
-
* Asynchronously
|
|
166
|
+
* Asynchronously reads the contents of a directory.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} path - The directory path relative to the container's working directory
|
|
169
|
+
* @returns {Promise<string[]>} A promise that resolves to an array of file and directory names
|
|
170
|
+
* @throws {Error} Throws an error if the directory doesn't exist or cannot be read
|
|
199
171
|
*
|
|
200
|
-
* @param {string} path - The file path where the file should be created
|
|
201
|
-
* @param {string} content - The content to write to the file
|
|
202
|
-
* @param {boolean} [overwrite=false] - Whether to overwrite the file if it already exists
|
|
203
|
-
* @returns {Promise<string>} A promise that resolves to the absolute file path
|
|
204
|
-
* @throws {Error} Throws an error if the file cannot be created or written
|
|
205
|
-
*
|
|
206
172
|
* @example
|
|
207
173
|
* ```typescript
|
|
208
|
-
* await fs.
|
|
209
|
-
* //
|
|
174
|
+
* const entries = await fs.readdir('src')
|
|
175
|
+
* console.log(entries) // ['index.ts', 'utils.ts', 'components']
|
|
210
176
|
* ```
|
|
211
177
|
*/
|
|
212
|
-
async
|
|
213
|
-
|
|
178
|
+
async readdir(path: string) {
|
|
179
|
+
return await readdir(this.container.paths.resolve(path))
|
|
180
|
+
}
|
|
214
181
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Write
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
218
185
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Synchronously writes content to a file.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} path - The file path where content should be written
|
|
190
|
+
* @param {Buffer | string} content - The content to write to the file
|
|
191
|
+
* @throws {Error} Throws an error if the file cannot be written
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* fs.writeFile('output.txt', 'Hello World')
|
|
196
|
+
* fs.writeFile('data.bin', Buffer.from([1, 2, 3, 4]))
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
writeFile(path: string, content: Buffer | string) {
|
|
200
|
+
writeFileSync(this.container.paths.resolve(path), content)
|
|
223
201
|
}
|
|
224
|
-
|
|
202
|
+
|
|
225
203
|
/**
|
|
226
204
|
* Asynchronously writes content to a file.
|
|
227
205
|
*
|
|
@@ -229,17 +207,52 @@ export class FS extends Feature {
|
|
|
229
207
|
* @param {Buffer | string} content - The content to write to the file
|
|
230
208
|
* @returns {Promise<void>} A promise that resolves when the file is written
|
|
231
209
|
* @throws {Error} Throws an error if the file cannot be written
|
|
232
|
-
*
|
|
210
|
+
*
|
|
233
211
|
* @example
|
|
234
212
|
* ```typescript
|
|
235
213
|
* await fs.writeFileAsync('output.txt', 'Hello World')
|
|
236
214
|
* await fs.writeFileAsync('data.bin', Buffer.from([1, 2, 3, 4]))
|
|
237
215
|
* ```
|
|
238
216
|
*/
|
|
239
|
-
async writeFileAsync(path:string, content: Buffer | string) {
|
|
217
|
+
async writeFileAsync(path: string, content: Buffer | string) {
|
|
240
218
|
return writeFile(this.container.paths.resolve(path), content)
|
|
241
219
|
}
|
|
242
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Synchronously writes an object to a file as JSON.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} path - The file path where the JSON should be written
|
|
225
|
+
* @param {any} data - The data to serialize as JSON
|
|
226
|
+
* @param {number} [indent=2] - The number of spaces to use for indentation
|
|
227
|
+
* @throws {Error} Throws an error if the file cannot be written
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* fs.writeJson('config.json', { version: '1.0.0', debug: false })
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
writeJson(path: string, data: any, indent: number = 2) {
|
|
235
|
+
this.writeFile(path, JSON.stringify(data, null, indent) + '\n')
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Asynchronously writes an object to a file as JSON.
|
|
240
|
+
*
|
|
241
|
+
* @param {string} path - The file path where the JSON should be written
|
|
242
|
+
* @param {any} data - The data to serialize as JSON
|
|
243
|
+
* @param {number} [indent=2] - The number of spaces to use for indentation
|
|
244
|
+
* @returns {Promise<void>} A promise that resolves when the file is written
|
|
245
|
+
* @throws {Error} Throws an error if the file cannot be written
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* await fs.writeJsonAsync('config.json', { version: '1.0.0', debug: false })
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
async writeJsonAsync(path: string, data: any, indent: number = 2) {
|
|
253
|
+
return this.writeFileAsync(path, JSON.stringify(data, null, indent) + '\n')
|
|
254
|
+
}
|
|
255
|
+
|
|
243
256
|
/**
|
|
244
257
|
* Synchronously appends content to a file.
|
|
245
258
|
*
|
|
@@ -271,27 +284,9 @@ export class FS extends Feature {
|
|
|
271
284
|
return appendFile(this.container.paths.resolve(path), content)
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
* @param {string} path - The directory path to create
|
|
278
|
-
* @returns {string} The resolved directory path
|
|
279
|
-
* @throws {Error} Throws an error if the directory cannot be created
|
|
280
|
-
*
|
|
281
|
-
* @example
|
|
282
|
-
* ```typescript
|
|
283
|
-
* fs.ensureFolder('logs/debug')
|
|
284
|
-
* // Creates logs and logs/debug directories if they don't exist
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
ensureFolder(path: string) {
|
|
288
|
-
mkdirSync(this.container.paths.resolve(path), { recursive: true });
|
|
289
|
-
return this.container.paths.resolve(path);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
mkdirp(folder: string) {
|
|
293
|
-
return this.ensureFolder(folder)
|
|
294
|
-
}
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// Ensure (create if missing)
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
295
290
|
|
|
296
291
|
/**
|
|
297
292
|
* Synchronously ensures a file exists with the specified content, creating directories as needed.
|
|
@@ -301,11 +296,10 @@ export class FS extends Feature {
|
|
|
301
296
|
* @param {boolean} [overwrite=false] - Whether to overwrite the file if it already exists
|
|
302
297
|
* @returns {string} The resolved file path
|
|
303
298
|
* @throws {Error} Throws an error if the file cannot be created or written
|
|
304
|
-
*
|
|
299
|
+
*
|
|
305
300
|
* @example
|
|
306
301
|
* ```typescript
|
|
307
302
|
* fs.ensureFile('logs/app.log', '', false)
|
|
308
|
-
* // Creates logs directory and app.log file if they don't exist
|
|
309
303
|
* ```
|
|
310
304
|
*/
|
|
311
305
|
ensureFile(path: string, content: string, overwrite = false) {
|
|
@@ -322,81 +316,101 @@ export class FS extends Feature {
|
|
|
322
316
|
}
|
|
323
317
|
|
|
324
318
|
/**
|
|
325
|
-
*
|
|
319
|
+
* Asynchronously ensures a file exists with the specified content, creating directories as needed.
|
|
320
|
+
*
|
|
321
|
+
* @param {string} path - The file path where the file should be created
|
|
322
|
+
* @param {string} content - The content to write to the file
|
|
323
|
+
* @param {boolean} [overwrite=false] - Whether to overwrite the file if it already exists
|
|
324
|
+
* @returns {Promise<string>} A promise that resolves to the absolute file path
|
|
325
|
+
* @throws {Error} Throws an error if the file cannot be created or written
|
|
326
326
|
*
|
|
327
|
-
* @param {string} fileName - The name of the file to search for
|
|
328
|
-
* @param {object} [options={}] - Options for the search
|
|
329
|
-
* @param {string} [options.cwd] - The directory to start searching from (defaults to container.cwd)
|
|
330
|
-
* @returns {string | null} The absolute path to the found file, or null if not found
|
|
331
|
-
*
|
|
332
327
|
* @example
|
|
333
328
|
* ```typescript
|
|
334
|
-
*
|
|
335
|
-
* if (packageJson) {
|
|
336
|
-
* console.log(`Found package.json at: ${packageJson}`)
|
|
337
|
-
* }
|
|
329
|
+
* await fs.ensureFileAsync('config/settings.json', '{}', true)
|
|
338
330
|
* ```
|
|
339
331
|
*/
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
let startAt = cwd;
|
|
343
|
-
|
|
344
|
-
// walk up the tree until we find the fileName exists
|
|
345
|
-
if (this.exists(join(startAt, fileName))) {
|
|
346
|
-
return resolve(startAt, fileName);
|
|
347
|
-
}
|
|
332
|
+
async ensureFileAsync(path: string, content: string, overwrite = false) {
|
|
333
|
+
path = this.container.paths.resolve(path);
|
|
348
334
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
startAt = dirname(startAt);
|
|
352
|
-
if (this.exists(join(startAt, fileName))) {
|
|
353
|
-
return resolve(startAt, fileName);
|
|
354
|
-
}
|
|
335
|
+
if (this.exists(path) && !overwrite) {
|
|
336
|
+
return path;
|
|
355
337
|
}
|
|
356
338
|
|
|
357
|
-
|
|
339
|
+
const { dir } = this.container.paths.parse(path);
|
|
340
|
+
await mkdir(dir, { recursive: true });
|
|
341
|
+
await writeFile(path, content);
|
|
342
|
+
return path;
|
|
358
343
|
}
|
|
359
344
|
|
|
360
345
|
/**
|
|
361
|
-
*
|
|
346
|
+
* Synchronously ensures a directory exists, creating parent directories as needed.
|
|
347
|
+
*
|
|
348
|
+
* @param {string} path - The directory path to create
|
|
349
|
+
* @returns {string} The resolved directory path
|
|
350
|
+
* @throws {Error} Throws an error if the directory cannot be created
|
|
362
351
|
*
|
|
363
|
-
* @param {string} path - The path to check for existence
|
|
364
|
-
* @returns {Promise<boolean>} A promise that resolves to true if the path exists, false otherwise
|
|
365
|
-
*
|
|
366
352
|
* @example
|
|
367
353
|
* ```typescript
|
|
368
|
-
*
|
|
369
|
-
* console.log('Config file exists!')
|
|
370
|
-
* }
|
|
354
|
+
* fs.ensureFolder('logs/debug')
|
|
371
355
|
* ```
|
|
372
356
|
*/
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const exists = await stat(filePath)
|
|
377
|
-
.then(() => true)
|
|
378
|
-
.catch((e) => false);
|
|
379
|
-
|
|
380
|
-
return exists
|
|
357
|
+
ensureFolder(path: string) {
|
|
358
|
+
mkdirSync(this.container.paths.resolve(path), { recursive: true });
|
|
359
|
+
return this.container.paths.resolve(path);
|
|
381
360
|
}
|
|
382
361
|
|
|
383
362
|
/**
|
|
384
|
-
*
|
|
363
|
+
* Asynchronously ensures a directory exists, creating parent directories as needed.
|
|
385
364
|
*
|
|
386
|
-
* @param {string} path - The path to
|
|
387
|
-
* @returns {
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
365
|
+
* @param {string} path - The directory path to create
|
|
366
|
+
* @returns {Promise<string>} A promise that resolves to the resolved directory path
|
|
367
|
+
* @throws {Error} Throws an error if the directory cannot be created
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* await fs.ensureFolderAsync('logs/debug')
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
async ensureFolderAsync(path: string) {
|
|
375
|
+
const resolved = this.container.paths.resolve(path);
|
|
376
|
+
await mkdir(resolved, { recursive: true });
|
|
377
|
+
return resolved;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Alias for ensureFolder. Synchronously creates a directory and all parent directories.
|
|
382
|
+
*
|
|
383
|
+
* @param {string} folder - The directory path to create
|
|
384
|
+
* @returns {string} The resolved directory path
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```typescript
|
|
388
|
+
* fs.mkdirp('deep/nested/path')
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
mkdirp(folder: string) {
|
|
392
|
+
return this.ensureFolder(folder)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ---------------------------------------------------------------------------
|
|
396
|
+
// Existence & stat
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Synchronously checks if a file or directory exists.
|
|
401
|
+
*
|
|
402
|
+
* @param {string} path - The path to check for existence
|
|
403
|
+
* @returns {boolean} True if the path exists, false otherwise
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
391
407
|
* if (fs.exists('config.json')) {
|
|
392
408
|
* console.log('Config file exists!')
|
|
393
409
|
* }
|
|
394
410
|
* ```
|
|
395
411
|
*/
|
|
396
412
|
exists(path: string): boolean {
|
|
397
|
-
const
|
|
398
|
-
const filePath = container.paths.resolve(path);
|
|
399
|
-
|
|
413
|
+
const filePath = this.container.paths.resolve(path);
|
|
400
414
|
try {
|
|
401
415
|
statSync(filePath);
|
|
402
416
|
return true;
|
|
@@ -405,62 +419,199 @@ export class FS extends Feature {
|
|
|
405
419
|
}
|
|
406
420
|
}
|
|
407
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Alias for exists. Synchronously checks if a file or directory exists.
|
|
424
|
+
*
|
|
425
|
+
* @param {string} path - The path to check for existence
|
|
426
|
+
* @returns {boolean} True if the path exists, false otherwise
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* if (fs.existsSync('config.json')) {
|
|
431
|
+
* console.log('Config file exists!')
|
|
432
|
+
* }
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
408
435
|
existsSync(path: string): boolean {
|
|
409
|
-
|
|
436
|
+
return this.exists(path)
|
|
410
437
|
}
|
|
411
438
|
|
|
412
439
|
/**
|
|
413
|
-
* Asynchronously
|
|
440
|
+
* Asynchronously checks if a file or directory exists.
|
|
441
|
+
*
|
|
442
|
+
* @param {string} path - The path to check for existence
|
|
443
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the path exists, false otherwise
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* if (await fs.existsAsync('config.json')) {
|
|
448
|
+
* console.log('Config file exists!')
|
|
449
|
+
* }
|
|
450
|
+
* ```
|
|
451
|
+
*/
|
|
452
|
+
async existsAsync(path: string) {
|
|
453
|
+
const filePath = this.container.paths.resolve(path);
|
|
454
|
+
return stat(filePath).then(() => true).catch(() => false)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Synchronously returns the stat object for a file or directory.
|
|
459
|
+
*
|
|
460
|
+
* @param {string} path - The path to stat
|
|
461
|
+
* @returns {import('fs').Stats} The Stats object with size, timestamps, and type checks
|
|
462
|
+
* @throws {Error} Throws an error if the path doesn't exist
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```typescript
|
|
466
|
+
* const info = fs.stat('package.json')
|
|
467
|
+
* console.log(info.size, info.mtime)
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
stat(path: string) {
|
|
471
|
+
return statSync(this.container.paths.resolve(path))
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Asynchronously returns the stat object for a file or directory.
|
|
476
|
+
*
|
|
477
|
+
* @param {string} path - The path to stat
|
|
478
|
+
* @returns {Promise<import('fs').Stats>} A promise that resolves to the Stats object
|
|
479
|
+
* @throws {Error} Throws an error if the path doesn't exist
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* const info = await fs.statAsync('package.json')
|
|
484
|
+
* console.log(info.size, info.mtime)
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
async statAsync(path: string) {
|
|
488
|
+
return stat(this.container.paths.resolve(path))
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Synchronously checks if a path is a file.
|
|
493
|
+
*
|
|
494
|
+
* @param {string} path - The path to check
|
|
495
|
+
* @returns {boolean} True if the path is a file, false otherwise
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```typescript
|
|
499
|
+
* if (fs.isFile('package.json')) {
|
|
500
|
+
* console.log('It is a file')
|
|
501
|
+
* }
|
|
502
|
+
* ```
|
|
503
|
+
*/
|
|
504
|
+
isFile(path: string): boolean {
|
|
505
|
+
try {
|
|
506
|
+
return statSync(this.container.paths.resolve(path)).isFile()
|
|
507
|
+
} catch {
|
|
508
|
+
return false
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Asynchronously checks if a path is a file.
|
|
514
|
+
*
|
|
515
|
+
* @param {string} path - The path to check
|
|
516
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the path is a file
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
* ```typescript
|
|
520
|
+
* if (await fs.isFileAsync('package.json')) {
|
|
521
|
+
* console.log('It is a file')
|
|
522
|
+
* }
|
|
523
|
+
* ```
|
|
524
|
+
*/
|
|
525
|
+
async isFileAsync(path: string): Promise<boolean> {
|
|
526
|
+
return stat(this.container.paths.resolve(path)).then(s => s.isFile()).catch(() => false)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Synchronously checks if a path is a directory.
|
|
531
|
+
*
|
|
532
|
+
* @param {string} path - The path to check
|
|
533
|
+
* @returns {boolean} True if the path is a directory, false otherwise
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* if (fs.isDirectory('src')) {
|
|
538
|
+
* console.log('It is a directory')
|
|
539
|
+
* }
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
isDirectory(path: string): boolean {
|
|
543
|
+
try {
|
|
544
|
+
return statSync(this.container.paths.resolve(path)).isDirectory()
|
|
545
|
+
} catch {
|
|
546
|
+
return false
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Asynchronously checks if a path is a directory.
|
|
552
|
+
*
|
|
553
|
+
* @param {string} path - The path to check
|
|
554
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the path is a directory
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```typescript
|
|
558
|
+
* if (await fs.isDirectoryAsync('src')) {
|
|
559
|
+
* console.log('It is a directory')
|
|
560
|
+
* }
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
563
|
+
async isDirectoryAsync(path: string): Promise<boolean> {
|
|
564
|
+
return stat(this.container.paths.resolve(path)).then(s => s.isDirectory()).catch(() => false)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
// Delete
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Synchronously removes a file.
|
|
414
573
|
*
|
|
415
574
|
* @param {string} path - The path of the file to remove
|
|
416
|
-
* @returns {Promise<void>} A promise that resolves when the file is removed
|
|
417
575
|
* @throws {Error} Throws an error if the file cannot be removed or doesn't exist
|
|
418
|
-
*
|
|
576
|
+
*
|
|
419
577
|
* @example
|
|
420
578
|
* ```typescript
|
|
421
|
-
*
|
|
579
|
+
* fs.rmSync('temp/cache.tmp')
|
|
422
580
|
* ```
|
|
423
581
|
*/
|
|
424
|
-
|
|
425
|
-
|
|
582
|
+
rmSync(path: string) {
|
|
583
|
+
nodeRmSync(this.container.paths.resolve(path), { force: true })
|
|
426
584
|
}
|
|
427
585
|
|
|
428
586
|
/**
|
|
429
|
-
*
|
|
587
|
+
* Asynchronously removes a file.
|
|
588
|
+
*
|
|
589
|
+
* @param {string} path - The path of the file to remove
|
|
590
|
+
* @returns {Promise<void>} A promise that resolves when the file is removed
|
|
591
|
+
* @throws {Error} Throws an error if the file cannot be removed or doesn't exist
|
|
430
592
|
*
|
|
431
|
-
* @param {string} path - The path to the JSON file
|
|
432
|
-
* @returns {any} The parsed JSON content
|
|
433
|
-
* @throws {Error} Throws an error if the file doesn't exist, cannot be read, or contains invalid JSON
|
|
434
|
-
*
|
|
435
593
|
* @example
|
|
436
594
|
* ```typescript
|
|
437
|
-
*
|
|
438
|
-
* console.log(config.version)
|
|
595
|
+
* await fs.rm('temp/cache.tmp')
|
|
439
596
|
* ```
|
|
440
597
|
*/
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const filePath = container.paths.resolve(path);
|
|
444
|
-
return JSON.parse(readFileSync(filePath).toString());
|
|
598
|
+
async rm(path: string) {
|
|
599
|
+
return await unlink(this.container.paths.resolve(path));
|
|
445
600
|
}
|
|
446
601
|
|
|
447
602
|
/**
|
|
448
|
-
* Synchronously
|
|
603
|
+
* Synchronously removes a directory and all its contents.
|
|
604
|
+
*
|
|
605
|
+
* @param {string} dirPath - The path of the directory to remove
|
|
606
|
+
* @throws {Error} Throws an error if the directory cannot be removed
|
|
449
607
|
*
|
|
450
|
-
* @param {string} path - The path to the file
|
|
451
|
-
* @returns {string} The file contents as a string
|
|
452
|
-
* @throws {Error} Throws an error if the file doesn't exist or cannot be read
|
|
453
|
-
*
|
|
454
608
|
* @example
|
|
455
609
|
* ```typescript
|
|
456
|
-
*
|
|
457
|
-
* console.log(content)
|
|
610
|
+
* fs.rmdirSync('temp/cache')
|
|
458
611
|
* ```
|
|
459
612
|
*/
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const filePath = container.paths.resolve(path);
|
|
463
|
-
return readFileSync(filePath).toString();
|
|
613
|
+
rmdirSync(dirPath: string) {
|
|
614
|
+
nodeRmSync(this.container.paths.resolve(dirPath), { recursive: true, force: true })
|
|
464
615
|
}
|
|
465
616
|
|
|
466
617
|
/**
|
|
@@ -469,17 +620,323 @@ export class FS extends Feature {
|
|
|
469
620
|
* @param {string} dirPath - The path of the directory to remove
|
|
470
621
|
* @returns {Promise<void>} A promise that resolves when the directory is removed
|
|
471
622
|
* @throws {Error} Throws an error if the directory cannot be removed
|
|
472
|
-
*
|
|
623
|
+
*
|
|
473
624
|
* @example
|
|
474
625
|
* ```typescript
|
|
475
626
|
* await fs.rmdir('temp/cache')
|
|
476
|
-
* // Removes the cache directory and all its contents
|
|
477
627
|
* ```
|
|
478
628
|
*/
|
|
479
629
|
async rmdir(dirPath: string) {
|
|
480
630
|
await rimraf(this.container.paths.resolve(dirPath));
|
|
481
631
|
}
|
|
482
632
|
|
|
633
|
+
// ---------------------------------------------------------------------------
|
|
634
|
+
// Copy & Move
|
|
635
|
+
// ---------------------------------------------------------------------------
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Synchronously copies a file or directory. Auto-detects whether the source is a file or directory
|
|
639
|
+
* and handles each appropriately (recursive for directories).
|
|
640
|
+
*
|
|
641
|
+
* @param {string} src - The source path to copy from
|
|
642
|
+
* @param {string} dest - The destination path to copy to
|
|
643
|
+
* @param {object} [options={}] - Copy options
|
|
644
|
+
* @param {boolean} [options.overwrite=true] - Whether to overwrite existing files at the destination
|
|
645
|
+
* @throws {Error} Throws an error if the source doesn't exist or the copy fails
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```typescript
|
|
649
|
+
* fs.copy('src/config.json', 'backup/config.json')
|
|
650
|
+
* fs.copy('src', 'backup/src')
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
653
|
+
copy(src: string, dest: string, options: { overwrite?: boolean } = {}) {
|
|
654
|
+
const { overwrite = true } = options
|
|
655
|
+
const resolvedSrc = this.container.paths.resolve(src)
|
|
656
|
+
const resolvedDest = this.container.paths.resolve(dest)
|
|
657
|
+
cpSync(resolvedSrc, resolvedDest, { recursive: true, force: overwrite })
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Asynchronously copies a file or directory. Auto-detects whether the source is a file or directory
|
|
662
|
+
* and handles each appropriately (recursive for directories).
|
|
663
|
+
*
|
|
664
|
+
* @param {string} src - The source path to copy from
|
|
665
|
+
* @param {string} dest - The destination path to copy to
|
|
666
|
+
* @param {object} [options={}] - Copy options
|
|
667
|
+
* @param {boolean} [options.overwrite=true] - Whether to overwrite existing files at the destination
|
|
668
|
+
* @returns {Promise<void>} A promise that resolves when the copy is complete
|
|
669
|
+
* @throws {Error} Throws an error if the source doesn't exist or the copy fails
|
|
670
|
+
*
|
|
671
|
+
* @example
|
|
672
|
+
* ```typescript
|
|
673
|
+
* await fs.copyAsync('src/config.json', 'backup/config.json')
|
|
674
|
+
* await fs.copyAsync('src', 'backup/src')
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
async copyAsync(src: string, dest: string, options: { overwrite?: boolean } = {}) {
|
|
678
|
+
const { overwrite = true } = options
|
|
679
|
+
const resolvedSrc = this.container.paths.resolve(src)
|
|
680
|
+
const resolvedDest = this.container.paths.resolve(dest)
|
|
681
|
+
await cp(resolvedSrc, resolvedDest, { recursive: true, force: overwrite })
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Synchronously moves (renames) a file or directory. Falls back to copy + delete for cross-device moves.
|
|
686
|
+
*
|
|
687
|
+
* @param {string} src - The source path to move from
|
|
688
|
+
* @param {string} dest - The destination path to move to
|
|
689
|
+
* @throws {Error} Throws an error if the source doesn't exist or the move fails
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* ```typescript
|
|
693
|
+
* fs.move('temp/draft.txt', 'final/document.txt')
|
|
694
|
+
* fs.move('old-dir', 'new-dir')
|
|
695
|
+
* ```
|
|
696
|
+
*/
|
|
697
|
+
move(src: string, dest: string) {
|
|
698
|
+
const resolvedSrc = this.container.paths.resolve(src)
|
|
699
|
+
const resolvedDest = this.container.paths.resolve(dest)
|
|
700
|
+
const destDir = dirname(resolvedDest)
|
|
701
|
+
mkdirSync(destDir, { recursive: true })
|
|
702
|
+
try {
|
|
703
|
+
renameSync(resolvedSrc, resolvedDest)
|
|
704
|
+
} catch (err: any) {
|
|
705
|
+
if (err.code === 'EXDEV') {
|
|
706
|
+
cpSync(resolvedSrc, resolvedDest, { recursive: true, force: true })
|
|
707
|
+
nodeRmSync(resolvedSrc, { recursive: true, force: true })
|
|
708
|
+
} else {
|
|
709
|
+
throw err
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Asynchronously moves (renames) a file or directory. Falls back to copy + delete for cross-device moves.
|
|
716
|
+
*
|
|
717
|
+
* @param {string} src - The source path to move from
|
|
718
|
+
* @param {string} dest - The destination path to move to
|
|
719
|
+
* @returns {Promise<void>} A promise that resolves when the move is complete
|
|
720
|
+
* @throws {Error} Throws an error if the source doesn't exist or the move fails
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* ```typescript
|
|
724
|
+
* await fs.moveAsync('temp/draft.txt', 'final/document.txt')
|
|
725
|
+
* await fs.moveAsync('old-dir', 'new-dir')
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
async moveAsync(src: string, dest: string) {
|
|
729
|
+
const resolvedSrc = this.container.paths.resolve(src)
|
|
730
|
+
const resolvedDest = this.container.paths.resolve(dest)
|
|
731
|
+
const destDir = dirname(resolvedDest)
|
|
732
|
+
await mkdir(destDir, { recursive: true })
|
|
733
|
+
try {
|
|
734
|
+
await rename(resolvedSrc, resolvedDest)
|
|
735
|
+
} catch (err: any) {
|
|
736
|
+
if (err.code === 'EXDEV') {
|
|
737
|
+
await cp(resolvedSrc, resolvedDest, { recursive: true, force: true })
|
|
738
|
+
await nodeRm(resolvedSrc, { recursive: true, force: true })
|
|
739
|
+
} else {
|
|
740
|
+
throw err
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// ---------------------------------------------------------------------------
|
|
746
|
+
// Walk
|
|
747
|
+
// ---------------------------------------------------------------------------
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Recursively walks a directory and returns arrays of file and directory paths.
|
|
751
|
+
* By default paths are absolute. Pass `relative: true` to get paths relative to `basePath`.
|
|
752
|
+
* Supports filtering with exclude and include glob patterns.
|
|
753
|
+
*
|
|
754
|
+
* @param {string} basePath - The base directory path to start walking from
|
|
755
|
+
* @param {WalkOptions} options - Options to configure the walk behavior
|
|
756
|
+
* @param {boolean} [options.directories=true] - Whether to include directories in results
|
|
757
|
+
* @param {boolean} [options.files=true] - Whether to include files in results
|
|
758
|
+
* @param {string | string[]} [options.exclude=[]] - Glob patterns to exclude (e.g. 'node_modules', '*.log')
|
|
759
|
+
* @param {string | string[]} [options.include=[]] - Glob patterns to include (only matching paths are returned)
|
|
760
|
+
* @param {boolean} [options.relative=false] - When true, returned paths are relative to basePath
|
|
761
|
+
* @returns {{ directories: string[], files: string[] }} Object containing arrays of directory and file paths
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```typescript
|
|
765
|
+
* const result = fs.walk('src', { files: true, directories: false })
|
|
766
|
+
* const filtered = fs.walk('.', { exclude: ['node_modules', '.git'], include: ['*.ts'] })
|
|
767
|
+
* const relative = fs.walk('inbox', { relative: true }) // => { files: ['contact-1.json', ...] }
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
walk(basePath: string, options: WalkOptions = {}) {
|
|
771
|
+
const {
|
|
772
|
+
directories = true,
|
|
773
|
+
files = true,
|
|
774
|
+
exclude = [],
|
|
775
|
+
include = [],
|
|
776
|
+
relative: useRelative = false,
|
|
777
|
+
} = options;
|
|
778
|
+
|
|
779
|
+
const excludePatterns = Array.isArray(exclude) ? exclude : [exclude]
|
|
780
|
+
const includePatterns = Array.isArray(include) ? include : [include]
|
|
781
|
+
const resolvedBase = this.container.paths.resolve(basePath)
|
|
782
|
+
|
|
783
|
+
const walk = (baseDir: string) => {
|
|
784
|
+
const results = {
|
|
785
|
+
directories: [] as string[],
|
|
786
|
+
files: [] as string[],
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
790
|
+
|
|
791
|
+
for (const entry of entries) {
|
|
792
|
+
const name = entry.name;
|
|
793
|
+
const fullPath = join(baseDir, name);
|
|
794
|
+
const relativePath = relative(resolvedBase, fullPath)
|
|
795
|
+
const outputPath = useRelative ? relativePath : fullPath;
|
|
796
|
+
const isDir = entry.isDirectory();
|
|
797
|
+
|
|
798
|
+
if (excludePatterns.length && matchesPattern(relativePath, excludePatterns)) {
|
|
799
|
+
continue
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const passes = !includePatterns.length || matchesPattern(relativePath, includePatterns)
|
|
803
|
+
|
|
804
|
+
if (isDir && directories && passes) {
|
|
805
|
+
results.directories.push(outputPath);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (!isDir && files && passes) {
|
|
809
|
+
results.files.push(outputPath);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (isDir) {
|
|
813
|
+
const subResults = walk(fullPath);
|
|
814
|
+
results.files.push(...subResults.files);
|
|
815
|
+
results.directories.push(...subResults.directories);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return results;
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
return walk(resolvedBase);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Asynchronously and recursively walks a directory and returns arrays of file and directory paths.
|
|
827
|
+
* By default paths are absolute. Pass `relative: true` to get paths relative to `baseDir`.
|
|
828
|
+
* Supports filtering with exclude and include glob patterns.
|
|
829
|
+
*
|
|
830
|
+
* @param {string} baseDir - The base directory path to start walking from
|
|
831
|
+
* @param {WalkOptions} options - Options to configure the walk behavior
|
|
832
|
+
* @param {boolean} [options.directories=true] - Whether to include directories in results
|
|
833
|
+
* @param {boolean} [options.files=true] - Whether to include files in results
|
|
834
|
+
* @param {string | string[]} [options.exclude=[]] - Glob patterns to exclude (e.g. 'node_modules', '.git')
|
|
835
|
+
* @param {string | string[]} [options.include=[]] - Glob patterns to include (only matching paths are returned)
|
|
836
|
+
* @param {boolean} [options.relative=false] - When true, returned paths are relative to baseDir
|
|
837
|
+
* @returns {Promise<{ directories: string[], files: string[] }>} Promise resolving to object with directory and file paths
|
|
838
|
+
* @throws {Error} Throws an error if the directory cannot be accessed
|
|
839
|
+
*
|
|
840
|
+
* @example
|
|
841
|
+
* ```typescript
|
|
842
|
+
* const result = await fs.walkAsync('src', { exclude: ['node_modules'] })
|
|
843
|
+
* const files = await fs.walkAsync('inbox', { relative: true })
|
|
844
|
+
* // files.files => ['contact-1.json', 'subfolder/file.txt', ...]
|
|
845
|
+
* ```
|
|
846
|
+
*/
|
|
847
|
+
async walkAsync(baseDir: string, options: WalkOptions = {}) {
|
|
848
|
+
const {
|
|
849
|
+
directories = true,
|
|
850
|
+
files = true,
|
|
851
|
+
exclude = [],
|
|
852
|
+
include = [],
|
|
853
|
+
relative: useRelative = false,
|
|
854
|
+
} = options;
|
|
855
|
+
|
|
856
|
+
const excludePatterns = Array.isArray(exclude) ? exclude : [exclude]
|
|
857
|
+
const includePatterns = Array.isArray(include) ? include : [include]
|
|
858
|
+
const resolvedBase = this.container.paths.resolve(baseDir)
|
|
859
|
+
|
|
860
|
+
const walk = async (currentDir: string) => {
|
|
861
|
+
const results = {
|
|
862
|
+
directories: [] as string[],
|
|
863
|
+
files: [] as string[],
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
867
|
+
|
|
868
|
+
for (const entry of entries) {
|
|
869
|
+
const name = entry.name;
|
|
870
|
+
const fullPath = join(currentDir, name);
|
|
871
|
+
const relativePath = relative(resolvedBase, fullPath)
|
|
872
|
+
const outputPath = useRelative ? relativePath : fullPath;
|
|
873
|
+
const isDir = entry.isDirectory();
|
|
874
|
+
|
|
875
|
+
if (excludePatterns.length && matchesPattern(relativePath, excludePatterns)) {
|
|
876
|
+
continue
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const passes = !includePatterns.length || matchesPattern(relativePath, includePatterns)
|
|
880
|
+
|
|
881
|
+
if (isDir && directories && passes) {
|
|
882
|
+
results.directories.push(outputPath);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (!isDir && files && passes) {
|
|
886
|
+
results.files.push(outputPath);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (isDir) {
|
|
890
|
+
const subResults = await walk(fullPath);
|
|
891
|
+
results.files.push(...subResults.files);
|
|
892
|
+
results.directories.push(...subResults.directories);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return results;
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
return walk(resolvedBase);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// ---------------------------------------------------------------------------
|
|
903
|
+
// Find Up
|
|
904
|
+
// ---------------------------------------------------------------------------
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Synchronously finds a file by walking up the directory tree from the current working directory.
|
|
908
|
+
*
|
|
909
|
+
* @param {string} fileName - The name of the file to search for
|
|
910
|
+
* @param {object} [options={}] - Options for the search
|
|
911
|
+
* @param {string} [options.cwd] - The directory to start searching from (defaults to container.cwd)
|
|
912
|
+
* @returns {string | null} The absolute path to the found file, or null if not found
|
|
913
|
+
*
|
|
914
|
+
* @example
|
|
915
|
+
* ```typescript
|
|
916
|
+
* const packageJson = fs.findUp('package.json')
|
|
917
|
+
* if (packageJson) {
|
|
918
|
+
* console.log(`Found package.json at: ${packageJson}`)
|
|
919
|
+
* }
|
|
920
|
+
* ```
|
|
921
|
+
*/
|
|
922
|
+
findUp(fileName: string, options: { cwd?: string } = {}): string | null {
|
|
923
|
+
const { cwd = this.container.cwd } = options;
|
|
924
|
+
let startAt = cwd;
|
|
925
|
+
|
|
926
|
+
if (this.exists(join(startAt, fileName))) {
|
|
927
|
+
return resolve(startAt, fileName);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
while (startAt !== dirname(startAt)) {
|
|
931
|
+
startAt = dirname(startAt);
|
|
932
|
+
if (this.exists(join(startAt, fileName))) {
|
|
933
|
+
return resolve(startAt, fileName);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
|
|
483
940
|
/**
|
|
484
941
|
* Asynchronously finds a file by walking up the directory tree.
|
|
485
942
|
*
|
|
@@ -488,8 +945,7 @@ export class FS extends Feature {
|
|
|
488
945
|
* @param {string} [options.cwd] - The directory to start searching from (defaults to container.cwd)
|
|
489
946
|
* @param {boolean} [options.multiple=false] - Whether to find multiple instances of the file
|
|
490
947
|
* @returns {Promise<string | string[] | null>} The path(s) to the found file(s), or null if not found
|
|
491
|
-
*
|
|
492
|
-
*
|
|
948
|
+
*
|
|
493
949
|
* @example
|
|
494
950
|
* ```typescript
|
|
495
951
|
* const packageJson = await fs.findUpAsync('package.json')
|
|
@@ -540,4 +996,4 @@ export class FS extends Feature {
|
|
|
540
996
|
}
|
|
541
997
|
}
|
|
542
998
|
|
|
543
|
-
export default FS
|
|
999
|
+
export default FS
|