@chatcode/chatcode-cli-test 1.0.47 → 3.0.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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +41 -135
- package/README.zh.md +63 -0
- package/lib/bin.js +303 -0
- package/lib/chatcode-home-B93iESZd.js +48 -0
- package/lib/dump-config-WbxM4B3p.js +53 -0
- package/lib/home-migration-DLaSvmfn.js +307 -0
- package/lib/plugin-CMMvQgyR.js +31 -0
- package/lib/profile-boot-CCxnpvy7.js +331 -0
- package/lib/profile-boot.js +2 -0
- package/lib/types/args.d.ts +76 -0
- package/lib/types/bin.d.ts +15 -0
- package/lib/types/chatcode-home.d.ts +30 -0
- package/lib/types/dump-config.d.ts +18 -0
- package/lib/types/home-migration.d.ts +58 -0
- package/lib/types/plugin.d.ts +7 -0
- package/lib/types/process-shutdown.d.ts +20 -0
- package/lib/types/profile-boot.d.ts +94 -0
- package/lib/types/startup-diagnostics.d.ts +19 -0
- package/package.json +310 -112
- package/bin/cli.js +0 -102
- package/bin/generation-supervisor.js +0 -142
- package/dist/cli.js +0 -8793
- package/dist/vendor/ripgrep/COPYING +0 -3
- package/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/vendor/ripgrep/arm64-win32/rg.exe +0 -0
- package/dist/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
//#region lib/types/chatcode-home.js
|
|
4
|
+
/**
|
|
5
|
+
* ChatCode CLI home resolution owned by the public launcher package.
|
|
6
|
+
* @module @chatcode/chatcode-cli/chatcode-home
|
|
7
|
+
*/
|
|
8
|
+
/** Canonical ChatCode CLI home environment variable. */
|
|
9
|
+
const CHATCODE_CLI_HOME_ENV = "CHATCODE_CLI_HOME";
|
|
10
|
+
/** Legacy Harness home variable accepted as a compatibility input. */
|
|
11
|
+
const LEGACY_DSH_HOME_ENV = "DSH_HOME";
|
|
12
|
+
/** Resolve the default writable ChatCode CLI home. */
|
|
13
|
+
function defaultChatCodeCliHome() {
|
|
14
|
+
return join(homedir(), ".chatcode-cli");
|
|
15
|
+
}
|
|
16
|
+
/** Expand the supported current-user tilde forms in a configured path.
|
|
17
|
+
* @param path - path that may begin with `~`, `~/`, or `~\`.
|
|
18
|
+
* @returns the path with its current-user tilde expanded.
|
|
19
|
+
*/
|
|
20
|
+
function expandHomePath(path) {
|
|
21
|
+
if (path === "~") return homedir();
|
|
22
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) return join(homedir(), path.slice(2));
|
|
23
|
+
return path;
|
|
24
|
+
}
|
|
25
|
+
/** Resolve the writable ChatCode CLI home with canonical-before-legacy precedence.
|
|
26
|
+
* @param configured - explicit home override with highest precedence.
|
|
27
|
+
* @param env - environment used for canonical and legacy inputs.
|
|
28
|
+
* @returns the normalized absolute home path.
|
|
29
|
+
*/
|
|
30
|
+
function resolveChatCodeCliHome(configured, env = process.env) {
|
|
31
|
+
const canonical = env[CHATCODE_CLI_HOME_ENV]?.trim();
|
|
32
|
+
const legacy = env[LEGACY_DSH_HOME_ENV]?.trim();
|
|
33
|
+
return resolve(expandHomePath(configured ?? (canonical === void 0 || canonical === "" ? legacy === void 0 || legacy === "" ? defaultChatCodeCliHome() : legacy : canonical)));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Point legacy ABI packages at the launcher-selected ChatCode CLI home.
|
|
37
|
+
* Published Harness packages still read `DSH_HOME`; this internal bridge keeps
|
|
38
|
+
* their storage under the canonical home without requiring republished ABI packages.
|
|
39
|
+
* @param env - mutable process environment supplied to legacy packages.
|
|
40
|
+
* @returns the resolved canonical home written to the legacy variable.
|
|
41
|
+
*/
|
|
42
|
+
function synchronizeLegacyHomeEnvironment(env = process.env) {
|
|
43
|
+
const home = resolveChatCodeCliHome(void 0, env);
|
|
44
|
+
env[LEGACY_DSH_HOME_ENV] = home;
|
|
45
|
+
return home;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { synchronizeLegacyHomeEnvironment as i, expandHomePath as n, resolveChatCodeCliHome as r, defaultChatCodeCliHome as t };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { i as homePatchPath, o as prepareProfile, r as PROFILE_ROOT_FILENAME } from "./profile-boot-CCxnpvy7.js";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { loadOptionalPatches, loadOverlayPatches, renderConfigDump } from "@deepseek-ai/dsh-app-boot";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
//#region lib/types/dump-config.js
|
|
6
|
+
/**
|
|
7
|
+
* Config-dump entry for `dsh --profile <name> --dump-config`: compose the
|
|
8
|
+
* profile's patch layers through the include plugin's patch algorithm without
|
|
9
|
+
* booting or evaluating `!!js`, with one source layer per bundle, the
|
|
10
|
+
* profile's own patch file, and each `--patch` overlay.
|
|
11
|
+
* @module @deepseek-ai/dsh/dump-config
|
|
12
|
+
*/
|
|
13
|
+
const NAME = "ChatCode CLI";
|
|
14
|
+
/* v8 ignore start -- built-bin acceptance drives this boot-free dispatch */
|
|
15
|
+
/**
|
|
16
|
+
* Print a profile composition with comments naming each source file and patch layer.
|
|
17
|
+
* @param profile - the profile name.
|
|
18
|
+
* @param defaultOnly - omit the profile's user layer and `--patch` overlays
|
|
19
|
+
* (the recovery diagnostic for a broken `cordis.patch.yml`, which is then
|
|
20
|
+
* never parsed).
|
|
21
|
+
* @param patches - `--patch` overlay paths, in argv order.
|
|
22
|
+
* @param fromDefaultProfile - shipped template used once to initialize a missing profile.
|
|
23
|
+
*/
|
|
24
|
+
function runDumpConfig(profile, defaultOnly, patches, fromDefaultProfile) {
|
|
25
|
+
const loaded = prepareProfile(profile, !defaultOnly, fromDefaultProfile);
|
|
26
|
+
const layers = loaded.layers.map((layer) => ({
|
|
27
|
+
label: layer.packageName,
|
|
28
|
+
patches: layer.patches
|
|
29
|
+
}));
|
|
30
|
+
if (!defaultOnly) {
|
|
31
|
+
if (existsSync(loaded.patchPath)) layers.push({
|
|
32
|
+
label: loaded.patchPath,
|
|
33
|
+
patches: loaded.patches
|
|
34
|
+
});
|
|
35
|
+
const homePatchFile = homePatchPath();
|
|
36
|
+
const homePatches = loadOptionalPatches(NAME, homePatchFile);
|
|
37
|
+
if (homePatches !== void 0) layers.push({
|
|
38
|
+
label: homePatchFile,
|
|
39
|
+
patches: homePatches
|
|
40
|
+
});
|
|
41
|
+
for (const file of patches) {
|
|
42
|
+
const absolute = resolve(file);
|
|
43
|
+
layers.push({
|
|
44
|
+
label: absolute,
|
|
45
|
+
patches: loadOverlayPatches(NAME, absolute)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
process.stdout.write(renderConfigDump(NAME, join(loaded.dir, PROFILE_ROOT_FILENAME), layers));
|
|
50
|
+
}
|
|
51
|
+
/* v8 ignore stop */
|
|
52
|
+
//#endregion
|
|
53
|
+
export { runDumpConfig };
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { n as expandHomePath, t as defaultChatCodeCliHome } from "./chatcode-home-B93iESZd.js";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
+
import { chmod, copyFile, link, lstat, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
7
|
+
const LEGACY_HOME_DIRECTORY = ".dsh";
|
|
8
|
+
const LEGACY_TUI_HOME_DIRECTORY = ".dsh-tui";
|
|
9
|
+
const MIGRATION_RECORD_DIRECTORY = "migration/chatcode-cli-home-v1";
|
|
10
|
+
const TRANSIENT_DIRECTORY_NAMES = new Set([
|
|
11
|
+
"cache",
|
|
12
|
+
"caches",
|
|
13
|
+
"tmp",
|
|
14
|
+
"temp",
|
|
15
|
+
"run",
|
|
16
|
+
"inject",
|
|
17
|
+
"sockets",
|
|
18
|
+
"locks"
|
|
19
|
+
]);
|
|
20
|
+
const TRANSIENT_FILE_NAMES = new Set(["lock", "pid"]);
|
|
21
|
+
const TRANSIENT_FILE_SUFFIXES = [
|
|
22
|
+
".pid",
|
|
23
|
+
".sock",
|
|
24
|
+
".socket",
|
|
25
|
+
".lock",
|
|
26
|
+
".tmp",
|
|
27
|
+
".temp"
|
|
28
|
+
];
|
|
29
|
+
const TRANSIENT_ENTRY_PREFIXES = [".chatcode-migrate-"];
|
|
30
|
+
function pathParts(relativePath) {
|
|
31
|
+
return relativePath.split(/[\\/]/u).filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
/** Classify state that must never be migrated because it is runtime-local or transient. */
|
|
34
|
+
function isTransientMigrationPath(relativePath, kind) {
|
|
35
|
+
const parts = pathParts(relativePath);
|
|
36
|
+
if (parts.some((part) => TRANSIENT_DIRECTORY_NAMES.has(part.toLowerCase()))) return true;
|
|
37
|
+
if (parts.some((part) => TRANSIENT_ENTRY_PREFIXES.some((prefix) => part.toLowerCase().startsWith(prefix)))) return true;
|
|
38
|
+
if (kind === "directory") return false;
|
|
39
|
+
const name = parts.at(-1)?.toLowerCase() ?? "";
|
|
40
|
+
return TRANSIENT_FILE_NAMES.has(name) || TRANSIENT_FILE_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
41
|
+
}
|
|
42
|
+
async function fingerprintFile(path) {
|
|
43
|
+
return createHash("sha256").update(await readFile(path)).digest("hex");
|
|
44
|
+
}
|
|
45
|
+
function safeRelativePath(relativePath) {
|
|
46
|
+
return relativePath !== "" && !isAbsolute(relativePath) && !pathParts(relativePath).some((part) => part === "." || part === "..");
|
|
47
|
+
}
|
|
48
|
+
async function pathKind(path) {
|
|
49
|
+
try {
|
|
50
|
+
const info = await lstat(path);
|
|
51
|
+
if (info.isSymbolicLink()) return "symlink";
|
|
52
|
+
if (info.isFile()) return "file";
|
|
53
|
+
if (info.isDirectory()) return "directory";
|
|
54
|
+
return "other";
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.code === "ENOENT") return "missing";
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Resolve the fixed legacy source and the canonical migration destination. */
|
|
61
|
+
function resolveHomeMigrationRoots(env = process.env) {
|
|
62
|
+
const canonical = env.CHATCODE_CLI_HOME?.trim();
|
|
63
|
+
const destination = canonical === void 0 || canonical === "" ? defaultChatCodeCliHome() : resolve(expandHomePath(canonical));
|
|
64
|
+
return {
|
|
65
|
+
source: join(homedir(), LEGACY_HOME_DIRECTORY),
|
|
66
|
+
destination,
|
|
67
|
+
tuiSource: join(homedir(), LEGACY_TUI_HOME_DIRECTORY),
|
|
68
|
+
tuiDestination: join(destination, "tui")
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Find legacy trees with durable entries that are still absent from the canonical home. */
|
|
72
|
+
async function detectPendingHomeMigrations(roots) {
|
|
73
|
+
return (await Promise.all([planHomeMigration(roots.source, roots.destination), planHomeMigration(roots.tuiSource, roots.tuiDestination)])).filter((plan) => plan.entries.some((entry) => entry.action === "copy-file" || entry.action === "create-directory"));
|
|
74
|
+
}
|
|
75
|
+
function createPlanId(plan) {
|
|
76
|
+
return createHash("sha256").update(JSON.stringify(plan)).digest("hex").slice(0, 24);
|
|
77
|
+
}
|
|
78
|
+
/** Inspect both trees and return a deterministic, side-effect-free migration plan. */
|
|
79
|
+
async function planHomeMigration(source, destination) {
|
|
80
|
+
const resolvedSource = resolve(source);
|
|
81
|
+
const resolvedDestination = resolve(destination);
|
|
82
|
+
if (resolvedSource === resolvedDestination) throw new Error("ChatCode CLI migration source and destination must be different directories");
|
|
83
|
+
const sourceKind = await pathKind(resolvedSource);
|
|
84
|
+
if (sourceKind !== "missing" && sourceKind !== "directory") throw new Error(`ChatCode CLI migration source is not a directory: ${resolvedSource}`);
|
|
85
|
+
const entries = [];
|
|
86
|
+
const conflicts = [];
|
|
87
|
+
const excluded = [];
|
|
88
|
+
const visit = async (relativeDirectory) => {
|
|
89
|
+
const children = await readdir(relativeDirectory === "" ? resolvedSource : join(resolvedSource, relativeDirectory), { withFileTypes: true });
|
|
90
|
+
children.sort((left, right) => left.name.localeCompare(right.name, "en"));
|
|
91
|
+
for (const child of children) {
|
|
92
|
+
const relativePath = relativeDirectory === "" ? child.name : join(relativeDirectory, child.name);
|
|
93
|
+
const sourcePath = join(resolvedSource, relativePath);
|
|
94
|
+
const kind = await pathKind(sourcePath);
|
|
95
|
+
if (kind === "missing") continue;
|
|
96
|
+
if (kind === "symlink") {
|
|
97
|
+
entries.push({
|
|
98
|
+
relativePath,
|
|
99
|
+
action: "skip-unsafe",
|
|
100
|
+
kind,
|
|
101
|
+
reason: "symbolic links are never followed"
|
|
102
|
+
});
|
|
103
|
+
excluded.push(relativePath);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (isTransientMigrationPath(relativePath, kind)) {
|
|
107
|
+
entries.push({
|
|
108
|
+
relativePath,
|
|
109
|
+
action: "exclude-transient",
|
|
110
|
+
kind,
|
|
111
|
+
reason: "transient runtime state"
|
|
112
|
+
});
|
|
113
|
+
excluded.push(relativePath);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const destinationKind = await pathKind(join(resolvedDestination, relativePath));
|
|
117
|
+
if (kind === "directory" && (destinationKind === "missing" || destinationKind === "directory")) {
|
|
118
|
+
if (destinationKind === "missing") entries.push({
|
|
119
|
+
relativePath,
|
|
120
|
+
action: "create-directory",
|
|
121
|
+
kind
|
|
122
|
+
});
|
|
123
|
+
await visit(relativePath);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (destinationKind !== "missing") {
|
|
127
|
+
entries.push({
|
|
128
|
+
relativePath,
|
|
129
|
+
action: "skip-existing",
|
|
130
|
+
kind,
|
|
131
|
+
reason: `destination already contains ${destinationKind}`
|
|
132
|
+
});
|
|
133
|
+
conflicts.push(relativePath);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (kind === "file") {
|
|
137
|
+
try {
|
|
138
|
+
const info = await stat(sourcePath);
|
|
139
|
+
const fingerprint = await fingerprintFile(sourcePath);
|
|
140
|
+
entries.push({
|
|
141
|
+
relativePath,
|
|
142
|
+
action: "copy-file",
|
|
143
|
+
kind,
|
|
144
|
+
size: info.size,
|
|
145
|
+
mtimeMs: info.mtimeMs,
|
|
146
|
+
fingerprint
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const code = error.code ?? "unknown error";
|
|
150
|
+
entries.push({
|
|
151
|
+
relativePath,
|
|
152
|
+
action: "skip-unreadable",
|
|
153
|
+
kind,
|
|
154
|
+
reason: `source could not be read (${code})`
|
|
155
|
+
});
|
|
156
|
+
excluded.push(relativePath);
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
entries.push({
|
|
161
|
+
relativePath,
|
|
162
|
+
action: "skip-unsafe",
|
|
163
|
+
kind,
|
|
164
|
+
reason: "unsupported filesystem entry"
|
|
165
|
+
});
|
|
166
|
+
excluded.push(relativePath);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
if (sourceKind === "directory") await visit("");
|
|
170
|
+
const base = {
|
|
171
|
+
version: 1,
|
|
172
|
+
source: resolvedSource,
|
|
173
|
+
destination: resolvedDestination,
|
|
174
|
+
sourceExists: sourceKind === "directory",
|
|
175
|
+
entries,
|
|
176
|
+
conflicts,
|
|
177
|
+
excluded
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
...base,
|
|
181
|
+
planId: createPlanId(base)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function assertSafeDestinationDirectory(root, directory) {
|
|
185
|
+
const rel = relative(root, directory);
|
|
186
|
+
if (rel.startsWith("..") || isAbsolute(rel)) throw new Error(`migration destination escapes root: ${directory}`);
|
|
187
|
+
const segments = rel === "" ? [] : rel.split(sep);
|
|
188
|
+
let current = root;
|
|
189
|
+
const ensureRealDirectory = async (path, label) => {
|
|
190
|
+
const kind = await pathKind(path);
|
|
191
|
+
if (kind === "directory") return;
|
|
192
|
+
if (kind !== "missing") throw new Error(`migration ${label} is not a real directory: ${path}`);
|
|
193
|
+
const parent = dirname(path);
|
|
194
|
+
if (parent === path) throw new Error(`migration ${label} cannot be created: ${path}`);
|
|
195
|
+
await ensureRealDirectory(parent, "destination parent");
|
|
196
|
+
try {
|
|
197
|
+
await mkdir(path);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error.code !== "EEXIST") throw error;
|
|
200
|
+
if (await pathKind(path) !== "directory") throw new Error(`migration ${label} is not a real directory: ${path}`);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
await ensureRealDirectory(root, "destination root");
|
|
204
|
+
for (const segment of segments) {
|
|
205
|
+
current = join(current, segment);
|
|
206
|
+
const kind = await pathKind(current);
|
|
207
|
+
if (kind === "missing") try {
|
|
208
|
+
await mkdir(current);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error.code !== "EEXIST") throw error;
|
|
211
|
+
if (await pathKind(current) !== "directory") throw new Error(`migration destination parent is not a real directory: ${current}`);
|
|
212
|
+
}
|
|
213
|
+
else if (kind !== "directory") throw new Error(`migration destination parent is not a real directory: ${current}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function copyFileExclusively(plan, entry) {
|
|
217
|
+
if (!safeRelativePath(entry.relativePath)) throw new Error(`invalid migration path: ${entry.relativePath}`);
|
|
218
|
+
const source = join(plan.source, entry.relativePath);
|
|
219
|
+
const destination = join(plan.destination, entry.relativePath);
|
|
220
|
+
const sourceBefore = await lstat(source);
|
|
221
|
+
if (!sourceBefore.isFile() || sourceBefore.isSymbolicLink()) return "source changed type after planning";
|
|
222
|
+
if (entry.size !== sourceBefore.size || entry.mtimeMs !== sourceBefore.mtimeMs || entry.fingerprint !== await fingerprintFile(source)) return "source changed after planning";
|
|
223
|
+
await assertSafeDestinationDirectory(plan.destination, dirname(destination));
|
|
224
|
+
if (await pathKind(destination) !== "missing") return "destination appeared after planning";
|
|
225
|
+
const staging = join(dirname(destination), `.chatcode-migrate-${randomUUID()}.tmp`);
|
|
226
|
+
try {
|
|
227
|
+
await copyFile(source, staging, constants.COPYFILE_EXCL);
|
|
228
|
+
await chmod(staging, sourceBefore.mode);
|
|
229
|
+
const sourceAfter = await lstat(source);
|
|
230
|
+
if (!sourceAfter.isFile() || sourceAfter.isSymbolicLink() || sourceAfter.size !== sourceBefore.size || sourceAfter.mtimeMs !== sourceBefore.mtimeMs || entry.fingerprint !== await fingerprintFile(source)) return "source changed while copying";
|
|
231
|
+
try {
|
|
232
|
+
await link(staging, destination);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error.code === "EEXIST") return "destination appeared while copying";
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
} finally {
|
|
239
|
+
await rm(staging, { force: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/** Apply exactly one reviewed plan without overwriting destinations or removing legacy data. */
|
|
243
|
+
async function applyHomeMigration(plan) {
|
|
244
|
+
if ((await planHomeMigration(plan.source, plan.destination)).planId !== plan.planId) throw new Error("ChatCode CLI migration plan is stale; run the dry-run again before applying");
|
|
245
|
+
const result = {
|
|
246
|
+
plan,
|
|
247
|
+
copied: [],
|
|
248
|
+
createdDirectories: [],
|
|
249
|
+
skipped: []
|
|
250
|
+
};
|
|
251
|
+
if (!plan.sourceExists) return result;
|
|
252
|
+
await assertSafeDestinationDirectory(plan.destination, plan.destination);
|
|
253
|
+
for (const entry of plan.entries) if (entry.action === "create-directory") {
|
|
254
|
+
const directory = join(plan.destination, entry.relativePath);
|
|
255
|
+
await assertSafeDestinationDirectory(plan.destination, directory);
|
|
256
|
+
result.createdDirectories.push(entry.relativePath);
|
|
257
|
+
} else if (entry.action === "copy-file") {
|
|
258
|
+
const reason = await copyFileExclusively(plan, entry);
|
|
259
|
+
if (reason === void 0) result.copied.push(entry.relativePath);
|
|
260
|
+
else result.skipped.push({
|
|
261
|
+
relativePath: entry.relativePath,
|
|
262
|
+
reason
|
|
263
|
+
});
|
|
264
|
+
} else result.skipped.push({
|
|
265
|
+
relativePath: entry.relativePath,
|
|
266
|
+
reason: entry.reason ?? entry.action
|
|
267
|
+
});
|
|
268
|
+
const recordDirectory = join(plan.destination, MIGRATION_RECORD_DIRECTORY);
|
|
269
|
+
await assertSafeDestinationDirectory(plan.destination, recordDirectory);
|
|
270
|
+
const recordPath = join(recordDirectory, `${plan.planId}.json`);
|
|
271
|
+
const record = `${JSON.stringify({
|
|
272
|
+
...result,
|
|
273
|
+
recordPath,
|
|
274
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
275
|
+
}, null, 2)}\n`;
|
|
276
|
+
try {
|
|
277
|
+
await writeFile(recordPath, record, { flag: "wx" });
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (error.code !== "EEXIST") throw error;
|
|
280
|
+
const existing = await readFile(recordPath, "utf8");
|
|
281
|
+
const parsed = JSON.parse(existing);
|
|
282
|
+
const recordedPlan = typeof parsed === "object" && parsed !== null && "plan" in parsed ? parsed.plan : void 0;
|
|
283
|
+
if ((typeof recordedPlan === "object" && recordedPlan !== null && "planId" in recordedPlan ? recordedPlan.planId : void 0) !== plan.planId) throw error;
|
|
284
|
+
}
|
|
285
|
+
result.recordPath = recordPath;
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
/** Human-readable review output shared by dry-run and apply. */
|
|
289
|
+
function formatHomeMigrationPlan(plan) {
|
|
290
|
+
const counts = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const entry of plan.entries) counts.set(entry.action, (counts.get(entry.action) ?? 0) + 1);
|
|
292
|
+
return [
|
|
293
|
+
"ChatCode CLI home migration plan",
|
|
294
|
+
`Source (preserved): ${plan.source}`,
|
|
295
|
+
`Destination: ${plan.destination}`,
|
|
296
|
+
`Source exists: ${plan.sourceExists ? "yes" : "no"}`,
|
|
297
|
+
`Plan: ${plan.planId}`,
|
|
298
|
+
`Copy files: ${counts.get("copy-file") ?? 0}`,
|
|
299
|
+
`Create directories: ${counts.get("create-directory") ?? 0}`,
|
|
300
|
+
`Conflicts (destination wins): ${plan.conflicts.length}`,
|
|
301
|
+
`Excluded transient or unsafe entries: ${plan.excluded.length}`,
|
|
302
|
+
...plan.conflicts.map((path) => ` conflict: ${path}`),
|
|
303
|
+
...plan.excluded.map((path) => ` excluded: ${path}`)
|
|
304
|
+
].join("\n");
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
export { applyHomeMigration, detectPendingHomeMigrations, formatHomeMigrationPlan, planHomeMigration, resolveHomeMigrationRoots };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { n as INSTALL_ANCHOR } from "./profile-boot-CCxnpvy7.js";
|
|
2
|
+
import { resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { runPluginCommand } from "@deepseek-ai/dsh-plugin-manager/operations";
|
|
5
|
+
//#region lib/types/plugin.js
|
|
6
|
+
/** ChatCode CLI plugin management forwards pnpm through the shared profile package operations. */
|
|
7
|
+
/** Run package management for a profile.
|
|
8
|
+
* @param profile Profile name.
|
|
9
|
+
* @param args Pnpm arguments relative to the invoking directory.
|
|
10
|
+
* @returns Pnpm exit code.
|
|
11
|
+
*/
|
|
12
|
+
async function runPlugin(profile, args) {
|
|
13
|
+
const result = await runPluginCommand({
|
|
14
|
+
profile,
|
|
15
|
+
installAnchor: INSTALL_ANCHOR,
|
|
16
|
+
cwd: process.cwd()
|
|
17
|
+
}, args, {
|
|
18
|
+
execution: "cli",
|
|
19
|
+
outputBytes: 16384,
|
|
20
|
+
lockWaitMs: 12e4,
|
|
21
|
+
onOutput: (text, stream) => {
|
|
22
|
+
process[stream].write(text);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
if (result.exitCode === 127) process.stderr.write("ChatCode CLI: pnpm was not found; install pnpm and make it available on PATH.\n");
|
|
26
|
+
if (result.exitCode !== 0) process.stderr.write(`ChatCode CLI: pnpm failed; diagnostics: ${result.logPath}\n`);
|
|
27
|
+
if (result.exitCode !== 0 && args.some((argument) => /^git\+|^github:|\.git(?:#|$)/.test(argument))) process.stderr.write(`ChatCode CLI: git-hosted plugins build on install via their prepare script, which pnpm blocks until allowed — add the exact key pnpm printed above under allowBuilds in ${join(resolveProfileDir(profile), "pnpm-workspace.yaml")}, then re-run\n`);
|
|
28
|
+
return result.exitCode;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { runPlugin };
|