@sylphx/flow 1.0.1 → 1.0.3
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/CHANGELOG.md +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- package/dist/shared/chunk-x66eh37x.js +0 -137
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async File Operations Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides async wrappers for common file operations with better error handling,
|
|
5
|
+
* concurrency control, and performance optimizations
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import * as fs from 'node:fs/promises';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
|
|
12
|
+
export interface FileOperationOptions {
|
|
13
|
+
encoding?: BufferEncoding;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
retryAttempts?: number;
|
|
16
|
+
retryDelay?: number;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FileStats {
|
|
21
|
+
size: number;
|
|
22
|
+
created: Date;
|
|
23
|
+
modified: Date;
|
|
24
|
+
accessed: Date;
|
|
25
|
+
isFile: boolean;
|
|
26
|
+
isDirectory: boolean;
|
|
27
|
+
permissions: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DirectoryEntry {
|
|
31
|
+
name: string;
|
|
32
|
+
path: string;
|
|
33
|
+
isFile: boolean;
|
|
34
|
+
isDirectory: boolean;
|
|
35
|
+
stats?: FileStats;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CopyOptions {
|
|
39
|
+
overwrite?: boolean;
|
|
40
|
+
preserveTimestamps?: boolean;
|
|
41
|
+
filter?: (source: string, dest: string) => boolean;
|
|
42
|
+
concurrency?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ReadDirOptions {
|
|
46
|
+
withFileTypes?: boolean;
|
|
47
|
+
recursive?: boolean;
|
|
48
|
+
includeStats?: boolean;
|
|
49
|
+
filter?: (entry: DirectoryEntry) => boolean;
|
|
50
|
+
maxDepth?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Async file operations with error handling and retries
|
|
55
|
+
*/
|
|
56
|
+
export class AsyncFileOperations {
|
|
57
|
+
private readonly defaultOptions: Required<FileOperationOptions> = {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
retryAttempts: 3,
|
|
60
|
+
retryDelay: 1000,
|
|
61
|
+
timeout: 30000,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Read file content with retries
|
|
66
|
+
*/
|
|
67
|
+
async readFile(filePath: string, options: FileOperationOptions = {}): Promise<string | Buffer> {
|
|
68
|
+
const opts = { ...this.defaultOptions, ...options };
|
|
69
|
+
|
|
70
|
+
return this.withRetry(
|
|
71
|
+
async () => {
|
|
72
|
+
if (opts.timeout) {
|
|
73
|
+
return await this.withTimeout(() => fs.readFile(filePath, opts.encoding), opts.timeout);
|
|
74
|
+
}
|
|
75
|
+
return await fs.readFile(filePath, opts.encoding);
|
|
76
|
+
},
|
|
77
|
+
opts.retryAttempts,
|
|
78
|
+
opts.retryDelay
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Write file content with retries and backup
|
|
84
|
+
*/
|
|
85
|
+
async writeFile(
|
|
86
|
+
filePath: string,
|
|
87
|
+
content: string | Buffer | NodeJS.ArrayBufferView,
|
|
88
|
+
options: FileOperationOptions & { createBackup?: boolean } = {}
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
const opts = { ...this.defaultOptions, createBackup: true, ...options };
|
|
91
|
+
|
|
92
|
+
// Create backup if requested and file exists
|
|
93
|
+
if (opts.createBackup && (await this.exists(filePath))) {
|
|
94
|
+
await this.createBackup(filePath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.withRetry(
|
|
98
|
+
async () => {
|
|
99
|
+
// Ensure directory exists
|
|
100
|
+
await this.ensureDir(path.dirname(filePath));
|
|
101
|
+
|
|
102
|
+
if (opts.timeout) {
|
|
103
|
+
return await this.withTimeout(
|
|
104
|
+
() => fs.writeFile(filePath, content, opts.encoding),
|
|
105
|
+
opts.timeout
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return await fs.writeFile(filePath, content, opts.encoding);
|
|
109
|
+
},
|
|
110
|
+
opts.retryAttempts,
|
|
111
|
+
opts.retryDelay
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Append to file with retries
|
|
117
|
+
*/
|
|
118
|
+
async appendFile(
|
|
119
|
+
filePath: string,
|
|
120
|
+
content: string | Buffer,
|
|
121
|
+
options: FileOperationOptions = {}
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
const opts = { ...this.defaultOptions, ...options };
|
|
124
|
+
|
|
125
|
+
return this.withRetry(
|
|
126
|
+
async () => {
|
|
127
|
+
// Ensure directory exists
|
|
128
|
+
await this.ensureDir(path.dirname(filePath));
|
|
129
|
+
|
|
130
|
+
if (opts.timeout) {
|
|
131
|
+
return await this.withTimeout(
|
|
132
|
+
() => fs.appendFile(filePath, content, opts.encoding),
|
|
133
|
+
opts.timeout
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return await fs.appendFile(filePath, content, opts.encoding);
|
|
137
|
+
},
|
|
138
|
+
opts.retryAttempts,
|
|
139
|
+
opts.retryDelay
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if file or directory exists
|
|
145
|
+
*/
|
|
146
|
+
async exists(filePath: string): Promise<boolean> {
|
|
147
|
+
try {
|
|
148
|
+
await fs.access(filePath);
|
|
149
|
+
return true;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get file statistics
|
|
157
|
+
*/
|
|
158
|
+
async getStats(filePath: string): Promise<FileStats> {
|
|
159
|
+
const stats = await fs.stat(filePath);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
size: stats.size,
|
|
163
|
+
created: stats.birthtime,
|
|
164
|
+
modified: stats.mtime,
|
|
165
|
+
accessed: stats.atime,
|
|
166
|
+
isFile: stats.isFile(),
|
|
167
|
+
isDirectory: stats.isDirectory(),
|
|
168
|
+
permissions: stats.mode.toString(8),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create directory recursively
|
|
174
|
+
*/
|
|
175
|
+
async ensureDir(dirPath: string): Promise<void> {
|
|
176
|
+
try {
|
|
177
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
// Check if directory already exists
|
|
180
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST')) {
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Remove file or directory recursively
|
|
188
|
+
*/
|
|
189
|
+
async remove(
|
|
190
|
+
targetPath: string,
|
|
191
|
+
options: { recursive?: boolean; force?: boolean } = {}
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
const { recursive = true, force = false } = options;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const stats = await fs.lstat(targetPath);
|
|
197
|
+
|
|
198
|
+
if (stats.isDirectory() && recursive) {
|
|
199
|
+
await fs.rm(targetPath, { recursive: true, force });
|
|
200
|
+
} else if (stats.isDirectory() && !recursive) {
|
|
201
|
+
throw new Error('Cannot remove directory without recursive option');
|
|
202
|
+
} else {
|
|
203
|
+
await fs.unlink(targetPath);
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (!force || !(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Copy file or directory
|
|
214
|
+
*/
|
|
215
|
+
async copy(source: string, destination: string, options: CopyOptions = {}): Promise<void> {
|
|
216
|
+
const { overwrite = false, preserveTimestamps = true, filter, concurrency = 10 } = options;
|
|
217
|
+
|
|
218
|
+
const sourceStats = await fs.lstat(source);
|
|
219
|
+
|
|
220
|
+
if (sourceStats.isDirectory()) {
|
|
221
|
+
await this.copyDirectory(source, destination, {
|
|
222
|
+
overwrite,
|
|
223
|
+
preserveTimestamps,
|
|
224
|
+
filter,
|
|
225
|
+
concurrency,
|
|
226
|
+
});
|
|
227
|
+
} else {
|
|
228
|
+
await this.copyFile(source, destination, {
|
|
229
|
+
overwrite,
|
|
230
|
+
preserveTimestamps,
|
|
231
|
+
filter,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Move file or directory
|
|
238
|
+
*/
|
|
239
|
+
async move(
|
|
240
|
+
source: string,
|
|
241
|
+
destination: string,
|
|
242
|
+
options: { overwrite?: boolean } = {}
|
|
243
|
+
): Promise<void> {
|
|
244
|
+
const { overwrite = false } = options;
|
|
245
|
+
|
|
246
|
+
// Check if destination exists
|
|
247
|
+
if ((await this.exists(destination)) && !overwrite) {
|
|
248
|
+
throw new Error(`Destination already exists: ${destination}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
await fs.rename(source, destination);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
// Fallback to copy + delete if rename fails (cross-device)
|
|
255
|
+
if (error instanceof Error && 'code' in error && error.code === 'EXDEV') {
|
|
256
|
+
await this.copy(source, destination, { overwrite });
|
|
257
|
+
await this.remove(source, { recursive: true });
|
|
258
|
+
} else {
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Read directory contents
|
|
266
|
+
*/
|
|
267
|
+
async readDir(dirPath: string, options: ReadDirOptions = {}): Promise<DirectoryEntry[]> {
|
|
268
|
+
const {
|
|
269
|
+
withFileTypes = false,
|
|
270
|
+
recursive = false,
|
|
271
|
+
includeStats = false,
|
|
272
|
+
filter,
|
|
273
|
+
maxDepth = 10,
|
|
274
|
+
} = options;
|
|
275
|
+
|
|
276
|
+
const results: DirectoryEntry[] = [];
|
|
277
|
+
|
|
278
|
+
const processDirectory = async (currentPath: string, currentDepth: number): Promise<void> => {
|
|
279
|
+
if (currentDepth > maxDepth) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const entries = await fs.readdir(currentPath, { withFileTypes });
|
|
285
|
+
|
|
286
|
+
const promises = entries.map(async (entry) => {
|
|
287
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
288
|
+
const directoryEntry: DirectoryEntry = {
|
|
289
|
+
name: entry.name,
|
|
290
|
+
path: fullPath,
|
|
291
|
+
isFile: entry.isFile(),
|
|
292
|
+
isDirectory: entry.isDirectory(),
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (includeStats) {
|
|
296
|
+
try {
|
|
297
|
+
directoryEntry.stats = await this.getStats(fullPath);
|
|
298
|
+
} catch (_error) {
|
|
299
|
+
// Skip if stats can't be retrieved
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Apply filter if provided
|
|
304
|
+
if (filter && !filter(directoryEntry)) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
results.push(directoryEntry);
|
|
309
|
+
|
|
310
|
+
// Recursively process subdirectories
|
|
311
|
+
if (recursive && entry.isDirectory()) {
|
|
312
|
+
await processDirectory(fullPath, currentDepth + 1);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await Promise.all(promises);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
throw new Error(`Failed to read directory ${currentPath}: ${error}`);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
await processDirectory(dirPath, 0);
|
|
323
|
+
return results;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Calculate file hash
|
|
328
|
+
*/
|
|
329
|
+
async calculateHash(filePath: string, algorithm = 'sha256'): Promise<string> {
|
|
330
|
+
const content = await this.readFile(filePath);
|
|
331
|
+
return createHash(algorithm)
|
|
332
|
+
.update(content as Buffer)
|
|
333
|
+
.digest('hex');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Watch file or directory for changes
|
|
338
|
+
*/
|
|
339
|
+
async watch(
|
|
340
|
+
targetPath: string,
|
|
341
|
+
callback: (eventType: string, filename: string | null) => void,
|
|
342
|
+
options: { recursive?: boolean } = {}
|
|
343
|
+
): Promise<fs.FSWatcher> {
|
|
344
|
+
const { recursive = false } = options;
|
|
345
|
+
|
|
346
|
+
return new Promise((resolve, reject) => {
|
|
347
|
+
try {
|
|
348
|
+
const watcher = fs.watch(targetPath, { recursive }, callback);
|
|
349
|
+
resolve(watcher);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
reject(error);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Create backup of file
|
|
358
|
+
*/
|
|
359
|
+
private async createBackup(filePath: string): Promise<string> {
|
|
360
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
361
|
+
const backupPath = `${filePath}.backup.${timestamp}`;
|
|
362
|
+
await this.copy(filePath, backupPath);
|
|
363
|
+
return backupPath;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Copy file with options
|
|
368
|
+
*/
|
|
369
|
+
private async copyFile(
|
|
370
|
+
source: string,
|
|
371
|
+
destination: string,
|
|
372
|
+
options: {
|
|
373
|
+
overwrite?: boolean;
|
|
374
|
+
preserveTimestamps?: boolean;
|
|
375
|
+
filter?: (source: string, dest: string) => boolean;
|
|
376
|
+
}
|
|
377
|
+
): Promise<void> {
|
|
378
|
+
const { overwrite = false, preserveTimestamps = true, filter } = options;
|
|
379
|
+
|
|
380
|
+
// Apply filter
|
|
381
|
+
if (filter && !filter(source, destination)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Check if destination exists
|
|
386
|
+
if ((await this.exists(destination)) && !overwrite) {
|
|
387
|
+
throw new Error(`Destination already exists: ${destination}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Ensure destination directory exists
|
|
391
|
+
await this.ensureDir(path.dirname(destination));
|
|
392
|
+
|
|
393
|
+
// Copy file
|
|
394
|
+
await fs.copyFile(source, destination);
|
|
395
|
+
|
|
396
|
+
// Preserve timestamps if requested
|
|
397
|
+
if (preserveTimestamps) {
|
|
398
|
+
const sourceStats = await fs.stat(source);
|
|
399
|
+
await fs.utimes(destination, sourceStats.atime, sourceStats.mtime);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Copy directory with options
|
|
405
|
+
*/
|
|
406
|
+
private async copyDirectory(
|
|
407
|
+
source: string,
|
|
408
|
+
destination: string,
|
|
409
|
+
options: {
|
|
410
|
+
overwrite?: boolean;
|
|
411
|
+
preserveTimestamps?: boolean;
|
|
412
|
+
filter?: (source: string, dest: string) => boolean;
|
|
413
|
+
concurrency?: number;
|
|
414
|
+
}
|
|
415
|
+
): Promise<void> {
|
|
416
|
+
const { concurrency = 10 } = options;
|
|
417
|
+
|
|
418
|
+
// Ensure destination directory exists
|
|
419
|
+
await this.ensureDir(destination);
|
|
420
|
+
|
|
421
|
+
// Get all entries
|
|
422
|
+
const entries = await this.readDir(source, { recursive: true });
|
|
423
|
+
|
|
424
|
+
// Process entries in batches
|
|
425
|
+
for (let i = 0; i < entries.length; i += concurrency) {
|
|
426
|
+
const batch = entries.slice(i, i + concurrency);
|
|
427
|
+
|
|
428
|
+
await Promise.all(
|
|
429
|
+
batch.map(async (entry) => {
|
|
430
|
+
const relativePath = path.relative(source, entry.path);
|
|
431
|
+
const destPath = path.join(destination, relativePath);
|
|
432
|
+
|
|
433
|
+
if (entry.isFile) {
|
|
434
|
+
await this.copyFile(entry.path, destPath, options);
|
|
435
|
+
} else if (entry.isDirectory) {
|
|
436
|
+
await this.ensureDir(destPath);
|
|
437
|
+
}
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Execute operation with retry logic
|
|
445
|
+
*/
|
|
446
|
+
private async withRetry<T>(
|
|
447
|
+
operation: () => Promise<T>,
|
|
448
|
+
attempts: number,
|
|
449
|
+
delay: number
|
|
450
|
+
): Promise<T> {
|
|
451
|
+
let lastError: Error;
|
|
452
|
+
|
|
453
|
+
for (let i = 0; i < attempts; i++) {
|
|
454
|
+
try {
|
|
455
|
+
return await operation();
|
|
456
|
+
} catch (error) {
|
|
457
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
458
|
+
|
|
459
|
+
if (i < attempts - 1) {
|
|
460
|
+
await this.sleep(delay * 2 ** i); // Exponential backoff
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
throw lastError!;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Execute operation with timeout
|
|
470
|
+
*/
|
|
471
|
+
private async withTimeout<T>(operation: () => Promise<T>, timeoutMs: number): Promise<T> {
|
|
472
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
473
|
+
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
return Promise.race([operation(), timeoutPromise]);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Sleep utility
|
|
481
|
+
*/
|
|
482
|
+
private sleep(ms: number): Promise<void> {
|
|
483
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Export singleton instance
|
|
488
|
+
export const asyncFileOps = new AsyncFileOperations();
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Convenience functions for common operations
|
|
492
|
+
*/
|
|
493
|
+
export const readFile = (filePath: string, options?: FileOperationOptions) =>
|
|
494
|
+
asyncFileOps.readFile(filePath, options);
|
|
495
|
+
|
|
496
|
+
export const writeFile = (
|
|
497
|
+
filePath: string,
|
|
498
|
+
content: string | Buffer,
|
|
499
|
+
options?: FileOperationOptions
|
|
500
|
+
) => asyncFileOps.writeFile(filePath, content, options);
|
|
501
|
+
|
|
502
|
+
export const exists = (filePath: string) => asyncFileOps.exists(filePath);
|
|
503
|
+
|
|
504
|
+
export const ensureDir = (dirPath: string) => asyncFileOps.ensureDir(dirPath);
|
|
505
|
+
|
|
506
|
+
export const remove = (targetPath: string, options?: { recursive?: boolean; force?: boolean }) =>
|
|
507
|
+
asyncFileOps.remove(targetPath, options);
|
|
508
|
+
|
|
509
|
+
export const copy = (source: string, destination: string, options?: CopyOptions) =>
|
|
510
|
+
asyncFileOps.copy(source, destination, options);
|
|
511
|
+
|
|
512
|
+
export const move = (source: string, destination: string, options?: { overwrite?: boolean }) =>
|
|
513
|
+
asyncFileOps.move(source, destination, options);
|
|
514
|
+
|
|
515
|
+
export const readDir = (dirPath: string, options?: ReadDirOptions) =>
|
|
516
|
+
asyncFileOps.readDir(dirPath, options);
|