diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 493d1effdba..cd7c3e48449 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -fd941e0485a92ebb8256cf2256330b58c2d5bd94189f4a05d7394353ef7bed88 plugin-sdk-api-baseline.json -11ef8362518a0d9f221dc1958b25db46956d1916f278b53e52199bf6c2cbc65b plugin-sdk-api-baseline.jsonl +21914ef8c5840e0defc36d571834dc28a92d6d5ca2d42a088c33b4de681e836a plugin-sdk-api-baseline.json +3f22e6af0dad3433d25d996802d7436a3cc0e68bc86ecaf813a22e2b4e5333eb plugin-sdk-api-baseline.jsonl diff --git a/package.json b/package.json index bc630dc9596..e1f45690c76 100644 --- a/package.json +++ b/package.json @@ -37,14 +37,20 @@ "!dist/extensions/qa-channel/**", "!dist/extensions/qa-lab/**", "!dist/extensions/qa-matrix/**", + "!dist/plugin-sdk/extensions/qa-channel/**", "!dist/plugin-sdk/extensions/qa-lab/**", + "!dist/plugin-sdk/qa-channel.*", + "!dist/plugin-sdk/qa-channel-protocol.*", "!dist/plugin-sdk/qa-lab.*", "!dist/plugin-sdk/qa-runtime.*", + "!dist/plugin-sdk/src/plugin-sdk/qa-channel.d.ts", + "!dist/plugin-sdk/src/plugin-sdk/qa-channel-protocol.d.ts", "!dist/plugin-sdk/src/plugin-sdk/qa-lab.d.ts", "!dist/plugin-sdk/src/plugin-sdk/qa-runtime.d.ts", "!dist/qa-runtime-*.js", "docs/", "!docs/.generated/**", + "!docs/channels/qa-channel.md", "patches/", "skills/", "scripts/npm-runner.mjs", @@ -1044,14 +1050,6 @@ "types": "./dist/plugin-sdk/nostr.d.ts", "default": "./dist/plugin-sdk/nostr.js" }, - "./plugin-sdk/qa-channel": { - "types": "./dist/plugin-sdk/qa-channel.d.ts", - "default": "./dist/plugin-sdk/qa-channel.js" - }, - "./plugin-sdk/qa-channel-protocol": { - "types": "./dist/plugin-sdk/qa-channel-protocol.d.ts", - "default": "./dist/plugin-sdk/qa-channel-protocol.js" - }, "./plugin-sdk/provider-auth": { "types": "./dist/plugin-sdk/provider-auth.d.ts", "default": "./dist/plugin-sdk/provider-auth.js" diff --git a/scripts/check-openclaw-package-tarball.mjs b/scripts/check-openclaw-package-tarball.mjs index e9150325f3d..bdf62b00ded 100644 --- a/scripts/check-openclaw-package-tarball.mjs +++ b/scripts/check-openclaw-package-tarball.mjs @@ -5,8 +5,6 @@ import { spawnSync } from "node:child_process"; import fs from "node:fs"; -const INVENTORY_COMPAT_MISSING_ENTRIES = new Set(["dist/extensions/qa-channel/runtime-api.js"]); - function usage() { return "Usage: node scripts/check-openclaw-package-tarball.mjs "; } @@ -77,9 +75,6 @@ if (entrySet.has("dist/postinstall-inventory.json")) { } else { for (const inventoryEntry of inventory) { const normalizedEntry = inventoryEntry.replace(/\\/gu, "/"); - if (INVENTORY_COMPAT_MISSING_ENTRIES.has(normalizedEntry)) { - continue; - } if (!entrySet.has(normalizedEntry)) { errors.push(`inventory references missing tar entry ${normalizedEntry}`); } diff --git a/scripts/check-plugin-sdk-subpath-exports.mjs b/scripts/check-plugin-sdk-subpath-exports.mjs index d494844ce13..061e37e9dfd 100644 --- a/scripts/check-plugin-sdk-subpath-exports.mjs +++ b/scripts/check-plugin-sdk-subpath-exports.mjs @@ -30,6 +30,16 @@ function readEntrypoints() { return new Set(entrypoints.filter((entry) => entry !== "index")); } +function readPrivateLocalOnlySubpaths() { + const subpaths = JSON.parse( + readFileSync( + path.join(repoRoot, "scripts/lib/plugin-sdk-private-local-only-subpaths.json"), + "utf8", + ), + ); + return new Set(subpaths.filter((entry) => typeof entry === "string" && !entry.includes("/"))); +} + function parsePluginSdkSubpath(specifier) { if (!specifier.startsWith("openclaw/plugin-sdk/")) { return null; @@ -51,6 +61,7 @@ function compareEntries(left, right) { async function collectViolations() { const entrypoints = readEntrypoints(); const exports = readPackageExports(); + const privateLocalOnlySubpaths = readPrivateLocalOnlySubpaths(); const files = (await collectTypeScriptFilesFromRoots(scanRoots, { includeTests: true })).toSorted( (left, right) => normalizeRepoPath(repoRoot, left).localeCompare(normalizeRepoPath(repoRoot, right)), @@ -72,6 +83,9 @@ async function collectViolations() { if (!subpath) { return; } + if (privateLocalOnlySubpaths.has(subpath)) { + return; + } const missingFrom = []; if (!entrypoints.has(subpath)) { diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 3b01bf352fa..ee0371c4f72 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -246,8 +246,6 @@ "native-command-registry", "nextcloud-talk", "nostr", - "qa-channel", - "qa-channel-protocol", "provider-auth", "provider-auth-runtime", "provider-auth-api-key", diff --git a/scripts/lib/plugin-sdk-private-local-only-subpaths.json b/scripts/lib/plugin-sdk-private-local-only-subpaths.json index 6b4a7af24a7..5e7ea8c64ca 100644 --- a/scripts/lib/plugin-sdk-private-local-only-subpaths.json +++ b/scripts/lib/plugin-sdk-private-local-only-subpaths.json @@ -1 +1 @@ -["qa-lab", "qa-runtime"] +["qa-channel", "qa-channel-protocol", "qa-lab", "qa-runtime"] diff --git a/scripts/openclaw-npm-release-check.ts b/scripts/openclaw-npm-release-check.ts index 328e8bbc167..1d975d20f98 100644 --- a/scripts/openclaw-npm-release-check.ts +++ b/scripts/openclaw-npm-release-check.ts @@ -74,6 +74,11 @@ const FORBIDDEN_PACKED_PATH_RULES = [ describe: (packedPath: string) => `npm package must not include generated docs artifact "${packedPath}".`, }, + { + prefix: "docs/channels/qa-channel.md", + describe: (packedPath: string) => + `npm package must not include private QA channel docs "${packedPath}".`, + }, { prefix: "dist/extensions/qa-channel/", describe: (packedPath: string) => @@ -84,11 +89,26 @@ const FORBIDDEN_PACKED_PATH_RULES = [ describe: (packedPath: string) => `npm package must not include private QA lab artifact "${packedPath}".`, }, + { + prefix: "dist/plugin-sdk/extensions/qa-channel/", + describe: (packedPath: string) => + `npm package must not include private QA channel type artifact "${packedPath}".`, + }, { prefix: "dist/plugin-sdk/extensions/qa-lab/", describe: (packedPath: string) => `npm package must not include private QA lab type artifact "${packedPath}".`, }, + { + prefix: "dist/plugin-sdk/qa-channel.", + describe: (packedPath: string) => + `npm package must not include private QA channel SDK artifact "${packedPath}".`, + }, + { + prefix: "dist/plugin-sdk/qa-channel-protocol.", + describe: (packedPath: string) => + `npm package must not include private QA channel SDK artifact "${packedPath}".`, + }, { prefix: "dist/qa-runtime-", describe: (packedPath: string) => @@ -103,6 +123,8 @@ const FORBIDDEN_PACKED_PATH_RULES = [ const FORBIDDEN_PRIVATE_QA_CONTENT_MARKERS = [ "//#region extensions/qa-lab/", "qa-channel/runtime-api.js", + "qa-channel.js", + "qa-channel-protocol.js", "qa-lab/cli.js", "qa-lab/runtime-api.js", ] as const; @@ -559,9 +581,6 @@ export function collectForbiddenPackedContentErrors( const textPathPattern = /\.(?:[cm]?js|d\.ts|json|md|mjs|cjs)$/u; const errors: string[] = []; for (const packedPath of paths) { - if (packedPath === PACKAGE_DIST_INVENTORY_RELATIVE_PATH) { - continue; - } if ( !FORBIDDEN_PRIVATE_QA_CONTENT_SCAN_PREFIXES.some((prefix) => packedPath.startsWith(prefix)) ) { diff --git a/scripts/postinstall-bundled-plugins.mjs b/scripts/postinstall-bundled-plugins.mjs index 5e5573ae571..c4917a0ca43 100644 --- a/scripts/postinstall-bundled-plugins.mjs +++ b/scripts/postinstall-bundled-plugins.mjs @@ -11,7 +11,6 @@ import { closeSync, existsSync, lstatSync, - mkdirSync, openSync, readdirSync, readFileSync, @@ -35,18 +34,6 @@ const DISABLE_POSTINSTALL_ENV = "OPENCLAW_DISABLE_BUNDLED_PLUGIN_POSTINSTALL"; const DISABLE_PLUGIN_REGISTRY_MIGRATION_ENV = "OPENCLAW_DISABLE_PLUGIN_REGISTRY_MIGRATION"; const EAGER_BUNDLED_PLUGIN_DEPS_ENV = "OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS"; const DIST_INVENTORY_PATH = "dist/postinstall-inventory.json"; -const LEGACY_QA_CHANNEL_DIR = ["qa", "channel"].join("-"); -const LEGACY_QA_LAB_DIR = ["qa", "lab"].join("-"); -const LEGACY_UPDATE_COMPAT_SIDECARS = [ - { - path: `dist/extensions/${LEGACY_QA_CHANNEL_DIR}/runtime-api.js`, - content: "export {};\n", - }, - { - path: `dist/extensions/${LEGACY_QA_LAB_DIR}/runtime-api.js`, - content: "export {};\n", - }, -]; const BAILEYS_MEDIA_FILE = join( "node_modules", "@whiskeysockets", @@ -329,29 +316,6 @@ export function pruneInstalledPackageDist(params = {}) { return removed; } -export function restoreLegacyUpdaterCompatSidecars(params = {}) { - const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT; - const writeFile = params.writeFileSync ?? writeFileSync; - const makeDirectory = params.mkdirSync ?? mkdirSync; - const log = params.log ?? console; - const restored = []; - - for (const sidecar of LEGACY_UPDATE_COMPAT_SIDECARS) { - // Older npm updater builds verify these exact sidecars after npm has - // already replaced the package, so generate them independently of prune - // results. - const sidecarPath = join(packageRoot, sidecar.path); - makeDirectory(dirname(sidecarPath), { recursive: true }); - writeFile(sidecarPath, sidecar.content, "utf8"); - restored.push(sidecar.path); - } - - if (restored.length > 0) { - log.log(`[postinstall] restored legacy updater compat sidecars: ${restored.join(", ")}`); - } - return restored; -} - function dependencySentinelPath(depName) { return join("node_modules", ...depName.split("/"), "package.json"); } @@ -781,7 +745,7 @@ export function runBundledPluginPostinstall(params = {}) { }); return; } - const prunedDistFiles = pruneInstalledPackageDist({ + pruneInstalledPackageDist({ packageRoot, existsSync: pathExists, readFileSync: params.readFileSync, @@ -789,13 +753,6 @@ export function runBundledPluginPostinstall(params = {}) { rmSync: params.rmSync, log, }); - restoreLegacyUpdaterCompatSidecars({ - packageRoot, - removedFiles: prunedDistFiles, - mkdirSync: params.mkdirSync, - writeFileSync: params.writeFileSync, - log, - }); if ( !shouldRunBundledPluginPostinstall({ env, diff --git a/scripts/release-check.ts b/scripts/release-check.ts index 1c6746f9e97..ea039f7d2fc 100755 --- a/scripts/release-check.ts +++ b/scripts/release-check.ts @@ -79,19 +79,27 @@ const forbiddenPrefixes = [ "dist/OpenClaw.app/", "dist/extensions/qa-channel/", "dist/extensions/qa-lab/", + "dist/plugin-sdk/extensions/qa-channel/", "dist/plugin-sdk/extensions/qa-lab/", + "dist/plugin-sdk/qa-channel.", + "dist/plugin-sdk/qa-channel-protocol.", "dist/plugin-sdk/qa-lab.", "dist/plugin-sdk/qa-runtime.", + "dist/plugin-sdk/src/plugin-sdk/qa-channel.d.ts", + "dist/plugin-sdk/src/plugin-sdk/qa-channel-protocol.d.ts", "dist/plugin-sdk/src/plugin-sdk/qa-lab.d.ts", "dist/plugin-sdk/src/plugin-sdk/qa-runtime.d.ts", "dist/qa-runtime-", "dist/plugin-sdk/.tsbuildinfo", "docs/.generated/", + "docs/channels/qa-channel.md", "qa/", ]; const forbiddenPrivateQaContentMarkers = [ "//#region extensions/qa-lab/", "qa-channel/runtime-api.js", + "qa-channel.js", + "qa-channel-protocol.js", "qa-lab/cli.js", "qa-lab/runtime-api.js", ] as const; @@ -602,9 +610,6 @@ export function collectForbiddenPackContentPaths( const textPathPattern = /\.(?:[cm]?js|d\.ts|json|md|mjs|cjs)$/u; return [...paths] .filter((packedPath) => { - if (packedPath === PACKAGE_DIST_INVENTORY_RELATIVE_PATH) { - return false; - } if (!forbiddenPrivateQaContentScanPrefixes.some((prefix) => packedPath.startsWith(prefix))) { return false; } diff --git a/scripts/write-npm-update-compat-sidecars.ts b/scripts/write-npm-update-compat-sidecars.ts deleted file mode 100644 index f002f6f81fa..00000000000 --- a/scripts/write-npm-update-compat-sidecars.ts +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env -S node --import tsx - -import fs from "node:fs"; -import path from "node:path"; -import { NPM_UPDATE_COMPAT_SIDECARS } from "../src/infra/npm-update-compat-sidecars.ts"; - -for (const entry of NPM_UPDATE_COMPAT_SIDECARS) { - fs.mkdirSync(path.dirname(entry.path), { recursive: true }); - fs.writeFileSync(entry.path, entry.content, "utf8"); -} diff --git a/src/infra/npm-update-compat-sidecars.ts b/src/infra/npm-update-compat-sidecars.ts deleted file mode 100644 index 456746f9770..00000000000 --- a/src/infra/npm-update-compat-sidecars.ts +++ /dev/null @@ -1,30 +0,0 @@ -const LEGACY_QA_CHANNEL_DIR = ["qa", "channel"].join("-"); -const LEGACY_QA_LAB_DIR = ["qa", "lab"].join("-"); - -type NpmUpdateCompatSidecar = { - path: string; - content: string; -}; - -const EMPTY_RUNTIME_SIDECAR = "export {};\n"; - -export const NPM_UPDATE_COMPAT_SIDECARS = [ - { - path: `dist/extensions/${LEGACY_QA_CHANNEL_DIR}/runtime-api.js`, - content: EMPTY_RUNTIME_SIDECAR, - }, - { - path: `dist/extensions/${LEGACY_QA_LAB_DIR}/runtime-api.js`, - content: EMPTY_RUNTIME_SIDECAR, - }, -] as const satisfies readonly NpmUpdateCompatSidecar[]; - -export const NPM_UPDATE_COMPAT_SIDECAR_PATHS = new Set( - NPM_UPDATE_COMPAT_SIDECARS.map((entry) => entry.path), -); - -export const NPM_UPDATE_OMITTED_BUNDLED_PLUGIN_ROOTS = new Set([ - `dist/extensions/${LEGACY_QA_CHANNEL_DIR}`, - `dist/extensions/${LEGACY_QA_LAB_DIR}`, - "dist/extensions/qa-matrix", -]); diff --git a/src/infra/package-dist-inventory.test.ts b/src/infra/package-dist-inventory.test.ts index 292077d1883..4f9e7029600 100644 --- a/src/infra/package-dist-inventory.test.ts +++ b/src/infra/package-dist-inventory.test.ts @@ -21,7 +21,6 @@ describe("package dist inventory", () => { await expect(writePackageDistInventory(packageRoot)).resolves.toEqual([ "dist/current-BR6xv1a1.js", - "dist/extensions/qa-channel/runtime-api.js", ]); await expect(collectPackageDistInventoryErrors(packageRoot)).resolves.toEqual([]); @@ -65,6 +64,18 @@ describe("package dist inventory", () => { "index.js", ); const omittedQaLabPluginSdk = path.join(packageRoot, "dist", "plugin-sdk", "qa-lab.js"); + const omittedQaChannelPluginSdk = path.join( + packageRoot, + "dist", + "plugin-sdk", + "qa-channel.js", + ); + const omittedQaChannelProtocolPluginSdk = path.join( + packageRoot, + "dist", + "plugin-sdk", + "qa-channel-protocol.js", + ); const omittedQaLabTypes = path.join( packageRoot, "dist", @@ -135,6 +146,8 @@ describe("package dist inventory", () => { await fs.writeFile(omittedQaLabChunk, "export {};\n", "utf8"); await fs.writeFile(omittedQaMatrixChunk, "export {};\n", "utf8"); await fs.writeFile(omittedQaLabPluginSdk, "export {};\n", "utf8"); + await fs.writeFile(omittedQaChannelPluginSdk, "export {};\n", "utf8"); + await fs.writeFile(omittedQaChannelProtocolPluginSdk, "export {};\n", "utf8"); await fs.writeFile(omittedQaLabTypes, "export {};\n", "utf8"); await fs.writeFile(omittedQaRuntimeChunk, "export {};\n", "utf8"); await fs.writeFile(omittedRuntimeDepsStamp, "{}\n", "utf8"); @@ -150,9 +163,7 @@ describe("package dist inventory", () => { ); await fs.writeFile(omittedMap, "{}", "utf8"); - await expect(writePackageDistInventory(packageRoot)).resolves.toEqual([ - "dist/extensions/qa-channel/runtime-api.js", - ]); + await expect(writePackageDistInventory(packageRoot)).resolves.toEqual([]); }); }); diff --git a/src/infra/package-dist-inventory.ts b/src/infra/package-dist-inventory.ts index 34b893e968c..7afefd88a95 100644 --- a/src/infra/package-dist-inventory.ts +++ b/src/infra/package-dist-inventory.ts @@ -1,24 +1,29 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { NPM_UPDATE_COMPAT_SIDECAR_PATHS } from "./npm-update-compat-sidecars.js"; export const PACKAGE_DIST_INVENTORY_RELATIVE_PATH = "dist/postinstall-inventory.json"; const LEGACY_QA_CHANNEL_DIR = ["qa", "channel"].join("-"); const LEGACY_QA_LAB_DIR = ["qa", "lab"].join("-"); -const LEGACY_VERIFIER_COMPAT_INVENTORY_PATHS = [ - `dist/extensions/${LEGACY_QA_CHANNEL_DIR}/runtime-api.js`, -]; const OMITTED_QA_EXTENSION_PREFIXES = [ `dist/extensions/${LEGACY_QA_CHANNEL_DIR}/`, `dist/extensions/${LEGACY_QA_LAB_DIR}/`, "dist/extensions/qa-matrix/", ]; -const OMITTED_PRIVATE_QA_PLUGIN_SDK_PREFIXES = [`dist/plugin-sdk/extensions/${LEGACY_QA_LAB_DIR}/`]; +const OMITTED_PRIVATE_QA_PLUGIN_SDK_PREFIXES = [ + `dist/plugin-sdk/extensions/${LEGACY_QA_CHANNEL_DIR}/`, + `dist/plugin-sdk/extensions/${LEGACY_QA_LAB_DIR}/`, +]; const OMITTED_PRIVATE_QA_PLUGIN_SDK_FILES = new Set([ + `dist/plugin-sdk/${LEGACY_QA_CHANNEL_DIR}.d.ts`, + `dist/plugin-sdk/${LEGACY_QA_CHANNEL_DIR}.js`, + `dist/plugin-sdk/${LEGACY_QA_CHANNEL_DIR}-protocol.d.ts`, + `dist/plugin-sdk/${LEGACY_QA_CHANNEL_DIR}-protocol.js`, `dist/plugin-sdk/${LEGACY_QA_LAB_DIR}.d.ts`, `dist/plugin-sdk/${LEGACY_QA_LAB_DIR}.js`, "dist/plugin-sdk/qa-runtime.d.ts", "dist/plugin-sdk/qa-runtime.js", + `dist/plugin-sdk/src/plugin-sdk/${LEGACY_QA_CHANNEL_DIR}.d.ts`, + `dist/plugin-sdk/src/plugin-sdk/${LEGACY_QA_CHANNEL_DIR}-protocol.d.ts`, `dist/plugin-sdk/src/plugin-sdk/${LEGACY_QA_LAB_DIR}.d.ts`, "dist/plugin-sdk/src/plugin-sdk/qa-runtime.d.ts", ]); @@ -28,6 +33,7 @@ const OMITTED_DIST_SUBTREE_PATTERNS = [ /^dist\/extensions\/[^/]+\/node_modules(?:\/|$)/u, /^dist\/extensions\/[^/]+\/\.openclaw-runtime-deps-[^/]+(?:\/|$)/u, /^dist\/extensions\/qa-matrix(?:\/|$)/u, + new RegExp(`^dist/plugin-sdk/extensions/${LEGACY_QA_CHANNEL_DIR}(?:/|$)`, "u"), new RegExp(`^dist/plugin-sdk/extensions/${LEGACY_QA_LAB_DIR}(?:/|$)`, "u"), ] as const; const INSTALL_STAGE_DEBRIS_DIR_PATTERN = /^\.openclaw-install-stage(?:-[^/]+)?$/iu; @@ -67,9 +73,6 @@ function isPackagedDistPath(relativePath: string): boolean { if (relativePath === "dist/plugin-sdk/.tsbuildinfo") { return false; } - if (LEGACY_VERIFIER_COMPAT_INVENTORY_PATHS.includes(relativePath)) { - return true; - } if ( OMITTED_PRIVATE_QA_PLUGIN_SDK_PREFIXES.some((prefix) => relativePath.startsWith(prefix)) || OMITTED_PRIVATE_QA_PLUGIN_SDK_FILES.has(relativePath) || @@ -219,12 +222,9 @@ export async function assertNoBundledRuntimeDepsStagingDebris(packageRoot: strin export async function writePackageDistInventory(packageRoot: string): Promise { await assertNoBundledRuntimeDepsStagingDebris(packageRoot); - const inventory = [ - ...new Set([ - ...(await collectPackageDistInventory(packageRoot)), - ...LEGACY_VERIFIER_COMPAT_INVENTORY_PATHS, - ]), - ].toSorted((left, right) => left.localeCompare(right)); + const inventory = [...new Set(await collectPackageDistInventory(packageRoot))].toSorted( + (left, right) => left.localeCompare(right), + ); const inventoryPath = path.join(packageRoot, PACKAGE_DIST_INVENTORY_RELATIVE_PATH); await fs.mkdir(path.dirname(inventoryPath), { recursive: true }); await fs.writeFile(inventoryPath, `${JSON.stringify(inventory, null, 2)}\n`, "utf8"); @@ -269,9 +269,6 @@ export async function collectPackageDistInventoryErrors(packageRoot: string): Pr for (const relativePath of expectedFiles) { if (!actualSet.has(relativePath)) { - if (NPM_UPDATE_COMPAT_SIDECAR_PATHS.has(relativePath)) { - continue; - } errors.push(`missing packaged dist file ${relativePath}`); } } diff --git a/src/infra/update-global.test.ts b/src/infra/update-global.test.ts index 869a0943c18..f406afbb8a5 100644 --- a/src/infra/update-global.test.ts +++ b/src/infra/update-global.test.ts @@ -5,7 +5,6 @@ import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.j import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../plugins/runtime-sidecar-paths.js"; import { withTempDir } from "../test-helpers/temp-dir.js"; import { captureEnv } from "../test-utils/env.js"; -import { NPM_UPDATE_COMPAT_SIDECAR_PATHS } from "./npm-update-compat-sidecars.js"; import { PACKAGE_DIST_INVENTORY_RELATIVE_PATH, writePackageDistInventory, @@ -39,14 +38,6 @@ async function writeGlobalPackageJson(packageRoot: string, version = "1.0.0") { ); } -async function writeCompatSidecars(packageRoot: string) { - for (const relativePath of NPM_UPDATE_COMPAT_SIDECAR_PATHS) { - const absolutePath = path.join(packageRoot, relativePath); - await fs.mkdir(path.dirname(absolutePath), { recursive: true }); - await fs.writeFile(absolutePath, "export {};\n", "utf-8"); - } -} - async function writeBundledPluginPackageJson( packageRoot: string, pluginId: string, @@ -399,7 +390,6 @@ describe("update global helpers", () => { it("checks installed dist against the packaged inventory", async () => { await withTempDir({ prefix: "openclaw-update-global-pkg-" }, async (packageRoot) => { await writeGlobalPackageJson(packageRoot); - await writeCompatSidecars(packageRoot); for (const relativePath of BUNDLED_RUNTIME_SIDECAR_PATHS) { const absolutePath = path.join(packageRoot, relativePath); await fs.mkdir(path.dirname(absolutePath), { recursive: true }); @@ -428,7 +418,6 @@ describe("update global helpers", () => { it("ignores bundled plugin install stages during installed dist verification", async () => { await withTempDir({ prefix: "openclaw-update-global-plugin-stage-" }, async (packageRoot) => { await writeGlobalPackageJson(packageRoot); - await writeCompatSidecars(packageRoot); await fs.mkdir(path.join(packageRoot, "dist", "extensions", "brave"), { recursive: true }); await writePackageDistInventory(packageRoot); @@ -456,7 +445,6 @@ describe("update global helpers", () => { it("does not require private QA sidecars when the inventory is missing", async () => { await withTempDir({ prefix: "openclaw-update-global-legacy-" }, async (packageRoot) => { await writeGlobalPackageJson(packageRoot); - await writeCompatSidecars(packageRoot); await expect(collectInstalledGlobalPackageErrors({ packageRoot })).resolves.toEqual([]); }); @@ -467,7 +455,6 @@ describe("update global helpers", () => { { prefix: "openclaw-update-global-missing-inventory-new-" }, async (packageRoot) => { await writeGlobalPackageJson(packageRoot, "2026.4.15"); - await writeCompatSidecars(packageRoot); await expect(collectInstalledGlobalPackageErrors({ packageRoot })).resolves.toContain( `missing package dist inventory ${PACKAGE_DIST_INVENTORY_RELATIVE_PATH}`, @@ -511,7 +498,6 @@ describe("update global helpers", () => { { prefix: "openclaw-update-global-critical-sidecars-" }, async (packageRoot) => { await writeGlobalPackageJson(packageRoot, "2026.4.15"); - await writeCompatSidecars(packageRoot); await writeBundledPluginPackageJson(packageRoot, "matrix", "@openclaw/matrix"); await writePackageDistInventory(packageRoot); @@ -527,7 +513,6 @@ describe("update global helpers", () => { { prefix: "openclaw-update-global-stale-private-qa-" }, async (packageRoot) => { await writeGlobalPackageJson(packageRoot, "2026.4.15"); - await writeCompatSidecars(packageRoot); await writeBundledPluginPackageJson(packageRoot, "qa-lab", "@openclaw/qa-lab"); await writePackageDistInventory(packageRoot); diff --git a/src/infra/update-global.ts b/src/infra/update-global.ts index 5c1f479499c..c2359125008 100644 --- a/src/infra/update-global.ts +++ b/src/infra/update-global.ts @@ -5,10 +5,6 @@ import path from "node:path"; import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../plugins/runtime-sidecar-paths.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { pathExists } from "../utils.js"; -import { - NPM_UPDATE_COMPAT_SIDECAR_PATHS, - NPM_UPDATE_OMITTED_BUNDLED_PLUGIN_ROOTS, -} from "./npm-update-compat-sidecars.js"; import { collectPackageDistInventory, PACKAGE_DIST_INVENTORY_RELATIVE_PATH, @@ -46,6 +42,11 @@ const NPM_GLOBAL_INSTALL_OMIT_OPTIONAL_FLAGS = [ ...NPM_GLOBAL_INSTALL_QUIET_FLAGS, ] as const; const FIRST_PACKAGED_DIST_INVENTORY_VERSION = { major: 2026, minor: 4, patch: 15 }; +const OMITTED_PRIVATE_QA_BUNDLED_PLUGIN_ROOTS = new Set([ + "dist/extensions/qa-channel", + "dist/extensions/qa-lab", + "dist/extensions/qa-matrix", +]); function normalizePackageTarget(value: string): string { return value.trim(); @@ -187,25 +188,18 @@ async function collectInstalledPackageDistErrors(params: { } async function collectLegacyInstalledPackageDistPaths(packageRoot: string): Promise { - const expectedFiles = new Set(NPM_UPDATE_COMPAT_SIDECAR_PATHS); - for (const relativePath of await collectCriticalInstalledPackageDistPaths(packageRoot)) { - expectedFiles.add(relativePath); - } - return [...expectedFiles].toSorted((left, right) => left.localeCompare(right)); + return await collectCriticalInstalledPackageDistPaths(packageRoot); } async function collectCriticalInstalledPackageDistPaths(packageRoot: string): Promise { const expectedFiles = new Set(); await Promise.all( BUNDLED_RUNTIME_SIDECAR_PATHS.map(async (relativePath) => { - if (NPM_UPDATE_COMPAT_SIDECAR_PATHS.has(relativePath)) { - return; - } const pluginRoot = resolveBundledPluginRoot(relativePath); if (pluginRoot === null) { return; } - if (NPM_UPDATE_OMITTED_BUNDLED_PLUGIN_ROOTS.has(pluginRoot)) { + if (OMITTED_PRIVATE_QA_BUNDLED_PLUGIN_ROOTS.has(pluginRoot)) { return; } if ( @@ -239,18 +233,12 @@ async function collectInstalledPathErrors(params: { ? actualSet.has(relativePath) : await pathExists(path.join(params.packageRoot, relativePath)); if (!exists) { - if (NPM_UPDATE_COMPAT_SIDECAR_PATHS.has(relativePath)) { - continue; - } errors.push(params.missingMessage(relativePath)); } } if (actualSet !== null && params.unexpectedMessage) { const expectedSet = new Set(params.expectedFiles); for (const relativePath of params.actualFiles ?? []) { - if (NPM_UPDATE_COMPAT_SIDECAR_PATHS.has(relativePath)) { - continue; - } if (!expectedSet.has(relativePath)) { errors.push(params.unexpectedMessage(relativePath)); } diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index f383efbd260..553aad87c0a 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -354,6 +354,8 @@ describe("plugin-sdk subpath exports", () => { "lobster", "pairing-access", "provider-model-definitions", + "qa-channel", + "qa-channel-protocol", "reply-prefix", "secret-input-schema", "signal-core", diff --git a/test/openclaw-npm-release-check.test.ts b/test/openclaw-npm-release-check.test.ts index 6e4b49a1928..0016959c9d3 100644 --- a/test/openclaw-npm-release-check.test.ts +++ b/test/openclaw-npm-release-check.test.ts @@ -333,16 +333,26 @@ describe("collectForbiddenPackedPathErrors", () => { "dist/extensions/qa-channel/package.json", "dist/extensions/qa-lab/runtime-api.js", "dist/extensions/qa-lab/src/cli.js", + "dist/plugin-sdk/extensions/qa-channel/api.d.ts", "dist/plugin-sdk/extensions/qa-lab/cli.d.ts", + "dist/plugin-sdk/qa-channel.js", + "dist/plugin-sdk/qa-channel-protocol.d.ts", "dist/qa-runtime-B9LDtssJ.js", + "docs/channels/qa-channel.md", + "docs/refactor/qa.md", "qa/scenarios/index.md", ]), ).toEqual([ 'npm package must not include private QA channel artifact "dist/extensions/qa-channel/package.json".', 'npm package must not include private QA channel artifact "dist/extensions/qa-channel/runtime-api.js".', + 'npm package must not include private QA channel docs "docs/channels/qa-channel.md".', + 'npm package must not include private QA channel SDK artifact "dist/plugin-sdk/qa-channel-protocol.d.ts".', + 'npm package must not include private QA channel SDK artifact "dist/plugin-sdk/qa-channel.js".', + 'npm package must not include private QA channel type artifact "dist/plugin-sdk/extensions/qa-channel/api.d.ts".', 'npm package must not include private QA lab artifact "dist/extensions/qa-lab/runtime-api.js".', 'npm package must not include private QA lab artifact "dist/extensions/qa-lab/src/cli.js".', 'npm package must not include private QA lab type artifact "dist/plugin-sdk/extensions/qa-lab/cli.d.ts".', + 'npm package must not include private QA refactor docs "docs/refactor/qa.md".', 'npm package must not include private QA runtime chunk "dist/qa-runtime-B9LDtssJ.js".', 'npm package must not include private QA suite artifact "qa/scenarios/index.md".', ]); @@ -380,7 +390,7 @@ describe("collectForbiddenPackedPathErrors", () => { } }); - it("allows legacy QA compatibility paths in the generated dist inventory", () => { + it("rejects private QA paths in the generated dist inventory", () => { const rootDir = mkdtempSync(join(tmpdir(), "openclaw-pack-inventory-")); try { @@ -393,7 +403,9 @@ describe("collectForbiddenPackedPathErrors", () => { expect( collectForbiddenPackedContentErrors([PACKAGE_DIST_INVENTORY_RELATIVE_PATH], rootDir), - ).toEqual([]); + ).toEqual([ + 'npm package must not include private QA lab marker "qa-lab/runtime-api.js" in "dist/postinstall-inventory.json".', + ]); } finally { rmSync(rootDir, { recursive: true, force: true }); } diff --git a/test/release-check.test.ts b/test/release-check.test.ts index da894422940..c8347f5542f 100644 --- a/test/release-check.test.ts +++ b/test/release-check.test.ts @@ -451,19 +451,29 @@ describe("collectForbiddenPackPaths", () => { "dist/index.js", "dist/extensions/qa-channel/runtime-api.js", "dist/extensions/qa-lab/runtime-api.js", + "dist/plugin-sdk/extensions/qa-channel/api.d.ts", "dist/plugin-sdk/extensions/qa-lab/cli.d.ts", + "dist/plugin-sdk/qa-channel.js", + "dist/plugin-sdk/qa-channel-protocol.d.ts", "dist/plugin-sdk/qa-lab.js", "dist/plugin-sdk/qa-runtime.js", "dist/qa-runtime-B9LDtssJ.js", + "docs/channels/qa-channel.md", + "docs/refactor/qa.md", "qa/scenarios/index.md", ]), ).toEqual([ "dist/extensions/qa-channel/runtime-api.js", "dist/extensions/qa-lab/runtime-api.js", + "dist/plugin-sdk/extensions/qa-channel/api.d.ts", "dist/plugin-sdk/extensions/qa-lab/cli.d.ts", + "dist/plugin-sdk/qa-channel-protocol.d.ts", + "dist/plugin-sdk/qa-channel.js", "dist/plugin-sdk/qa-lab.js", "dist/plugin-sdk/qa-runtime.js", "dist/qa-runtime-B9LDtssJ.js", + "docs/channels/qa-channel.md", + "docs/refactor/qa.md", "qa/scenarios/index.md", ]); }); @@ -488,7 +498,7 @@ describe("collectForbiddenPackPaths", () => { } }); - it("allows legacy QA compatibility paths in the generated dist inventory", () => { + it("blocks private QA paths in the generated dist inventory", () => { const tempRoot = mkdtempSync(join(tmpdir(), "openclaw-release-inventory-")); try { @@ -501,7 +511,7 @@ describe("collectForbiddenPackPaths", () => { expect( collectForbiddenPackContentPaths([PACKAGE_DIST_INVENTORY_RELATIVE_PATH], tempRoot), - ).toEqual([]); + ).toEqual([PACKAGE_DIST_INVENTORY_RELATIVE_PATH]); } finally { rmSync(tempRoot, { recursive: true, force: true }); } diff --git a/test/scripts/postinstall-bundled-plugins.test.ts b/test/scripts/postinstall-bundled-plugins.test.ts index 90d25a0185f..d32542c8d2e 100644 --- a/test/scripts/postinstall-bundled-plugins.test.ts +++ b/test/scripts/postinstall-bundled-plugins.test.ts @@ -11,9 +11,7 @@ import { pruneBundledPluginSourceNodeModules, runBundledPluginPostinstall, runPluginRegistryPostinstallMigration, - restoreLegacyUpdaterCompatSidecars, } from "../../scripts/postinstall-bundled-plugins.mjs"; -import { NPM_UPDATE_COMPAT_SIDECARS } from "../../src/infra/npm-update-compat-sidecars.ts"; import { writePackageDistInventory } from "../../src/infra/package-dist-inventory.ts"; import { createScriptTestHarness } from "./test-helpers.js"; @@ -396,7 +394,7 @@ describe("bundled plugin postinstall", () => { await expect(fs.stat(staleFile)).rejects.toMatchObject({ code: "ENOENT" }); }); - it("restores only postinstall-generated QA compat sidecars after pruning old installs", async () => { + it("prunes stale private QA files without restoring compat sidecars", async () => { const packageRoot = await createTempDirAsync("openclaw-packaged-install-qa-compat-"); const currentFile = path.join(packageRoot, "dist", "entry.js"); const stalePackage = path.join(packageRoot, "dist", "extensions", "qa-lab", "package.json"); @@ -422,10 +420,8 @@ describe("bundled plugin postinstall", () => { await expect(fs.stat(stalePackage)).rejects.toMatchObject({ code: "ENOENT" }); await expect(fs.stat(staleManifest)).rejects.toMatchObject({ code: "ENOENT" }); await expect( - fs.readFile(path.join(packageRoot, "dist", "extensions", "qa-channel", "runtime-api.js"), { - encoding: "utf8", - }), - ).resolves.toBe("export {};\n"); + fs.stat(path.join(packageRoot, "dist", "extensions", "qa-channel", "runtime-api.js")), + ).rejects.toMatchObject({ code: "ENOENT" }); await expect( fs.stat(path.join(packageRoot, "dist", "extensions", "qa-channel", "package.json")), ).rejects.toMatchObject({ code: "ENOENT" }); @@ -433,26 +429,8 @@ describe("bundled plugin postinstall", () => { fs.stat(path.join(packageRoot, "dist", "extensions", "qa-channel", "openclaw.plugin.json")), ).rejects.toMatchObject({ code: "ENOENT" }); await expect( - fs.readFile(path.join(packageRoot, "dist", "extensions", "qa-lab", "runtime-api.js"), { - encoding: "utf8", - }), - ).resolves.toBe("export {};\n"); - }); - - it("keeps postinstall QA compat sidecars aligned with update verification metadata", async () => { - const packageRoot = await createTempDirAsync("openclaw-packaged-install-qa-compat-"); - - const restored = restoreLegacyUpdaterCompatSidecars({ - packageRoot, - log: { log: vi.fn(), warn: vi.fn() }, - }); - - expect(restored).toEqual(NPM_UPDATE_COMPAT_SIDECARS.map((sidecar) => sidecar.path)); - for (const sidecar of NPM_UPDATE_COMPAT_SIDECARS) { - await expect(fs.readFile(path.join(packageRoot, sidecar.path), "utf8")).resolves.toBe( - sidecar.content, - ); - } + fs.stat(path.join(packageRoot, "dist", "extensions", "qa-lab", "runtime-api.js")), + ).rejects.toMatchObject({ code: "ENOENT" }); }); it("keeps packaged postinstall non-fatal when the dist inventory is missing", async () => {