Skip to content

Commit 0901785

Browse files
authored
chore(ci): rewrite upgrade deps script in typescript (#1514)
since node can nativly run typescript, lets write the script in typescript, and get all the typechecking benifits that come alongside that.
1 parent 8b7b6b6 commit 0901785

2 files changed

Lines changed: 109 additions & 37 deletions

File tree

Lines changed: 107 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,79 @@ import path from 'node:path';
44
const ROOT = process.cwd();
55
const META_DIR = process.env.UPGRADE_DEPS_META_DIR;
66

7-
const isFullSha = (s) => /^[0-9a-f]{40}$/.test(s);
7+
type Change = {
8+
old: string | null;
9+
new: string;
10+
tag?: string;
11+
};
12+
13+
type GitHubTag = {
14+
name?: unknown;
15+
commit?: {
16+
sha?: unknown;
17+
};
18+
};
19+
20+
type LatestTag = {
21+
sha: string;
22+
tag: string;
23+
};
824

9-
/** @type {Map<string, { old: string | null, new: string, tag?: string }>} */
10-
const changes = new Map();
25+
type NpmLatestResponse = {
26+
version?: unknown;
27+
};
1128

12-
function recordChange(name, oldValue, newValue, tag) {
13-
const entry = { old: oldValue ?? null, new: newValue };
29+
type UpstreamVersions = {
30+
rolldown: {
31+
hash: string;
32+
};
33+
vite: {
34+
hash: string;
35+
};
36+
};
37+
38+
type PnpmWorkspaceVersions = {
39+
vitest: string;
40+
tsdown: string;
41+
oxcNodeCli: string;
42+
oxcNodeCore: string;
43+
oxfmt: string;
44+
oxlint: string;
45+
oxlintTsgolint: string;
46+
oxcProjectRuntime: string;
47+
oxcProjectTypes: string;
48+
oxcMinify: string;
49+
oxcParser: string;
50+
oxcTransform: string;
51+
};
52+
53+
type PnpmWorkspaceEntry = {
54+
name: string;
55+
pattern: RegExp;
56+
replacement: string;
57+
newVersion: string;
58+
};
59+
60+
type PackageJson = {
61+
devDependencies?: Record<string, string>;
62+
peerDependencies?: Record<string, string>;
63+
};
64+
65+
const isFullSha = (s: string): boolean => /^[0-9a-f]{40}$/.test(s);
66+
67+
const changes = new Map<string, Change>();
68+
69+
function readJsonFile(filePath: string) {
70+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
71+
}
72+
73+
function recordChange(
74+
name: string,
75+
oldValue: string | null | undefined,
76+
newValue: string,
77+
tag?: string,
78+
) {
79+
const entry: Change = { old: oldValue ?? null, new: newValue };
1480
if (tag) {
1581
entry.tag = tag;
1682
}
@@ -23,7 +89,7 @@ function recordChange(name, oldValue, newValue, tag) {
2389
}
2490

2591
// ============ GitHub API ============
26-
async function getLatestTag(owner, repo) {
92+
async function getLatestTag(owner: string, repo: string): Promise<LatestTag> {
2793
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/tags?per_page=1`, {
2894
headers: {
2995
Authorization: `token ${process.env.GITHUB_TOKEN}`,
@@ -33,45 +99,46 @@ async function getLatestTag(owner, repo) {
3399
if (!res.ok) {
34100
throw new Error(`Failed to fetch tags for ${owner}/${repo}: ${res.status} ${res.statusText}`);
35101
}
36-
const tags = await res.json();
102+
const tags = (await res.json()) as GitHubTag[];
37103
if (!Array.isArray(tags) || !tags.length) {
38104
throw new Error(`No tags found for ${owner}/${repo}`);
39105
}
40-
if (!tags[0]?.commit?.sha || !tags[0]?.name) {
106+
const [latest] = tags;
107+
if (typeof latest?.commit?.sha !== 'string' || typeof latest.name !== 'string') {
41108
throw new Error(`Invalid tag structure for ${owner}/${repo}: missing SHA or name`);
42109
}
43-
console.log(`${repo} -> ${tags[0].name} (${tags[0].commit.sha.slice(0, 7)})`);
44-
return { sha: tags[0].commit.sha, tag: tags[0].name };
110+
console.log(`${repo} -> ${latest.name} (${latest.commit.sha.slice(0, 7)})`);
111+
return { sha: latest.commit.sha, tag: latest.name };
45112
}
46113

47114
// ============ npm Registry ============
48-
async function getLatestNpmVersion(packageName) {
115+
async function getLatestNpmVersion(packageName: string): Promise<string> {
49116
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
50117
if (!res.ok) {
51118
throw new Error(
52119
`Failed to fetch npm version for ${packageName}: ${res.status} ${res.statusText}`,
53120
);
54121
}
55-
const data = await res.json();
56-
if (!data?.version) {
122+
const data = (await res.json()) as NpmLatestResponse;
123+
if (typeof data.version !== 'string') {
57124
throw new Error(`Invalid npm response for ${packageName}: missing version field`);
58125
}
59126
return data.version;
60127
}
61128

62129
// ============ Update .upstream-versions.json ============
63-
async function updateUpstreamVersions() {
130+
async function updateUpstreamVersions(): Promise<void> {
64131
const filePath = path.join(ROOT, 'packages/tools/.upstream-versions.json');
65-
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
132+
const data: UpstreamVersions = readJsonFile(filePath);
66133

67134
const oldRolldownHash = data.rolldown.hash;
68-
const oldViteHash = data['vite'].hash;
135+
const oldViteHash = data.vite.hash;
69136
const [rolldown, vite] = await Promise.all([
70137
getLatestTag('rolldown', 'rolldown'),
71138
getLatestTag('vitejs', 'vite'),
72139
]);
73140
data.rolldown.hash = rolldown.sha;
74-
data['vite'].hash = vite.sha;
141+
data.vite.hash = vite.sha;
75142
recordChange('rolldown', oldRolldownHash, rolldown.sha, rolldown.tag);
76143
recordChange('vite', oldViteHash, vite.sha, vite.tag);
77144

@@ -80,12 +147,12 @@ async function updateUpstreamVersions() {
80147
}
81148

82149
// ============ Update pnpm-workspace.yaml ============
83-
async function updatePnpmWorkspace(versions) {
150+
async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<void> {
84151
const filePath = path.join(ROOT, 'pnpm-workspace.yaml');
85152
let content = fs.readFileSync(filePath, 'utf8');
86153

87154
// oxlint's trailing \n in the pattern disambiguates from oxlint-tsgolint.
88-
const entries = [
155+
const entries: PnpmWorkspaceEntry[] = [
89156
{
90157
name: 'vitest',
91158
pattern: /vitest-dev: npm:vitest@\^([\d.]+(?:-[\w.]+)?)/,
@@ -161,15 +228,15 @@ async function updatePnpmWorkspace(versions) {
161228
];
162229

163230
for (const { name, pattern, replacement, newVersion } of entries) {
164-
let oldVersion;
165-
content = content.replace(pattern, (_match, captured) => {
231+
let oldVersion: string | undefined;
232+
content = content.replace(pattern, (_match: string, captured: string) => {
166233
oldVersion = captured;
167234
return replacement;
168235
});
169236
if (oldVersion === undefined) {
170237
throw new Error(
171238
`Failed to match ${name} in pnpm-workspace.yaml — the pattern ${pattern} is stale, ` +
172-
`please update it in .github/scripts/upgrade-deps.mjs`,
239+
`please update it in .github/scripts/upgrade-deps.ts`,
173240
);
174241
}
175242
recordChange(name, oldVersion, newVersion);
@@ -180,20 +247,24 @@ async function updatePnpmWorkspace(versions) {
180247
}
181248

182249
// ============ Update packages/test/package.json ============
183-
async function updateTestPackage(vitestVersion) {
250+
async function updateTestPackage(vitestVersion: string): Promise<void> {
184251
const filePath = path.join(ROOT, 'packages/test/package.json');
185-
const pkg = JSON.parse(fs.readFileSync(filePath, 'utf8'));
252+
const pkg: PackageJson = readJsonFile(filePath);
253+
const devDependencies = pkg.devDependencies;
254+
if (!devDependencies) {
255+
throw new Error('packages/test/package.json is missing devDependencies');
256+
}
186257

187258
// Update all @vitest/* devDependencies
188-
for (const dep of Object.keys(pkg.devDependencies)) {
259+
for (const dep of Object.keys(devDependencies)) {
189260
if (dep.startsWith('@vitest/')) {
190-
pkg.devDependencies[dep] = vitestVersion;
261+
devDependencies[dep] = vitestVersion;
191262
}
192263
}
193264

194265
// Update vitest-dev devDependency
195-
if (pkg.devDependencies['vitest-dev']) {
196-
pkg.devDependencies['vitest-dev'] = `^${vitestVersion}`;
266+
if (devDependencies['vitest-dev']) {
267+
devDependencies['vitest-dev'] = `^${vitestVersion}`;
197268
}
198269

199270
// Update @vitest/ui peerDependency if present
@@ -206,23 +277,24 @@ async function updateTestPackage(vitestVersion) {
206277
}
207278

208279
// ============ Update packages/core/package.json ============
209-
async function updateCorePackage(devtoolsVersion) {
280+
async function updateCorePackage(devtoolsVersion: string): Promise<void> {
210281
const filePath = path.join(ROOT, 'packages/core/package.json');
211-
const pkg = JSON.parse(fs.readFileSync(filePath, 'utf8'));
282+
const pkg: PackageJson = readJsonFile(filePath);
212283

213-
const currentDevtools = pkg.devDependencies?.['@vitejs/devtools'];
284+
const devDependencies = pkg.devDependencies;
285+
const currentDevtools = devDependencies?.['@vitejs/devtools'];
214286
if (!currentDevtools) {
215287
return;
216288
}
217-
pkg.devDependencies['@vitejs/devtools'] = `^${devtoolsVersion}`;
289+
devDependencies['@vitejs/devtools'] = `^${devtoolsVersion}`;
218290
recordChange('@vitejs/devtools', currentDevtools.replace(/^[\^~]/, ''), devtoolsVersion);
219291

220292
fs.writeFileSync(filePath, JSON.stringify(pkg, null, 2) + '\n');
221293
console.log('Updated packages/core/package.json');
222294
}
223295

224296
// ============ Write metadata files for PR description ============
225-
function writeMetaFiles() {
297+
function writeMetaFiles(): void {
226298
if (!META_DIR) {
227299
return;
228300
}
@@ -238,7 +310,7 @@ function writeMetaFiles() {
238310
const changed = [...changes.entries()].filter(([, v]) => v.old !== v.new);
239311
const unchanged = [...changes.entries()].filter(([, v]) => v.old === v.new);
240312

241-
const formatVersion = (v) => {
313+
const formatVersion = (v: Change): string => {
242314
if (v.tag) {
243315
return `${v.tag} (${v.new.slice(0, 7)})`;
244316
}
@@ -247,7 +319,7 @@ function writeMetaFiles() {
247319
}
248320
return v.new;
249321
};
250-
const formatOld = (v) => {
322+
const formatOld = (v: Change): string => {
251323
if (!v.old) {
252324
return '(unset)';
253325
}

.github/workflows/upgrade-deps.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
id: upgrade
4343
env:
4444
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45-
run: node .github/scripts/upgrade-deps.mjs
45+
run: node .github/scripts/upgrade-deps.ts
4646

4747
- name: Sync remote and build
4848
id: build
@@ -81,7 +81,7 @@ jobs:
8181
then prove the fix is complete by running a final validation pass.
8282
8383
### Background
84-
- Upgrade script: `./.github/scripts/upgrade-deps.mjs`
84+
- Upgrade script: `./.github/scripts/upgrade-deps.ts`
8585
- Sync-remote tool: `pnpm tool sync-remote` (source in
8686
`packages/tools/src/sync-remote-deps.ts`) — clones rolldown/vite into the
8787
working tree and merges their pnpm-workspace catalogs into the root

0 commit comments

Comments
 (0)