From 5713d5c6ab28439f5588162b77736e5369f06e31 Mon Sep 17 00:00:00 2001 From: SerKo Date: Sat, 13 Dec 2025 21:48:36 +0800 Subject: [PATCH 1/3] ci: add vendor tracker --- .github/workflows/track-vendor.yml | 78 +++++++++++++++++++++++++ package.json | 1 + scripts/track-vendor.ts | 18 ++++++ tests/__snapshots__/vendor.spec.ts.snap | 16 +++++ tests/vendor.spec.ts | 42 +++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 .github/workflows/track-vendor.yml create mode 100644 scripts/track-vendor.ts create mode 100644 tests/__snapshots__/vendor.spec.ts.snap create mode 100644 tests/vendor.spec.ts diff --git a/.github/workflows/track-vendor.yml b/.github/workflows/track-vendor.yml new file mode 100644 index 0000000000..387597e816 --- /dev/null +++ b/.github/workflows/track-vendor.yml @@ -0,0 +1,78 @@ +name: Track vendor + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +permissions: + contents: write + pull-requests: write + +jobs: + track-vendor: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + + - run: pnpm install + + - name: Track vendor and get hash + id: track + shell: bash + run: | + node ./scripts/track-vendor.ts + + if git diff --quiet; then + echo "No unstaged changes." + echo "dirty=false" >> "$GITHUB_OUTPUT" + else + echo "Found unstaged changes." + echo "dirty=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create branch and draft PR + if: steps.track.outputs.track_hash != '' && steps.track.outputs.dirty == 'true' + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + HASH="${{ steps.track.outputs.track_hash }}" + BRANCH="track/$HASH" + + if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then + echo "Remote branch already exists: $BRANCH" + exit 0 + else + echo "Remote branch does not exist: $BRANCH" + git switch -c "$BRANCH" + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add . + + if git diff --cached --quiet; then + echo "Nothing to commit. Exit successfully." + exit 0 + fi + + git commit -m "chore(vendor): update vendor snapshot" + + git push --set-upstream origin "$BRANCH" + + gh pr create \ + --draft \ + --base "${GITHUB_REF_NAME}" \ + --head "$BRANCH" \ + --title "chore(vendor): update vendor \`$HASH\`" \ + --body "Vendor snapshot updated. Hash: \`$HASH\`" diff --git a/package.json b/package.json index 81714620b3..052d781689 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "watch": "tsgo -b -w", "test": "npm run build && vitest run", "test:update": "npm run build && vitest run --update", + "test:vendor": "TEST_VENDOR=1 vitest run vendor", "format": "dprint fmt", "lint": "tsslint --project {tsconfig.json,packages/*/tsconfig.json,extensions/*/tsconfig.json}", "lint:fix": "npm run lint -- --fix" diff --git a/scripts/track-vendor.ts b/scripts/track-vendor.ts new file mode 100644 index 0000000000..e43fd3b0db --- /dev/null +++ b/scripts/track-vendor.ts @@ -0,0 +1,18 @@ +import { appendFileSync, readFileSync } from 'node:fs'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { crc32 } from 'node:zlib'; +import { startVitest } from 'vitest/node'; + +const vitest = await startVitest('test', ['vendor'], { update: true, run: true, env: { TEST_VENDOR: '1' } }); +await vitest?.close(); + +const dir = path.dirname(fileURLToPath(import.meta.url)); +const TARGET_FILE = path.join(dir, '..', 'tests', '__snapshots__', 'vendor.spec.ts.snap'); +const hex = (crc32(readFileSync(TARGET_FILE)) >>> 0).toString(16).padStart(8, '0'); +console.log(`track hash: ${hex}`); + +const out = process.env.GITHUB_OUTPUT; +if (out) { + appendFileSync(out, `track_hash=${hex}\n`); +} diff --git a/tests/__snapshots__/vendor.spec.ts.snap b/tests/__snapshots__/vendor.spec.ts.snap new file mode 100644 index 0000000000..f9b8aaceaa --- /dev/null +++ b/tests/__snapshots__/vendor.spec.ts.snap @@ -0,0 +1,16 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ensure vendor is updated 1`] = ` +[ + { + "commit": "96acaa52902feb1320e1d8ec8936b8669cca447d", + "path": "src/services/types.ts", + "repo": "microsoft/TypeScript", + }, + { + "commit": "a134f3050c22fe80954241467cd429811792a81d", + "path": "src/services/htmlFormatter.ts", + "repo": "microsoft/vscode-html-languageservice", + }, +] +`; diff --git a/tests/vendor.spec.ts b/tests/vendor.spec.ts new file mode 100644 index 0000000000..65861921ff --- /dev/null +++ b/tests/vendor.spec.ts @@ -0,0 +1,42 @@ +import { expect, test } from 'vitest'; + +const VENDOR_LIST: { repo: string; path: string }[] = [ + { repo: 'microsoft/TypeScript', path: 'src/services/types.ts' }, + { repo: 'microsoft/vscode-html-languageservice', path: 'src/services/htmlFormatter.ts' }, +]; + +test.skipIf(process.env.TEST_VENDOR !== '1')(`ensure vendor is updated`, async () => { + const promises = VENDOR_LIST.map(async item => ({ + ...item, + commit: await retry(() => getRemoteCommit(item.repo, item.path)), + })); + const snapshot = await Promise.all(promises); + expect(snapshot).toMatchSnapshot(); +}); + +async function getRemoteCommit(repo: string, path: string): Promise { + console.log('fetching', repo, path); + const response = await fetch(`https://api.github.com/repos/${repo}/commits?path=${path}&per_page=1`); + const data = await response.json(); + const sha: string | undefined = data[0]?.sha; + if (!sha) { + throw new Error(`No commits found for ${repo}/${path}`); + } + return sha; +} + +async function retry(fn: () => Promise, retries = 3, delay = 1000): Promise { + try { + return await fn(); + } + catch (error) { + if (retries > 0) { + console.warn(`Retrying... (${retries} left)`); + await new Promise(res => setTimeout(res, delay)); + return retry(fn, retries - 1, delay); + } + else { + throw error; + } + } +} From 2cc5155bea655b57cb68c979df366820c299b9dd Mon Sep 17 00:00:00 2001 From: SerKo Date: Sat, 13 Dec 2025 23:35:52 +0800 Subject: [PATCH 2/3] feat: fetch commits with auth --- .github/workflows/track-vendor.yml | 2 ++ tests/vendor.spec.ts | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/track-vendor.yml b/.github/workflows/track-vendor.yml index 387597e816..938c239f95 100644 --- a/.github/workflows/track-vendor.yml +++ b/.github/workflows/track-vendor.yml @@ -28,6 +28,8 @@ jobs: - name: Track vendor and get hash id: track shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | node ./scripts/track-vendor.ts diff --git a/tests/vendor.spec.ts b/tests/vendor.spec.ts index 65861921ff..e9ed9e6e07 100644 --- a/tests/vendor.spec.ts +++ b/tests/vendor.spec.ts @@ -15,8 +15,14 @@ test.skipIf(process.env.TEST_VENDOR !== '1')(`ensure vendor is updated`, async ( }); async function getRemoteCommit(repo: string, path: string): Promise { - console.log('fetching', repo, path); - const response = await fetch(`https://api.github.com/repos/${repo}/commits?path=${path}&per_page=1`); + const token = process.env.GH_TOKEN; + console.log(`fetching${token ? ` with token` : ''}`, repo, path); + const headers: Record = { + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + ...token && { 'Authorization': `Bearer ${token}` }, + }; + const response = await fetch(`https://api.github.com/repos/${repo}/commits?path=${path}&per_page=1`, { headers }); const data = await response.json(); const sha: string | undefined = data[0]?.sha; if (!sha) { From da0ed63433407d29b10c98c506f03378e3d1a142 Mon Sep 17 00:00:00 2001 From: SerKo Date: Sun, 14 Dec 2025 20:16:49 +0800 Subject: [PATCH 3/3] chore: update vendor list --- tests/__snapshots__/vendor.spec.ts.snap | 25 +++++++++++++++++++++++++ tests/vendor.spec.ts | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/tests/__snapshots__/vendor.spec.ts.snap b/tests/__snapshots__/vendor.spec.ts.snap index f9b8aaceaa..61c8825c62 100644 --- a/tests/__snapshots__/vendor.spec.ts.snap +++ b/tests/__snapshots__/vendor.spec.ts.snap @@ -12,5 +12,30 @@ exports[`ensure vendor is updated 1`] = ` "path": "src/services/htmlFormatter.ts", "repo": "microsoft/vscode-html-languageservice", }, + { + "commit": "210541906e5a96ab39f9c753f921b1bd35f4138b", + "path": "extensions/css/syntaxes/css.tmLanguage.json", + "repo": "microsoft/vscode", + }, + { + "commit": "45324363153075dab0482312ae24d8c068d81e4f", + "path": "extensions/html/syntaxes/html.tmLanguage.json", + "repo": "microsoft/vscode", + }, + { + "commit": "210541906e5a96ab39f9c753f921b1bd35f4138b", + "path": "extensions/javascript/syntaxes/JavaScript.tmLanguage.json", + "repo": "microsoft/vscode", + }, + { + "commit": "cf8d61ebd2f022f4ce8280171f0360d1fe0a206d", + "path": "extensions/scss/syntaxes/scss.tmLanguage.json", + "repo": "microsoft/vscode", + }, + { + "commit": "210541906e5a96ab39f9c753f921b1bd35f4138b", + "path": "extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json", + "repo": "microsoft/vscode", + }, ] `; diff --git a/tests/vendor.spec.ts b/tests/vendor.spec.ts index e9ed9e6e07..a1f5f8b533 100644 --- a/tests/vendor.spec.ts +++ b/tests/vendor.spec.ts @@ -3,6 +3,11 @@ import { expect, test } from 'vitest'; const VENDOR_LIST: { repo: string; path: string }[] = [ { repo: 'microsoft/TypeScript', path: 'src/services/types.ts' }, { repo: 'microsoft/vscode-html-languageservice', path: 'src/services/htmlFormatter.ts' }, + { repo: 'microsoft/vscode', path: 'extensions/css/syntaxes/css.tmLanguage.json' }, + { repo: 'microsoft/vscode', path: 'extensions/html/syntaxes/html.tmLanguage.json' }, + { repo: 'microsoft/vscode', path: 'extensions/javascript/syntaxes/JavaScript.tmLanguage.json' }, + { repo: 'microsoft/vscode', path: 'extensions/scss/syntaxes/scss.tmLanguage.json' }, + { repo: 'microsoft/vscode', path: 'extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json' }, ]; test.skipIf(process.env.TEST_VENDOR !== '1')(`ensure vendor is updated`, async () => {