
























@@ -748,6 +748,7 @@ function recordClawHubPackageName(value: string | undefined): string | undefined
748748async function installCandidate(params: {
749749candidate: DownloadableInstallCandidate;
750750records: Record<string, PluginInstallRecord>;
751+env: NodeJS.ProcessEnv;
751752updateChannel?: UpdateChannel;
752753mode?: "install" | "update";
753754preferNpm?: boolean;
@@ -758,7 +759,7 @@ async function installCandidate(params: {
758759failedPluginId?: string;
759760}> {
760761const { candidate } = params;
761-const extensionsDir = resolveDefaultPluginExtensionsDir();
762+const extensionsDir = resolveDefaultPluginExtensionsDir(params.env);
762763const changes: string[] = [];
763764const clawhubSpecs = candidate.clawhubSpec
764765 ? resolveClawHubInstallSpecsForUpdateChannel({
@@ -774,6 +775,16 @@ async function installCandidate(params: {
774775 : null;
775776const clawhubInstallSpec = clawhubSpecs?.installSpec ?? candidate.clawhubSpec;
776777const npmInstallSpec = npmSpecs?.installSpec ?? candidate.npmSpec;
778+const npmDir = resolveDefaultPluginNpmDir(params.env);
779+const existingClawHubPackagePath = clawhubInstallSpec
780+ ? resolveExistingCandidateClawHubPackagePath({
781+ candidate,
782+ extensionsDir,
783+})
784+ : null;
785+const existingNpmPackagePath = npmInstallSpec
786+ ? resolveExistingCandidateNpmPackagePath({ candidate, npmDir })
787+ : null;
777788const shouldTryClawHub =
778789clawhubInstallSpec &&
779790!(params.preferNpm && npmInstallSpec) &&
@@ -783,7 +794,7 @@ async function installCandidate(params: {
783794spec: clawhubInstallSpec,
784795 extensionsDir,
785796expectedPluginId: candidate.pluginId,
786-mode: params.mode ?? "install",
797+mode: params.mode === "update" || existingClawHubPackagePath ? "update" : "install",
787798});
788799if (clawhubResult.ok) {
789800const pluginId = clawhubResult.pluginId;
@@ -828,16 +839,31 @@ async function installCandidate(params: {
828839failedPluginId: candidate.pluginId,
829840};
830841}
831-const result = await installPluginFromNpmSpec({
842+const npmInstallMode = params.mode === "update" || existingNpmPackagePath ? "update" : "install";
843+let result = await installPluginFromNpmSpec({
832844spec: npmInstallSpec,
833845 extensionsDir,
846+ npmDir,
834847expectedPluginId: candidate.pluginId,
835848expectedIntegrity: candidate.expectedIntegrity,
836849 ...(candidate.trustedSourceLinkedOfficialInstall
837850 ? { trustedSourceLinkedOfficialInstall: true }
838851 : {}),
839-mode: params.mode ?? "install",
852+mode: npmInstallMode,
840853});
854+if (!result.ok && npmInstallMode === "install" && isPluginAlreadyExistsError(result.error)) {
855+result = await installPluginFromNpmSpec({
856+spec: npmInstallSpec,
857+ extensionsDir,
858+ npmDir,
859+expectedPluginId: candidate.pluginId,
860+expectedIntegrity: candidate.expectedIntegrity,
861+ ...(candidate.trustedSourceLinkedOfficialInstall
862+ ? { trustedSourceLinkedOfficialInstall: true }
863+ : {}),
864+mode: "update",
865+});
866+}
841867if (!result.ok) {
842868return {
843869records: params.records,
@@ -869,6 +895,39 @@ async function installCandidate(params: {
869895};
870896}
871897898+function isPluginAlreadyExistsError(error: string): boolean {
899+return /\bplugin already exists:/.test(error);
900+}
901+902+function resolveExistingCandidateNpmPackagePath(params: {
903+candidate: DownloadableInstallCandidate;
904+npmDir: string;
905+}): string | null {
906+const npmName = params.candidate.npmSpec
907+ ? parseRegistryNpmSpec(params.candidate.npmSpec)?.name
908+ : undefined;
909+if (!npmName) {
910+return null;
911+}
912+const packagePath = resolveNpmPackageInstallPath({
913+packageName: npmName,
914+npmRoot: params.npmDir,
915+});
916+return existsSync(packagePath) ? packagePath : null;
917+}
918+919+function resolveExistingCandidateClawHubPackagePath(params: {
920+candidate: DownloadableInstallCandidate;
921+extensionsDir: string;
922+}): string | null {
923+try {
924+const packagePath = resolvePluginInstallDir(params.candidate.pluginId, params.extensionsDir);
925+return existsSync(packagePath) ? packagePath : null;
926+} catch {
927+return null;
928+}
929+}
930+872931export type RepairMissingPluginInstallsResult = {
873932changes: string[];
874933warnings: string[];
@@ -1165,6 +1224,7 @@ async function repairMissingPluginInstalls(params: {
11651224const installed = await installCandidate({
11661225 candidate,
11671226records: nextRecords,
1227+ env,
11681228 updateChannel,
11691229mode: shouldReplaceBrokenOfficialInstall ? "update" : "install",
11701230preferNpm: preferNpmInstalls,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。