mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-14 17:54:53 +01:00
Add a PR CI check that fails when the committed codex app-server protocol client (src/vs/platform/agentHost/node/codex/protocol/generated/**) does not match what `codex app-server generate-ts` produces for the @openai/codex dev-dependency version, but only when a PR actually changes the generated client or the version pin. - build/codex/generate-protocol.mjs: export the reusable pieces (generate, resolveCodexBinary, readBinaryVersion, readPinnedVersion, paths) and only run main() when invoked directly, so the check can import it side-effect free. Types provided by generate-protocol.d.mts. - build/codex/check-protocol-sync.ts: gate on a git diff touching the generated dir or codex-version.txt (its README.md excluded — preserved across regen), regenerate into a scratch dir with the dev-dependency binary, byte-compare against the committed client, and assert the pin matches. Never touches the working tree. - build/codex/test/checkProtocolSync.test.ts: unit tests for the gate + diff helpers; wired in via the build/package.json test glob (adds `codex`). - package.json: `codex:check-protocol` script. - .github/workflows/pr.yml: run the gated check in the Compile & Hygiene job. - generated/README.md: document the check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import assert from 'assert';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { suite, test, afterEach } from 'node:test';
|
|
import { CODEX_INPUT_PREFIXES, diffGeneratedTrees, filterCodexInputs, listFiles } from '../check-protocol-sync.ts';
|
|
|
|
const GENERATED_DIR = 'src/vs/platform/agentHost/node/codex/protocol/generated';
|
|
|
|
const scratchDirs: string[] = [];
|
|
function makeDir(tree: Readonly<Record<string, string>>): string {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-check-test-'));
|
|
scratchDirs.push(root);
|
|
for (const [rel, content] of Object.entries(tree)) {
|
|
const abs = path.join(root, rel);
|
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
fs.writeFileSync(abs, content);
|
|
}
|
|
return root;
|
|
}
|
|
|
|
afterEach(() => {
|
|
while (scratchDirs.length) {
|
|
fs.rmSync(scratchDirs.pop()!, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
suite('codex protocol freshness check helpers', () => {
|
|
test('CODEX_INPUT_PREFIXES targets the generated dir and version pin', () => {
|
|
assert.deepStrictEqual(CODEX_INPUT_PREFIXES, [
|
|
`${GENERATED_DIR}/`,
|
|
'build/codex/codex-version.txt',
|
|
]);
|
|
});
|
|
|
|
test('filterCodexInputs keeps only codex generation inputs', () => {
|
|
const changed = [
|
|
`${GENERATED_DIR}/index.ts`,
|
|
`${GENERATED_DIR}/nested/Thing.ts`,
|
|
`${GENERATED_DIR}/README.md`,
|
|
'build/codex/codex-version.txt',
|
|
'build/codex/generate-protocol.mjs',
|
|
'build/codex/check-protocol-sync.ts',
|
|
'src/vs/platform/agentHost/node/codex/codexAgent.ts',
|
|
'package.json',
|
|
'README.md',
|
|
];
|
|
assert.deepStrictEqual(filterCodexInputs(changed), [
|
|
`${GENERATED_DIR}/index.ts`,
|
|
`${GENERATED_DIR}/nested/Thing.ts`,
|
|
'build/codex/codex-version.txt',
|
|
]);
|
|
});
|
|
|
|
test('listFiles lists files recursively, sorted, excluding README.md', () => {
|
|
const dir = makeDir({
|
|
'index.ts': 'a',
|
|
'README.md': 'ignored',
|
|
'nested/Thing.ts': 'b',
|
|
'nested/deep/Other.ts': 'c',
|
|
});
|
|
assert.deepStrictEqual(listFiles(dir), ['index.ts', 'nested/Thing.ts', 'nested/deep/Other.ts']);
|
|
});
|
|
|
|
test('diffGeneratedTrees reports content, missing, and extra files (README ignored)', () => {
|
|
const committed = makeDir({
|
|
'index.ts': 'export const a = 1;',
|
|
'Same.ts': 'same',
|
|
'Changed.ts': 'old',
|
|
'OnlyCommitted.ts': 'gone',
|
|
'README.md': 'committed readme',
|
|
});
|
|
const fresh = makeDir({
|
|
'index.ts': 'export const a = 1;',
|
|
'Same.ts': 'same',
|
|
'Changed.ts': 'new',
|
|
'OnlyFresh.ts': 'added',
|
|
'README.md': 'different readme',
|
|
});
|
|
assert.deepStrictEqual(diffGeneratedTrees(committed, fresh), [
|
|
'Changed.ts (content differs from regeneration)',
|
|
'OnlyCommitted.ts (committed but not produced by regeneration)',
|
|
'OnlyFresh.ts (produced by regeneration but missing from the committed client)',
|
|
]);
|
|
});
|
|
|
|
test('diffGeneratedTrees returns no differences for identical trees', () => {
|
|
const tree = { 'index.ts': 'x', 'nested/Thing.ts': 'y', 'README.md': 'whatever' };
|
|
assert.deepStrictEqual(diffGeneratedTrees(makeDir(tree), makeDir(tree)), []);
|
|
});
|
|
});
|