From c1263f812b2b70d0a76f6dba54f484b52109b7e0 Mon Sep 17 00:00:00 2001 From: injaneity <44902825+injaneity@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:49:22 -0500 Subject: [PATCH 1/4] ci: select native docs runners before matrix allocation --- .github/workflows/ci-check-docs.yml | 87 ++++---- scripts/docs-generators/native-matrix.mjs | 69 +++++++ .../docs-generators/native-matrix.test.mjs | 192 ++++++++++++++++++ 3 files changed, 304 insertions(+), 44 deletions(-) create mode 100644 scripts/docs-generators/native-matrix.mjs create mode 100644 scripts/docs-generators/native-matrix.test.mjs diff --git a/.github/workflows/ci-check-docs.yml b/.github/workflows/ci-check-docs.yml index 23fc7300fe..3f7e6e82be 100644 --- a/.github/workflows/ci-check-docs.yml +++ b/.github/workflows/ci-check-docs.yml @@ -2,41 +2,42 @@ name: "CI: Check Docs" on: pull_request: - paths: - - "libs/cua-driver/rust/crates/**" - - "libs/cua-driver/rust/Cargo.toml" - - "libs/cua-driver/rust/Cargo.lock" - - "libs/lume/src/**" - - "docs/content/docs/reference/cua-driver/**" - - "docs/content/docs/reference/lume/**" - - "docs/package.json" - - "docs/pnpm-lock.yaml" - - "scripts/docs-generators/**" - - ".github/workflows/ci-check-docs.yml" - - ".gitattributes" permissions: contents: read jobs: + plan: + name: Select Native Documentation Checks + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.scope.outputs.matrix }} + has_work: ${{ steps.scope.outputs.has_work }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Test native routing + run: node --test scripts/docs-generators/native-matrix.test.mjs + - name: Select native jobs + id: scope + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + git diff --name-only --no-renames -z "$BASE_SHA" HEAD > "$RUNNER_TEMP/docs-changed-files" + node scripts/docs-generators/native-matrix.mjs "$RUNNER_TEMP/docs-changed-files" >> "$GITHUB_OUTPUT" + native-reference: name: Reference / ${{ matrix.library }} / ${{ matrix.platform }} + needs: plan + if: needs.plan.outputs.has_work == 'true' strategy: fail-fast: false - matrix: - include: - - library: cua-driver - platform: linux - os: ubuntu-latest - - library: cua-driver - platform: macos - os: macos-latest - - library: cua-driver - platform: windows - os: windows-latest - - library: lume - platform: macos - os: macos-latest + matrix: ${{ fromJSON(needs.plan.outputs.matrix) }} runs-on: ${{ matrix.os }} defaults: run: @@ -56,42 +57,40 @@ jobs: run: | node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --test-routing node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver.test.ts scripts/docs-generators/cua-driver-platform.test.ts - - name: Select affected generator - id: scope - env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - LIBRARY: ${{ matrix.library }} - run: | - git diff --name-only "$BASE_SHA" HEAD > "$RUNNER_TEMP/docs-changed-files.txt" - GENERATORS=$(node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --changed-files-file "$RUNNER_TEMP/docs-changed-files.txt") - if [[ " $GENERATORS " == *" $LIBRARY "* ]]; then - echo "selected=true" >> "$GITHUB_OUTPUT" - fi - name: Install Linux build dependencies - if: steps.scope.outputs.selected == 'true' && matrix.platform == 'linux' + if: matrix.platform == 'linux' run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ clang pkg-config libdbus-1-dev libpipewire-0.3-dev libspa-0.2-dev \ libei-dev libxkbcommon-dev libx11-dev libxi-dev libxtst-dev libxext-dev - name: Check generated reference - if: steps.scope.outputs.selected == 'true' && matrix.platform != 'windows' + if: matrix.platform != 'windows' run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --library ${{ matrix.library }} --check - name: Check generated reference in PowerShell - if: steps.scope.outputs.selected == 'true' && matrix.platform == 'windows' + if: matrix.platform == 'windows' shell: pwsh run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --library cua-driver --check - name: Verify docs extraction preserves policy-filtered discovery - if: steps.scope.outputs.selected == 'true' && matrix.library == 'cua-driver' + if: matrix.library == 'cua-driver' run: node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver-policy.test.ts check-docs-sync: name: Check Documentation Sync if: always() - needs: native-reference + needs: [plan, native-reference] runs-on: ubuntu-latest steps: - name: Require native checks to pass env: - RESULT: ${{ needs.native-reference.result }} - run: test "$RESULT" = success + PLAN_RESULT: ${{ needs.plan.result }} + HAS_WORK: ${{ needs.plan.outputs.has_work }} + NATIVE_RESULT: ${{ needs.native-reference.result }} + run: | + test "$PLAN_RESULT" = success + if [[ "$HAS_WORK" == true ]]; then + test "$NATIVE_RESULT" = success + else + test "$HAS_WORK" = false + test "$NATIVE_RESULT" = skipped + fi diff --git a/scripts/docs-generators/native-matrix.mjs b/scripts/docs-generators/native-matrix.mjs new file mode 100644 index 0000000000..4be81416e2 --- /dev/null +++ b/scripts/docs-generators/native-matrix.mjs @@ -0,0 +1,69 @@ +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +const config = JSON.parse(fs.readFileSync(new URL('./config.json', import.meta.url), 'utf8')); +const rows = [ + { library: 'cua-driver', platform: 'linux', os: 'ubuntu-latest' }, + { library: 'cua-driver', platform: 'macos', os: 'macos-latest' }, + { library: 'cua-driver', platform: 'windows', os: 'windows-latest' }, + { library: 'lume', platform: 'macos', os: 'macos-latest' }, +]; +const hosts = { linux: 'linux', darwin: 'macos', win32: 'windows' }; + +export function selectNativeMatrix(files) { + const selected = new Set(); + const select = (library, platform) => { + for (const row of rows) { + if ((!library || row.library === library) && (!platform || row.platform === platform)) { + selected.add(row); + } + } + }; + for (const file of files) { + if ( + [ + '.github/workflows/ci-check-docs.yml', + '.gitattributes', + 'package.json', + 'pnpm-workspace.yaml', + 'docs/package.json', + 'docs/pnpm-lock.yaml', + ].includes(file) + ) { + select(); + } else if (file.startsWith('scripts/docs-generators/')) { + if (/\/cua-driver(?:[.-])/.test(file)) select('cua-driver'); + else if (/\/lume(?:[.-])/.test(file)) select('lume'); + else select(); + } else if ( + file.startsWith('libs/cua-driver/rust/') || + file.startsWith('.cargo/') || + /^rust-toolchain(?:\.toml)?$/.test(file) + ) { + const platform = file.match( + /^libs\/cua-driver\/rust\/crates\/platform-(linux|macos|windows)\// + )?.[1]; + select('cua-driver', platform); + } else if (file.startsWith('libs/lume/')) { + select('lume'); + } else { + for (const library of ['cua-driver', 'lume']) { + const generator = config.generators[library]; + for (const output of generator.outputs) { + if (file !== `${generator.docsOutputPath}/${output.outputFile}`) continue; + const platform = output.platform ? hosts[output.platform.host] : undefined; + if (output.platform && !platform) + throw new Error(`Unknown output host: ${output.platform.host}`); + select(library, platform); + } + } + } + } + return { include: rows.filter((row) => selected.has(row)) }; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const files = fs.readFileSync(process.argv[2], 'utf8').split('\0').filter(Boolean); + const matrix = selectNativeMatrix(files); + process.stdout.write(`matrix=${JSON.stringify(matrix)}\nhas_work=${matrix.include.length > 0}\n`); +} diff --git a/scripts/docs-generators/native-matrix.test.mjs b/scripts/docs-generators/native-matrix.test.mjs new file mode 100644 index 0000000000..7c542cece8 --- /dev/null +++ b/scripts/docs-generators/native-matrix.test.mjs @@ -0,0 +1,192 @@ +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; +import { selectNativeMatrix } from './native-matrix.mjs'; + +const driver = ['cua-driver/linux', 'cua-driver/macos', 'cua-driver/windows']; +const all = [...driver, 'lume/macos']; +const selected = (files) => + selectNativeMatrix(files).include.map((row) => `${row.library}/${row.platform}`); + +test('unrelated and curated changes allocate no native runners', () => { + assert.deepEqual(selected([]), []); + assert.deepEqual( + selected([ + 'README.md', + 'docs/content/docs/reference/cua-driver/macos-permissions.mdx', + 'docs/content/docs/reference/cua-driver/mcp-tool-notes.mdx', + 'libs/python/agent/agent/main.py', + ]), + [] + ); +}); + +test('platform source, manifests, and build scripts select their owner', () => { + for (const platform of ['linux', 'macos', 'windows']) { + for (const input of ['src/lib.rs', 'Cargo.toml', 'build.rs']) { + assert.deepEqual(selected([`libs/cua-driver/rust/crates/platform-${platform}/${input}`]), [ + `cua-driver/${platform}`, + ]); + } + } +}); + +test('shared source and dependency/build inputs select all driver hosts', () => { + for (const input of [ + 'libs/cua-driver/rust/crates/cua-driver-core/src/lib.rs', + 'libs/cua-driver/rust/crates/cua-driver-contract/Cargo.toml', + 'libs/cua-driver/rust/crates/cua-driver/build.rs', + 'libs/cua-driver/rust/crates/new-shared-crate/src/lib.rs', + 'libs/cua-driver/rust/Cargo.lock', + 'libs/cua-driver/rust/Cargo.toml', + 'libs/cua-driver/rust/.cargo/config.toml', + '.cargo/config.toml', + 'rust-toolchain.toml', + ]) + assert.deepEqual(selected([input]), driver); +}); + +test('generated references select configured owners; shared CLI checks all hosts', () => { + for (const [file, expected] of [ + ['mcp-tools.mdx', ['cua-driver/macos']], + ['mcp-tools-linux.mdx', ['cua-driver/linux']], + ['mcp-tools-windows.mdx', ['cua-driver/windows']], + ['cli-reference.mdx', driver], + ]) + assert.deepEqual(selected([`docs/content/docs/reference/cua-driver/${file}`]), expected); +}); + +test('lume source, build configuration, and reference changes stay on lume', () => { + for (const input of [ + 'libs/lume/src/Commands/List.swift', + 'libs/lume/Package.swift', + 'libs/lume/Package.resolved', + 'docs/content/docs/reference/lume/http-api.mdx', + 'scripts/docs-generators/lume.ts', + ]) + assert.deepEqual(selected([input]), ['lume/macos']); +}); + +test('generator dependencies and workflow changes conservatively fan out', () => { + for (const input of [ + 'scripts/docs-generators/runner.ts', + 'scripts/docs-generators/config.json', + 'scripts/docs-generators/native-matrix.mjs', + '.github/workflows/ci-check-docs.yml', + '.gitattributes', + 'docs/package.json', + 'docs/pnpm-lock.yaml', + 'package.json', + ]) + assert.deepEqual(selected([input]), all); + assert.deepEqual(selected(['scripts/docs-generators/cua-driver-policy.test.ts']), driver); +}); + +test('mixed and duplicate inputs produce a deterministic union', () => { + assert.deepEqual( + selected([ + 'libs/lume/Package.swift', + 'libs/cua-driver/rust/crates/platform-windows/Cargo.toml', + 'libs/cua-driver/rust/crates/platform-macos/src/lib.rs', + 'libs/cua-driver/rust/crates/platform-windows/Cargo.toml', + ]), + ['cua-driver/macos', 'cua-driver/windows', 'lume/macos'] + ); +}); + +test('diff against a stacked parent includes both rename paths but excludes parent changes', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'native-matrix-git-')); + const git = (...args) => execFileSync('git', args, { cwd: dir, encoding: 'utf8' }); + try { + git('init', '-q'); + git('config', 'user.name', 'Routing Test'); + git('config', 'user.email', 'routing@example.invalid'); + const oldPath = 'libs/cua-driver/rust/crates/platform-linux/src/old.rs'; + const newPath = 'libs/cua-driver/rust/crates/platform-macos/src/new.rs'; + fs.mkdirSync(path.dirname(path.join(dir, oldPath)), { recursive: true }); + fs.writeFileSync(path.join(dir, oldPath), 'fixture'); + git('add', '.'); + git( + '-c', + 'core.hooksPath=/dev/null', + '-c', + 'commit.gpgsign=false', + 'commit', + '-qm', + 'baseline' + ); + fs.mkdirSync(path.join(dir, 'libs/lume/src'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'libs/lume/src/Parent.swift'), 'parent'); + git('add', '.'); + git('-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false', 'commit', '-qm', 'parent'); + const base = git('rev-parse', 'HEAD').trim(); + fs.mkdirSync(path.dirname(path.join(dir, newPath)), { recursive: true }); + fs.renameSync(path.join(dir, oldPath), path.join(dir, newPath)); + git('add', '-A'); + git('-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false', 'commit', '-qm', 'child'); + const files = git('diff', '--name-only', '--no-renames', '-z', base, 'HEAD') + .split('\0') + .filter(Boolean); + assert.deepEqual(files.sort(), [oldPath, newPath].sort()); + assert.deepEqual(selected(files), ['cua-driver/linux', 'cua-driver/macos']); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test('required-check shell accepts only successful selected jobs or intentional skips', () => { + const workflow = fs.readFileSync( + new URL('../../.github/workflows/ci-check-docs.yml', import.meta.url), + 'utf8' + ); + const gate = workflow.split(' check-docs-sync:')[1].split(' run: |\n')[1]; + assert.ok(gate); + for (const [plan, work, native, succeeds] of [ + ['success', 'true', 'success', true], + ['success', 'false', 'skipped', true], + ['failure', 'false', 'skipped', false], + ['cancelled', '', 'skipped', false], + ['success', 'true', 'failure', false], + ['success', 'true', 'cancelled', false], + ['success', 'true', 'skipped', false], + ['success', '', 'skipped', false], + ['success', 'false', 'success', false], + ]) { + const run = () => + execFileSync('bash', ['-e', '-c', gate], { + env: { ...process.env, PLAN_RESULT: plan, HAS_WORK: work, NATIVE_RESULT: native }, + stdio: 'pipe', + }); + if (succeeds) assert.doesNotThrow(run); + else assert.throws(run); + } +}); + +test('CLI reads NUL-delimited paths and reports intentional empty selections', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'native-matrix-')); + try { + const input = path.join(dir, 'changes'); + const script = fileURLToPath(new URL('./native-matrix.mjs', import.meta.url)); + fs.writeFileSync(input, 'unrelated\nfile.md\0'); + assert.equal( + execFileSync(process.execPath, [script, input], { encoding: 'utf8' }), + 'matrix={"include":[]}\nhas_work=false\n' + ); + fs.writeFileSync(input, 'libs/cua-driver/rust/crates/platform-macos/src/deleted.rs\0'); + const result = execFileSync(process.execPath, [script, input], { encoding: 'utf8' }); + assert.match(result, /has_work=true/); + assert.deepEqual( + JSON.parse(result.split('\n')[0].slice('matrix='.length)), + selectNativeMatrix(['libs/cua-driver/rust/crates/platform-macos/src/deleted.rs']) + ); + assert.throws(() => + execFileSync(process.execPath, [script, path.join(dir, 'missing')], { stdio: 'pipe' }) + ); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); From 7c28292866af5dd72bf4a928a5e4fb32516ceec3 Mon Sep 17 00:00:00 2001 From: injaneity <44902825+injaneity@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:58:19 -0500 Subject: [PATCH 2/4] ci: reuse generator routing for native matrix selection --- .github/workflows/ci-check-docs.yml | 32 +-- scripts/docs-generators/config.json | 9 +- scripts/docs-generators/native-matrix.mjs | 69 ------- .../docs-generators/native-matrix.test.mjs | 192 ------------------ scripts/docs-generators/runner.ts | 82 +++++++- 5 files changed, 99 insertions(+), 285 deletions(-) delete mode 100644 scripts/docs-generators/native-matrix.mjs delete mode 100644 scripts/docs-generators/native-matrix.test.mjs diff --git a/.github/workflows/ci-check-docs.yml b/.github/workflows/ci-check-docs.yml index 3f7e6e82be..12bd5bc900 100644 --- a/.github/workflows/ci-check-docs.yml +++ b/.github/workflows/ci-check-docs.yml @@ -12,7 +12,6 @@ jobs: runs-on: ubuntu-latest outputs: matrix: ${{ steps.scope.outputs.matrix }} - has_work: ${{ steps.scope.outputs.has_work }} steps: - uses: actions/checkout@v4 with: @@ -21,23 +20,28 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "20" - - name: Test native routing - run: node --test scripts/docs-generators/native-matrix.test.mjs + - uses: pnpm/action-setup@v4 + - name: Install docs dependencies + run: pnpm --dir docs install --frozen-lockfile + - name: Test generator routing + run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --test-routing - name: Select native jobs id: scope env: BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | git diff --name-only --no-renames -z "$BASE_SHA" HEAD > "$RUNNER_TEMP/docs-changed-files" - node scripts/docs-generators/native-matrix.mjs "$RUNNER_TEMP/docs-changed-files" >> "$GITHUB_OUTPUT" + MATRIX=$(node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --changed-files-file "$RUNNER_TEMP/docs-changed-files" --native-matrix) + echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" native-reference: name: Reference / ${{ matrix.library }} / ${{ matrix.platform }} needs: plan - if: needs.plan.outputs.has_work == 'true' + if: needs.plan.outputs.matrix != '[]' strategy: fail-fast: false - matrix: ${{ fromJSON(needs.plan.outputs.matrix) }} + matrix: + include: ${{ fromJSON(needs.plan.outputs.matrix) }} runs-on: ${{ matrix.os }} defaults: run: @@ -53,10 +57,8 @@ jobs: - uses: pnpm/action-setup@v4 - name: Install docs dependencies run: pnpm --dir docs install --frozen-lockfile - - name: Test generator routing and ownership - run: | - node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --test-routing - node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver.test.ts scripts/docs-generators/cua-driver-platform.test.ts + - name: Test generator ownership + run: node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver.test.ts scripts/docs-generators/cua-driver-platform.test.ts - name: Install Linux build dependencies if: matrix.platform == 'linux' run: | @@ -84,13 +86,13 @@ jobs: - name: Require native checks to pass env: PLAN_RESULT: ${{ needs.plan.result }} - HAS_WORK: ${{ needs.plan.outputs.has_work }} + MATRIX: ${{ needs.plan.outputs.matrix }} NATIVE_RESULT: ${{ needs.native-reference.result }} run: | test "$PLAN_RESULT" = success - if [[ "$HAS_WORK" == true ]]; then - test "$NATIVE_RESULT" = success - else - test "$HAS_WORK" = false + if [[ "$MATRIX" == '[]' ]]; then test "$NATIVE_RESULT" = skipped + else + test -n "$MATRIX" + test "$NATIVE_RESULT" = success fi diff --git a/scripts/docs-generators/config.json b/scripts/docs-generators/config.json index a0c0ad70c9..94015b4db1 100644 --- a/scripts/docs-generators/config.json +++ b/scripts/docs-generators/config.json @@ -9,9 +9,10 @@ "docsOutputPath": "docs/content/docs/reference/cua-driver", "generatorScript": "scripts/docs-generators/cua-driver.ts", "watchPaths": [ - "libs/cua-driver/rust/crates/**/*.rs", - "libs/cua-driver/rust/Cargo.toml", - "libs/cua-driver/rust/Cargo.lock", + "libs/cua-driver/rust/**", + ".cargo/**", + "rust-toolchain*", + "scripts/docs-generators/cua-driver*.ts", "scripts/docs-generators/cua-driver-policy.test.ts" ], "buildCommand": "cargo build -p cua-driver --release", @@ -52,6 +53,8 @@ "docsOutputPath": "docs/content/docs/reference/lume", "generatorScript": "scripts/docs-generators/lume.ts", "watchPaths": [ + "libs/lume/Package.swift", + "libs/lume/Package.resolved", "libs/lume/src/Commands/**/*.swift", "libs/lume/src/Server/**/*.swift", "libs/lume/src/Utils/CommandDocExtractor.swift", diff --git a/scripts/docs-generators/native-matrix.mjs b/scripts/docs-generators/native-matrix.mjs deleted file mode 100644 index 4be81416e2..0000000000 --- a/scripts/docs-generators/native-matrix.mjs +++ /dev/null @@ -1,69 +0,0 @@ -import fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; - -const config = JSON.parse(fs.readFileSync(new URL('./config.json', import.meta.url), 'utf8')); -const rows = [ - { library: 'cua-driver', platform: 'linux', os: 'ubuntu-latest' }, - { library: 'cua-driver', platform: 'macos', os: 'macos-latest' }, - { library: 'cua-driver', platform: 'windows', os: 'windows-latest' }, - { library: 'lume', platform: 'macos', os: 'macos-latest' }, -]; -const hosts = { linux: 'linux', darwin: 'macos', win32: 'windows' }; - -export function selectNativeMatrix(files) { - const selected = new Set(); - const select = (library, platform) => { - for (const row of rows) { - if ((!library || row.library === library) && (!platform || row.platform === platform)) { - selected.add(row); - } - } - }; - for (const file of files) { - if ( - [ - '.github/workflows/ci-check-docs.yml', - '.gitattributes', - 'package.json', - 'pnpm-workspace.yaml', - 'docs/package.json', - 'docs/pnpm-lock.yaml', - ].includes(file) - ) { - select(); - } else if (file.startsWith('scripts/docs-generators/')) { - if (/\/cua-driver(?:[.-])/.test(file)) select('cua-driver'); - else if (/\/lume(?:[.-])/.test(file)) select('lume'); - else select(); - } else if ( - file.startsWith('libs/cua-driver/rust/') || - file.startsWith('.cargo/') || - /^rust-toolchain(?:\.toml)?$/.test(file) - ) { - const platform = file.match( - /^libs\/cua-driver\/rust\/crates\/platform-(linux|macos|windows)\// - )?.[1]; - select('cua-driver', platform); - } else if (file.startsWith('libs/lume/')) { - select('lume'); - } else { - for (const library of ['cua-driver', 'lume']) { - const generator = config.generators[library]; - for (const output of generator.outputs) { - if (file !== `${generator.docsOutputPath}/${output.outputFile}`) continue; - const platform = output.platform ? hosts[output.platform.host] : undefined; - if (output.platform && !platform) - throw new Error(`Unknown output host: ${output.platform.host}`); - select(library, platform); - } - } - } - } - return { include: rows.filter((row) => selected.has(row)) }; -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) { - const files = fs.readFileSync(process.argv[2], 'utf8').split('\0').filter(Boolean); - const matrix = selectNativeMatrix(files); - process.stdout.write(`matrix=${JSON.stringify(matrix)}\nhas_work=${matrix.include.length > 0}\n`); -} diff --git a/scripts/docs-generators/native-matrix.test.mjs b/scripts/docs-generators/native-matrix.test.mjs deleted file mode 100644 index 7c542cece8..0000000000 --- a/scripts/docs-generators/native-matrix.test.mjs +++ /dev/null @@ -1,192 +0,0 @@ -import assert from 'node:assert/strict'; -import { execFileSync } from 'node:child_process'; -import fs from 'node:fs'; -import os from 'node:os'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import test from 'node:test'; -import { selectNativeMatrix } from './native-matrix.mjs'; - -const driver = ['cua-driver/linux', 'cua-driver/macos', 'cua-driver/windows']; -const all = [...driver, 'lume/macos']; -const selected = (files) => - selectNativeMatrix(files).include.map((row) => `${row.library}/${row.platform}`); - -test('unrelated and curated changes allocate no native runners', () => { - assert.deepEqual(selected([]), []); - assert.deepEqual( - selected([ - 'README.md', - 'docs/content/docs/reference/cua-driver/macos-permissions.mdx', - 'docs/content/docs/reference/cua-driver/mcp-tool-notes.mdx', - 'libs/python/agent/agent/main.py', - ]), - [] - ); -}); - -test('platform source, manifests, and build scripts select their owner', () => { - for (const platform of ['linux', 'macos', 'windows']) { - for (const input of ['src/lib.rs', 'Cargo.toml', 'build.rs']) { - assert.deepEqual(selected([`libs/cua-driver/rust/crates/platform-${platform}/${input}`]), [ - `cua-driver/${platform}`, - ]); - } - } -}); - -test('shared source and dependency/build inputs select all driver hosts', () => { - for (const input of [ - 'libs/cua-driver/rust/crates/cua-driver-core/src/lib.rs', - 'libs/cua-driver/rust/crates/cua-driver-contract/Cargo.toml', - 'libs/cua-driver/rust/crates/cua-driver/build.rs', - 'libs/cua-driver/rust/crates/new-shared-crate/src/lib.rs', - 'libs/cua-driver/rust/Cargo.lock', - 'libs/cua-driver/rust/Cargo.toml', - 'libs/cua-driver/rust/.cargo/config.toml', - '.cargo/config.toml', - 'rust-toolchain.toml', - ]) - assert.deepEqual(selected([input]), driver); -}); - -test('generated references select configured owners; shared CLI checks all hosts', () => { - for (const [file, expected] of [ - ['mcp-tools.mdx', ['cua-driver/macos']], - ['mcp-tools-linux.mdx', ['cua-driver/linux']], - ['mcp-tools-windows.mdx', ['cua-driver/windows']], - ['cli-reference.mdx', driver], - ]) - assert.deepEqual(selected([`docs/content/docs/reference/cua-driver/${file}`]), expected); -}); - -test('lume source, build configuration, and reference changes stay on lume', () => { - for (const input of [ - 'libs/lume/src/Commands/List.swift', - 'libs/lume/Package.swift', - 'libs/lume/Package.resolved', - 'docs/content/docs/reference/lume/http-api.mdx', - 'scripts/docs-generators/lume.ts', - ]) - assert.deepEqual(selected([input]), ['lume/macos']); -}); - -test('generator dependencies and workflow changes conservatively fan out', () => { - for (const input of [ - 'scripts/docs-generators/runner.ts', - 'scripts/docs-generators/config.json', - 'scripts/docs-generators/native-matrix.mjs', - '.github/workflows/ci-check-docs.yml', - '.gitattributes', - 'docs/package.json', - 'docs/pnpm-lock.yaml', - 'package.json', - ]) - assert.deepEqual(selected([input]), all); - assert.deepEqual(selected(['scripts/docs-generators/cua-driver-policy.test.ts']), driver); -}); - -test('mixed and duplicate inputs produce a deterministic union', () => { - assert.deepEqual( - selected([ - 'libs/lume/Package.swift', - 'libs/cua-driver/rust/crates/platform-windows/Cargo.toml', - 'libs/cua-driver/rust/crates/platform-macos/src/lib.rs', - 'libs/cua-driver/rust/crates/platform-windows/Cargo.toml', - ]), - ['cua-driver/macos', 'cua-driver/windows', 'lume/macos'] - ); -}); - -test('diff against a stacked parent includes both rename paths but excludes parent changes', () => { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'native-matrix-git-')); - const git = (...args) => execFileSync('git', args, { cwd: dir, encoding: 'utf8' }); - try { - git('init', '-q'); - git('config', 'user.name', 'Routing Test'); - git('config', 'user.email', 'routing@example.invalid'); - const oldPath = 'libs/cua-driver/rust/crates/platform-linux/src/old.rs'; - const newPath = 'libs/cua-driver/rust/crates/platform-macos/src/new.rs'; - fs.mkdirSync(path.dirname(path.join(dir, oldPath)), { recursive: true }); - fs.writeFileSync(path.join(dir, oldPath), 'fixture'); - git('add', '.'); - git( - '-c', - 'core.hooksPath=/dev/null', - '-c', - 'commit.gpgsign=false', - 'commit', - '-qm', - 'baseline' - ); - fs.mkdirSync(path.join(dir, 'libs/lume/src'), { recursive: true }); - fs.writeFileSync(path.join(dir, 'libs/lume/src/Parent.swift'), 'parent'); - git('add', '.'); - git('-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false', 'commit', '-qm', 'parent'); - const base = git('rev-parse', 'HEAD').trim(); - fs.mkdirSync(path.dirname(path.join(dir, newPath)), { recursive: true }); - fs.renameSync(path.join(dir, oldPath), path.join(dir, newPath)); - git('add', '-A'); - git('-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false', 'commit', '-qm', 'child'); - const files = git('diff', '--name-only', '--no-renames', '-z', base, 'HEAD') - .split('\0') - .filter(Boolean); - assert.deepEqual(files.sort(), [oldPath, newPath].sort()); - assert.deepEqual(selected(files), ['cua-driver/linux', 'cua-driver/macos']); - } finally { - fs.rmSync(dir, { recursive: true, force: true }); - } -}); - -test('required-check shell accepts only successful selected jobs or intentional skips', () => { - const workflow = fs.readFileSync( - new URL('../../.github/workflows/ci-check-docs.yml', import.meta.url), - 'utf8' - ); - const gate = workflow.split(' check-docs-sync:')[1].split(' run: |\n')[1]; - assert.ok(gate); - for (const [plan, work, native, succeeds] of [ - ['success', 'true', 'success', true], - ['success', 'false', 'skipped', true], - ['failure', 'false', 'skipped', false], - ['cancelled', '', 'skipped', false], - ['success', 'true', 'failure', false], - ['success', 'true', 'cancelled', false], - ['success', 'true', 'skipped', false], - ['success', '', 'skipped', false], - ['success', 'false', 'success', false], - ]) { - const run = () => - execFileSync('bash', ['-e', '-c', gate], { - env: { ...process.env, PLAN_RESULT: plan, HAS_WORK: work, NATIVE_RESULT: native }, - stdio: 'pipe', - }); - if (succeeds) assert.doesNotThrow(run); - else assert.throws(run); - } -}); - -test('CLI reads NUL-delimited paths and reports intentional empty selections', () => { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'native-matrix-')); - try { - const input = path.join(dir, 'changes'); - const script = fileURLToPath(new URL('./native-matrix.mjs', import.meta.url)); - fs.writeFileSync(input, 'unrelated\nfile.md\0'); - assert.equal( - execFileSync(process.execPath, [script, input], { encoding: 'utf8' }), - 'matrix={"include":[]}\nhas_work=false\n' - ); - fs.writeFileSync(input, 'libs/cua-driver/rust/crates/platform-macos/src/deleted.rs\0'); - const result = execFileSync(process.execPath, [script, input], { encoding: 'utf8' }); - assert.match(result, /has_work=true/); - assert.deepEqual( - JSON.parse(result.split('\n')[0].slice('matrix='.length)), - selectNativeMatrix(['libs/cua-driver/rust/crates/platform-macos/src/deleted.rs']) - ); - assert.throws(() => - execFileSync(process.execPath, [script, path.join(dir, 'missing')], { stdio: 'pipe' }) - ); - } finally { - fs.rmSync(dir, { recursive: true, force: true }); - } -}); diff --git a/scripts/docs-generators/runner.ts b/scripts/docs-generators/runner.ts index 7d99822f0f..07e50b1127 100644 --- a/scripts/docs-generators/runner.ts +++ b/scripts/docs-generators/runner.ts @@ -25,6 +25,7 @@ interface GeneratorOutput { type: string; outputFile: string; extractCommand: string | null; + platform?: { host: string; name: string }; } interface GeneratorConfig { @@ -59,6 +60,8 @@ const SHARED_GENERATOR_FILES = new Set([ 'scripts/docs-generators/config.json', '.github/workflows/ci-check-docs.yml', '.gitattributes', + 'package.json', + 'pnpm-workspace.yaml', 'docs/package.json', 'docs/pnpm-lock.yaml', ]); @@ -99,8 +102,13 @@ async function main() { console.error(`Changed-files input not found: ${changedFilesFile}`); process.exit(1); } - const changedFiles = fs.readFileSync(changedFilesFile, 'utf-8').split(/\r?\n/).filter(Boolean); - console.log(selectGenerators(config, changedFiles).join(' ')); + const contents = fs.readFileSync(changedFilesFile, 'utf-8'); + const changedFiles = contents.split(contents.includes('\0') ? '\0' : /\r?\n/).filter(Boolean); + console.log( + args.includes('--native-matrix') + ? JSON.stringify(selectNativeMatrix(config, changedFiles)) + : selectGenerators(config, changedFiles).join(' ') + ); return; } @@ -262,7 +270,22 @@ function globToRegExp(glob: string): RegExp { return new RegExp(`^${pattern}$`); } -function selectGenerators(config: Config, changedFiles: readonly string[]): string[] { +function selectNativeMatrix(config: Config, changedFiles: readonly string[]) { + return [ + { library: 'cua-driver', platform: 'linux', os: 'ubuntu-latest', host: 'linux' }, + { library: 'cua-driver', platform: 'macos', os: 'macos-latest', host: 'darwin' }, + { library: 'cua-driver', platform: 'windows', os: 'windows-latest', host: 'win32' }, + { library: 'lume', platform: 'macos', os: 'macos-latest', host: 'darwin' }, + ] + .filter((row) => selectGenerators(config, changedFiles, row.host).includes(row.library)) + .map(({ host, ...row }) => row); +} + +function selectGenerators( + config: Config, + changedFiles: readonly string[], + host?: string +): string[] { const enabledGenerators = Object.entries(config.generators).filter(([_, cfg]) => cfg.enabled); const normalizedFiles = changedFiles.map((file) => file.replace(/\\/g, '/')); @@ -278,12 +301,23 @@ function selectGenerators(config: Config, changedFiles: readonly string[]): stri ), ]); const watchPatterns = generator.watchPaths.map(globToRegExp); - const selected = normalizedFiles.some( - (file) => + const selected = normalizedFiles.some((file) => { + const adapter = file.match( + /^libs\/cua-driver\/rust\/crates\/platform-(linux|macos|windows)\// + )?.[1]; + const adapterHost = + adapter === 'macos' ? 'darwin' : adapter === 'windows' ? 'win32' : adapter; + const output = generator.outputs.find( + (output) => file === path.posix.join(generator.docsOutputPath, output.outputFile) + ); + const owner = adapterHost ?? output?.platform?.host; + if (host && owner && owner !== host) return false; + return ( file.startsWith(`${generator.sourcePath}/`) || ownedFiles.has(file) || watchPatterns.some((pattern) => pattern.test(file)) - ); + ); + }); return selected ? [key] : []; }); @@ -338,6 +372,42 @@ function testGeneratorRouting(config: Config): void { assertSelection(config, ['scripts/docs-generators/runner.ts'], ['cua-driver', 'lume']); assertSelection(config, ['scripts/docs-generators/config.json'], ['cua-driver', 'lume']); + const driver = ['cua-driver/linux', 'cua-driver/macos', 'cua-driver/windows']; + const cases: [string[], string[]][] = [ + [[], []], + [['docs/content/docs/reference/cua-driver/macos-permissions.mdx'], []], + [['libs/cua-driver/rust/Cargo.lock'], driver], + [['libs/cua-driver/rust/crates/cua-driver-core/src/lib.rs'], driver], + [['docs/content/docs/reference/cua-driver/cli-reference.mdx'], driver], + [['libs/lume/Package.swift'], ['lume/macos']], + [['scripts/docs-generators/runner.ts'], [...driver, 'lume/macos']], + [ + [ + 'libs/cua-driver/rust/crates/platform-linux/src/deleted.rs', + 'libs/cua-driver/rust/crates/platform-macos/src/renamed.rs', + ], + driver.slice(0, 2), + ], + ]; + for (const platform of ['linux', 'macos', 'windows']) { + for (const file of ['src/lib.rs', 'Cargo.toml', 'build.rs']) { + cases.push([ + [`libs/cua-driver/rust/crates/platform-${platform}/${file}`], + [`cua-driver/${platform}`], + ]); + } + const suffix = platform === 'macos' ? '' : `-${platform}`; + cases.push([ + [`docs/content/docs/reference/cua-driver/mcp-tools${suffix}.mdx`], + [`cua-driver/${platform}`], + ]); + } + for (const [files, expected] of cases) { + const actual = selectNativeMatrix(config, files).map((row) => `${row.library}/${row.platform}`); + if (actual.join() !== expected.join()) + throw new Error(`Native routing failed for ${files}: ${actual}`); + } + console.log(`Generator routing assertions passed with pinned tsx ${tsxVersion}`); } From a2c2cec8505462a6505d8c2706ce91de2ef4b3bc Mon Sep 17 00:00:00 2001 From: injaneity <44902825+injaneity@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:16:32 -0500 Subject: [PATCH 3/4] ci: use path filters to select native docs checks --- .github/workflows/ci-check-docs.yml | 107 ++++++++++++++++++---------- scripts/docs-generators/config.json | 9 +-- scripts/docs-generators/runner.ts | 82 ++------------------- 3 files changed, 77 insertions(+), 121 deletions(-) diff --git a/.github/workflows/ci-check-docs.yml b/.github/workflows/ci-check-docs.yml index 12bd5bc900..87459572d6 100644 --- a/.github/workflows/ci-check-docs.yml +++ b/.github/workflows/ci-check-docs.yml @@ -7,42 +7,69 @@ permissions: contents: read jobs: - plan: + changes: name: Select Native Documentation Checks runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read outputs: - matrix: ${{ steps.scope.outputs.matrix }} + targets: ${{ github.event.pull_request.changed_files > 3000 && '["linux","macos","windows","lume"]' || steps.filter.outputs.changes }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: false - - uses: actions/setup-node@v4 + - uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad + id: filter with: - node-version: "20" - - uses: pnpm/action-setup@v4 - - name: Install docs dependencies - run: pnpm --dir docs install --frozen-lockfile - - name: Test generator routing - run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --test-routing - - name: Select native jobs - id: scope - env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - run: | - git diff --name-only --no-renames -z "$BASE_SHA" HEAD > "$RUNNER_TEMP/docs-changed-files" - MATRIX=$(node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --changed-files-file "$RUNNER_TEMP/docs-changed-files" --native-matrix) - echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" + filters: | + linux: + - &shared + - '.github/workflows/ci-check-docs.yml' + - '.gitattributes' + - 'package.json' + - 'pnpm-workspace.yaml' + - 'docs/package.json' + - 'docs/pnpm-lock.yaml' + - 'scripts/docs-generators/runner.ts' + - 'scripts/docs-generators/config.json' + - &driver + - 'libs/cua-driver/rust/crates/!(platform-linux|platform-macos|platform-windows)/**' + - 'libs/cua-driver/rust/*.{toml,lock}' + - 'libs/cua-driver/rust/.cargo/**' + - 'libs/cua-driver/rust/rust-toolchain*' + - '.cargo/**' + - 'rust-toolchain*' + - 'scripts/docs-generators/cua-driver*.ts' + - 'docs/content/docs/reference/cua-driver/cli-reference.mdx' + - 'libs/cua-driver/rust/crates/platform-linux/**' + - 'docs/content/docs/reference/cua-driver/mcp-tools-linux.mdx' + macos: + - *shared + - *driver + - 'libs/cua-driver/rust/crates/platform-macos/**' + - 'docs/content/docs/reference/cua-driver/mcp-tools.mdx' + windows: + - *shared + - *driver + - 'libs/cua-driver/rust/crates/platform-windows/**' + - 'docs/content/docs/reference/cua-driver/mcp-tools-windows.mdx' + lume: + - *shared + - 'libs/lume/src/**' + - 'libs/lume/Package.swift' + - 'libs/lume/Package.resolved' + - 'scripts/docs-generators/lume*.ts' + - 'docs/content/docs/reference/lume/{cli-reference,http-api}.mdx' native-reference: - name: Reference / ${{ matrix.library }} / ${{ matrix.platform }} - needs: plan - if: needs.plan.outputs.matrix != '[]' + name: Reference / ${{ matrix.target == 'lume' && 'lume' || 'cua-driver' }} / ${{ matrix.target == 'lume' && 'macos' || matrix.target }} + needs: changes + if: needs.changes.outputs.targets != '[]' strategy: fail-fast: false matrix: - include: ${{ fromJSON(needs.plan.outputs.matrix) }} - runs-on: ${{ matrix.os }} + target: ${{ fromJSON(needs.changes.outputs.targets) }} + runs-on: ${{ matrix.target == 'linux' && 'ubuntu-latest' || matrix.target == 'windows' && 'windows-latest' || 'macos-latest' }} + env: + LIBRARY: ${{ matrix.target == 'lume' && 'lume' || 'cua-driver' }} defaults: run: shell: bash @@ -57,42 +84,44 @@ jobs: - uses: pnpm/action-setup@v4 - name: Install docs dependencies run: pnpm --dir docs install --frozen-lockfile - - name: Test generator ownership - run: node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver.test.ts scripts/docs-generators/cua-driver-platform.test.ts + - name: Test generator routing and ownership + run: | + node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --test-routing + node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver.test.ts scripts/docs-generators/cua-driver-platform.test.ts - name: Install Linux build dependencies - if: matrix.platform == 'linux' + if: matrix.target == 'linux' run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ clang pkg-config libdbus-1-dev libpipewire-0.3-dev libspa-0.2-dev \ libei-dev libxkbcommon-dev libx11-dev libxi-dev libxtst-dev libxext-dev - name: Check generated reference - if: matrix.platform != 'windows' - run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --library ${{ matrix.library }} --check + if: matrix.target != 'windows' + run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --library "$LIBRARY" --check - name: Check generated reference in PowerShell - if: matrix.platform == 'windows' + if: matrix.target == 'windows' shell: pwsh run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --library cua-driver --check - name: Verify docs extraction preserves policy-filtered discovery - if: matrix.library == 'cua-driver' + if: matrix.target != 'lume' run: node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver-policy.test.ts check-docs-sync: name: Check Documentation Sync if: always() - needs: [plan, native-reference] + needs: [changes, native-reference] runs-on: ubuntu-latest steps: - name: Require native checks to pass env: - PLAN_RESULT: ${{ needs.plan.result }} - MATRIX: ${{ needs.plan.outputs.matrix }} + FILTER_RESULT: ${{ needs.changes.result }} + TARGETS: ${{ needs.changes.outputs.targets }} NATIVE_RESULT: ${{ needs.native-reference.result }} run: | - test "$PLAN_RESULT" = success - if [[ "$MATRIX" == '[]' ]]; then + test "$FILTER_RESULT" = success + if [[ "$TARGETS" == '[]' ]]; then test "$NATIVE_RESULT" = skipped else - test -n "$MATRIX" + test -n "$TARGETS" test "$NATIVE_RESULT" = success fi diff --git a/scripts/docs-generators/config.json b/scripts/docs-generators/config.json index 94015b4db1..a0c0ad70c9 100644 --- a/scripts/docs-generators/config.json +++ b/scripts/docs-generators/config.json @@ -9,10 +9,9 @@ "docsOutputPath": "docs/content/docs/reference/cua-driver", "generatorScript": "scripts/docs-generators/cua-driver.ts", "watchPaths": [ - "libs/cua-driver/rust/**", - ".cargo/**", - "rust-toolchain*", - "scripts/docs-generators/cua-driver*.ts", + "libs/cua-driver/rust/crates/**/*.rs", + "libs/cua-driver/rust/Cargo.toml", + "libs/cua-driver/rust/Cargo.lock", "scripts/docs-generators/cua-driver-policy.test.ts" ], "buildCommand": "cargo build -p cua-driver --release", @@ -53,8 +52,6 @@ "docsOutputPath": "docs/content/docs/reference/lume", "generatorScript": "scripts/docs-generators/lume.ts", "watchPaths": [ - "libs/lume/Package.swift", - "libs/lume/Package.resolved", "libs/lume/src/Commands/**/*.swift", "libs/lume/src/Server/**/*.swift", "libs/lume/src/Utils/CommandDocExtractor.swift", diff --git a/scripts/docs-generators/runner.ts b/scripts/docs-generators/runner.ts index 07e50b1127..7d99822f0f 100644 --- a/scripts/docs-generators/runner.ts +++ b/scripts/docs-generators/runner.ts @@ -25,7 +25,6 @@ interface GeneratorOutput { type: string; outputFile: string; extractCommand: string | null; - platform?: { host: string; name: string }; } interface GeneratorConfig { @@ -60,8 +59,6 @@ const SHARED_GENERATOR_FILES = new Set([ 'scripts/docs-generators/config.json', '.github/workflows/ci-check-docs.yml', '.gitattributes', - 'package.json', - 'pnpm-workspace.yaml', 'docs/package.json', 'docs/pnpm-lock.yaml', ]); @@ -102,13 +99,8 @@ async function main() { console.error(`Changed-files input not found: ${changedFilesFile}`); process.exit(1); } - const contents = fs.readFileSync(changedFilesFile, 'utf-8'); - const changedFiles = contents.split(contents.includes('\0') ? '\0' : /\r?\n/).filter(Boolean); - console.log( - args.includes('--native-matrix') - ? JSON.stringify(selectNativeMatrix(config, changedFiles)) - : selectGenerators(config, changedFiles).join(' ') - ); + const changedFiles = fs.readFileSync(changedFilesFile, 'utf-8').split(/\r?\n/).filter(Boolean); + console.log(selectGenerators(config, changedFiles).join(' ')); return; } @@ -270,22 +262,7 @@ function globToRegExp(glob: string): RegExp { return new RegExp(`^${pattern}$`); } -function selectNativeMatrix(config: Config, changedFiles: readonly string[]) { - return [ - { library: 'cua-driver', platform: 'linux', os: 'ubuntu-latest', host: 'linux' }, - { library: 'cua-driver', platform: 'macos', os: 'macos-latest', host: 'darwin' }, - { library: 'cua-driver', platform: 'windows', os: 'windows-latest', host: 'win32' }, - { library: 'lume', platform: 'macos', os: 'macos-latest', host: 'darwin' }, - ] - .filter((row) => selectGenerators(config, changedFiles, row.host).includes(row.library)) - .map(({ host, ...row }) => row); -} - -function selectGenerators( - config: Config, - changedFiles: readonly string[], - host?: string -): string[] { +function selectGenerators(config: Config, changedFiles: readonly string[]): string[] { const enabledGenerators = Object.entries(config.generators).filter(([_, cfg]) => cfg.enabled); const normalizedFiles = changedFiles.map((file) => file.replace(/\\/g, '/')); @@ -301,23 +278,12 @@ function selectGenerators( ), ]); const watchPatterns = generator.watchPaths.map(globToRegExp); - const selected = normalizedFiles.some((file) => { - const adapter = file.match( - /^libs\/cua-driver\/rust\/crates\/platform-(linux|macos|windows)\// - )?.[1]; - const adapterHost = - adapter === 'macos' ? 'darwin' : adapter === 'windows' ? 'win32' : adapter; - const output = generator.outputs.find( - (output) => file === path.posix.join(generator.docsOutputPath, output.outputFile) - ); - const owner = adapterHost ?? output?.platform?.host; - if (host && owner && owner !== host) return false; - return ( + const selected = normalizedFiles.some( + (file) => file.startsWith(`${generator.sourcePath}/`) || ownedFiles.has(file) || watchPatterns.some((pattern) => pattern.test(file)) - ); - }); + ); return selected ? [key] : []; }); @@ -372,42 +338,6 @@ function testGeneratorRouting(config: Config): void { assertSelection(config, ['scripts/docs-generators/runner.ts'], ['cua-driver', 'lume']); assertSelection(config, ['scripts/docs-generators/config.json'], ['cua-driver', 'lume']); - const driver = ['cua-driver/linux', 'cua-driver/macos', 'cua-driver/windows']; - const cases: [string[], string[]][] = [ - [[], []], - [['docs/content/docs/reference/cua-driver/macos-permissions.mdx'], []], - [['libs/cua-driver/rust/Cargo.lock'], driver], - [['libs/cua-driver/rust/crates/cua-driver-core/src/lib.rs'], driver], - [['docs/content/docs/reference/cua-driver/cli-reference.mdx'], driver], - [['libs/lume/Package.swift'], ['lume/macos']], - [['scripts/docs-generators/runner.ts'], [...driver, 'lume/macos']], - [ - [ - 'libs/cua-driver/rust/crates/platform-linux/src/deleted.rs', - 'libs/cua-driver/rust/crates/platform-macos/src/renamed.rs', - ], - driver.slice(0, 2), - ], - ]; - for (const platform of ['linux', 'macos', 'windows']) { - for (const file of ['src/lib.rs', 'Cargo.toml', 'build.rs']) { - cases.push([ - [`libs/cua-driver/rust/crates/platform-${platform}/${file}`], - [`cua-driver/${platform}`], - ]); - } - const suffix = platform === 'macos' ? '' : `-${platform}`; - cases.push([ - [`docs/content/docs/reference/cua-driver/mcp-tools${suffix}.mdx`], - [`cua-driver/${platform}`], - ]); - } - for (const [files, expected] of cases) { - const actual = selectNativeMatrix(config, files).map((row) => `${row.library}/${row.platform}`); - if (actual.join() !== expected.join()) - throw new Error(`Native routing failed for ${files}: ${actual}`); - } - console.log(`Generator routing assertions passed with pinned tsx ${tsxVersion}`); } From 97d564b2bdc670ed5dad9318de9d3ee275d9f0e3 Mon Sep 17 00:00:00 2001 From: injaneity <44902825+injaneity@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:23:27 -0500 Subject: [PATCH 4/4] ci: trigger native docs checks with workflow path filters --- .github/workflows/ci-check-docs-linux.yml | 37 +++++++ .github/workflows/ci-check-docs-lume.yml | 31 ++++++ .github/workflows/ci-check-docs-macos.yml | 37 +++++++ .github/workflows/ci-check-docs-windows.yml | 37 +++++++ .github/workflows/ci-check-docs.yml | 106 ++++---------------- 5 files changed, 161 insertions(+), 87 deletions(-) create mode 100644 .github/workflows/ci-check-docs-linux.yml create mode 100644 .github/workflows/ci-check-docs-lume.yml create mode 100644 .github/workflows/ci-check-docs-macos.yml create mode 100644 .github/workflows/ci-check-docs-windows.yml diff --git a/.github/workflows/ci-check-docs-linux.yml b/.github/workflows/ci-check-docs-linux.yml new file mode 100644 index 0000000000..a531820165 --- /dev/null +++ b/.github/workflows/ci-check-docs-linux.yml @@ -0,0 +1,37 @@ +name: "CI: Driver Docs / Linux" + +on: + pull_request: + paths: + - "libs/cua-driver/rust/crates/**" + - "!libs/cua-driver/rust/crates/platform-macos/**" + - "!libs/cua-driver/rust/crates/platform-windows/**" + - "libs/cua-driver/rust/Cargo.toml" + - "libs/cua-driver/rust/Cargo.lock" + - "libs/cua-driver/rust/.cargo/**" + - "libs/cua-driver/rust/rust-toolchain*" + - ".cargo/**" + - "rust-toolchain*" + - "scripts/docs-generators/cua-driver*.ts" + - "scripts/docs-generators/runner.ts" + - "scripts/docs-generators/config.json" + - "docs/content/docs/reference/cua-driver/cli-reference.mdx" + - "docs/content/docs/reference/cua-driver/mcp-tools-linux.mdx" + - "docs/package.json" + - "docs/pnpm-lock.yaml" + - "package.json" + - "pnpm-workspace.yaml" + - ".gitattributes" + - ".github/workflows/ci-check-docs.yml" + - ".github/workflows/ci-check-docs-linux.yml" + +permissions: + contents: read + +jobs: + reference: + uses: ./.github/workflows/ci-check-docs.yml + with: + library: cua-driver + platform: linux + runner: ubuntu-latest diff --git a/.github/workflows/ci-check-docs-lume.yml b/.github/workflows/ci-check-docs-lume.yml new file mode 100644 index 0000000000..4ce5c286a9 --- /dev/null +++ b/.github/workflows/ci-check-docs-lume.yml @@ -0,0 +1,31 @@ +name: "CI: Lume Docs / macOS" + +on: + pull_request: + paths: + - "libs/lume/src/**" + - "libs/lume/Package.swift" + - "libs/lume/Package.resolved" + - "scripts/docs-generators/lume*.ts" + - "scripts/docs-generators/runner.ts" + - "scripts/docs-generators/config.json" + - "docs/content/docs/reference/lume/cli-reference.mdx" + - "docs/content/docs/reference/lume/http-api.mdx" + - "docs/package.json" + - "docs/pnpm-lock.yaml" + - "package.json" + - "pnpm-workspace.yaml" + - ".gitattributes" + - ".github/workflows/ci-check-docs.yml" + - ".github/workflows/ci-check-docs-lume.yml" + +permissions: + contents: read + +jobs: + reference: + uses: ./.github/workflows/ci-check-docs.yml + with: + library: lume + platform: macos + runner: macos-latest diff --git a/.github/workflows/ci-check-docs-macos.yml b/.github/workflows/ci-check-docs-macos.yml new file mode 100644 index 0000000000..1282702f94 --- /dev/null +++ b/.github/workflows/ci-check-docs-macos.yml @@ -0,0 +1,37 @@ +name: "CI: Driver Docs / macOS" + +on: + pull_request: + paths: + - "libs/cua-driver/rust/crates/**" + - "!libs/cua-driver/rust/crates/platform-linux/**" + - "!libs/cua-driver/rust/crates/platform-windows/**" + - "libs/cua-driver/rust/Cargo.toml" + - "libs/cua-driver/rust/Cargo.lock" + - "libs/cua-driver/rust/.cargo/**" + - "libs/cua-driver/rust/rust-toolchain*" + - ".cargo/**" + - "rust-toolchain*" + - "scripts/docs-generators/cua-driver*.ts" + - "scripts/docs-generators/runner.ts" + - "scripts/docs-generators/config.json" + - "docs/content/docs/reference/cua-driver/cli-reference.mdx" + - "docs/content/docs/reference/cua-driver/mcp-tools.mdx" + - "docs/package.json" + - "docs/pnpm-lock.yaml" + - "package.json" + - "pnpm-workspace.yaml" + - ".gitattributes" + - ".github/workflows/ci-check-docs.yml" + - ".github/workflows/ci-check-docs-macos.yml" + +permissions: + contents: read + +jobs: + reference: + uses: ./.github/workflows/ci-check-docs.yml + with: + library: cua-driver + platform: macos + runner: macos-latest diff --git a/.github/workflows/ci-check-docs-windows.yml b/.github/workflows/ci-check-docs-windows.yml new file mode 100644 index 0000000000..d3a4e183dd --- /dev/null +++ b/.github/workflows/ci-check-docs-windows.yml @@ -0,0 +1,37 @@ +name: "CI: Driver Docs / Windows" + +on: + pull_request: + paths: + - "libs/cua-driver/rust/crates/**" + - "!libs/cua-driver/rust/crates/platform-linux/**" + - "!libs/cua-driver/rust/crates/platform-macos/**" + - "libs/cua-driver/rust/Cargo.toml" + - "libs/cua-driver/rust/Cargo.lock" + - "libs/cua-driver/rust/.cargo/**" + - "libs/cua-driver/rust/rust-toolchain*" + - ".cargo/**" + - "rust-toolchain*" + - "scripts/docs-generators/cua-driver*.ts" + - "scripts/docs-generators/runner.ts" + - "scripts/docs-generators/config.json" + - "docs/content/docs/reference/cua-driver/cli-reference.mdx" + - "docs/content/docs/reference/cua-driver/mcp-tools-windows.mdx" + - "docs/package.json" + - "docs/pnpm-lock.yaml" + - "package.json" + - "pnpm-workspace.yaml" + - ".gitattributes" + - ".github/workflows/ci-check-docs.yml" + - ".github/workflows/ci-check-docs-windows.yml" + +permissions: + contents: read + +jobs: + reference: + uses: ./.github/workflows/ci-check-docs.yml + with: + library: cua-driver + platform: windows + runner: windows-latest diff --git a/.github/workflows/ci-check-docs.yml b/.github/workflows/ci-check-docs.yml index 87459572d6..1c3de94a80 100644 --- a/.github/workflows/ci-check-docs.yml +++ b/.github/workflows/ci-check-docs.yml @@ -1,75 +1,27 @@ -name: "CI: Check Docs" +name: "CI: Native Docs Check" on: - pull_request: + workflow_call: + inputs: + library: + required: true + type: string + platform: + required: true + type: string + runner: + required: true + type: string permissions: contents: read jobs: - changes: - name: Select Native Documentation Checks - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - outputs: - targets: ${{ github.event.pull_request.changed_files > 3000 && '["linux","macos","windows","lume"]' || steps.filter.outputs.changes }} - steps: - - uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad - id: filter - with: - filters: | - linux: - - &shared - - '.github/workflows/ci-check-docs.yml' - - '.gitattributes' - - 'package.json' - - 'pnpm-workspace.yaml' - - 'docs/package.json' - - 'docs/pnpm-lock.yaml' - - 'scripts/docs-generators/runner.ts' - - 'scripts/docs-generators/config.json' - - &driver - - 'libs/cua-driver/rust/crates/!(platform-linux|platform-macos|platform-windows)/**' - - 'libs/cua-driver/rust/*.{toml,lock}' - - 'libs/cua-driver/rust/.cargo/**' - - 'libs/cua-driver/rust/rust-toolchain*' - - '.cargo/**' - - 'rust-toolchain*' - - 'scripts/docs-generators/cua-driver*.ts' - - 'docs/content/docs/reference/cua-driver/cli-reference.mdx' - - 'libs/cua-driver/rust/crates/platform-linux/**' - - 'docs/content/docs/reference/cua-driver/mcp-tools-linux.mdx' - macos: - - *shared - - *driver - - 'libs/cua-driver/rust/crates/platform-macos/**' - - 'docs/content/docs/reference/cua-driver/mcp-tools.mdx' - windows: - - *shared - - *driver - - 'libs/cua-driver/rust/crates/platform-windows/**' - - 'docs/content/docs/reference/cua-driver/mcp-tools-windows.mdx' - lume: - - *shared - - 'libs/lume/src/**' - - 'libs/lume/Package.swift' - - 'libs/lume/Package.resolved' - - 'scripts/docs-generators/lume*.ts' - - 'docs/content/docs/reference/lume/{cli-reference,http-api}.mdx' - native-reference: - name: Reference / ${{ matrix.target == 'lume' && 'lume' || 'cua-driver' }} / ${{ matrix.target == 'lume' && 'macos' || matrix.target }} - needs: changes - if: needs.changes.outputs.targets != '[]' - strategy: - fail-fast: false - matrix: - target: ${{ fromJSON(needs.changes.outputs.targets) }} - runs-on: ${{ matrix.target == 'linux' && 'ubuntu-latest' || matrix.target == 'windows' && 'windows-latest' || 'macos-latest' }} + name: Reference / ${{ inputs.library }} / ${{ inputs.platform }} + runs-on: ${{ inputs.runner }} env: - LIBRARY: ${{ matrix.target == 'lume' && 'lume' || 'cua-driver' }} + LIBRARY: ${{ inputs.library }} defaults: run: shell: bash @@ -89,39 +41,19 @@ jobs: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --test-routing node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver.test.ts scripts/docs-generators/cua-driver-platform.test.ts - name: Install Linux build dependencies - if: matrix.target == 'linux' + if: inputs.platform == 'linux' run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ clang pkg-config libdbus-1-dev libpipewire-0.3-dev libspa-0.2-dev \ libei-dev libxkbcommon-dev libx11-dev libxi-dev libxtst-dev libxext-dev - name: Check generated reference - if: matrix.target != 'windows' + if: inputs.platform != 'windows' run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --library "$LIBRARY" --check - name: Check generated reference in PowerShell - if: matrix.target == 'windows' + if: inputs.platform == 'windows' shell: pwsh run: node docs/node_modules/tsx/dist/cli.mjs scripts/docs-generators/runner.ts --library cua-driver --check - name: Verify docs extraction preserves policy-filtered discovery - if: matrix.target != 'lume' + if: inputs.library == 'cua-driver' run: node docs/node_modules/tsx/dist/cli.mjs --test scripts/docs-generators/cua-driver-policy.test.ts - - check-docs-sync: - name: Check Documentation Sync - if: always() - needs: [changes, native-reference] - runs-on: ubuntu-latest - steps: - - name: Require native checks to pass - env: - FILTER_RESULT: ${{ needs.changes.result }} - TARGETS: ${{ needs.changes.outputs.targets }} - NATIVE_RESULT: ${{ needs.native-reference.result }} - run: | - test "$FILTER_RESULT" = success - if [[ "$TARGETS" == '[]' ]]; then - test "$NATIVE_RESULT" = skipped - else - test -n "$TARGETS" - test "$NATIVE_RESULT" = success - fi