diff --git a/test/helpers/publishable-plugin-fixture.ts b/test/helpers/publishable-plugin-fixture.ts new file mode 100644 index 000000000000..947185ffd190 --- /dev/null +++ b/test/helpers/publishable-plugin-fixture.ts @@ -0,0 +1,62 @@ +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { writeJsonFile } from "./temp-repo.js"; + +type PublishablePluginSurface = "npm" | "clawhub" | "both" | "clawhub-disabled"; + +type PublishablePluginFixtureOptions = { + extensionId?: string; + version: string; + publishTo: PublishablePluginSurface; + bundledDist?: boolean; + dependency?: { + packageName: string; + version: string; + requireLatest?: boolean; + }; +}; + +export function writePublishablePluginFixture( + repoDir: string, + options: PublishablePluginFixtureOptions, +) { + const extensionId = options.extensionId ?? "demo-plugin"; + const packageName = `@openclaw/${extensionId}`; + const packageDir = join(repoDir, "extensions", extensionId); + const publishToNpm = options.publishTo === "npm" || options.publishTo === "both"; + const publishToClawHub = options.publishTo === "clawhub" || options.publishTo === "both"; + const release = { + ...(publishToNpm ? { publishToNpm: true } : {}), + ...(publishToClawHub ? { publishToClawHub: true } : {}), + ...(options.publishTo === "clawhub-disabled" ? { publishToClawHub: false } : {}), + ...(options.dependency?.requireLatest + ? { requireLatestDependencies: [options.dependency.packageName] } + : {}), + }; + writeJsonFile(join(packageDir, "package.json"), { + name: packageName, + version: options.version, + type: "module", + repository: { + type: "git", + url: "https://github.com/openclaw/openclaw", + }, + ...(options.dependency + ? { dependencies: { [options.dependency.packageName]: options.dependency.version } } + : {}), + openclaw: { + extensions: ["./index.ts"], + compat: { pluginApi: `>=${options.version}` }, + build: { + openclawVersion: options.version, + ...(options.bundledDist ? { bundledDist: true } : {}), + }, + install: { npmSpec: packageName }, + release, + }, + }); + const exportName = extensionId.replaceAll(/[-.]/g, "_"); + writeFileSync(join(packageDir, "index.ts"), `export const ${exportName} = 1;\n`); + writeFileSync(join(packageDir, "README.md"), "# Demo plugin\n"); + return { extensionId, packageDir, packageName }; +} diff --git a/test/plugin-clawhub-release.test.ts b/test/plugin-clawhub-release.test.ts index f238f0a4b4e7..116046ccffd7 100644 --- a/test/plugin-clawhub-release.test.ts +++ b/test/plugin-clawhub-release.test.ts @@ -31,7 +31,8 @@ import { OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, } from "../scripts/lib/plugin-npm-release.ts"; import { runPluginClawHubReleaseCheck } from "../scripts/plugin-clawhub-release-check.ts"; -import { cleanupTempDirs, makeTempRepoRoot } from "./helpers/temp-repo.js"; +import { writePublishablePluginFixture } from "./helpers/publishable-plugin-fixture.js"; +import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "./helpers/temp-repo.js"; const tempDirs: string[] = []; @@ -2109,59 +2110,31 @@ function createTempPluginRepo( ); writeFileSync(join(repoDir, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n"); for (const currentExtensionId of extensionIds) { - mkdirSync(join(repoDir, "extensions", currentExtensionId), { recursive: true }); - writeFileSync( - join(repoDir, "extensions", currentExtensionId, "package.json"), - JSON.stringify( - { - name: `@openclaw/${currentExtensionId}`, - version: "2026.4.1", - type: "module", - repository: { - type: "git", - url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, - }, - ...(options.requiredLatestDependencyVersion - ? { - dependencies: { - "demo-runtime": options.requiredLatestDependencyVersion, - }, - } - : {}), - openclaw: { - extensions: ["./index.ts"], - ...(options.includeClawHubContract === false - ? {} - : { - compat: { - pluginApi: ">=2026.4.1", - }, - build: { - openclawVersion: "2026.4.1", - }, - }), - install: { - npmSpec: `@openclaw/${currentExtensionId}`, + const fixture = writePublishablePluginFixture(repoDir, { + extensionId: currentExtensionId, + version: "2026.4.1", + publishTo: options.publishToClawHub === false ? "clawhub-disabled" : "clawhub", + ...(options.requiredLatestDependencyVersion + ? { + dependency: { + packageName: "demo-runtime", + version: options.requiredLatestDependencyVersion, + requireLatest: true, }, - release: { - publishToClawHub: options.publishToClawHub ?? true, - ...(options.requiredLatestDependencyVersion - ? { - requireLatestDependencies: ["demo-runtime"], - } - : {}), - }, - }, - }, - null, - 2, - ), - ); - writeFileSync( - join(repoDir, "extensions", currentExtensionId, "index.ts"), - `export const ${currentExtensionId.replaceAll(/[-.]/g, "_")} = 1;\n`, - ); - writeFileSync(join(repoDir, "extensions", currentExtensionId, "README.md"), "# Demo plugin\n"); + } + : {}), + }); + if (options.includeClawHubContract === false) { + const manifestPath = join(fixture.packageDir, "package.json"); + const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as { + openclaw?: { build?: unknown; compat?: unknown }; + }; + if (manifest.openclaw) { + delete manifest.openclaw.compat; + delete manifest.openclaw.build; + } + writeJsonFile(manifestPath, manifest); + } } git(repoDir, ["init", "-b", "main"]); diff --git a/test/plugin-npm-release.test.ts b/test/plugin-npm-release.test.ts index d4606e0038f4..6ec1ad609ca8 100644 --- a/test/plugin-npm-release.test.ts +++ b/test/plugin-npm-release.test.ts @@ -1,5 +1,5 @@ // Plugin npm release tests validate plugin npm release artifacts. -import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { bundledPluginFile, bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -20,6 +20,7 @@ import { resolveSelectedPublishablePluginPackages, type PublishablePluginPackage, } from "../scripts/lib/plugin-npm-release.ts"; +import { writePublishablePluginFixture } from "./helpers/publishable-plugin-fixture.js"; import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "./helpers/temp-repo.js"; type ExecFileSync = typeof import("node:child_process").execFileSync; @@ -169,12 +170,6 @@ function externalPluginContract(version: string) { }; } -function writePluginReadme(repoDir: string, extensionId: string): void { - const packageDir = join(repoDir, "extensions", extensionId); - mkdirSync(packageDir, { recursive: true }); - writeFileSync(join(packageDir, "README.md"), `# ${extensionId}\n`); -} - describe("collectPublishablePluginPackageErrors", () => { it("accepts a valid publishable plugin package candidate", () => { expect( @@ -500,26 +495,9 @@ describe("collectPluginReleaseDependencyFreshnessErrors", () => { describe("collectPluginReleasePlan", () => { it("fails closed when the published-version lookup times out", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); - mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.4.10", - type: "module", - repository: { - type: "git", - url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, - }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.4.10"), - install: { - npmSpec: "@openclaw/demo-plugin", - }, - release: { - publishToNpm: true, - }, - }, + publishTo: "npm", }); childProcessMock.execFileSyncOverride = ((command: string, args?: readonly string[]) => { expect(command).toBe("npm"); @@ -542,20 +520,10 @@ describe("collectPluginReleasePlan", () => { describe("collectPublishablePluginPackages", () => { it("defers explicitly bundled plugins from npm and ClawHub release plans", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); - mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.4.10", - type: "module", - repository: { type: "git", url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.4.10"), - build: { openclawVersion: "2026.4.10", bundledDist: true }, - install: { npmSpec: "@openclaw/demo-plugin" }, - release: { publishToClawHub: true, publishToNpm: true }, - }, + publishTo: "both", + bundledDist: true, }); expect(collectPublishablePluginPackages(repoDir)).toStrictEqual([]); @@ -599,26 +567,9 @@ describe("collectPublishablePluginPackages", () => { it("collects publishable npm plugins from extension package manifests", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); - mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.4.10", - type: "module", - repository: { - type: "git", - url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, - }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.4.10"), - install: { - npmSpec: "@openclaw/demo-plugin", - }, - release: { - publishToNpm: true, - }, - }, + publishTo: "npm", }); expect(collectPublishablePluginPackages(repoDir)).toEqual([ @@ -637,18 +588,9 @@ describe("collectPublishablePluginPackages", () => { it("uses extended-stable for every publishable plugin at the exact root version", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); writeJsonFile(join(repoDir, "package.json"), { version: "2026.7.33" }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.7.33", - type: "module", - repository: { type: "git", url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.7.33"), - install: { npmSpec: "@openclaw/demo-plugin" }, - release: { publishToNpm: true }, - }, + publishTo: "npm", }); expect( @@ -659,18 +601,9 @@ describe("collectPublishablePluginPackages", () => { it("rejects extended-stable plugins whose version differs from core", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); writeJsonFile(join(repoDir, "package.json"), { version: "2026.7.34" }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.7.33", - type: "module", - repository: { type: "git", url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.7.33"), - install: { npmSpec: "@openclaw/demo-plugin" }, - release: { publishToNpm: true }, - }, + publishTo: "npm", }); expect(() => @@ -680,29 +613,13 @@ describe("collectPublishablePluginPackages", () => { it("collects exact release dependencies that must match npm latest", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); - mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.4.10", - type: "module", - repository: { - type: "git", - url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, - }, - dependencies: { - "demo-runtime": "1.2.3", - }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.4.10"), - install: { - npmSpec: "@openclaw/demo-plugin", - }, - release: { - publishToNpm: true, - requireLatestDependencies: ["demo-runtime"], - }, + publishTo: "npm", + dependency: { + packageName: "demo-runtime", + version: "1.2.3", + requireLatest: true, }, }); @@ -727,26 +644,9 @@ describe("collectPublishablePluginPackages", () => { it("does not validate unselected publishable plugin manifests", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); - mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.4.10-beta.1", - type: "module", - repository: { - type: "git", - url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, - }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.4.10-beta.1"), - install: { - npmSpec: "@openclaw/demo-plugin", - }, - release: { - publishToNpm: true, - }, - }, + publishTo: "npm", }); mkdirSync(join(repoDir, "extensions", "private-plugin"), { recursive: true }); writeJsonFile(join(repoDir, "extensions", "private-plugin", "package.json"), { @@ -807,26 +707,9 @@ describe("collectPublishablePluginPackages", () => { it("publishes alpha plugin packages to the alpha dist-tag", () => { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-npm-release-"); - mkdirSync(join(repoDir, "extensions", "demo-plugin"), { recursive: true }); - writePluginReadme(repoDir, "demo-plugin"); - writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.4.10-alpha.1", - type: "module", - repository: { - type: "git", - url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, - }, - openclaw: { - extensions: ["./index.ts"], - ...externalPluginContract("2026.4.10-alpha.1"), - install: { - npmSpec: "@openclaw/demo-plugin", - }, - release: { - publishToNpm: true, - }, - }, + publishTo: "npm", }); expect(collectPublishablePluginPackages(repoDir)).toEqual([ diff --git a/test/scripts/plugin-release-pretag-pack-check.test.ts b/test/scripts/plugin-release-pretag-pack-check.test.ts index 6ee96d0aecad..229a3b864e2e 100644 --- a/test/scripts/plugin-release-pretag-pack-check.test.ts +++ b/test/scripts/plugin-release-pretag-pack-check.test.ts @@ -1,12 +1,11 @@ // Plugin release pretag pack check tests cover its script-local target and command routing. -import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { OPENCLAW_PLUGIN_NPM_REPOSITORY_URL } from "../../scripts/lib/plugin-npm-release.ts"; import { collectPluginReleasePretagPackTargets, runPluginReleasePretagPackCheck, } from "../../scripts/plugin-release-pretag-pack-check.ts"; +import { writePublishablePluginFixture } from "../helpers/publishable-plugin-fixture.js"; import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "../helpers/temp-repo.js"; const { execFileSyncMock } = vi.hoisted(() => ({ @@ -36,37 +35,11 @@ afterEach(() => { function createDualPublishPluginRepo() { const repoDir = makeTempRepoRoot(tempDirs, "openclaw-plugin-pretag-pack-"); - const packageDir = join(repoDir, "extensions", "demo-plugin"); - mkdirSync(packageDir, { recursive: true }); writeJsonFile(join(repoDir, "package.json"), { name: "openclaw-test-root", type: "module" }); - writeJsonFile(join(packageDir, "package.json"), { - name: "@openclaw/demo-plugin", + writePublishablePluginFixture(repoDir, { version: "2026.4.10", - type: "module", - repository: { - type: "git", - url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, - }, - openclaw: { - extensions: ["./index.ts"], - compat: { - pluginApi: ">=2026.4.10", - }, - build: { - openclawVersion: "2026.4.10", - }, - install: { - npmSpec: "@openclaw/demo-plugin", - }, - release: { - publishToClawHub: true, - publishToNpm: true, - }, - }, + publishTo: "both", }); - writeFileSync(join(packageDir, "README.md"), "# Demo plugin\n"); - writeFileSync(join(packageDir, "index.ts"), "export const demo = 1;\n"); - return repoDir; }