

























@@ -53,17 +53,31 @@ describe("ensureOnboardingPluginInstalled", () => {
5353});
54545555it("passes npm specs and optional expected integrity to npm installs with progress", async () => {
56+const npmResolution = {
57+name: "@wecom/wecom-openclaw-plugin",
58+version: "1.2.3",
59+resolvedSpec: "@wecom/wecom-openclaw-plugin@1.2.3",
60+integrity: "sha512-wecom",
61+shasum: "deadbeef",
62+resolvedAt: "2026-04-24T00:00:00.000Z",
63+};
64+const installFields = {
65+resolvedName: npmResolution.name,
66+resolvedVersion: npmResolution.version,
67+resolvedSpec: npmResolution.resolvedSpec,
68+integrity: npmResolution.integrity,
69+shasum: npmResolution.shasum,
70+resolvedAt: npmResolution.resolvedAt,
71+};
72+buildNpmResolutionInstallFields.mockReturnValueOnce(installFields);
5673installPluginFromNpmSpec.mockImplementation(async (params) => {
5774params.logger?.info?.("Downloading demo-plugin…");
5875return {
5976ok: true,
6077pluginId: "demo-plugin",
6178targetDir: "/tmp/demo-plugin",
6279version: "1.2.3",
63-npmResolution: {
64-resolvedSpec: "@wecom/wecom-openclaw-plugin@1.2.3",
65-integrity: "sha512-wecom",
66-},
80+ npmResolution,
6781};
6882});
6983const stop = vi.fn();
@@ -95,6 +109,18 @@ describe("ensureOnboardingPluginInstalled", () => {
95109);
96110expect(update).toHaveBeenCalledWith("Downloading demo-plugin…");
97111expect(stop).toHaveBeenCalledWith("Installed WeCom plugin");
112+expect(buildNpmResolutionInstallFields).toHaveBeenCalledWith(npmResolution);
113+expect(recordPluginInstall).toHaveBeenCalledWith(
114+expect.anything(),
115+expect.objectContaining({
116+pluginId: "demo-plugin",
117+source: "npm",
118+spec: "@wecom/wecom-openclaw-plugin@1.2.3",
119+installPath: "/tmp/demo-plugin",
120+version: "1.2.3",
121+ ...installFields,
122+}),
123+);
98124expect(result.installed).toBe(true);
99125expect(result.status).toBe("installed");
100126});
@@ -375,6 +401,143 @@ describe("ensureOnboardingPluginInstalled", () => {
375401});
376402});
377403404+it("records local install source metadata when a local path is selected", async () => {
405+await withTempDir({ prefix: "openclaw-onboarding-install-local-record-" }, async (temp) => {
406+const workspaceDir = path.join(temp, "workspace");
407+const pluginDir = path.join(workspaceDir, "plugins", "demo");
408+await fs.mkdir(path.join(workspaceDir, ".git"), { recursive: true });
409+await fs.mkdir(pluginDir, { recursive: true });
410+411+const result = await ensureOnboardingPluginInstalled({
412+cfg: {},
413+entry: {
414+pluginId: "demo-plugin",
415+label: "Demo Plugin",
416+install: {
417+npmSpec: "@demo/plugin@1.2.3",
418+localPath: "plugins/demo",
419+},
420+},
421+prompter: {
422+select: vi.fn(async () => "local"),
423+} as never,
424+runtime: {} as never,
425+ workspaceDir,
426+});
427+428+const realPluginDir = await fs.realpath(pluginDir);
429+expect(recordPluginInstall).toHaveBeenCalledWith(
430+expect.objectContaining({
431+plugins: {
432+load: {
433+paths: [realPluginDir],
434+},
435+},
436+}),
437+{
438+pluginId: "demo-plugin",
439+source: "path",
440+sourcePath: "./plugins/demo",
441+spec: "@demo/plugin@1.2.3",
442+},
443+);
444+expect(result.installed).toBe(true);
445+expect(result.status).toBe("installed");
446+});
447+});
448+449+it("records local install source metadata when npm install falls back to local", async () => {
450+await withTempDir(
451+{ prefix: "openclaw-onboarding-install-npm-fallback-record-" },
452+async (temp) => {
453+const workspaceDir = path.join(temp, "workspace");
454+const pluginDir = path.join(workspaceDir, "plugins", "demo");
455+await fs.mkdir(path.join(workspaceDir, ".git"), { recursive: true });
456+await fs.mkdir(pluginDir, { recursive: true });
457+installPluginFromNpmSpec.mockResolvedValueOnce({
458+ok: false,
459+error: "registry unavailable",
460+});
461+const note = vi.fn(async () => {});
462+463+const result = await ensureOnboardingPluginInstalled({
464+cfg: {},
465+entry: {
466+pluginId: "demo-plugin",
467+label: "Demo Plugin",
468+install: {
469+npmSpec: "@demo/plugin@1.2.3",
470+localPath: "plugins/demo",
471+},
472+},
473+prompter: {
474+select: vi.fn(async () => "npm"),
475+ note,
476+confirm: vi.fn(async () => true),
477+progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
478+} as never,
479+runtime: {} as never,
480+ workspaceDir,
481+});
482+483+const realPluginDir = await fs.realpath(pluginDir);
484+expect(note).toHaveBeenCalledWith(
485+"Failed to install @demo/plugin@1.2.3: registry unavailable\nReturning to selection.",
486+"Plugin install",
487+);
488+expect(recordPluginInstall).toHaveBeenCalledWith(
489+expect.objectContaining({
490+plugins: {
491+load: {
492+paths: [realPluginDir],
493+},
494+},
495+}),
496+{
497+pluginId: "demo-plugin",
498+source: "path",
499+sourcePath: "./plugins/demo",
500+spec: "@demo/plugin@1.2.3",
501+},
502+);
503+expect(result.installed).toBe(true);
504+expect(result.status).toBe("installed");
505+},
506+);
507+});
508+509+it("records absolute local catalog paths as workspace-relative source metadata", async () => {
510+await withTempDir({ prefix: "openclaw-onboarding-install-portable-record-" }, async (temp) => {
511+const workspaceDir = path.join(temp, "workspace");
512+const pluginDir = path.join(workspaceDir, "plugins", "demo");
513+await fs.mkdir(path.join(workspaceDir, ".git"), { recursive: true });
514+await fs.mkdir(pluginDir, { recursive: true });
515+const realPluginDir = await fs.realpath(pluginDir);
516+517+await ensureOnboardingPluginInstalled({
518+cfg: {},
519+entry: {
520+pluginId: "demo-plugin",
521+label: "Demo Plugin",
522+install: {
523+localPath: realPluginDir,
524+},
525+},
526+prompter: {
527+select: vi.fn(async () => "local"),
528+} as never,
529+runtime: {} as never,
530+ workspaceDir,
531+});
532+533+expect(recordPluginInstall).toHaveBeenCalledWith(expect.anything(), {
534+pluginId: "demo-plugin",
535+source: "path",
536+sourcePath: "./plugins/demo",
537+});
538+});
539+});
540+378541it("keeps local installs available when cwd is a git repo but workspaceDir is not", async () => {
379542await withTempDir({ prefix: "openclaw-onboarding-install-cwd-git-" }, async (temp) => {
380543const repoDir = path.join(temp, "repo");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。