From 4cad738c68573154d662215b99741011e463e4cd Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 5 Aug 2026 09:48:01 +0800 Subject: [PATCH 1/4] ci(deps): report upstream CLI help changes --- .../scripts/__tests__/cli-help-diff.spec.ts | 62 ++++ .github/scripts/cli-help-diff.ts | 268 ++++++++++++++++++ .github/workflows/upgrade-deps.yml | 19 ++ 3 files changed, 349 insertions(+) create mode 100644 .github/scripts/__tests__/cli-help-diff.spec.ts create mode 100644 .github/scripts/cli-help-diff.ts diff --git a/.github/scripts/__tests__/cli-help-diff.spec.ts b/.github/scripts/__tests__/cli-help-diff.spec.ts new file mode 100644 index 0000000000..ddc26a239b --- /dev/null +++ b/.github/scripts/__tests__/cli-help-diff.spec.ts @@ -0,0 +1,62 @@ +/// + +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; + +import { afterEach, expect, test } from 'vitest'; + +const SCRIPT_PATH = resolve(import.meta.dirname, '../cli-help-diff.ts'); +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { force: true, recursive: true }); + } +}); + +test('reports changed, unchanged, and not-updated CLI help in one comment', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'vite-plus-cli-help-test-')); + tempDirs.push(tempDir); + const beforePath = join(tempDir, 'before.json'); + const afterPath = join(tempDir, 'after.json'); + const reportPath = join(tempDir, 'report.md'); + const before = { + tools: { + vite: { help: 'vite/1.0.0\n--old-option', version: '1.0.0' }, + vitest: { help: 'vitest/1.0.0\n--watch', version: '1.0.0' }, + oxlint: { help: 'oxlint 1.0.0\n--fix', version: '1.0.0' }, + oxfmt: { help: 'oxfmt\n--write', version: '1.0.0' }, + tsdown: { help: 'tsdown 1.0.0\n--old-option', version: '1.0.0' }, + }, + }; + const after = { + tools: { + vite: { help: 'vite/2.0.0\n--new-option', version: '2.0.0' }, + vitest: { help: 'vitest/1.0.0\n--watch', version: '1.0.0' }, + oxlint: { help: 'oxlint 2.0.0\n--fix', version: '2.0.0' }, + oxfmt: { help: 'oxfmt\n--write', version: '1.0.0' }, + tsdown: { help: 'tsdown 2.0.0\n--new-option', version: '2.0.0' }, + }, + }; + writeFileSync(beforePath, JSON.stringify(before)); + writeFileSync(afterPath, JSON.stringify(after)); + + execFileSync( + process.execPath, + [SCRIPT_PATH, 'report', '--before', beforePath, '--after', afterPath, '--output', reportPath], + { cwd: resolve(import.meta.dirname, '../../..') }, + ); + + const report = readFileSync(reportPath, 'utf8'); + expect(report).toContain('## ⚠️ Upstream CLI help changes detected'); + expect(report).toContain('⚠️ Vite: CLI help changed (1.0.0 → 2.0.0)'); + expect(report).toContain('✅ Oxlint: no CLI help changes (1.0.0 → 2.0.0)'); + expect(report).toContain('➖ Vitest: no version update (1.0.0)'); + expect(report).toContain('```diff\n--- vite@1.0.0\n+++ vite@2.0.0'); + expect(report).toContain('---old-option'); + expect(report).toContain('+--new-option'); + expect(report).not.toContain('-vite/1.0.0'); + expect(report).not.toContain('+vite/2.0.0'); +}); diff --git a/.github/scripts/cli-help-diff.ts b/.github/scripts/cli-help-diff.ts new file mode 100644 index 0000000000..2d8869ca8e --- /dev/null +++ b/.github/scripts/cli-help-diff.ts @@ -0,0 +1,268 @@ +/// + +import { spawnSync } from 'node:child_process'; +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { parseArgs, stripVTControlCharacters } from 'node:util'; + +type ToolName = 'vite' | 'vitest' | 'oxlint' | 'oxfmt' | 'tsdown'; + +type Tool = { + commands: string[][]; + name: ToolName; + packageName: string; + title: string; +}; + +type ToolSnapshot = { + help: string; + version: string; +}; + +type Snapshot = { + tools: Record; +}; + +const ROOT = process.cwd(); +const WORKSPACE_PATH = join(ROOT, 'pnpm-workspace.yaml'); +// Leave room for five reports plus Markdown within GitHub's 65,536-character comment limit. +const MAX_DIFF_LENGTH = 12_000; +const TOOLS: Tool[] = [ + { + // Vite+ mirrors the root command plus the build and preview option sets. + commands: [['--help'], ['build', '--help'], ['preview', '--help']], + name: 'vite', + packageName: 'vite', + title: 'Vite', + }, + { + commands: [['--help']], + name: 'vitest', + packageName: 'vitest', + title: 'Vitest', + }, + { + commands: [['--help']], + name: 'oxlint', + packageName: 'oxlint', + title: 'Oxlint', + }, + { + commands: [['--help']], + name: 'oxfmt', + packageName: 'oxfmt', + title: 'Oxfmt', + }, + { + commands: [['--help']], + name: 'tsdown', + packageName: 'tsdown', + title: 'tsdown', + }, +]; + +function readJson(filePath: string): unknown { + return JSON.parse(readFileSync(filePath, 'utf8')); +} + +function readToolVersion(tool: Tool): string { + if (tool.name === 'vite') { + const pkg = readJson(join(ROOT, 'vite/packages/vite/package.json')) as { version?: unknown }; + if (typeof pkg.version !== 'string') { + throw new TypeError('vite/packages/vite/package.json has no version'); + } + return pkg.version; + } + + const workspace = readFileSync(WORKSPACE_PATH, 'utf8'); + const match = new RegExp(`^ ${tool.name}: [=~^]?([^\\s#]+)`, 'm').exec(workspace); + if (!match) { + throw new Error(`Could not find ${tool.name} in the pnpm workspace catalog`); + } + return match[1]; +} + +function normalizeOutput(output: string, version: string): string { + // Version banners change on every release but do not represent CLI option drift. + return stripVTControlCharacters(output) + .replaceAll('\r\n', '\n') + .replaceAll(version, '') + .split('\n') + .map((line) => line.trimEnd()) + .join('\n') + .trimEnd(); +} + +function captureToolHelp(tool: Tool, version: string): string { + return tool.commands + .map((command) => { + const result = spawnSync( + 'pnpm', + ['--silent', 'dlx', `${tool.packageName}@${version}`, ...command], + { + encoding: 'utf8', + env: { + ...process.env, + CI: '1', + COLUMNS: '120', + FORCE_COLOR: '0', + NO_COLOR: '1', + TERM: 'dumb', + }, + }, + ); + if (result.status !== 0) { + throw new Error( + `Failed to capture ${tool.title} help (${command.join(' ')}):\n${result.stderr}`, + ); + } + return [`$ ${tool.packageName} ${command.join(' ')}`, result.stdout.trimEnd()].join('\n'); + }) + .join('\n\n'); +} + +function captureSnapshot(outputPath: string): void { + const tools = {} as Record; + for (const tool of TOOLS) { + const version = readToolVersion(tool); + console.log(`Capturing ${tool.title} ${version} help...`); + tools[tool.name] = { + help: captureToolHelp(tool, version), + version, + }; + } + + mkdirSync(dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, `${JSON.stringify({ tools } satisfies Snapshot, null, 2)}\n`); +} + +function createUnifiedDiff(tool: Tool, before: ToolSnapshot, after: ToolSnapshot): string { + const tempDir = mkdtempSync(join(tmpdir(), 'vite-plus-cli-help-')); + const beforePath = join(tempDir, 'before.txt'); + const afterPath = join(tempDir, 'after.txt'); + + try { + writeFileSync(beforePath, `${normalizeOutput(before.help, before.version)}\n`); + writeFileSync(afterPath, `${normalizeOutput(after.help, after.version)}\n`); + const result = spawnSync( + 'git', + [ + 'diff', + '--no-index', + '--no-color', + '--no-ext-diff', + '--unified=3', + '--', + beforePath, + afterPath, + ], + { encoding: 'utf8' }, + ); + // `git diff --no-index` exits with 1 when it successfully finds differences. + if (result.status !== 1) { + throw new Error(`Failed to diff ${tool.title} help:\n${result.stderr}`); + } + const firstHunk = result.stdout.indexOf('@@'); + if (firstHunk === -1) { + throw new Error(`Git produced no diff hunk for ${tool.title}`); + } + return [ + `--- ${tool.packageName}@${before.version}`, + `+++ ${tool.packageName}@${after.version}`, + result.stdout.slice(firstHunk).trimEnd(), + ].join('\n'); + } finally { + rmSync(tempDir, { force: true, recursive: true }); + } +} + +function truncateDiff(diff: string): string { + if (diff.length <= MAX_DIFF_LENGTH) { + return diff; + } + return `${diff.slice(0, MAX_DIFF_LENGTH)}\n... diff truncated to fit in one GitHub comment ...`; +} + +function renderReport(before: Snapshot, after: Snapshot): string { + const changedTools = TOOLS.filter((tool) => { + const previous = before.tools[tool.name]; + const current = after.tools[tool.name]; + return ( + previous.version !== current.version && + normalizeOutput(previous.help, previous.version) !== + normalizeOutput(current.help, current.version) + ); + }); + const lines = [ + changedTools.length > 0 + ? '## ⚠️ Upstream CLI help changes detected' + : '## ✅ No upstream CLI help changes detected', + '', + 'Compared normalized `--help` output for the upstream CLIs mirrored by Vite+.', + '', + ]; + + for (const tool of TOOLS) { + const previous = before.tools[tool.name]; + const current = after.tools[tool.name]; + lines.push('
'); + + if (previous.version === current.version) { + lines.push( + `➖ ${tool.title}: no version update (${current.version})`, + '', + 'No version update was detected, so there is no CLI help diff.', + ); + } else if ( + normalizeOutput(previous.help, previous.version) === + normalizeOutput(current.help, current.version) + ) { + lines.push( + `✅ ${tool.title}: no CLI help changes (${previous.version} → ${current.version})`, + '', + 'The version was updated, but the normalized CLI help output has no differences.', + ); + } else { + lines.push( + `⚠️ ${tool.title}: CLI help changed (${previous.version} → ${current.version})`, + '', + '```diff', + truncateDiff(createUnifiedDiff(tool, previous, current)), + '```', + ); + } + + lines.push('', '
', ''); + } + + return `${lines.join('\n').trimEnd()}\n`; +} + +function generateReport(beforePath: string, afterPath: string, outputPath: string): void { + const before = readJson(beforePath) as Snapshot; + const after = readJson(afterPath) as Snapshot; + mkdirSync(dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, renderReport(before, after)); + console.log(`Wrote CLI help report to ${outputPath}`); +} + +const { positionals, values } = parseArgs({ + allowPositionals: true, + options: { + after: { type: 'string' }, + before: { type: 'string' }, + output: { short: 'o', type: 'string' }, + }, +}); +const [command] = positionals; + +if (command === 'capture' && values.output) { + captureSnapshot(values.output); +} else if (command === 'report' && values.before && values.after && values.output) { + generateReport(values.before, values.after, values.output); +} else { + throw new Error( + 'Usage: cli-help-diff.ts capture --output | report --before --after --output ', + ); +} diff --git a/.github/workflows/upgrade-deps.yml b/.github/workflows/upgrade-deps.yml index 12d7c47dfe..95be846c6e 100644 --- a/.github/workflows/upgrade-deps.yml +++ b/.github/workflows/upgrade-deps.yml @@ -29,6 +29,9 @@ jobs: - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 + - name: Capture current upstream CLI help + run: node .github/scripts/cli-help-diff.ts capture --output "${UPGRADE_DEPS_META_DIR}/cli-help-before.json" + - name: Rustup Adds Target run: rustup target add x86_64-unknown-linux-gnu @@ -198,6 +201,14 @@ jobs: continue-on-error: true run: pnpm fmt + - name: Generate upstream CLI help diff report + run: | + node .github/scripts/cli-help-diff.ts capture --output "${UPGRADE_DEPS_META_DIR}/cli-help-after.json" + node .github/scripts/cli-help-diff.ts report \ + --before "${UPGRADE_DEPS_META_DIR}/cli-help-before.json" \ + --after "${UPGRADE_DEPS_META_DIR}/cli-help-after.json" \ + --output "${UPGRADE_DEPS_META_DIR}/cli-help-report.md" + - name: Enhance PR description with Claude id: enhance-pr-description continue-on-error: true @@ -319,6 +330,7 @@ jobs: fi - name: Create/Update PR + id: create-pr uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: base: main @@ -328,3 +340,10 @@ jobs: token: ${{ steps.app-token.outputs.token }} body: ${{ steps.pr-content.outputs.body }} commit-message: ${{ steps.pr-content.outputs.commit-message }} + + - name: Comment upstream CLI help diff + if: steps.create-pr.outputs.pull-request-number != '' + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }} + run: gh pr comment "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --body-file "${UPGRADE_DEPS_META_DIR}/cli-help-report.md" From 749b9ad52495adc73b37176f2e7bdc40827b85b6 Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 5 Aug 2026 10:12:26 +0800 Subject: [PATCH 2/4] ci(deps): sync upstream CLI help with Claude --- .../skills/sync-upstream-cli-help/SKILL.md | 64 ++++++++++++++++++ .../scripts/__tests__/cli-help-diff.spec.ts | 51 ++++++++++++++- .github/scripts/cli-help-diff.ts | 65 +++++++++++++++---- .github/workflows/upgrade-deps.yml | 36 ++++++---- 4 files changed, 190 insertions(+), 26 deletions(-) create mode 100644 .claude/skills/sync-upstream-cli-help/SKILL.md diff --git a/.claude/skills/sync-upstream-cli-help/SKILL.md b/.claude/skills/sync-upstream-cli-help/SKILL.md new file mode 100644 index 0000000000..6ed506461b --- /dev/null +++ b/.claude/skills/sync-upstream-cli-help/SKILL.md @@ -0,0 +1,64 @@ +--- +name: sync-upstream-cli-help +description: Sync Vite+'s mirrored help documents with semantic CLI changes from Vite, Vitest, Oxlint, Oxfmt, and tsdown. Use after an upstream dependency upgrade produces a CLI help diff or when packages/cli/src/help.ts has drifted from the tool options Vite+ exposes. +allowed-tools: Read, Grep, Glob, Edit, Bash +--- + +# Sync upstream CLI help + +Update the static help documents in `packages/cli/src/help.ts` from the report at +`$CLI_HELP_DIFF_REPORT`. Keep Vite+ terminology and intentional omissions; do not +blindly copy upstream output. + +## Command mapping + +| Upstream help | `commandHelpDocs` entries | +| --------------------- | ------------------------- | +| `vite --help` | `dev` | +| `vite build --help` | `build` | +| `vite preview --help` | `preview` | +| `vitest --help` | `test` | +| `oxlint --help` | `lint` | +| `oxfmt --help` | `fmt` | +| `tsdown --help` | `pack` | + +## Workflow + +1. Confirm `$CLI_HELP_DIFF_CHANGED` is `true`, then read `$CLI_HELP_DIFF_REPORT`. +2. Inspect only the `` sections marked `CLI help changed`. If a diff was + truncated, rerun that exact target version with `pnpm dlx @ --help`; + include Vite's `build --help` and `preview --help` where applicable. +3. Compare semantic changes with the mapped entry in `packages/cli/src/help.ts`. +4. Update commands, arguments, options, section names, and descriptions that Vite+ + actually exposes. Preserve the existing `vp` usage strings, examples, + documentation URLs, capitalization, and concise description style. +5. For removed upstream options, remove the mirrored row only after confirming Vite+ + does not deliberately retain or implement it. +6. Re-record and inspect the focused help snapshots: + + ```bash + UPDATE_SNAPSHOTS=1 just snapshot-test command_tool_help + git diff -- crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/command_tool_help + ``` + +## Intentional differences + +- Ignore formatting-only changes: whitespace, wrapping, alignment, color, ordering, + and version banners. Wording changes matter only when they change meaning, accepted + values, or defaults. +- Do not expose upstream config-file selectors or loaders. Vite+ reads tool settings + from `vite.config.ts`; known omissions include `--config`, `--configLoader`, and + `--disable-nested-config`. +- Do not expose standalone tool-management modes that bypass the Vite+ command flow, + such as `--init`, `--migrate`, or `--lsp`. +- Do not add upstream `--version` flags; version reporting belongs to the top-level + `vp` command. +- Keep Vite+-specific behavior, including `vp test` running once by default and + `vp pack --env-prefix` defaulting to `VITE_PACK_,TSDOWN_`. +- Do not change runtime argument forwarding in this skill. Use + `.claude/skills/sync-tsdown-cli/SKILL.md` separately when tsdown's executable option + handling also needs an update. + +If a changed upstream flag falls into an intentional category or is not forwarded by +Vite+, leave the document unchanged. Do not manufacture a code change merely because +the report contains a diff. diff --git a/.github/scripts/__tests__/cli-help-diff.spec.ts b/.github/scripts/__tests__/cli-help-diff.spec.ts index ddc26a239b..befc5ea1fc 100644 --- a/.github/scripts/__tests__/cli-help-diff.spec.ts +++ b/.github/scripts/__tests__/cli-help-diff.spec.ts @@ -21,6 +21,7 @@ test('reports changed, unchanged, and not-updated CLI help in one comment', () = tempDirs.push(tempDir); const beforePath = join(tempDir, 'before.json'); const afterPath = join(tempDir, 'after.json'); + const githubOutputPath = join(tempDir, 'github-output.txt'); const reportPath = join(tempDir, 'report.md'); const before = { tools: { @@ -45,7 +46,18 @@ test('reports changed, unchanged, and not-updated CLI help in one comment', () = execFileSync( process.execPath, - [SCRIPT_PATH, 'report', '--before', beforePath, '--after', afterPath, '--output', reportPath], + [ + SCRIPT_PATH, + 'report', + '--before', + beforePath, + '--after', + afterPath, + '--output', + reportPath, + '--github-output', + githubOutputPath, + ], { cwd: resolve(import.meta.dirname, '../../..') }, ); @@ -59,4 +71,41 @@ test('reports changed, unchanged, and not-updated CLI help in one comment', () = expect(report).toContain('+--new-option'); expect(report).not.toContain('-vite/1.0.0'); expect(report).not.toContain('+vite/2.0.0'); + expect(readFileSync(githubOutputPath, 'utf8')).toBe('has-changes=true\n'); +}); + +test('reports no machine-readable changes when help is unchanged', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'vite-plus-cli-help-test-')); + tempDirs.push(tempDir); + const snapshotPath = join(tempDir, 'snapshot.json'); + const githubOutputPath = join(tempDir, 'github-output.txt'); + const reportPath = join(tempDir, 'report.md'); + const snapshot = { + tools: Object.fromEntries( + ['vite', 'vitest', 'oxlint', 'oxfmt', 'tsdown'].map((tool) => [ + tool, + { help: `${tool}/1.0.0\n--help`, version: '1.0.0' }, + ]), + ), + }; + writeFileSync(snapshotPath, JSON.stringify(snapshot)); + + execFileSync( + process.execPath, + [ + SCRIPT_PATH, + 'report', + '--before', + snapshotPath, + '--after', + snapshotPath, + '--output', + reportPath, + '--github-output', + githubOutputPath, + ], + { cwd: resolve(import.meta.dirname, '../../..') }, + ); + + expect(readFileSync(githubOutputPath, 'utf8')).toBe('has-changes=false\n'); }); diff --git a/.github/scripts/cli-help-diff.ts b/.github/scripts/cli-help-diff.ts index 2d8869ca8e..54fbc35a20 100644 --- a/.github/scripts/cli-help-diff.ts +++ b/.github/scripts/cli-help-diff.ts @@ -1,7 +1,14 @@ /// import { spawnSync } from 'node:child_process'; -import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { + appendFileSync, + mkdtempSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { parseArgs, stripVTControlCharacters } from 'node:util'; @@ -24,6 +31,16 @@ type Snapshot = { tools: Record; }; +type VersionMetadata = Partial< + Record< + ToolName, + { + new: string; + tag?: string; + } + > +>; + const ROOT = process.cwd(); const WORKSPACE_PATH = join(ROOT, 'pnpm-workspace.yaml'); // Leave room for five reports plus Markdown within GitHub's 65,536-character comment limit. @@ -66,7 +83,16 @@ function readJson(filePath: string): unknown { return JSON.parse(readFileSync(filePath, 'utf8')); } -function readToolVersion(tool: Tool): string { +function readToolVersion(tool: Tool, versions?: VersionMetadata): string { + if (versions) { + const change = versions[tool.name]; + const version = tool.name === 'vite' ? change?.tag?.replace(/^v/, '') : change?.new; + if (!version) { + throw new Error(`Upgrade metadata has no target version for ${tool.name}`); + } + return version; + } + if (tool.name === 'vite') { const pkg = readJson(join(ROOT, 'vite/packages/vite/package.json')) as { version?: unknown }; if (typeof pkg.version !== 'string') { @@ -122,10 +148,11 @@ function captureToolHelp(tool: Tool, version: string): string { .join('\n\n'); } -function captureSnapshot(outputPath: string): void { +function captureSnapshot(outputPath: string, versionsPath?: string): void { + const versions = versionsPath ? (readJson(versionsPath) as VersionMetadata) : undefined; const tools = {} as Record; for (const tool of TOOLS) { - const version = readToolVersion(tool); + const version = readToolVersion(tool, versions); console.log(`Capturing ${tool.title} ${version} help...`); tools[tool.name] = { help: captureToolHelp(tool, version), @@ -184,8 +211,8 @@ function truncateDiff(diff: string): string { return `${diff.slice(0, MAX_DIFF_LENGTH)}\n... diff truncated to fit in one GitHub comment ...`; } -function renderReport(before: Snapshot, after: Snapshot): string { - const changedTools = TOOLS.filter((tool) => { +function hasHelpChanges(before: Snapshot, after: Snapshot): boolean { + return TOOLS.some((tool) => { const previous = before.tools[tool.name]; const current = after.tools[tool.name]; return ( @@ -194,8 +221,11 @@ function renderReport(before: Snapshot, after: Snapshot): string { normalizeOutput(current.help, current.version) ); }); +} + +function renderReport(before: Snapshot, after: Snapshot, hasChanges: boolean): string { const lines = [ - changedTools.length > 0 + hasChanges ? '## ⚠️ Upstream CLI help changes detected' : '## ✅ No upstream CLI help changes detected', '', @@ -239,11 +269,20 @@ function renderReport(before: Snapshot, after: Snapshot): string { return `${lines.join('\n').trimEnd()}\n`; } -function generateReport(beforePath: string, afterPath: string, outputPath: string): void { +function generateReport( + beforePath: string, + afterPath: string, + outputPath: string, + githubOutputPath?: string, +): void { const before = readJson(beforePath) as Snapshot; const after = readJson(afterPath) as Snapshot; + const hasChanges = hasHelpChanges(before, after); mkdirSync(dirname(outputPath), { recursive: true }); - writeFileSync(outputPath, renderReport(before, after)); + writeFileSync(outputPath, renderReport(before, after, hasChanges)); + if (githubOutputPath) { + appendFileSync(githubOutputPath, `has-changes=${hasChanges}\n`); + } console.log(`Wrote CLI help report to ${outputPath}`); } @@ -252,17 +291,19 @@ const { positionals, values } = parseArgs({ options: { after: { type: 'string' }, before: { type: 'string' }, + 'github-output': { type: 'string' }, output: { short: 'o', type: 'string' }, + versions: { type: 'string' }, }, }); const [command] = positionals; if (command === 'capture' && values.output) { - captureSnapshot(values.output); + captureSnapshot(values.output, values.versions); } else if (command === 'report' && values.before && values.after && values.output) { - generateReport(values.before, values.after, values.output); + generateReport(values.before, values.after, values.output, values['github-output']); } else { throw new Error( - 'Usage: cli-help-diff.ts capture --output | report --before --after --output ', + 'Usage: cli-help-diff.ts capture --output [--versions ] | report --before --after --output [--github-output ]', ); } diff --git a/.github/workflows/upgrade-deps.yml b/.github/workflows/upgrade-deps.yml index 95be846c6e..52c2c43c34 100644 --- a/.github/workflows/upgrade-deps.yml +++ b/.github/workflows/upgrade-deps.yml @@ -63,11 +63,25 @@ jobs: env: RELEASE_BUILD: 'true' + - name: Generate upstream CLI help diff report + id: cli-help-diff + run: | + node .github/scripts/cli-help-diff.ts capture \ + --versions "${UPGRADE_DEPS_META_DIR}/versions.json" \ + --output "${UPGRADE_DEPS_META_DIR}/cli-help-after.json" + node .github/scripts/cli-help-diff.ts report \ + --before "${UPGRADE_DEPS_META_DIR}/cli-help-before.json" \ + --after "${UPGRADE_DEPS_META_DIR}/cli-help-after.json" \ + --output "${UPGRADE_DEPS_META_DIR}/cli-help-report.md" \ + --github-output "${GITHUB_OUTPUT}" + - name: Check upgrade dependencies id: check-upgrade-dependencies timeout-minutes: 180 uses: anthropics/claude-code-action@558b1d6cab4085c7753fe402c10bef0fbb92ac7a # v1.0.165 env: + CLI_HELP_DIFF_CHANGED: ${{ steps.cli-help-diff.outputs.has-changes }} + CLI_HELP_DIFF_REPORT: ${{ env.UPGRADE_DEPS_META_DIR }}/cli-help-report.md RELEASE_BUILD: 'true' with: claude_code_oauth_token: ${{ secrets.ANTHROPIC_API_KEY }} @@ -120,15 +134,19 @@ jobs: and rebuild. 3. If the rolldown hash changed, follow `.claude/agents/cargo-workspace-merger.md` to resync the workspace. - 4. Compare tsdown CLI options with `vp pack` and sync new/removed options per + 4. If `CLI_HELP_DIFF_CHANGED` is `true`, read the upstream help diff at + `$CLI_HELP_DIFF_REPORT`, then follow + `.claude/skills/sync-upstream-cli-help/SKILL.md` to update the mirrored + help documents. If it is `false`, do not modify help documents. + 5. Compare tsdown CLI options with `vp pack` and sync new/removed options per `.claude/skills/sync-tsdown-cli/SKILL.md`. - 5. Install the global CLI: + 6. Install the global CLI: - `pnpm bootstrap-cli:ci` - `echo "$HOME/.vite-plus/bin" >> $GITHUB_PATH` - 6. If any Rust code or `Cargo.toml` was modified, run `cargo check + 7. If any Rust code or `Cargo.toml` was modified, run `cargo check --all-targets --all-features` and `cargo shear`; fix anything they report. - 7. Run `pnpm run lint` (requires a prior `just build`); fix any errors. - 8. Smoke-test the CLI: `vp -h`, `vp run -h`, `vp lint -h`, `vp test -h`, + 8. Run `pnpm run lint` (requires a prior `just build`); fix any errors. + 9. Smoke-test the CLI: `vp -h`, `vp run -h`, `vp lint -h`, `vp test -h`, `vp build -h`, `vp fmt -h`, `vp pack -h`. ### Generated artifacts and build diffs @@ -201,14 +219,6 @@ jobs: continue-on-error: true run: pnpm fmt - - name: Generate upstream CLI help diff report - run: | - node .github/scripts/cli-help-diff.ts capture --output "${UPGRADE_DEPS_META_DIR}/cli-help-after.json" - node .github/scripts/cli-help-diff.ts report \ - --before "${UPGRADE_DEPS_META_DIR}/cli-help-before.json" \ - --after "${UPGRADE_DEPS_META_DIR}/cli-help-after.json" \ - --output "${UPGRADE_DEPS_META_DIR}/cli-help-report.md" - - name: Enhance PR description with Claude id: enhance-pr-description continue-on-error: true From c910ef7215c9baf9dc24293b56359ae046200bc8 Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 5 Aug 2026 21:16:04 +0800 Subject: [PATCH 3/4] ci(deps): mirror upstream CLI help wording --- .claude/skills/sync-tsdown-cli/SKILL.md | 9 +++-- .../skills/sync-upstream-cli-help/SKILL.md | 34 ++++++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.claude/skills/sync-tsdown-cli/SKILL.md b/.claude/skills/sync-tsdown-cli/SKILL.md index c3744acc82..4589095593 100644 --- a/.claude/skills/sync-tsdown-cli/SKILL.md +++ b/.claude/skills/sync-tsdown-cli/SKILL.md @@ -17,5 +17,10 @@ Compare the upstream `tsdown` CLI options with `vp pack` (defined in `packages/c 5. Preserve intentional differences: - `-c, --config` is intentionally commented out (vp pack uses vite.config.ts) - `--env-prefix` has a different default (`['VITE_PACK_', 'TSDOWN_']`) -6. Verify with `pnpm --filter vite-plus build-ts` and `vp pack -h` -7. If new parameters were added, add a corresponding PTY snapshot case under `crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/` to verify the new option works correctly +6. Keep runtime differences separate from the static help document. If an affected + item is retained in `packages/cli/src/help.ts`, follow + `.claude/skills/sync-upstream-cli-help/SKILL.md` and copy tsdown's label and + description verbatim, even when Vite+ intentionally uses a different runtime + default. Do not paraphrase the mirrored help to explain the wrapper behavior. +7. Verify with `pnpm --filter vite-plus build-ts` and `vp pack -h` +8. If new parameters were added, add a corresponding PTY snapshot case under `crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/` to verify the new option works correctly diff --git a/.claude/skills/sync-upstream-cli-help/SKILL.md b/.claude/skills/sync-upstream-cli-help/SKILL.md index 6ed506461b..f92ad939b9 100644 --- a/.claude/skills/sync-upstream-cli-help/SKILL.md +++ b/.claude/skills/sync-upstream-cli-help/SKILL.md @@ -1,14 +1,14 @@ --- name: sync-upstream-cli-help -description: Sync Vite+'s mirrored help documents with semantic CLI changes from Vite, Vitest, Oxlint, Oxfmt, and tsdown. Use after an upstream dependency upgrade produces a CLI help diff or when packages/cli/src/help.ts has drifted from the tool options Vite+ exposes. +description: Sync Vite+'s mirrored help labels and descriptions verbatim with Vite, Vitest, Oxlint, Oxfmt, and tsdown while preserving intentional omissions. Use after an upstream dependency upgrade produces a CLI help diff or when packages/cli/src/help.ts has drifted from the tool options Vite+ exposes. allowed-tools: Read, Grep, Glob, Edit, Bash --- # Sync upstream CLI help Update the static help documents in `packages/cli/src/help.ts` from the report at -`$CLI_HELP_DIFF_REPORT`. Keep Vite+ terminology and intentional omissions; do not -blindly copy upstream output. +`$CLI_HELP_DIFF_REPORT`. Keep Vite+ command framing and intentional omissions, but +copy the labels and descriptions of every retained upstream help item verbatim. ## Command mapping @@ -28,13 +28,19 @@ blindly copy upstream output. 2. Inspect only the `` sections marked `CLI help changed`. If a diff was truncated, rerun that exact target version with `pnpm dlx @ --help`; include Vite's `build --help` and `preview --help` where applicable. -3. Compare semantic changes with the mapped entry in `packages/cli/src/help.ts`. +3. Compare each changed upstream help item with the mapped entry in + `packages/cli/src/help.ts`. 4. Update commands, arguments, options, section names, and descriptions that Vite+ - actually exposes. Preserve the existing `vp` usage strings, examples, - documentation URLs, capitalization, and concise description style. -5. For removed upstream options, remove the mirrored row only after confirming Vite+ + actually exposes. For each retained item, copy the upstream label and description + verbatim, including placeholder syntax and casing, type annotations, accepted + values, defaults, capitalization, punctuation, and multiline text. Do not + paraphrase, shorten, or normalize upstream wording. +5. Preserve Vite+-owned command summaries, `vp` usage strings, examples, and + documentation URLs. Do not add an intentionally omitted upstream item merely to + make the option sets identical. +6. For removed upstream options, remove the mirrored row only after confirming Vite+ does not deliberately retain or implement it. -6. Re-record and inspect the focused help snapshots: +7. Re-record and inspect the focused help snapshots: ```bash UPDATE_SNAPSHOTS=1 just snapshot-test command_tool_help @@ -43,9 +49,10 @@ blindly copy upstream output. ## Intentional differences -- Ignore formatting-only changes: whitespace, wrapping, alignment, color, ordering, - and version banners. Wording changes matter only when they change meaning, accepted - values, or defaults. +- Ignore only presentation changes such as terminal alignment, wrapping, ANSI color, + ordering, and version banners. Treat any wording, capitalization, punctuation, + placeholder syntax, type annotation, accepted-value, or displayed-default change + as actionable for an item Vite+ already shows. - Do not expose upstream config-file selectors or loaders. Vite+ reads tool settings from `vite.config.ts`; known omissions include `--config`, `--configLoader`, and `--disable-nested-config`. @@ -53,8 +60,9 @@ blindly copy upstream output. such as `--init`, `--migrate`, or `--lsp`. - Do not add upstream `--version` flags; version reporting belongs to the top-level `vp` command. -- Keep Vite+-specific behavior, including `vp test` running once by default and - `vp pack --env-prefix` defaulting to `VITE_PACK_,TSDOWN_`. +- Keep Vite+-owned command framing, such as `vp test` running once by default, outside + the mirrored item descriptions. A Vite+-specific runtime behavior or default does + not justify rewriting an upstream label or description in the mirrored help table. - Do not change runtime argument forwarding in this skill. Use `.claude/skills/sync-tsdown-cli/SKILL.md` separately when tsdown's executable option handling also needs an update. From c0ad11286ea25de59d7a076dce85f0d6f4605d00 Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 5 Aug 2026 21:44:18 +0800 Subject: [PATCH 4/4] ci(deps): clarify upstream help sync skill --- .claude/skills/sync-tsdown-cli/SKILL.md | 32 +++--- .../skills/sync-upstream-cli-help/SKILL.md | 103 +++++++----------- 2 files changed, 53 insertions(+), 82 deletions(-) diff --git a/.claude/skills/sync-tsdown-cli/SKILL.md b/.claude/skills/sync-tsdown-cli/SKILL.md index 4589095593..faa827866f 100644 --- a/.claude/skills/sync-tsdown-cli/SKILL.md +++ b/.claude/skills/sync-tsdown-cli/SKILL.md @@ -1,26 +1,20 @@ --- name: sync-tsdown-cli -description: Compare tsdown CLI options with vp pack and sync any new or removed options. Use when tsdown is upgraded or when you need to check for CLI option drift between tsdown and vp pack. +description: Sync tsdown runtime CLI options with vp pack after a tsdown upgrade. Use for option forwarding changes; use sync-upstream-cli-help for static help wording. allowed-tools: Read, Grep, Glob, Edit, Bash --- -# Sync tsdown CLI Options with vp pack +# Sync tsdown CLI -Compare the upstream `tsdown` CLI options with `vp pack` (defined in `packages/cli/src/pack-bin.ts`) and sync any differences. +Runtime options live in `packages/cli/src/pack-bin.ts`; static help lives in +`packages/cli/src/help.ts`. -## Steps - -1. Run `npx tsdown --help` from `packages/cli/` to get tsdown's current CLI options -2. Read `packages/cli/src/pack-bin.ts` to see vp pack's current options -3. Compare and add any new tsdown options to `pack-bin.ts` using the existing cac `.option()` pattern -4. If tsdown removed options, do NOT remove them from `pack-bin.ts` -- instead add a code comment like `// NOTE: removed from tsdown CLI in vX.Y.Z` above the option so reviewers can decide whether to follow up -5. Preserve intentional differences: - - `-c, --config` is intentionally commented out (vp pack uses vite.config.ts) - - `--env-prefix` has a different default (`['VITE_PACK_', 'TSDOWN_']`) -6. Keep runtime differences separate from the static help document. If an affected - item is retained in `packages/cli/src/help.ts`, follow - `.claude/skills/sync-upstream-cli-help/SKILL.md` and copy tsdown's label and - description verbatim, even when Vite+ intentionally uses a different runtime - default. Do not paraphrase the mirrored help to explain the wrapper behavior. -7. Verify with `pnpm --filter vite-plus build-ts` and `vp pack -h` -8. If new parameters were added, add a corresponding PTY snapshot case under `crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/` to verify the new option works correctly +1. Run `npx tsdown --help` from `packages/cli/` and compare it with `pack-bin.ts`. +2. Add new forwarded options using the existing cac `.option()` pattern. For removed + options, add `// NOTE: removed from tsdown CLI in vX.Y.Z` for reviewer follow-up. +3. Preserve runtime differences: `-c, --config` stays disabled because Vite+ uses + `vite.config.ts`, and `--env-prefix` keeps the `['VITE_PACK_', 'TSDOWN_']` default. +4. For static labels and descriptions, follow `sync-upstream-cli-help`; do not adapt + upstream wording to explain runtime differences. +5. Run `pnpm --filter vite-plus build-ts` and `vp pack -h`. Add a focused PTY snapshot + case when a new runtime option is exposed. diff --git a/.claude/skills/sync-upstream-cli-help/SKILL.md b/.claude/skills/sync-upstream-cli-help/SKILL.md index f92ad939b9..b64abcc2d8 100644 --- a/.claude/skills/sync-upstream-cli-help/SKILL.md +++ b/.claude/skills/sync-upstream-cli-help/SKILL.md @@ -1,72 +1,49 @@ --- name: sync-upstream-cli-help -description: Sync Vite+'s mirrored help labels and descriptions verbatim with Vite, Vitest, Oxlint, Oxfmt, and tsdown while preserving intentional omissions. Use after an upstream dependency upgrade produces a CLI help diff or when packages/cli/src/help.ts has drifted from the tool options Vite+ exposes. +description: Sync Vite+'s static CLI help with Vite, Vitest, Oxlint, Oxfmt, and tsdown while preserving intentional omissions. Use when an upstream dependency upgrade changes CLI help. allowed-tools: Read, Grep, Glob, Edit, Bash --- # Sync upstream CLI help -Update the static help documents in `packages/cli/src/help.ts` from the report at -`$CLI_HELP_DIFF_REPORT`. Keep Vite+ command framing and intentional omissions, but -copy the labels and descriptions of every retained upstream help item verbatim. - -## Command mapping - -| Upstream help | `commandHelpDocs` entries | -| --------------------- | ------------------------- | -| `vite --help` | `dev` | -| `vite build --help` | `build` | -| `vite preview --help` | `preview` | -| `vitest --help` | `test` | -| `oxlint --help` | `lint` | -| `oxfmt --help` | `fmt` | -| `tsdown --help` | `pack` | - -## Workflow - -1. Confirm `$CLI_HELP_DIFF_CHANGED` is `true`, then read `$CLI_HELP_DIFF_REPORT`. -2. Inspect only the `` sections marked `CLI help changed`. If a diff was - truncated, rerun that exact target version with `pnpm dlx @ --help`; - include Vite's `build --help` and `preview --help` where applicable. -3. Compare each changed upstream help item with the mapped entry in - `packages/cli/src/help.ts`. -4. Update commands, arguments, options, section names, and descriptions that Vite+ - actually exposes. For each retained item, copy the upstream label and description - verbatim, including placeholder syntax and casing, type annotations, accepted - values, defaults, capitalization, punctuation, and multiline text. Do not - paraphrase, shorten, or normalize upstream wording. -5. Preserve Vite+-owned command summaries, `vp` usage strings, examples, and - documentation URLs. Do not add an intentionally omitted upstream item merely to - make the option sets identical. -6. For removed upstream options, remove the mirrored row only after confirming Vite+ - does not deliberately retain or implement it. -7. Re-record and inspect the focused help snapshots: - - ```bash - UPDATE_SNAPSHOTS=1 just snapshot-test command_tool_help - git diff -- crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/command_tool_help - ``` - -## Intentional differences - -- Ignore only presentation changes such as terminal alignment, wrapping, ANSI color, - ordering, and version banners. Treat any wording, capitalization, punctuation, - placeholder syntax, type annotation, accepted-value, or displayed-default change - as actionable for an item Vite+ already shows. -- Do not expose upstream config-file selectors or loaders. Vite+ reads tool settings - from `vite.config.ts`; known omissions include `--config`, `--configLoader`, and +## Input and target + +- Read the diff from `$CLI_HELP_DIFF_REPORT`; first run + `test -r "$CLI_HELP_DIFF_REPORT"`. Act only when `$CLI_HELP_DIFF_CHANGED` is + `true`. +- Treat the upgraded tool's `--help` output as the source of truth. The report locates + changes and versions; rerun the exact version when its diff is truncated. +- Edit `commandHelpDocs` in `packages/cli/src/help.ts`. +- Do not edit `packages/cli/src/utils/help.ts` for content drift. It owns terminal + wrapping, alignment, and the right margin. + +| Upstream help | Document entry | +| --------------------- | -------------- | +| `vite --help` | `dev` | +| `vite build --help` | `build` | +| `vite preview --help` | `preview` | +| `vitest --help` | `test` | +| `oxlint --help` | `lint` | +| `oxfmt --help` | `fmt` | +| `tsdown --help` | `pack` | + +## Change + +- For items Vite+ exposes, copy upstream labels, descriptions, section titles, and + section guidance exactly. Preserve intentional lines and lists, but not terminal + padding, automatic wrapping, ANSI color, or version banners. +- Remove an upstream item only after confirming Vite+ no longer supports or + deliberately retains it. + +## Do not change + +- Keep Vite+-owned usage, summaries, examples, and documentation URLs. +- Keep config selectors/loaders hidden, including `--config`, `--configLoader`, and `--disable-nested-config`. -- Do not expose standalone tool-management modes that bypass the Vite+ command flow, - such as `--init`, `--migrate`, or `--lsp`. -- Do not add upstream `--version` flags; version reporting belongs to the top-level - `vp` command. -- Keep Vite+-owned command framing, such as `vp test` running once by default, outside - the mirrored item descriptions. A Vite+-specific runtime behavior or default does - not justify rewriting an upstream label or description in the mirrored help table. -- Do not change runtime argument forwarding in this skill. Use - `.claude/skills/sync-tsdown-cli/SKILL.md` separately when tsdown's executable option - handling also needs an update. +- Do not add standalone modes such as `--init`, `--migrate`, or `--lsp`, top-level + `--version`, or options Vite+ does not forward. +- Do not change runtime forwarding or rewrite upstream wording for a Vite+-specific + runtime default. Use `sync-tsdown-cli` for tsdown runtime changes. -If a changed upstream flag falls into an intentional category or is not forwarded by -Vite+, leave the document unchanged. Do not manufacture a code change merely because -the report contains a diff. +Re-record the affected CLI help snapshots and inspect their diffs. Do not modify help +documents when the report contains no actionable exposed change.