diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 627b4582100..9e7ab811ebe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,10 @@ jobs: checks_node_core_dist_matrix: ${{ steps.manifest.outputs.checks_node_core_dist_matrix }} run_check: ${{ steps.manifest.outputs.run_check }} run_check_additional: ${{ steps.manifest.outputs.run_check_additional }} + run_plugin_prerelease_suite: ${{ steps.manifest.outputs.run_plugin_prerelease_suite }} + plugin_prerelease_ref: ${{ steps.manifest.outputs.plugin_prerelease_ref }} + plugin_prerelease_static_matrix: ${{ steps.manifest.outputs.plugin_prerelease_static_matrix }} + plugin_prerelease_docker_lanes: ${{ steps.manifest.outputs.plugin_prerelease_docker_lanes }} run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }} run_check_docs: ${{ steps.manifest.outputs.run_check_docs }} run_control_ui_i18n: ${{ steps.manifest.outputs.run_control_ui_i18n }} @@ -124,6 +128,10 @@ jobs: OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }} OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_skills_python || 'false' }} OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_control_ui_i18n || 'false' }} + OPENCLAW_CI_CHECKOUT_REVISION: ${{ steps.checkout_ref.outputs.sha }} + OPENCLAW_CI_EVENT_NAME: ${{ github.event_name }} + OPENCLAW_CI_PR_HEAD_REPOSITORY: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }} + OPENCLAW_CI_PR_HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }} OPENCLAW_CI_REPOSITORY: ${{ github.repository }} run: | node --input-type=module <<'EOF' @@ -131,6 +139,9 @@ jobs: import { createNodeTestShards, } from "./scripts/lib/ci-node-test-plan.mjs"; + import { + assertPluginPrereleaseTestPlanComplete, + } from "./scripts/lib/plugin-prerelease-test-plan.mjs"; import { createChannelContractTestShards, } from "./scripts/lib/channel-contract-test-plan.mjs"; @@ -173,6 +184,16 @@ jobs: const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly; const runControlUiI18n = parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly; + const pluginPrereleasePlan = assertPluginPrereleaseTestPlanComplete(); + const trustedPluginPrereleaseRef = + process.env.OPENCLAW_CI_EVENT_NAME !== "pull_request" || + process.env.OPENCLAW_CI_PR_HEAD_REPOSITORY === process.env.OPENCLAW_CI_REPOSITORY; + const pluginPrereleaseRef = + process.env.OPENCLAW_CI_EVENT_NAME === "pull_request" && trustedPluginPrereleaseRef + ? process.env.OPENCLAW_CI_PR_HEAD_SHA + : process.env.OPENCLAW_CI_CHECKOUT_REVISION; + const runPluginPrereleaseSuite = + runNodeFull && isCanonicalRepository && trustedPluginPrereleaseRef; const extensionTestShardCount = isCanonicalRepository ? DEFAULT_EXTENSION_TEST_SHARD_COUNT : Math.max(DEFAULT_EXTENSION_TEST_SHARD_COUNT, 36); @@ -264,6 +285,20 @@ jobs: checks_node_core_dist_matrix: createMatrix(nodeTestDistShards), run_check: runNodeFull, run_check_additional: runNodeFull, + run_plugin_prerelease_suite: runPluginPrereleaseSuite, + plugin_prerelease_ref: runPluginPrereleaseSuite ? pluginPrereleaseRef : "", + plugin_prerelease_static_matrix: createMatrix( + runPluginPrereleaseSuite + ? pluginPrereleasePlan.staticChecks.map((check) => ({ + check_name: check.checkName, + command: check.command, + task: check.check, + })) + : [], + ), + plugin_prerelease_docker_lanes: runPluginPrereleaseSuite + ? pluginPrereleasePlan.dockerLanes.join(" ") + : "", run_build_smoke: runNodeFull, run_check_docs: docsChanged, run_control_ui_i18n: runControlUiI18n, @@ -1621,6 +1656,91 @@ jobs: exit 1 fi + plugin-prerelease-static-shard: + permissions: + contents: read + name: ${{ matrix.check_name }} + needs: [preflight] + if: needs.preflight.outputs.run_plugin_prerelease_suite == 'true' + runs-on: blacksmith-8vcpu-ubuntu-2404 + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.preflight.outputs.plugin_prerelease_static_matrix) }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ needs.preflight.outputs.checkout_revision }} + fetch-depth: 1 + fetch-tags: false + persist-credentials: false + submodules: false + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + install-bun: "false" + + - name: Run plugin prerelease static shard + env: + PLUGIN_PRERELEASE_COMMAND: ${{ matrix.command }} + PLUGIN_PRERELEASE_TASK: ${{ matrix.task }} + shell: bash + run: | + set -euo pipefail + echo "Running ${PLUGIN_PRERELEASE_TASK}: ${PLUGIN_PRERELEASE_COMMAND}" + bash -c "$PLUGIN_PRERELEASE_COMMAND" + + plugin-prerelease-docker-suite: + name: plugin-prerelease-docker-suite + needs: [preflight] + if: needs.preflight.outputs.run_plugin_prerelease_suite == 'true' + permissions: + actions: read + contents: read + packages: write + pull-requests: read + uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml + with: + ref: ${{ needs.preflight.outputs.plugin_prerelease_ref }} + include_repo_e2e: false + include_release_path_suites: false + include_openwebui: false + docker_lanes: ${{ needs.preflight.outputs.plugin_prerelease_docker_lanes }} + include_live_suites: false + live_models_only: false + + plugin-prerelease-suite: + permissions: + contents: read + name: plugin-prerelease-suite + needs: [preflight, plugin-prerelease-static-shard, plugin-prerelease-docker-suite] + if: ${{ !cancelled() && always() && needs.preflight.outputs.run_plugin_prerelease_suite == 'true' }} + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Verify plugin prerelease suite + env: + DOCKER_RESULT: ${{ needs.plugin-prerelease-docker-suite.result }} + STATIC_RESULT: ${{ needs.plugin-prerelease-static-shard.result }} + shell: bash + run: | + set -euo pipefail + failed=0 + for result in \ + "plugin-prerelease-static=${STATIC_RESULT}" \ + "plugin-prerelease-docker=${DOCKER_RESULT}" + do + name="${result%%=*}" + status="${result#*=}" + if [ "$status" != "success" ]; then + echo "::error::${name} ended with ${status}" + failed=1 + fi + done + exit "$failed" + build-smoke: permissions: contents: read diff --git a/scripts/lib/plugin-prerelease-test-plan.mjs b/scripts/lib/plugin-prerelease-test-plan.mjs new file mode 100644 index 00000000000..96d37a8c1d1 --- /dev/null +++ b/scripts/lib/plugin-prerelease-test-plan.mjs @@ -0,0 +1,122 @@ +import { BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS } from "./docker-e2e-scenarios.mjs"; + +export const PLUGIN_PRERELEASE_REQUIRED_SURFACES = Object.freeze([ + "package-artifact", + "bundled-lifecycle", + "external-plugins", + "update-no-op", + "channel-runtime-deps", + "doctor-fix", + "config-round-trip", + "gateway-bootstrap", + "sdk-compatibility", + "status-diagnostics", + "live-ish-availability", +]); + +const pluginPrereleaseDockerLanes = Object.freeze([ + { + lane: "npm-onboard-channel-agent", + surfaces: ["package-artifact", "gateway-bootstrap", "status-diagnostics"], + }, + { + lane: "doctor-switch", + surfaces: ["package-artifact", "doctor-fix"], + }, + { + lane: "update-channel-switch", + surfaces: ["package-artifact", "channel-runtime-deps", "update-no-op"], + }, + { + lane: "bundled-channel-deps-compat", + surfaces: ["package-artifact", "channel-runtime-deps", "gateway-bootstrap"], + }, + { + lane: "plugins-offline", + surfaces: ["external-plugins", "sdk-compatibility", "status-diagnostics"], + }, + { + lane: "plugins", + surfaces: ["external-plugins", "sdk-compatibility", "status-diagnostics"], + }, + { + lane: "plugin-update", + surfaces: ["package-artifact", "update-no-op"], + }, + { + lane: "config-reload", + surfaces: ["config-round-trip", "gateway-bootstrap"], + }, + { + lane: "gateway-network", + surfaces: ["gateway-bootstrap", "status-diagnostics"], + }, + { + lane: "mcp-channels", + surfaces: ["gateway-bootstrap", "status-diagnostics"], + }, + { + lane: "cron-mcp-cleanup", + surfaces: ["gateway-bootstrap", "status-diagnostics"], + }, + ...Array.from({ length: BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS }, (_, index) => ({ + lane: `bundled-plugin-install-uninstall-${index}`, + surfaces: ["bundled-lifecycle", "package-artifact", "status-diagnostics"], + })), +]); + +const staticChecks = Object.freeze([ + { + check: "test:extensions:package-boundary:compile", + checkName: "checks-plugin-prerelease-package-boundary-compile", + command: "pnpm run test:extensions:package-boundary:compile", + surfaces: ["package-artifact", "sdk-compatibility"], + }, + { + check: "test:extensions:package-boundary:canary", + checkName: "checks-plugin-prerelease-package-boundary-canary", + command: "pnpm run test:extensions:package-boundary:canary", + surfaces: ["package-artifact", "sdk-compatibility"], + }, + { + check: "live-ish-availability", + checkName: "checks-plugin-prerelease-live-ish-availability", + command: "node scripts/plugin-prerelease-liveish-matrix.mjs", + surfaces: ["live-ish-availability"], + }, +]); + +function coveredSurfaces(entries) { + return [ + ...new Set( + entries + .flatMap((entry) => entry.surfaces) + .filter((surface) => typeof surface === "string" && surface.length > 0), + ), + ].toSorted((a, b) => a.localeCompare(b)); +} + +export function createPluginPrereleaseTestPlan() { + const dockerLanes = pluginPrereleaseDockerLanes.map((entry) => entry.lane); + const allEntries = [...pluginPrereleaseDockerLanes, ...staticChecks]; + return { + dockerLanes, + staticChecks: staticChecks.map((entry) => ({ + check: entry.check, + checkName: entry.checkName, + command: entry.command, + surfaces: entry.surfaces.slice(), + })), + surfaces: coveredSurfaces(allEntries), + }; +} + +export function assertPluginPrereleaseTestPlanComplete(plan = createPluginPrereleaseTestPlan()) { + const missing = PLUGIN_PRERELEASE_REQUIRED_SURFACES.filter( + (surface) => !plan.surfaces.includes(surface), + ); + if (missing.length > 0) { + throw new Error(`Plugin prerelease test plan is missing surfaces: ${missing.join(", ")}`); + } + return plan; +} diff --git a/scripts/plugin-prerelease-liveish-matrix.mjs b/scripts/plugin-prerelease-liveish-matrix.mjs new file mode 100644 index 00000000000..526e0bc4fd2 --- /dev/null +++ b/scripts/plugin-prerelease-liveish-matrix.mjs @@ -0,0 +1,54 @@ +const LIVEISH_INPUTS = Object.freeze([ + { + probe: "provider-openai", + env: ["OPENAI_API_KEY", "OPENAI_BASE_URL"], + }, + { + probe: "provider-anthropic", + env: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_TOKEN"], + }, + { + probe: "provider-mistral", + env: ["MISTRAL_API_KEY"], + }, + { + probe: "provider-openrouter", + env: ["OPENROUTER_API_KEY"], + }, + { + probe: "channel-telegram", + env: ["TELEGRAM_BOT_TOKEN", "OPENCLAW_TELEGRAM_BOT_TOKEN"], + }, + { + probe: "channel-discord", + env: ["DISCORD_TOKEN", "OPENCLAW_DISCORD_TOKEN"], + }, + { + probe: "channel-slack", + env: ["SLACK_BOT_TOKEN", "OPENCLAW_SLACK_BOT_TOKEN"], + }, + { + probe: "channel-whatsapp", + env: ["WHATSAPP_ACCESS_TOKEN", "OPENCLAW_WHATSAPP_ACCESS_TOKEN"], + }, +]); + +function hasValue(name) { + return typeof process.env[name] === "string" && process.env[name].trim().length > 0; +} + +const rows = LIVEISH_INPUTS.map((entry) => ({ + available: entry.env.some(hasValue), + env: entry.env, + probe: entry.probe, +})); + +console.log("Plugin prerelease live-ish availability matrix:"); +for (const row of rows) { + const status = row.available ? "present" : "missing"; + console.log(`- ${row.probe}: ${status} (${row.env.join(", ")})`); +} + +if (!rows.some((row) => row.available)) { + console.log("No live-ish credentials present; skipping external probes by design."); +} diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index e25439c2cfb..592257fed63 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -247,7 +247,15 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([ ["scripts/lib/extension-test-plan.mjs", ["test/scripts/test-extension.test.ts"]], ["scripts/lib/vitest-batch-runner.mjs", ["test/scripts/test-extension.test.ts"]], ["scripts/lib/ci-node-test-plan.mjs", ["test/scripts/ci-node-test-plan.test.ts"]], + [ + "scripts/lib/plugin-prerelease-test-plan.mjs", + ["test/scripts/plugin-prerelease-test-plan.test.ts"], + ], ["scripts/lib/vitest-shard-timings.mjs", ["test/scripts/vitest-shard-timings.test.ts"]], + [ + "scripts/plugin-prerelease-liveish-matrix.mjs", + ["test/scripts/plugin-prerelease-test-plan.test.ts"], + ], ["scripts/test-projects.mjs", ["test/scripts/test-projects.test.ts"]], ["scripts/test-projects.test-support.d.mts", ["test/scripts/test-projects.test.ts"]], ["scripts/test-projects.test-support.mjs", ["test/scripts/test-projects.test.ts"]], @@ -258,6 +266,10 @@ const TOOLING_TEST_TARGETS = new Map([ ["test/scripts/changed-lanes.test.ts", ["test/scripts/changed-lanes.test.ts"]], ["test/scripts/live-docker-stage.test.ts", ["test/scripts/live-docker-stage.test.ts"]], ["test/scripts/openclaw-test-state.test.ts", ["test/scripts/openclaw-test-state.test.ts"]], + [ + "test/scripts/plugin-prerelease-test-plan.test.ts", + ["test/scripts/plugin-prerelease-test-plan.test.ts"], + ], ["test/scripts/test-projects.test.ts", ["test/scripts/test-projects.test.ts"]], ["test/scripts/testbox-sync-sanity.test.ts", ["test/scripts/testbox-sync-sanity.test.ts"]], [ diff --git a/test/scripts/ci-node-test-plan.test.ts b/test/scripts/ci-node-test-plan.test.ts index 7ad4a1851f2..35d53be7996 100644 --- a/test/scripts/ci-node-test-plan.test.ts +++ b/test/scripts/ci-node-test-plan.test.ts @@ -1,7 +1,21 @@ import { existsSync, readdirSync } from "node:fs"; -import { join } from "node:path"; +import { join, relative, resolve } from "node:path"; +import fg from "fast-glob"; import { describe, expect, it } from "vitest"; import { createNodeTestShards } from "../../scripts/lib/ci-node-test-plan.mjs"; +import { createPluginsVitestConfig } from "../vitest/vitest.plugins.config.ts"; + +type VitestTestConfig = { + dir?: string; + exclude?: string[]; + include?: string[]; +}; + +type VitestConfig = { + test?: VitestTestConfig; +}; + +const PLUGIN_PRERELEASE_NPM_SPEC_TEST = "src/plugins/install.npm-spec.test.ts"; function listTestFiles(rootDir: string): string[] { if (!existsSync(rootDir)) { @@ -24,6 +38,20 @@ function listTestFiles(rootDir: string): string[] { return files.toSorted((a, b) => a.localeCompare(b)); } +function listMatchedTestFiles(config: VitestConfig): string[] { + const testConfig = config.test ?? {}; + const cwd = testConfig.dir ? resolve(testConfig.dir) : process.cwd(); + return fg + .sync(testConfig.include ?? [], { + absolute: false, + cwd, + dot: false, + ignore: testConfig.exclude ?? [], + }) + .map((file) => relative(process.cwd(), resolve(cwd, file)).replaceAll("\\", "/")) + .toSorted((a, b) => a.localeCompare(b)); +} + describe("scripts/lib/ci-node-test-plan.mjs", () => { it("combines the small core unit shards to reduce CI runner fanout", () => { const coreUnitShards = createNodeTestShards() @@ -155,7 +183,6 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { configs: ["test/vitest/vitest.gateway-server.config.ts"], runner: "blacksmith-4vcpu-ubuntu-2404", requiresDist: false, - runner: "blacksmith-4vcpu-ubuntu-2404", }); expect(commandsShard).toEqual({ checkName: "checks-node-agentic-commands", @@ -199,6 +226,22 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { }); }); + it("keeps plugin prerelease npm install coverage on the agentic plugin CI shard", () => { + const pluginsShard = createNodeTestShards().find( + (shard) => shard.shardName === "agentic-plugins", + ); + + expect(pluginsShard).toMatchObject({ + checkName: "checks-node-agentic-plugins", + configs: ["test/vitest/vitest.plugins.config.ts"], + requiresDist: false, + shardName: "agentic-plugins", + }); + expect(listMatchedTestFiles(createPluginsVitestConfig({}))).toContain( + PLUGIN_PRERELEASE_NPM_SPEC_TEST, + ); + }); + it("splits auto-reply into balanced core/top-level and reply subtree shards", () => { const shards = createNodeTestShards(); const autoReplyShards = shards diff --git a/test/scripts/plugin-prerelease-test-plan.test.ts b/test/scripts/plugin-prerelease-test-plan.test.ts new file mode 100644 index 00000000000..d3372174f3d --- /dev/null +++ b/test/scripts/plugin-prerelease-test-plan.test.ts @@ -0,0 +1,143 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; +import { parse } from "yaml"; +import { findLaneByName } from "../../scripts/lib/docker-e2e-plan.mjs"; +import { + PLUGIN_PRERELEASE_REQUIRED_SURFACES, + assertPluginPrereleaseTestPlanComplete, + createPluginPrereleaseTestPlan, +} from "../../scripts/lib/plugin-prerelease-test-plan.mjs"; + +function readCiWorkflow() { + return parse(readFileSync(".github/workflows/ci.yml", "utf8")); +} + +describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => { + it("covers every pre-release plugin skill surface in normal CI", () => { + const plan = assertPluginPrereleaseTestPlanComplete(); + + expect(plan.surfaces).toEqual( + [...PLUGIN_PRERELEASE_REQUIRED_SURFACES].toSorted((a, b) => a.localeCompare(b)), + ); + }); + + it("runs the package and Docker product lanes through the existing scheduler", () => { + const plan = createPluginPrereleaseTestPlan(); + + expect(plan.dockerLanes).toEqual([ + "npm-onboard-channel-agent", + "doctor-switch", + "update-channel-switch", + "bundled-channel-deps-compat", + "plugins-offline", + "plugins", + "plugin-update", + "config-reload", + "gateway-network", + "mcp-channels", + "cron-mcp-cleanup", + "bundled-plugin-install-uninstall-0", + "bundled-plugin-install-uninstall-1", + "bundled-plugin-install-uninstall-2", + "bundled-plugin-install-uninstall-3", + "bundled-plugin-install-uninstall-4", + "bundled-plugin-install-uninstall-5", + "bundled-plugin-install-uninstall-6", + "bundled-plugin-install-uninstall-7", + ]); + + for (const lane of plan.dockerLanes) { + expect(findLaneByName(lane), lane).toBeTruthy(); + } + }); + + it("keeps live-ish coverage credential-gated in PR CI", () => { + const plan = createPluginPrereleaseTestPlan(); + + expect(plan.dockerLanes).not.toContain("openai-web-search-minimal"); + expect(plan.dockerLanes.some((lane) => lane.startsWith("live-"))).toBe(false); + expect(plan.staticChecks).toContainEqual({ + check: "live-ish-availability", + checkName: "checks-plugin-prerelease-live-ish-availability", + command: "node scripts/plugin-prerelease-liveish-matrix.mjs", + surfaces: ["live-ish-availability"], + }); + }); + + it("keeps SDK/package boundary checks inside the plugin prerelease suite", () => { + const plan = createPluginPrereleaseTestPlan(); + + expect(plan.staticChecks.map((check) => check.checkName)).toEqual([ + "checks-plugin-prerelease-package-boundary-compile", + "checks-plugin-prerelease-package-boundary-canary", + "checks-plugin-prerelease-live-ish-availability", + ]); + }); + + it("wires the full plugin prerelease plan into the mega CI workflow", () => { + const workflow = readCiWorkflow(); + const preflight = workflow.jobs.preflight; + const staticShard = workflow.jobs["plugin-prerelease-static-shard"]; + const dockerSuite = workflow.jobs["plugin-prerelease-docker-suite"]; + const suite = workflow.jobs["plugin-prerelease-suite"]; + + expect(preflight.outputs).toMatchObject({ + plugin_prerelease_docker_lanes: + "${{ steps.manifest.outputs.plugin_prerelease_docker_lanes }}", + plugin_prerelease_ref: "${{ steps.manifest.outputs.plugin_prerelease_ref }}", + plugin_prerelease_static_matrix: + "${{ steps.manifest.outputs.plugin_prerelease_static_matrix }}", + run_plugin_prerelease_suite: "${{ steps.manifest.outputs.run_plugin_prerelease_suite }}", + }); + expect(staticShard).toMatchObject({ + name: "${{ matrix.check_name }}", + "runs-on": "blacksmith-8vcpu-ubuntu-2404", + }); + expect(staticShard.strategy.matrix).toBe( + "${{ fromJson(needs.preflight.outputs.plugin_prerelease_static_matrix) }}", + ); + expect( + staticShard.steps.find((step) => step.name === "Run plugin prerelease static shard").run, + ).toContain('bash -c "$PLUGIN_PRERELEASE_COMMAND"'); + expect(dockerSuite).toMatchObject({ + if: "needs.preflight.outputs.run_plugin_prerelease_suite == 'true'", + needs: ["preflight"], + uses: "./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml", + with: { + docker_lanes: "${{ needs.preflight.outputs.plugin_prerelease_docker_lanes }}", + include_live_suites: false, + include_openwebui: false, + include_release_path_suites: false, + include_repo_e2e: false, + live_models_only: false, + ref: "${{ needs.preflight.outputs.plugin_prerelease_ref }}", + }, + }); + expect(dockerSuite.secrets).toBeUndefined(); + expect(suite.needs).toEqual([ + "preflight", + "plugin-prerelease-static-shard", + "plugin-prerelease-docker-suite", + ]); + }); + + it("keeps the live-ish availability check redacted", () => { + const output = execFileSync( + process.execPath, + ["scripts/plugin-prerelease-liveish-matrix.mjs"], + { + encoding: "utf8", + env: { + DISCORD_TOKEN: "discord-token-should-not-print", + OPENAI_API_KEY: "openai-token-should-not-print", + }, + }, + ); + + expect(output).toContain("provider-openai: present (OPENAI_API_KEY, OPENAI_BASE_URL)"); + expect(output).toContain("channel-discord: present (DISCORD_TOKEN, OPENCLAW_DISCORD_TOKEN)"); + expect(output).not.toContain("openai-token-should-not-print"); + expect(output).not.toContain("discord-token-should-not-print"); + }); +}); diff --git a/test/vitest-unit-fast-config.test.ts b/test/vitest-unit-fast-config.test.ts index cf014559b8e..90fdf754c7f 100644 --- a/test/vitest-unit-fast-config.test.ts +++ b/test/vitest-unit-fast-config.test.ts @@ -74,6 +74,7 @@ describe("unit-fast vitest lane", () => { expect(isUnitFastTestFile("src/plugin-sdk/temp-path.test.ts")).toBe(false); expect(isUnitFastTestFile("src/agents/sandbox.resolveSandboxContext.test.ts")).toBe(false); expect(isUnitFastTestFile("src/crestodian/assistant.test.ts")).toBe(false); + expect(isUnitFastTestFile("src/plugins/install.npm-spec.test.ts")).toBe(false); expect(isUnitFastTestFile("src/secrets/runtime.test.ts")).toBe(false); expect(resolveUnitFastTestIncludePattern("src/plugin-sdk/temp-path.ts")).toBeNull(); expect(classifyUnitFastTestFileContent("vi.resetModules(); await import('./x.js')")).toEqual([ diff --git a/test/vitest/vitest.unit-fast-paths.mjs b/test/vitest/vitest.unit-fast-paths.mjs index 19d1e78c7f6..362e83e48b7 100644 --- a/test/vitest/vitest.unit-fast-paths.mjs +++ b/test/vitest/vitest.unit-fast-paths.mjs @@ -224,6 +224,7 @@ const broadUnitFastCandidateSkipGlobs = [ "src/media-generation/runtime-shared.test.ts", "src/music-generation/runtime.test.ts", "src/proxy-capture/runtime.test.ts", + "src/plugins/install.npm-spec.test.ts", "src/plugins/contracts/**/*.test.ts", "src/plugin-sdk/browser-subpaths.test.ts", "src/security/**/*.test.ts",