@aigne/afs-cli 1.11.0-beta.1 → 1.11.0-beta.2
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 +13 -9
- package/dist/_virtual/rolldown_runtime.cjs +29 -0
- package/dist/cli.cjs +215 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.mjs +205 -19
- package/dist/cli.mjs.map +1 -0
- package/dist/commands/exec.cjs +46 -0
- package/dist/commands/exec.mjs +45 -0
- package/dist/commands/exec.mjs.map +1 -0
- package/dist/commands/explain.cjs +244 -0
- package/dist/commands/explain.mjs +242 -0
- package/dist/commands/explain.mjs.map +1 -0
- package/dist/commands/index.cjs +7 -0
- package/dist/commands/index.mjs +9 -0
- package/dist/commands/ls.cjs +136 -0
- package/dist/commands/ls.mjs +135 -0
- package/dist/commands/ls.mjs.map +1 -0
- package/dist/commands/mount.cjs +157 -0
- package/dist/commands/mount.mjs +153 -0
- package/dist/commands/mount.mjs.map +1 -0
- package/dist/commands/read.cjs +65 -0
- package/dist/commands/read.mjs +64 -0
- package/dist/commands/read.mjs.map +1 -0
- package/dist/commands/stat.cjs +113 -0
- package/dist/commands/stat.mjs +112 -0
- package/dist/commands/stat.mjs.map +1 -0
- package/dist/commands/write.cjs +52 -0
- package/dist/commands/write.mjs +51 -0
- package/dist/commands/write.mjs.map +1 -0
- package/dist/config/env.cjs +46 -0
- package/dist/config/env.mjs +46 -0
- package/dist/config/env.mjs.map +1 -0
- package/dist/config/loader.cjs +160 -0
- package/dist/config/loader.mjs +158 -0
- package/dist/config/loader.mjs.map +1 -0
- package/dist/config/provider-factory.cjs +74 -0
- package/dist/config/provider-factory.mjs +75 -0
- package/dist/config/provider-factory.mjs.map +1 -0
- package/dist/config/schema.cjs +22 -0
- package/dist/config/schema.mjs +22 -0
- package/dist/config/schema.mjs.map +1 -0
- package/dist/config/uri-parser.cjs +75 -0
- package/dist/config/uri-parser.mjs +75 -0
- package/dist/config/uri-parser.mjs.map +1 -0
- package/dist/errors.cjs +29 -0
- package/dist/errors.mjs +28 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +1 -3
- package/dist/index.mjs +1 -1
- package/dist/runtime.cjs +82 -0
- package/dist/runtime.mjs +82 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/version.cjs +9 -0
- package/dist/version.d.cts +5 -0
- package/dist/version.d.cts.map +1 -0
- package/dist/version.d.mts +5 -0
- package/dist/version.d.mts.map +1 -0
- package/dist/version.mjs +9 -0
- package/dist/version.mjs.map +1 -0
- package/package.json +51 -11
- package/.turbo/turbo-build.log +0 -18
- package/.turbo/turbo-check-types.log +0 -4
- package/dist/version--p6A8sKX.mjs +0 -5
- package/src/cli.test.ts +0 -8
- package/src/cli.ts +0 -29
- package/src/index.ts +0 -7
- package/src/version.ts +0 -1
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/commands/stat.ts
|
|
3
|
+
/**
|
|
4
|
+
* Get stat information for a path
|
|
5
|
+
*/
|
|
6
|
+
async function statCommand(runtime, path) {
|
|
7
|
+
const readResult = await runtime.read(path);
|
|
8
|
+
if (!readResult.data) {
|
|
9
|
+
const entry$1 = (await runtime.list(path, { maxDepth: 0 })).data.find((e) => e.path === path);
|
|
10
|
+
if (!entry$1) return {
|
|
11
|
+
path,
|
|
12
|
+
type: "file"
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
path: entry$1.path,
|
|
16
|
+
type: mapEntryType(entry$1.metadata?.type),
|
|
17
|
+
size: entry$1.metadata?.size,
|
|
18
|
+
modified: entry$1.updatedAt?.toISOString(),
|
|
19
|
+
created: entry$1.createdAt?.toISOString()
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const entry = readResult.data;
|
|
23
|
+
return {
|
|
24
|
+
path: entry.path,
|
|
25
|
+
type: mapEntryType(entry.metadata?.type),
|
|
26
|
+
size: entry.metadata?.size,
|
|
27
|
+
modified: entry.updatedAt?.toISOString(),
|
|
28
|
+
created: entry.createdAt?.toISOString()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function mapEntryType(type) {
|
|
32
|
+
switch (type) {
|
|
33
|
+
case "file": return "file";
|
|
34
|
+
case "directory":
|
|
35
|
+
case "dir": return "directory";
|
|
36
|
+
case "exec": return "exec";
|
|
37
|
+
case "link": return "link";
|
|
38
|
+
default: return "file";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format stat output for different views
|
|
43
|
+
*/
|
|
44
|
+
function formatStatOutput(stat, view) {
|
|
45
|
+
switch (view) {
|
|
46
|
+
case "json": return formatJson(stat);
|
|
47
|
+
case "llm": return formatLlm(stat);
|
|
48
|
+
case "human": return formatHuman(stat);
|
|
49
|
+
default: return formatDefault(stat);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Default format: KEY=VALUE pairs
|
|
54
|
+
*/
|
|
55
|
+
function formatDefault(stat) {
|
|
56
|
+
const lines = [];
|
|
57
|
+
lines.push(`PATH=${stat.path}`);
|
|
58
|
+
lines.push(`TYPE=${stat.type}`);
|
|
59
|
+
if (stat.size !== void 0) lines.push(`SIZE=${stat.size}`);
|
|
60
|
+
if (stat.modified) lines.push(`MODIFIED=${stat.modified}`);
|
|
61
|
+
if (stat.created) lines.push(`CREATED=${stat.created}`);
|
|
62
|
+
if (stat.hash) lines.push(`HASH=${stat.hash}`);
|
|
63
|
+
if (stat.provider) lines.push(`PROVIDER=${stat.provider}`);
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* JSON format
|
|
68
|
+
*/
|
|
69
|
+
function formatJson(stat) {
|
|
70
|
+
return JSON.stringify(stat, null, 2);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* LLM format: Uppercase keys, semantic
|
|
74
|
+
*/
|
|
75
|
+
function formatLlm(stat) {
|
|
76
|
+
const lines = [];
|
|
77
|
+
const typeLabel = stat.type.toUpperCase();
|
|
78
|
+
lines.push(`${typeLabel} ${stat.path}`);
|
|
79
|
+
lines.push(`TYPE ${typeLabel}`);
|
|
80
|
+
if (stat.size !== void 0) lines.push(`SIZE ${stat.size}`);
|
|
81
|
+
if (stat.hash) lines.push(`HASH ${stat.hash}`);
|
|
82
|
+
if (stat.modified) lines.push(`UPDATED ${stat.modified}`);
|
|
83
|
+
lines.push(`SIDE_EFFECT NONE`);
|
|
84
|
+
return lines.join("\n");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Human format: Formatted table
|
|
88
|
+
*/
|
|
89
|
+
function formatHuman(stat) {
|
|
90
|
+
const lines = [];
|
|
91
|
+
lines.push(`Path: ${stat.path}`);
|
|
92
|
+
lines.push(`Type: ${stat.type}`);
|
|
93
|
+
if (stat.size !== void 0) lines.push(`Size: ${formatSize(stat.size)}`);
|
|
94
|
+
if (stat.modified) lines.push(`Modified: ${formatDate(stat.modified)}`);
|
|
95
|
+
if (stat.provider) lines.push(`Provider: ${stat.provider}`);
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
function formatSize(bytes) {
|
|
99
|
+
if (bytes < 1024) return `${bytes} bytes`;
|
|
100
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
101
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
102
|
+
}
|
|
103
|
+
function formatDate(isoString) {
|
|
104
|
+
try {
|
|
105
|
+
return new Date(isoString).toLocaleString();
|
|
106
|
+
} catch {
|
|
107
|
+
return isoString;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
exports.formatStatOutput = formatStatOutput;
|
|
113
|
+
exports.statCommand = statCommand;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//#region src/commands/stat.ts
|
|
2
|
+
/**
|
|
3
|
+
* Get stat information for a path
|
|
4
|
+
*/
|
|
5
|
+
async function statCommand(runtime, path) {
|
|
6
|
+
const readResult = await runtime.read(path);
|
|
7
|
+
if (!readResult.data) {
|
|
8
|
+
const entry$1 = (await runtime.list(path, { maxDepth: 0 })).data.find((e) => e.path === path);
|
|
9
|
+
if (!entry$1) return {
|
|
10
|
+
path,
|
|
11
|
+
type: "file"
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
path: entry$1.path,
|
|
15
|
+
type: mapEntryType(entry$1.metadata?.type),
|
|
16
|
+
size: entry$1.metadata?.size,
|
|
17
|
+
modified: entry$1.updatedAt?.toISOString(),
|
|
18
|
+
created: entry$1.createdAt?.toISOString()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const entry = readResult.data;
|
|
22
|
+
return {
|
|
23
|
+
path: entry.path,
|
|
24
|
+
type: mapEntryType(entry.metadata?.type),
|
|
25
|
+
size: entry.metadata?.size,
|
|
26
|
+
modified: entry.updatedAt?.toISOString(),
|
|
27
|
+
created: entry.createdAt?.toISOString()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function mapEntryType(type) {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case "file": return "file";
|
|
33
|
+
case "directory":
|
|
34
|
+
case "dir": return "directory";
|
|
35
|
+
case "exec": return "exec";
|
|
36
|
+
case "link": return "link";
|
|
37
|
+
default: return "file";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format stat output for different views
|
|
42
|
+
*/
|
|
43
|
+
function formatStatOutput(stat, view) {
|
|
44
|
+
switch (view) {
|
|
45
|
+
case "json": return formatJson(stat);
|
|
46
|
+
case "llm": return formatLlm(stat);
|
|
47
|
+
case "human": return formatHuman(stat);
|
|
48
|
+
default: return formatDefault(stat);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Default format: KEY=VALUE pairs
|
|
53
|
+
*/
|
|
54
|
+
function formatDefault(stat) {
|
|
55
|
+
const lines = [];
|
|
56
|
+
lines.push(`PATH=${stat.path}`);
|
|
57
|
+
lines.push(`TYPE=${stat.type}`);
|
|
58
|
+
if (stat.size !== void 0) lines.push(`SIZE=${stat.size}`);
|
|
59
|
+
if (stat.modified) lines.push(`MODIFIED=${stat.modified}`);
|
|
60
|
+
if (stat.created) lines.push(`CREATED=${stat.created}`);
|
|
61
|
+
if (stat.hash) lines.push(`HASH=${stat.hash}`);
|
|
62
|
+
if (stat.provider) lines.push(`PROVIDER=${stat.provider}`);
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* JSON format
|
|
67
|
+
*/
|
|
68
|
+
function formatJson(stat) {
|
|
69
|
+
return JSON.stringify(stat, null, 2);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* LLM format: Uppercase keys, semantic
|
|
73
|
+
*/
|
|
74
|
+
function formatLlm(stat) {
|
|
75
|
+
const lines = [];
|
|
76
|
+
const typeLabel = stat.type.toUpperCase();
|
|
77
|
+
lines.push(`${typeLabel} ${stat.path}`);
|
|
78
|
+
lines.push(`TYPE ${typeLabel}`);
|
|
79
|
+
if (stat.size !== void 0) lines.push(`SIZE ${stat.size}`);
|
|
80
|
+
if (stat.hash) lines.push(`HASH ${stat.hash}`);
|
|
81
|
+
if (stat.modified) lines.push(`UPDATED ${stat.modified}`);
|
|
82
|
+
lines.push(`SIDE_EFFECT NONE`);
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Human format: Formatted table
|
|
87
|
+
*/
|
|
88
|
+
function formatHuman(stat) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
lines.push(`Path: ${stat.path}`);
|
|
91
|
+
lines.push(`Type: ${stat.type}`);
|
|
92
|
+
if (stat.size !== void 0) lines.push(`Size: ${formatSize(stat.size)}`);
|
|
93
|
+
if (stat.modified) lines.push(`Modified: ${formatDate(stat.modified)}`);
|
|
94
|
+
if (stat.provider) lines.push(`Provider: ${stat.provider}`);
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
function formatSize(bytes) {
|
|
98
|
+
if (bytes < 1024) return `${bytes} bytes`;
|
|
99
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
100
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
101
|
+
}
|
|
102
|
+
function formatDate(isoString) {
|
|
103
|
+
try {
|
|
104
|
+
return new Date(isoString).toLocaleString();
|
|
105
|
+
} catch {
|
|
106
|
+
return isoString;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
export { formatStatOutput, statCommand };
|
|
112
|
+
//# sourceMappingURL=stat.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stat.mjs","names":["entry"],"sources":["../../src/commands/stat.ts"],"sourcesContent":["import type { AFSEntry } from \"@aigne/afs\";\nimport type { AFSRuntime } from \"../runtime.js\";\nimport type { ViewType } from \"./ls.js\";\n\nexport interface StatResult {\n path: string;\n type: \"file\" | \"directory\" | \"exec\" | \"link\";\n size?: number;\n modified?: string;\n created?: string;\n hash?: string;\n provider?: string;\n permissions?: string[];\n}\n\n/**\n * Get stat information for a path\n */\nexport async function statCommand(runtime: AFSRuntime, path: string): Promise<StatResult> {\n // Use read to get detailed metadata\n const readResult = await runtime.read(path);\n\n if (!readResult.data) {\n // Try list as fallback\n const listResult = await runtime.list(path, { maxDepth: 0 });\n const entry = listResult.data.find((e: AFSEntry) => e.path === path);\n\n if (!entry) {\n return {\n path,\n type: \"file\",\n };\n }\n\n return {\n path: entry.path,\n type: mapEntryType(entry.metadata?.type),\n size: entry.metadata?.size,\n modified: entry.updatedAt?.toISOString(),\n created: entry.createdAt?.toISOString(),\n };\n }\n\n const entry = readResult.data;\n return {\n path: entry.path,\n type: mapEntryType(entry.metadata?.type),\n size: entry.metadata?.size,\n modified: entry.updatedAt?.toISOString(),\n created: entry.createdAt?.toISOString(),\n };\n}\n\nfunction mapEntryType(type?: string): StatResult[\"type\"] {\n switch (type) {\n case \"file\":\n return \"file\";\n case \"directory\":\n case \"dir\":\n return \"directory\";\n case \"exec\":\n return \"exec\";\n case \"link\":\n return \"link\";\n default:\n return \"file\";\n }\n}\n\n/**\n * Format stat output for different views\n */\nexport function formatStatOutput(stat: StatResult, view: ViewType): string {\n switch (view) {\n case \"json\":\n return formatJson(stat);\n case \"llm\":\n return formatLlm(stat);\n case \"human\":\n return formatHuman(stat);\n default:\n return formatDefault(stat);\n }\n}\n\n/**\n * Default format: KEY=VALUE pairs\n */\nfunction formatDefault(stat: StatResult): string {\n const lines: string[] = [];\n\n lines.push(`PATH=${stat.path}`);\n lines.push(`TYPE=${stat.type}`);\n\n if (stat.size !== undefined) {\n lines.push(`SIZE=${stat.size}`);\n }\n\n if (stat.modified) {\n lines.push(`MODIFIED=${stat.modified}`);\n }\n\n if (stat.created) {\n lines.push(`CREATED=${stat.created}`);\n }\n\n if (stat.hash) {\n lines.push(`HASH=${stat.hash}`);\n }\n\n if (stat.provider) {\n lines.push(`PROVIDER=${stat.provider}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * JSON format\n */\nfunction formatJson(stat: StatResult): string {\n return JSON.stringify(stat, null, 2);\n}\n\n/**\n * LLM format: Uppercase keys, semantic\n */\nfunction formatLlm(stat: StatResult): string {\n const lines: string[] = [];\n\n const typeLabel = stat.type.toUpperCase();\n lines.push(`${typeLabel} ${stat.path}`);\n lines.push(`TYPE ${typeLabel}`);\n\n if (stat.size !== undefined) {\n lines.push(`SIZE ${stat.size}`);\n }\n\n if (stat.hash) {\n lines.push(`HASH ${stat.hash}`);\n }\n\n if (stat.modified) {\n lines.push(`UPDATED ${stat.modified}`);\n }\n\n lines.push(`SIDE_EFFECT NONE`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Human format: Formatted table\n */\nfunction formatHuman(stat: StatResult): string {\n const lines: string[] = [];\n\n lines.push(`Path: ${stat.path}`);\n lines.push(`Type: ${stat.type}`);\n\n if (stat.size !== undefined) {\n lines.push(`Size: ${formatSize(stat.size)}`);\n }\n\n if (stat.modified) {\n lines.push(`Modified: ${formatDate(stat.modified)}`);\n }\n\n if (stat.provider) {\n lines.push(`Provider: ${stat.provider}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} bytes`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDate(isoString: string): string {\n try {\n const date = new Date(isoString);\n return date.toLocaleString();\n } catch {\n return isoString;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/commands/write.ts
|
|
3
|
+
/**
|
|
4
|
+
* Write content to a file
|
|
5
|
+
*/
|
|
6
|
+
async function writeCommand(runtime, path, content, options = {}) {
|
|
7
|
+
try {
|
|
8
|
+
const result = await runtime.write(path, { content }, { append: options.append });
|
|
9
|
+
return {
|
|
10
|
+
path: result.data.path,
|
|
11
|
+
success: true,
|
|
12
|
+
size: result.data.metadata?.size
|
|
13
|
+
};
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return {
|
|
16
|
+
path,
|
|
17
|
+
success: false,
|
|
18
|
+
message: error instanceof Error ? error.message : String(error)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Format write output for different views
|
|
24
|
+
*/
|
|
25
|
+
function formatWriteOutput(result, view) {
|
|
26
|
+
switch (view) {
|
|
27
|
+
case "json": return JSON.stringify(result, null, 2);
|
|
28
|
+
case "llm": return formatLlm(result);
|
|
29
|
+
case "human": return formatHuman(result);
|
|
30
|
+
default: return formatDefault(result);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function formatDefault(result) {
|
|
34
|
+
if (result.success) return `OK ${result.path}`;
|
|
35
|
+
return `ERROR ${result.path} ${result.message}`;
|
|
36
|
+
}
|
|
37
|
+
function formatLlm(result) {
|
|
38
|
+
const lines = [];
|
|
39
|
+
lines.push(`WRITE ${result.path}`);
|
|
40
|
+
lines.push(`STATUS ${result.success ? "SUCCESS" : "FAILED"}`);
|
|
41
|
+
if (result.size !== void 0) lines.push(`SIZE ${result.size}`);
|
|
42
|
+
if (result.message) lines.push(`MESSAGE ${result.message}`);
|
|
43
|
+
return lines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
function formatHuman(result) {
|
|
46
|
+
if (result.success) return `Successfully wrote to ${result.path}${result.size ? ` (${result.size} bytes)` : ""}`;
|
|
47
|
+
return `Failed to write to ${result.path}: ${result.message}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
exports.formatWriteOutput = formatWriteOutput;
|
|
52
|
+
exports.writeCommand = writeCommand;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/commands/write.ts
|
|
2
|
+
/**
|
|
3
|
+
* Write content to a file
|
|
4
|
+
*/
|
|
5
|
+
async function writeCommand(runtime, path, content, options = {}) {
|
|
6
|
+
try {
|
|
7
|
+
const result = await runtime.write(path, { content }, { append: options.append });
|
|
8
|
+
return {
|
|
9
|
+
path: result.data.path,
|
|
10
|
+
success: true,
|
|
11
|
+
size: result.data.metadata?.size
|
|
12
|
+
};
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return {
|
|
15
|
+
path,
|
|
16
|
+
success: false,
|
|
17
|
+
message: error instanceof Error ? error.message : String(error)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format write output for different views
|
|
23
|
+
*/
|
|
24
|
+
function formatWriteOutput(result, view) {
|
|
25
|
+
switch (view) {
|
|
26
|
+
case "json": return JSON.stringify(result, null, 2);
|
|
27
|
+
case "llm": return formatLlm(result);
|
|
28
|
+
case "human": return formatHuman(result);
|
|
29
|
+
default: return formatDefault(result);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function formatDefault(result) {
|
|
33
|
+
if (result.success) return `OK ${result.path}`;
|
|
34
|
+
return `ERROR ${result.path} ${result.message}`;
|
|
35
|
+
}
|
|
36
|
+
function formatLlm(result) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
lines.push(`WRITE ${result.path}`);
|
|
39
|
+
lines.push(`STATUS ${result.success ? "SUCCESS" : "FAILED"}`);
|
|
40
|
+
if (result.size !== void 0) lines.push(`SIZE ${result.size}`);
|
|
41
|
+
if (result.message) lines.push(`MESSAGE ${result.message}`);
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
function formatHuman(result) {
|
|
45
|
+
if (result.success) return `Successfully wrote to ${result.path}${result.size ? ` (${result.size} bytes)` : ""}`;
|
|
46
|
+
return `Failed to write to ${result.path}: ${result.message}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { formatWriteOutput, writeCommand };
|
|
51
|
+
//# sourceMappingURL=write.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.mjs","names":[],"sources":["../../src/commands/write.ts"],"sourcesContent":["import type { AFSRuntime } from \"../runtime.js\";\nimport type { ViewType } from \"./ls.js\";\n\nexport interface WriteResult {\n path: string;\n success: boolean;\n size?: number;\n message?: string;\n}\n\n/**\n * Write content to a file\n */\nexport async function writeCommand(\n runtime: AFSRuntime,\n path: string,\n content: string,\n options: { append?: boolean } = {},\n): Promise<WriteResult> {\n try {\n const result = await runtime.write(path, { content }, { append: options.append });\n\n return {\n path: result.data.path,\n success: true,\n size: result.data.metadata?.size,\n };\n } catch (error) {\n return {\n path,\n success: false,\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Format write output for different views\n */\nexport function formatWriteOutput(result: WriteResult, view: ViewType): string {\n switch (view) {\n case \"json\":\n return JSON.stringify(result, null, 2);\n case \"llm\":\n return formatLlm(result);\n case \"human\":\n return formatHuman(result);\n default:\n return formatDefault(result);\n }\n}\n\nfunction formatDefault(result: WriteResult): string {\n if (result.success) {\n return `OK ${result.path}`;\n }\n return `ERROR ${result.path} ${result.message}`;\n}\n\nfunction formatLlm(result: WriteResult): string {\n const lines: string[] = [];\n\n lines.push(`WRITE ${result.path}`);\n lines.push(`STATUS ${result.success ? \"SUCCESS\" : \"FAILED\"}`);\n\n if (result.size !== undefined) {\n lines.push(`SIZE ${result.size}`);\n }\n\n if (result.message) {\n lines.push(`MESSAGE ${result.message}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction formatHuman(result: WriteResult): string {\n if (result.success) {\n return `Successfully wrote to ${result.path}${result.size ? ` (${result.size} bytes)` : \"\"}`;\n }\n return `Failed to write to ${result.path}: ${result.message}`;\n}\n"],"mappings":";;;;AAaA,eAAsB,aACpB,SACA,MACA,SACA,UAAgC,EAAE,EACZ;AACtB,KAAI;EACF,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,QAAQ,QAAQ,CAAC;AAEjF,SAAO;GACL,MAAM,OAAO,KAAK;GAClB,SAAS;GACT,MAAM,OAAO,KAAK,UAAU;GAC7B;UACM,OAAO;AACd,SAAO;GACL;GACA,SAAS;GACT,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAChE;;;;;;AAOL,SAAgB,kBAAkB,QAAqB,MAAwB;AAC7E,SAAQ,MAAR;EACE,KAAK,OACH,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;EACxC,KAAK,MACH,QAAO,UAAU,OAAO;EAC1B,KAAK,QACH,QAAO,YAAY,OAAO;EAC5B,QACE,QAAO,cAAc,OAAO;;;AAIlC,SAAS,cAAc,QAA6B;AAClD,KAAI,OAAO,QACT,QAAO,MAAM,OAAO;AAEtB,QAAO,SAAS,OAAO,KAAK,GAAG,OAAO;;AAGxC,SAAS,UAAU,QAA6B;CAC9C,MAAM,QAAkB,EAAE;AAE1B,OAAM,KAAK,SAAS,OAAO,OAAO;AAClC,OAAM,KAAK,UAAU,OAAO,UAAU,YAAY,WAAW;AAE7D,KAAI,OAAO,SAAS,OAClB,OAAM,KAAK,QAAQ,OAAO,OAAO;AAGnC,KAAI,OAAO,QACT,OAAM,KAAK,WAAW,OAAO,UAAU;AAGzC,QAAO,MAAM,KAAK,KAAK;;AAGzB,SAAS,YAAY,QAA6B;AAChD,KAAI,OAAO,QACT,QAAO,yBAAyB,OAAO,OAAO,OAAO,OAAO,KAAK,OAAO,KAAK,WAAW;AAE1F,QAAO,sBAAsB,OAAO,KAAK,IAAI,OAAO"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/config/env.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolve environment variable references in a string
|
|
5
|
+
*
|
|
6
|
+
* Syntax: ${VAR_NAME}
|
|
7
|
+
* Escape: \${VAR_NAME} (will not be resolved)
|
|
8
|
+
*
|
|
9
|
+
* @param value - String containing ${VAR} references
|
|
10
|
+
* @param options - Resolution options
|
|
11
|
+
* @returns String with env vars resolved
|
|
12
|
+
* @throws Error if env var is undefined and allowUndefined is false
|
|
13
|
+
*/
|
|
14
|
+
function resolveEnvVars(value, options = {}) {
|
|
15
|
+
const { allowUndefined = false } = options;
|
|
16
|
+
if (!value) return value;
|
|
17
|
+
const ESCAPE_PLACEHOLDER = "\0ESCAPED_ENV\0";
|
|
18
|
+
let result = value.replace(/\\\$\{/g, ESCAPE_PLACEHOLDER);
|
|
19
|
+
result = result.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
20
|
+
const envValue = process.env[varName];
|
|
21
|
+
if (envValue === void 0) {
|
|
22
|
+
if (allowUndefined) return "";
|
|
23
|
+
throw new Error(`Environment variable ${varName} is not defined`);
|
|
24
|
+
}
|
|
25
|
+
return envValue;
|
|
26
|
+
});
|
|
27
|
+
result = result.replace(new RegExp(ESCAPE_PLACEHOLDER, "g"), "${");
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve environment variables in all string fields of an object (deep)
|
|
32
|
+
*/
|
|
33
|
+
function resolveEnvVarsInObject(obj, options = {}) {
|
|
34
|
+
if (obj === null || obj === void 0) return obj;
|
|
35
|
+
if (typeof obj === "string") return resolveEnvVars(obj, options);
|
|
36
|
+
if (Array.isArray(obj)) return obj.map((item) => resolveEnvVarsInObject(item, options));
|
|
37
|
+
if (typeof obj === "object") {
|
|
38
|
+
const result = {};
|
|
39
|
+
for (const [key, value] of Object.entries(obj)) result[key] = resolveEnvVarsInObject(value, options);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
return obj;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
exports.resolveEnvVarsInObject = resolveEnvVarsInObject;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/config/env.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolve environment variable references in a string
|
|
4
|
+
*
|
|
5
|
+
* Syntax: ${VAR_NAME}
|
|
6
|
+
* Escape: \${VAR_NAME} (will not be resolved)
|
|
7
|
+
*
|
|
8
|
+
* @param value - String containing ${VAR} references
|
|
9
|
+
* @param options - Resolution options
|
|
10
|
+
* @returns String with env vars resolved
|
|
11
|
+
* @throws Error if env var is undefined and allowUndefined is false
|
|
12
|
+
*/
|
|
13
|
+
function resolveEnvVars(value, options = {}) {
|
|
14
|
+
const { allowUndefined = false } = options;
|
|
15
|
+
if (!value) return value;
|
|
16
|
+
const ESCAPE_PLACEHOLDER = "\0ESCAPED_ENV\0";
|
|
17
|
+
let result = value.replace(/\\\$\{/g, ESCAPE_PLACEHOLDER);
|
|
18
|
+
result = result.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
19
|
+
const envValue = process.env[varName];
|
|
20
|
+
if (envValue === void 0) {
|
|
21
|
+
if (allowUndefined) return "";
|
|
22
|
+
throw new Error(`Environment variable ${varName} is not defined`);
|
|
23
|
+
}
|
|
24
|
+
return envValue;
|
|
25
|
+
});
|
|
26
|
+
result = result.replace(new RegExp(ESCAPE_PLACEHOLDER, "g"), "${");
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve environment variables in all string fields of an object (deep)
|
|
31
|
+
*/
|
|
32
|
+
function resolveEnvVarsInObject(obj, options = {}) {
|
|
33
|
+
if (obj === null || obj === void 0) return obj;
|
|
34
|
+
if (typeof obj === "string") return resolveEnvVars(obj, options);
|
|
35
|
+
if (Array.isArray(obj)) return obj.map((item) => resolveEnvVarsInObject(item, options));
|
|
36
|
+
if (typeof obj === "object") {
|
|
37
|
+
const result = {};
|
|
38
|
+
for (const [key, value] of Object.entries(obj)) result[key] = resolveEnvVarsInObject(value, options);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
return obj;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { resolveEnvVarsInObject };
|
|
46
|
+
//# sourceMappingURL=env.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.mjs","names":[],"sources":["../../src/config/env.ts"],"sourcesContent":["/**\n * Options for environment variable resolution\n */\nexport interface ResolveEnvOptions {\n /** If true, undefined env vars resolve to empty string instead of throwing */\n allowUndefined?: boolean;\n}\n\n/**\n * Resolve environment variable references in a string\n *\n * Syntax: ${VAR_NAME}\n * Escape: \\${VAR_NAME} (will not be resolved)\n *\n * @param value - String containing ${VAR} references\n * @param options - Resolution options\n * @returns String with env vars resolved\n * @throws Error if env var is undefined and allowUndefined is false\n */\nexport function resolveEnvVars(value: string, options: ResolveEnvOptions = {}): string {\n const { allowUndefined = false } = options;\n\n if (!value) {\n return value;\n }\n\n // First, handle escaped env vars (replace \\${ with a placeholder)\n const ESCAPE_PLACEHOLDER = \"\\x00ESCAPED_ENV\\x00\";\n let result = value.replace(/\\\\\\$\\{/g, ESCAPE_PLACEHOLDER);\n\n // Match ${VAR_NAME} pattern\n const envVarPattern = /\\$\\{([^}]+)\\}/g;\n\n result = result.replace(envVarPattern, (_match, varName: string) => {\n const envValue = process.env[varName];\n\n if (envValue === undefined) {\n if (allowUndefined) {\n return \"\";\n }\n throw new Error(`Environment variable ${varName} is not defined`);\n }\n\n return envValue;\n });\n\n // Restore escaped env vars\n result = result.replace(new RegExp(ESCAPE_PLACEHOLDER, \"g\"), \"${\");\n\n return result;\n}\n\n/**\n * Resolve environment variables in all string fields of an object (deep)\n */\nexport function resolveEnvVarsInObject<T>(obj: T, options: ResolveEnvOptions = {}): T {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (typeof obj === \"string\") {\n return resolveEnvVars(obj, options) as T;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => resolveEnvVarsInObject(item, options)) as T;\n }\n\n if (typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = resolveEnvVarsInObject(value, options);\n }\n return result as T;\n }\n\n return obj;\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,SAAgB,eAAe,OAAe,UAA6B,EAAE,EAAU;CACrF,MAAM,EAAE,iBAAiB,UAAU;AAEnC,KAAI,CAAC,MACH,QAAO;CAIT,MAAM,qBAAqB;CAC3B,IAAI,SAAS,MAAM,QAAQ,WAAW,mBAAmB;AAKzD,UAAS,OAAO,QAFM,mBAEkB,QAAQ,YAAoB;EAClE,MAAM,WAAW,QAAQ,IAAI;AAE7B,MAAI,aAAa,QAAW;AAC1B,OAAI,eACF,QAAO;AAET,SAAM,IAAI,MAAM,wBAAwB,QAAQ,iBAAiB;;AAGnE,SAAO;GACP;AAGF,UAAS,OAAO,QAAQ,IAAI,OAAO,oBAAoB,IAAI,EAAE,KAAK;AAElE,QAAO;;;;;AAMT,SAAgB,uBAA0B,KAAQ,UAA6B,EAAE,EAAK;AACpF,KAAI,QAAQ,QAAQ,QAAQ,OAC1B,QAAO;AAGT,KAAI,OAAO,QAAQ,SACjB,QAAO,eAAe,KAAK,QAAQ;AAGrC,KAAI,MAAM,QAAQ,IAAI,CACpB,QAAO,IAAI,KAAK,SAAS,uBAAuB,MAAM,QAAQ,CAAC;AAGjE,KAAI,OAAO,QAAQ,UAAU;EAC3B,MAAM,SAAkC,EAAE;AAC1C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,CAC5C,QAAO,OAAO,uBAAuB,OAAO,QAAQ;AAEtD,SAAO;;AAGT,QAAO"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_env = require('./env.cjs');
|
|
3
|
+
const require_schema = require('./schema.cjs');
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
let smol_toml = require("smol-toml");
|
|
7
|
+
let node_os = require("node:os");
|
|
8
|
+
|
|
9
|
+
//#region src/config/loader.ts
|
|
10
|
+
const CONFIG_DIR_NAME = ".afs-config";
|
|
11
|
+
const CONFIG_FILE_NAME = "config.toml";
|
|
12
|
+
/**
|
|
13
|
+
* Loads and merges AFS configuration from multiple layers
|
|
14
|
+
*
|
|
15
|
+
* Layer priority (lowest to highest):
|
|
16
|
+
* 1. User-level: ~/.afs-config/config.toml
|
|
17
|
+
* 2. All intermediate directories from project root to cwd
|
|
18
|
+
*
|
|
19
|
+
* Example: if cwd is /project/packages/cli, configs are merged from:
|
|
20
|
+
* ~/.afs-config/config.toml (user)
|
|
21
|
+
* /project/.afs-config/config.toml (project root, has .git)
|
|
22
|
+
* /project/packages/.afs-config/config.toml (intermediate)
|
|
23
|
+
* /project/packages/cli/.afs-config/config.toml (cwd)
|
|
24
|
+
*/
|
|
25
|
+
var ConfigLoader = class {
|
|
26
|
+
userConfigDir;
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.userConfigDir = options.userConfigDir ?? (0, node_path.join)((0, node_os.homedir)(), CONFIG_DIR_NAME);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Load and merge configuration from all layers
|
|
32
|
+
*
|
|
33
|
+
* @param cwd - Current working directory (defaults to process.cwd())
|
|
34
|
+
* @returns Merged configuration
|
|
35
|
+
* @throws Error on invalid config, TOML parse error, or duplicate mount paths
|
|
36
|
+
*/
|
|
37
|
+
async load(cwd = process.cwd()) {
|
|
38
|
+
const configPaths = await this.getConfigPaths(cwd);
|
|
39
|
+
const configs = [];
|
|
40
|
+
for (const configPath of configPaths) {
|
|
41
|
+
const config = await this.loadSingleConfig(configPath);
|
|
42
|
+
configs.push(config);
|
|
43
|
+
}
|
|
44
|
+
return this.mergeConfigs(configs);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get paths to all existing config files
|
|
48
|
+
*
|
|
49
|
+
* Collects configs from:
|
|
50
|
+
* 1. User-level: ~/.afs-config/config.toml
|
|
51
|
+
* 2. Project root (or topmost .afs-config dir) to cwd: all .afs-config/config.toml files
|
|
52
|
+
*/
|
|
53
|
+
async getConfigPaths(cwd = process.cwd()) {
|
|
54
|
+
const paths = [];
|
|
55
|
+
const userConfigPath = (0, node_path.join)(this.userConfigDir, CONFIG_FILE_NAME);
|
|
56
|
+
if (await this.fileExists(userConfigPath)) paths.push(userConfigPath);
|
|
57
|
+
const startDir = await this.findProjectRoot(cwd) ?? await this.findTopmostAfsDir(cwd) ?? cwd;
|
|
58
|
+
const intermediatePaths = await this.collectConfigsFromTo(startDir, cwd);
|
|
59
|
+
paths.push(...intermediatePaths);
|
|
60
|
+
return paths;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Find the topmost directory containing .afs-config from startDir going up
|
|
64
|
+
*/
|
|
65
|
+
async findTopmostAfsDir(startDir) {
|
|
66
|
+
let currentDir = startDir;
|
|
67
|
+
let topmostAfsDir = null;
|
|
68
|
+
while (true) {
|
|
69
|
+
if (await this.fileExists((0, node_path.join)(currentDir, CONFIG_DIR_NAME))) topmostAfsDir = currentDir;
|
|
70
|
+
const parentDir = (0, node_path.dirname)(currentDir);
|
|
71
|
+
if (parentDir === currentDir) break;
|
|
72
|
+
currentDir = parentDir;
|
|
73
|
+
}
|
|
74
|
+
return topmostAfsDir;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Collect all config files from startDir to endDir (inclusive)
|
|
78
|
+
* Returns paths in order from startDir to endDir (parent to child)
|
|
79
|
+
*/
|
|
80
|
+
async collectConfigsFromTo(startDir, endDir) {
|
|
81
|
+
const paths = [];
|
|
82
|
+
const dirs = [];
|
|
83
|
+
let current = endDir;
|
|
84
|
+
while (true) {
|
|
85
|
+
dirs.unshift(current);
|
|
86
|
+
if (current === startDir) break;
|
|
87
|
+
const parent = (0, node_path.dirname)(current);
|
|
88
|
+
if (parent === current) break;
|
|
89
|
+
current = parent;
|
|
90
|
+
}
|
|
91
|
+
for (const dir of dirs) {
|
|
92
|
+
const configPath = (0, node_path.join)(dir, CONFIG_DIR_NAME, CONFIG_FILE_NAME);
|
|
93
|
+
if (await this.fileExists(configPath)) paths.push(configPath);
|
|
94
|
+
}
|
|
95
|
+
return paths;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Load a single config file
|
|
99
|
+
*/
|
|
100
|
+
async loadSingleConfig(configPath) {
|
|
101
|
+
const content = await (0, node_fs_promises.readFile)(configPath, "utf-8");
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
parsed = (0, smol_toml.parse)(content);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new Error(`Failed to parse TOML config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
+
}
|
|
108
|
+
const resolved = require_env.resolveEnvVarsInObject(parsed);
|
|
109
|
+
const result = require_schema.ConfigSchema.safeParse(resolved);
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
const errors = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
|
|
112
|
+
throw new Error(`Invalid config at ${configPath}: ${errors}`);
|
|
113
|
+
}
|
|
114
|
+
return result.data;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Merge multiple configs, checking for duplicate mount paths
|
|
118
|
+
*/
|
|
119
|
+
mergeConfigs(configs) {
|
|
120
|
+
const allMounts = [];
|
|
121
|
+
const seenPaths = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const config of configs) for (const mount of config.mounts) {
|
|
123
|
+
if (seenPaths.has(mount.path)) throw new Error(`Duplicate mount path "${mount.path}" found in configuration. Mount paths must be unique across all config files.`);
|
|
124
|
+
seenPaths.set(mount.path, mount.uri);
|
|
125
|
+
allMounts.push(mount);
|
|
126
|
+
}
|
|
127
|
+
return { mounts: allMounts };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Find project root by looking for .git
|
|
131
|
+
* Note: Only .git is used as project root marker, not .afs-config,
|
|
132
|
+
* because .afs-config can exist at multiple levels for hierarchical config
|
|
133
|
+
*/
|
|
134
|
+
async findProjectRoot(startDir) {
|
|
135
|
+
let currentDir = startDir;
|
|
136
|
+
while (true) {
|
|
137
|
+
if (await this.fileExists((0, node_path.join)(currentDir, ".git"))) return currentDir;
|
|
138
|
+
const parentDir = (0, node_path.dirname)(currentDir);
|
|
139
|
+
if (parentDir === currentDir) return null;
|
|
140
|
+
currentDir = parentDir;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if a file or directory exists
|
|
145
|
+
*/
|
|
146
|
+
async fileExists(path) {
|
|
147
|
+
try {
|
|
148
|
+
await (0, node_fs_promises.access)(path);
|
|
149
|
+
return true;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const configLoader = new ConfigLoader();
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
exports.CONFIG_DIR_NAME = CONFIG_DIR_NAME;
|
|
159
|
+
exports.CONFIG_FILE_NAME = CONFIG_FILE_NAME;
|
|
160
|
+
exports.ConfigLoader = ConfigLoader;
|