diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea4644a7039..ee7f3c67b8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ jobs: run_node: ${{ steps.scope.outputs.run_node }} run_macos: ${{ steps.scope.outputs.run_macos }} run_android: ${{ steps.scope.outputs.run_android }} + run_windows: ${{ steps.scope.outputs.run_windows }} steps: - name: Checkout uses: actions/checkout@v4 @@ -329,8 +330,8 @@ jobs: checks-windows: needs: [docs-scope, changed-scope, build-artifacts, check] - if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true') - runs-on: blacksmith-16vcpu-windows-2025 + if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_windows == 'true') + runs-on: blacksmith-32vcpu-windows-2025 timeout-minutes: 45 env: NODE_OPTIONS: --max-old-space-size=6144 diff --git a/docs/ci.md b/docs/ci.md index 4eb2162eece..16a7e670964 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -13,20 +13,20 @@ The CI runs on every push to `main` and every pull request. It uses smart scopin ## Job Overview -| Job | Purpose | When it runs | -| ----------------- | ----------------------------------------------- | ------------------------------------------------- | -| `docs-scope` | Detect docs-only changes | Always | -| `changed-scope` | Detect which areas changed (node/macos/android) | Non-docs PRs | -| `check` | TypeScript types, lint, format | Push to `main`, or PRs with Node-relevant changes | -| `check-docs` | Markdown lint + broken link check | Docs changed | -| `code-analysis` | LOC threshold check (1000 lines) | PRs only | -| `secrets` | Detect leaked secrets | Always | -| `build-artifacts` | Build dist once, share with other jobs | Non-docs, node changes | -| `release-check` | Validate npm pack contents | After build | -| `checks` | Node/Bun tests + protocol check | Non-docs, node changes | -| `checks-windows` | Windows-specific tests | Non-docs, node changes | -| `macos` | Swift lint/build/test + TS tests | PRs with macos changes | -| `android` | Gradle build + tests | Non-docs, android changes | +| Job | Purpose | When it runs | +| ----------------- | ------------------------------------------------------- | ------------------------------------------------- | +| `docs-scope` | Detect docs-only changes | Always | +| `changed-scope` | Detect which areas changed (node/macos/android/windows) | Non-docs PRs | +| `check` | TypeScript types, lint, format | Push to `main`, or PRs with Node-relevant changes | +| `check-docs` | Markdown lint + broken link check | Docs changed | +| `code-analysis` | LOC threshold check (1000 lines) | PRs only | +| `secrets` | Detect leaked secrets | Always | +| `build-artifacts` | Build dist once, share with other jobs | Non-docs, node changes | +| `release-check` | Validate npm pack contents | After build | +| `checks` | Node/Bun tests + protocol check | Non-docs, node changes | +| `checks-windows` | Windows-specific tests | Non-docs, windows-relevant changes | +| `macos` | Swift lint/build/test + TS tests | PRs with macos changes | +| `android` | Gradle build + tests | Non-docs, android changes | ## Fail-Fast Order diff --git a/scripts/ci-changed-scope.mjs b/scripts/ci-changed-scope.mjs index 84e0a508ee9..c3ccd931b42 100644 --- a/scripts/ci-changed-scope.mjs +++ b/scripts/ci-changed-scope.mjs @@ -1,7 +1,7 @@ import { execSync } from "node:child_process"; import { appendFileSync } from "node:fs"; -/** @typedef {{ runNode: boolean; runMacos: boolean; runAndroid: boolean }} ChangedScope */ +/** @typedef {{ runNode: boolean; runMacos: boolean; runAndroid: boolean; runWindows: boolean }} ChangedScope */ const DOCS_PATH_RE = /^(docs\/|.*\.mdx?$)/; const MACOS_PROTOCOL_GEN_RE = @@ -10,6 +10,8 @@ const MACOS_NATIVE_RE = /^(apps\/macos\/|apps\/ios\/|apps\/shared\/|Swabble\/)/; const ANDROID_NATIVE_RE = /^(apps\/android\/|apps\/shared\/)/; const NODE_SCOPE_RE = /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|\.github\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.oxlintrc\.json$|\.oxfmtrc\.jsonc$)/; +const WINDOWS_SCOPE_RE = + /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.github\/workflows\/ci\.yml$|\.github\/actions\/setup-node-env\/action\.yml$|\.github\/actions\/setup-pnpm-store-cache\/action\.yml$)/; const NATIVE_ONLY_RE = /^(apps\/android\/|apps\/ios\/|apps\/macos\/|apps\/shared\/|Swabble\/|appcast\.xml$)/; @@ -19,12 +21,13 @@ const NATIVE_ONLY_RE = */ export function detectChangedScope(changedPaths) { if (!Array.isArray(changedPaths) || changedPaths.length === 0) { - return { runNode: true, runMacos: true, runAndroid: true }; + return { runNode: true, runMacos: true, runAndroid: true, runWindows: true }; } let runNode = false; let runMacos = false; let runAndroid = false; + let runWindows = false; let hasNonDocs = false; let hasNonNativeNonDocs = false; @@ -52,6 +55,10 @@ export function detectChangedScope(changedPaths) { runNode = true; } + if (WINDOWS_SCOPE_RE.test(path)) { + runWindows = true; + } + if (!NATIVE_ONLY_RE.test(path)) { hasNonNativeNonDocs = true; } @@ -61,7 +68,7 @@ export function detectChangedScope(changedPaths) { runNode = true; } - return { runNode, runMacos, runAndroid }; + return { runNode, runMacos, runAndroid, runWindows }; } /** @@ -94,6 +101,7 @@ export function writeGitHubOutput(scope, outputPath = process.env.GITHUB_OUTPUT) appendFileSync(outputPath, `run_node=${scope.runNode}\n`, "utf8"); appendFileSync(outputPath, `run_macos=${scope.runMacos}\n`, "utf8"); appendFileSync(outputPath, `run_android=${scope.runAndroid}\n`, "utf8"); + appendFileSync(outputPath, `run_windows=${scope.runWindows}\n`, "utf8"); } function isDirectRun() { @@ -123,11 +131,11 @@ if (isDirectRun()) { try { const changedPaths = listChangedPaths(args.base, args.head); if (changedPaths.length === 0) { - writeGitHubOutput({ runNode: true, runMacos: true, runAndroid: true }); + writeGitHubOutput({ runNode: true, runMacos: true, runAndroid: true, runWindows: true }); process.exit(0); } writeGitHubOutput(detectChangedScope(changedPaths)); } catch { - writeGitHubOutput({ runNode: true, runMacos: true, runAndroid: true }); + writeGitHubOutput({ runNode: true, runMacos: true, runAndroid: true, runWindows: true }); } } diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index 70c00559594..91efaafdff1 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -1,5 +1,14 @@ import { describe, expect, it } from "vitest"; -import { detectChangedScope } from "../../scripts/ci-changed-scope.mjs"; + +const require = createRequire(import.meta.url); +const { detectChangedScope } = require("../../scripts/ci-changed-scope.mjs") as { + detectChangedScope: (paths: string[]) => { + runNode: boolean; + runMacos: boolean; + runAndroid: boolean; + runWindows: boolean; + }; +}; describe("detectChangedScope", () => { it("fails safe when no paths are provided", () => { @@ -7,6 +16,7 @@ describe("detectChangedScope", () => { runNode: true, runMacos: true, runAndroid: true, + runWindows: true, }); }); @@ -15,6 +25,7 @@ describe("detectChangedScope", () => { runNode: false, runMacos: false, runAndroid: false, + runWindows: false, }); }); @@ -23,6 +34,7 @@ describe("detectChangedScope", () => { runNode: true, runMacos: false, runAndroid: false, + runWindows: true, }); }); @@ -31,11 +43,13 @@ describe("detectChangedScope", () => { runNode: false, runMacos: true, runAndroid: false, + runWindows: false, }); expect(detectChangedScope(["apps/shared/OpenClawKit/Sources/Foo.swift"])).toEqual({ runNode: false, runMacos: true, runAndroid: true, + runWindows: false, }); }); @@ -45,6 +59,7 @@ describe("detectChangedScope", () => { runNode: false, runMacos: false, runAndroid: false, + runWindows: false, }, ); }); @@ -54,12 +69,23 @@ describe("detectChangedScope", () => { runNode: false, runMacos: false, runAndroid: false, + runWindows: false, }); expect(detectChangedScope(["assets/icon.png"])).toEqual({ runNode: true, runMacos: false, runAndroid: false, + runWindows: false, + }); + }); + + it("keeps windows lane off for non-runtime GitHub metadata files", () => { + expect(detectChangedScope([".github/labeler.yml"])).toEqual({ + runNode: true, + runMacos: false, + runAndroid: false, + runWindows: false, }); }); });