




















@@ -38,6 +38,15 @@ type StagedNpmInstall = {
3838packageRoot: string;
3939};
404041+type NpmBinShimBackup = {
42+backupDir: string;
43+targetBinDir: string;
44+entries: Array<{
45+name: string;
46+hadExisting: boolean;
47+}>;
48+};
49+4150function formatError(err: unknown): string {
4251return err instanceof Error ? err.message : String(err);
4352}
@@ -51,6 +60,17 @@ async function pathExists(targetPath: string): Promise<boolean> {
5160}
5261}
536263+async function readPackageVersionIfPresent(packageRoot: string | null): Promise<string | null> {
64+if (!packageRoot) {
65+return null;
66+}
67+try {
68+return await readPackageVersion(packageRoot);
69+} catch {
70+return null;
71+}
72+}
73+5474async function createStagedNpmInstall(
5575installTarget: ResolvedGlobalInstallTarget,
5676packageName: string,
@@ -152,12 +172,47 @@ async function replaceNpmBinShims(params: {
152172return;
153173}
154174155-await fs.mkdir(params.targetLayout.binDir, { recursive: true });
156-for (const entry of shimEntries) {
157-await copyPathEntry(
158-path.join(params.stageLayout.binDir, entry),
159-path.join(params.targetLayout.binDir, entry),
160-);
175+const backup: NpmBinShimBackup = {
176+backupDir: await fs.mkdtemp(
177+path.join(params.targetLayout.globalRoot, ".openclaw-shim-backup-"),
178+),
179+targetBinDir: params.targetLayout.binDir,
180+entries: [],
181+};
182+183+try {
184+await fs.mkdir(params.targetLayout.binDir, { recursive: true });
185+for (const entry of shimEntries) {
186+const destination = path.join(params.targetLayout.binDir, entry);
187+const hadExisting = await pathExists(destination);
188+backup.entries.push({ name: entry, hadExisting });
189+if (hadExisting) {
190+await copyPathEntry(destination, path.join(backup.backupDir, entry));
191+}
192+}
193+194+for (const entry of shimEntries) {
195+await copyPathEntry(
196+path.join(params.stageLayout.binDir, entry),
197+path.join(params.targetLayout.binDir, entry),
198+);
199+}
200+} catch (err) {
201+await restoreNpmBinShimBackup(backup);
202+throw err;
203+} finally {
204+await fs.rm(backup.backupDir, { recursive: true, force: true }).catch(() => undefined);
205+}
206+}
207+208+async function restoreNpmBinShimBackup(backup: NpmBinShimBackup): Promise<void> {
209+await fs.mkdir(backup.targetBinDir, { recursive: true });
210+for (const entry of backup.entries) {
211+const destination = path.join(backup.targetBinDir, entry.name);
212+await fs.rm(destination, { recursive: true, force: true }).catch(() => undefined);
213+if (entry.hadExisting) {
214+await copyPathEntry(path.join(backup.backupDir, entry.name), destination);
215+}
161216}
162217}
163218@@ -318,34 +373,39 @@ export async function runGlobalPackageUpdateSteps(params: {
318373}
319374}
320375321-let verifiedPackageRoot =
322-stagedInstall?.packageRoot ??
376+const livePackageRoot =
377+params.installTarget.packageRoot ??
378+params.packageRoot ??
323379(
324380await resolveGlobalInstallTarget({
325381manager: params.installTarget,
326382runCommand: params.runCommand,
327383timeoutMs: params.timeoutMs,
328384})
329385).packageRoot ??
330-params.packageRoot ??
331386null;
387+const verificationPackageRoot = stagedInstall?.packageRoot ?? livePackageRoot;
388+let verifiedPackageRoot = livePackageRoot ?? verificationPackageRoot;
332389333390let afterVersion: string | null = null;
334-if (finalInstallStep.exitCode === 0 && verifiedPackageRoot) {
335-afterVersion = await readPackageVersion(verifiedPackageRoot);
391+if (finalInstallStep.exitCode === 0 && verificationPackageRoot) {
392+const candidateVersion = await readPackageVersion(verificationPackageRoot);
393+if (!stagedInstall) {
394+afterVersion = candidateVersion;
395+}
336396const expectedVersion = resolveExpectedInstalledVersionFromSpec(
337397params.packageName,
338398params.installSpec,
339399);
340400const verificationErrors = await collectInstalledGlobalPackageErrors({
341-packageRoot: verifiedPackageRoot,
401+packageRoot: verificationPackageRoot,
342402 expectedVersion,
343403});
344404if (verificationErrors.length > 0) {
345405steps.push({
346406name: "global install verify",
347-command: `verify ${verifiedPackageRoot}`,
348-cwd: verifiedPackageRoot,
407+command: `verify ${verificationPackageRoot}`,
408+cwd: verificationPackageRoot,
349409durationMs: 0,
350410exitCode: 1,
351411stderrTail: verificationErrors.join("\n"),
@@ -362,6 +422,7 @@ export async function runGlobalPackageUpdateSteps(params: {
362422steps.push(swapStep);
363423if (swapStep.exitCode === 0) {
364424verifiedPackageRoot = params.installTarget.packageRoot ?? verifiedPackageRoot;
425+afterVersion = candidateVersion;
365426}
366427}
367428@@ -372,10 +433,15 @@ export async function runGlobalPackageUpdateSteps(params: {
372433);
373434const postVerifyStep = failedVerifyOrSwap
374435 ? null
375- : await params.postVerifyStep?.(verifiedPackageRoot);
436+ : verifiedPackageRoot
437+ ? await params.postVerifyStep?.(verifiedPackageRoot)
438+ : null;
376439if (postVerifyStep) {
377440steps.push(postVerifyStep);
378441}
442+if (failedVerifyOrSwap && stagedInstall) {
443+afterVersion = await readPackageVersionIfPresent(livePackageRoot);
444+}
379445}
380446381447const failedStep =
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。