Skip to content

Commit f8045cf

Browse files
committed
fix(scripts): correct corrupted versions in upgrade-deps replace callback
Cursor Bugbot caught a high-severity bug introduced by the previous single-pass refactor. The replace callback used: (_match, prefix, oldVersion, suffix = '') => `${prefix}${newVersion}${suffix}` `String.prototype.replace` always passes the match offset (a number) as the next positional argument after the captures, so for the 6 patterns with only two captures (vitest, tsdown, @oxc-node/cli, @oxc-node/core, oxfmt, oxlint-tsgolint), `suffix` received the offset instead of `undefined`. The `= ''` default never kicked in, and the offset got appended to every version. Example: `tsdown: ^0.21.8` was rewritten to `tsdown: ^9.9.90`. The earlier smoke test missed this because it used substring `includes()`, which still matched. Restructure each entry to use ONE capture group (just the version) plus a literal `replacement` string. The callback signature is now `(_match, captured)` and ignores the trailing offset/string args, so the positional ambiguity is gone. Verified with a byte-exact equality test on a synthetic fixture covering non-zero offsets.
1 parent 05af642 commit f8045cf

File tree

1 file changed

+36
-16
lines changed

1 file changed

+36
-16
lines changed

.github/scripts/upgrade-deps.mjs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,47 +84,67 @@ async function updatePnpmWorkspace(versions) {
8484
const filePath = path.join(ROOT, 'pnpm-workspace.yaml');
8585
let content = fs.readFileSync(filePath, 'utf8');
8686

87-
// The capture regex puts the current version in $1 and matches exactly what
88-
// the new string will replace. oxlint's trailing \n disambiguates from oxlint-tsgolint.
87+
// Each pattern has exactly one capture group (the current version) so the replace
88+
// callback signature is `(match, captured, offset, string)` — no positional ambiguity.
89+
// oxlint's trailing \n disambiguates from oxlint-tsgolint.
8990
const entries = [
9091
{
9192
name: 'vitest',
92-
pattern: /(vitest-dev: npm:vitest@\^)([\d.]+(?:-[\w.]+)?)/,
93+
pattern: /vitest-dev: npm:vitest@\^([\d.]+(?:-[\w.]+)?)/,
94+
replacement: `vitest-dev: npm:vitest@^${versions.vitest}`,
9395
newVersion: versions.vitest,
9496
},
95-
{ name: 'tsdown', pattern: /(tsdown: \^)([\d.]+(?:-[\w.]+)?)/, newVersion: versions.tsdown },
97+
{
98+
name: 'tsdown',
99+
pattern: /tsdown: \^([\d.]+(?:-[\w.]+)?)/,
100+
replacement: `tsdown: ^${versions.tsdown}`,
101+
newVersion: versions.tsdown,
102+
},
96103
{
97104
name: '@oxc-node/cli',
98-
pattern: /('@oxc-node\/cli': \^)([\d.]+(?:-[\w.]+)?)/,
105+
pattern: /'@oxc-node\/cli': \^([\d.]+(?:-[\w.]+)?)/,
106+
replacement: `'@oxc-node/cli': ^${versions.oxcNodeCli}`,
99107
newVersion: versions.oxcNodeCli,
100108
},
101109
{
102110
name: '@oxc-node/core',
103-
pattern: /('@oxc-node\/core': \^)([\d.]+(?:-[\w.]+)?)/,
111+
pattern: /'@oxc-node\/core': \^([\d.]+(?:-[\w.]+)?)/,
112+
replacement: `'@oxc-node/core': ^${versions.oxcNodeCore}`,
104113
newVersion: versions.oxcNodeCore,
105114
},
106-
{ name: 'oxfmt', pattern: /(oxfmt: =)([\d.]+(?:-[\w.]+)?)/, newVersion: versions.oxfmt },
107-
{ name: 'oxlint', pattern: /(oxlint: =)([\d.]+(?:-[\w.]+)?)(\n)/, newVersion: versions.oxlint },
115+
{
116+
name: 'oxfmt',
117+
pattern: /oxfmt: =([\d.]+(?:-[\w.]+)?)/,
118+
replacement: `oxfmt: =${versions.oxfmt}`,
119+
newVersion: versions.oxfmt,
120+
},
121+
{
122+
name: 'oxlint',
123+
pattern: /oxlint: =([\d.]+(?:-[\w.]+)?)\n/,
124+
replacement: `oxlint: =${versions.oxlint}\n`,
125+
newVersion: versions.oxlint,
126+
},
108127
{
109128
name: 'oxlint-tsgolint',
110-
pattern: /(oxlint-tsgolint: =)([\d.]+(?:-[\w.]+)?)/,
129+
pattern: /oxlint-tsgolint: =([\d.]+(?:-[\w.]+)?)/,
130+
replacement: `oxlint-tsgolint: =${versions.oxlintTsgolint}`,
111131
newVersion: versions.oxlintTsgolint,
112132
},
113133
];
114134

115-
for (const { name, pattern, newVersion } of entries) {
116-
let matched = false;
117-
content = content.replace(pattern, (_match, prefix, oldVersion, suffix = '') => {
118-
matched = true;
119-
recordChange(name, oldVersion, newVersion);
120-
return `${prefix}${newVersion}${suffix}`;
135+
for (const { name, pattern, replacement, newVersion } of entries) {
136+
let oldVersion;
137+
content = content.replace(pattern, (_match, captured) => {
138+
oldVersion = captured;
139+
return replacement;
121140
});
122-
if (!matched) {
141+
if (oldVersion === undefined) {
123142
throw new Error(
124143
`Failed to match ${name} in pnpm-workspace.yaml — the pattern ${pattern} is stale, ` +
125144
`please update it in .github/scripts/upgrade-deps.mjs`,
126145
);
127146
}
147+
recordChange(name, oldVersion, newVersion);
128148
}
129149

130150
fs.writeFileSync(filePath, content);

0 commit comments

Comments
 (0)