refactor(cli): share pinned npm install record helper

This commit is contained in:
Peter Steinberger
2026-02-21 21:59:53 +00:00
parent 2d4e4e2288
commit 9d17a30643
4 changed files with 127 additions and 37 deletions

View File

@@ -28,8 +28,7 @@ import { resolveUserPath, shortenHomePath } from "../utils.js";
import { formatCliCommand } from "./command-format.js";
import {
buildNpmInstallRecordFields,
logPinnedNpmSpecMessages,
resolvePinnedNpmSpec,
resolvePinnedNpmInstallRecordForCli,
} from "./npm-resolution.js";
import { promptYesNo } from "./prompt.js";
@@ -684,25 +683,19 @@ export function registerHooksCli(program: Command): void {
}
let next = enableInternalHookEntries(cfg, result.hooks);
const pinInfo = resolvePinnedNpmSpec({
rawSpec: raw,
pin: Boolean(opts.pin),
resolvedSpec: result.npmResolution?.resolvedSpec,
});
logPinnedNpmSpecMessages(
pinInfo,
(message) => defaultRuntime.log(message),
(message) => defaultRuntime.log(theme.warn(message)),
const installRecord = resolvePinnedNpmInstallRecordForCli(
raw,
Boolean(opts.pin),
result.targetDir,
result.version,
result.npmResolution,
defaultRuntime.log,
theme.warn,
);
next = recordHookInstall(next, {
hookId: result.hookPackId,
...buildNpmInstallRecordFields({
spec: pinInfo.recordSpec,
installPath: result.targetDir,
version: result.version,
resolution: result.npmResolution,
}),
...installRecord,
hooks: result.hooks,
});
await writeConfigFile(next);

View File

@@ -3,6 +3,8 @@ import {
buildNpmInstallRecordFields,
logPinnedNpmSpecMessages,
mapNpmResolutionMetadata,
resolvePinnedNpmInstallRecord,
resolvePinnedNpmInstallRecordForCli,
resolvePinnedNpmSpec,
} from "./npm-resolution.js";
@@ -103,4 +105,66 @@ describe("npm-resolution helpers", () => {
expect(logs).toEqual(["notice-1"]);
expect(warns).toEqual(["warn-1"]);
});
it("resolves pinned install record and emits pin notice", () => {
const logs: string[] = [];
const warns: string[] = [];
const record = resolvePinnedNpmInstallRecord({
rawSpec: "@openclaw/plugin-alpha@latest",
pin: true,
installPath: "/tmp/openclaw/extensions/alpha",
version: "1.2.3",
resolution: {
name: "@openclaw/plugin-alpha",
version: "1.2.3",
resolvedSpec: "@openclaw/plugin-alpha@1.2.3",
},
log: (message) => logs.push(message),
warn: (message) => warns.push(message),
});
expect(record).toEqual({
source: "npm",
spec: "@openclaw/plugin-alpha@1.2.3",
installPath: "/tmp/openclaw/extensions/alpha",
version: "1.2.3",
resolvedName: "@openclaw/plugin-alpha",
resolvedVersion: "1.2.3",
resolvedSpec: "@openclaw/plugin-alpha@1.2.3",
integrity: undefined,
shasum: undefined,
resolvedAt: undefined,
});
expect(logs).toEqual(["Pinned npm install record to @openclaw/plugin-alpha@1.2.3."]);
expect(warns).toEqual([]);
});
it("resolves pinned install record for CLI and formats warning output", () => {
const logs: string[] = [];
const record = resolvePinnedNpmInstallRecordForCli(
"@openclaw/plugin-alpha@latest",
true,
"/tmp/openclaw/extensions/alpha",
"1.2.3",
undefined,
(message) => logs.push(message),
(message) => `[warn] ${message}`,
);
expect(record).toEqual({
source: "npm",
spec: "@openclaw/plugin-alpha@latest",
installPath: "/tmp/openclaw/extensions/alpha",
version: "1.2.3",
resolvedName: undefined,
resolvedVersion: undefined,
resolvedSpec: undefined,
integrity: undefined,
shasum: undefined,
resolvedAt: undefined,
});
expect(logs).toEqual([
"[warn] Could not resolve exact npm version for --pin; storing original npm spec.",
]);
});
});

View File

@@ -72,6 +72,49 @@ export function buildNpmInstallRecordFields(params: {
};
}
export function resolvePinnedNpmInstallRecord(params: {
rawSpec: string;
pin: boolean;
installPath: string;
version?: string;
resolution?: NpmResolutionMetadata;
log: (message: string) => void;
warn: (message: string) => void;
}): ReturnType<typeof buildNpmInstallRecordFields> {
const pinInfo = resolvePinnedNpmSpec({
rawSpec: params.rawSpec,
pin: params.pin,
resolvedSpec: params.resolution?.resolvedSpec,
});
logPinnedNpmSpecMessages(pinInfo, params.log, params.warn);
return buildNpmInstallRecordFields({
spec: pinInfo.recordSpec,
installPath: params.installPath,
version: params.version,
resolution: params.resolution,
});
}
export function resolvePinnedNpmInstallRecordForCli(
rawSpec: string,
pin: boolean,
installPath: string,
version: string | undefined,
resolution: NpmResolutionMetadata | undefined,
log: (message: string) => void,
warnFormat: (message: string) => string,
): ReturnType<typeof buildNpmInstallRecordFields> {
return resolvePinnedNpmInstallRecord({
rawSpec,
pin,
installPath,
version,
resolution,
log,
warn: (message) => log(warnFormat(message)),
});
}
export function logPinnedNpmSpecMessages(
pinInfo: { pinWarning?: string; pinNotice?: string },
log: (message: string) => void,

View File

@@ -21,11 +21,7 @@ import { formatDocsLink } from "../terminal/links.js";
import { renderTable } from "../terminal/table.js";
import { theme } from "../terminal/theme.js";
import { resolveUserPath, shortenHomeInString, shortenHomePath } from "../utils.js";
import {
buildNpmInstallRecordFields,
logPinnedNpmSpecMessages,
resolvePinnedNpmSpec,
} from "./npm-resolution.js";
import { resolvePinnedNpmInstallRecordForCli } from "./npm-resolution.js";
import { setPluginEnabledInConfig } from "./plugins-config.js";
import { promptYesNo } from "./prompt.js";
@@ -625,24 +621,18 @@ export function registerPluginsCli(program: Command) {
clearPluginManifestRegistryCache();
let next = enablePluginInConfig(cfg, result.pluginId).config;
const pinInfo = resolvePinnedNpmSpec({
rawSpec: raw,
pin: Boolean(opts.pin),
resolvedSpec: result.npmResolution?.resolvedSpec,
});
logPinnedNpmSpecMessages(
pinInfo,
(message) => defaultRuntime.log(message),
(message) => defaultRuntime.log(theme.warn(message)),
const installRecord = resolvePinnedNpmInstallRecordForCli(
raw,
Boolean(opts.pin),
result.targetDir,
result.version,
result.npmResolution,
defaultRuntime.log,
theme.warn,
);
next = recordPluginInstall(next, {
pluginId: result.pluginId,
...buildNpmInstallRecordFields({
spec: pinInfo.recordSpec,
installPath: result.targetDir,
version: result.version,
resolution: result.npmResolution,
}),
...installRecord,
});
const slotResult = applySlotSelectionForPlugin(next, result.pluginId);
next = slotResult.config;