Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/workflows/track-vendor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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\`"
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"watch": "tsgo -b -w",
"test": "npm run build && vitest run",
"test:grammar": "vitest run extensions/vscode/tests/grammar.spec.ts",
"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"
Expand Down
18 changes: 18 additions & 0 deletions scripts/track-vendor.ts
Original file line number Diff line number Diff line change
@@ -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`);
}
41 changes: 41 additions & 0 deletions tests/__snapshots__/vendor.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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",
},
{
"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",
},
]
`;
53 changes: 53 additions & 0 deletions tests/vendor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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 () => {
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<string | undefined> {
const token = process.env.GH_TOKEN;
console.log(`fetching${token ? ` with token` : ''}`, repo, path);
const headers: Record<string, string> = {
'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) {
throw new Error(`No commits found for ${repo}/${path}`);
}
return sha;
}

async function retry<T>(fn: () => Promise<T>, retries = 3, delay = 1000): Promise<T> {
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;
}
}
}