mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 10:51:36 -04:00
build: put the @babel pins back, and make them fail loudly if dropped again (#653)
The pins that fixed the blank React /map were removed as collateral when the Mantine commit rewrote `overrides` for TypeScript 5. Nothing failed, so nothing objected: the map kept working only because package-lock.json still happened to resolve @babel/core to 7.20.12, and devices run `npm install`, not `npm ci`. Verified on this tree rather than assumed. Raising @babel/core to 7.29.7 and reinstalling floats the whole pipeline (traverse/generator/types to 7.29.8) and `npm run build` then fails outright: "'opera_mobile' is not a valid target", from a helper-compilation-targets that floated away from the browserslist data it was pinned against. With the pins restored the build compiles, all 141 resolved copies match, and /map renders from the built bundle. The test walks every path in the lockfile, not just node_modules/<name>: npm nests a second copy whenever versions conflict, and a nested copy that escaped the override would put the new pipeline back into the build while the top-level entry still read correctly. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f1bc52d9c6
commit
c560ba6fa5
3 changed files with 462 additions and 353 deletions
706
app/package-lock.json
generated
706
app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -72,6 +72,22 @@
|
|||
]
|
||||
},
|
||||
"overrides": {
|
||||
"@babel/code-frame": "7.29.0",
|
||||
"@babel/compat-data": "7.20.14",
|
||||
"@babel/core": "7.20.12",
|
||||
"@babel/generator": "7.29.1",
|
||||
"@babel/helper-compilation-targets": "7.20.7",
|
||||
"@babel/helper-globals": "7.28.0",
|
||||
"@babel/helper-module-imports": "7.28.6",
|
||||
"@babel/helper-module-transforms": "7.28.6",
|
||||
"@babel/helper-string-parser": "7.27.1",
|
||||
"@babel/helper-validator-identifier": "7.28.5",
|
||||
"@babel/helper-validator-option": "7.18.6",
|
||||
"@babel/helpers": "7.27.0",
|
||||
"@babel/parser": "7.29.3",
|
||||
"@babel/template": "7.28.6",
|
||||
"@babel/traverse": "7.29.0",
|
||||
"@babel/types": "7.29.0",
|
||||
"react-scripts": {
|
||||
"typescript": "$typescript"
|
||||
},
|
||||
|
|
|
|||
93
app/src/babel-pins.test.js
Normal file
93
app/src/babel-pins.test.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/*
|
||||
* The @babel core pipeline is pinned, and has to stay pinned.
|
||||
*
|
||||
* Floating it broke the React /map page: maplibre-gl's Web Worker is inlined into the
|
||||
* production bundle, Babel 7.29 transpiles it differently, and CRA's Terser mangle pass then
|
||||
* turns it into `ReferenceError: a is not defined`. The worker's style-layer index never
|
||||
* builds, every vector source fails to parse, and the map renders blank with only the pins.
|
||||
*
|
||||
* Two properties make this worth a test rather than a comment:
|
||||
*
|
||||
* - It is invisible to everything else we run. The dev server is fine, the jest suite is
|
||||
* fine, `npm run build` succeeds -- only the MINIFIED bundle is broken, and only on a page
|
||||
* whose failure looks like missing map data rather than a JS error.
|
||||
* - It has already regressed once by accident. The pins were added deliberately, then
|
||||
* removed as collateral when the Mantine commit rewrote `overrides` for TypeScript 5.
|
||||
* Nothing failed, so nothing objected; the map survived on lockfile inertia alone until
|
||||
* someone re-resolved it.
|
||||
*
|
||||
* Verified rather than assumed, on this tree: raising @babel/core to 7.29.7 and reinstalling
|
||||
* fails `npm run build` outright ("'opera_mobile' is not a valid target", from a
|
||||
* helper-compilation-targets that floated away from the browserslist data it was pinned
|
||||
* against). Restoring these pins builds, and /map renders from the built bundle.
|
||||
*/
|
||||
|
||||
const APP = path.join(__dirname, '..');
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(APP, 'package.json'), 'utf8'));
|
||||
const lock = JSON.parse(fs.readFileSync(path.join(APP, 'package-lock.json'), 'utf8'));
|
||||
|
||||
/** The 16 packages that differ between the last-good tree and the one that broke the map. */
|
||||
const PINNED = [
|
||||
'@babel/code-frame', '@babel/compat-data', '@babel/core', '@babel/generator',
|
||||
'@babel/helper-compilation-targets', '@babel/helper-globals', '@babel/helper-module-imports',
|
||||
'@babel/helper-module-transforms', '@babel/helper-string-parser',
|
||||
'@babel/helper-validator-identifier', '@babel/helper-validator-option', '@babel/helpers',
|
||||
'@babel/parser', '@babel/template', '@babel/traverse', '@babel/types',
|
||||
];
|
||||
|
||||
describe('@babel pins', () => {
|
||||
it('names every package in the core pipeline', () => {
|
||||
/*
|
||||
* The whole pipeline, not just @babel/core. Pinning core alone was tried when this was
|
||||
* first diagnosed and is not enough: the transform plugins resolve their own copies of
|
||||
* traverse/generator/types, so the new AST pipeline comes back in underneath a pinned
|
||||
* core.
|
||||
*/
|
||||
const overrides = pkg.overrides || {};
|
||||
const missing = PINNED.filter(name => !overrides[name]);
|
||||
|
||||
expect(missing).toEqual([]);
|
||||
});
|
||||
|
||||
it('pins them to an exact version, not a range', () => {
|
||||
// `^7.20.12` is not a pin -- it resolves to 7.29 the moment anything re-resolves, which
|
||||
// is exactly the event this guards against.
|
||||
const loose = PINNED
|
||||
.map(name => [name, (pkg.overrides || {})[name]])
|
||||
.filter(([, spec]) => typeof spec !== 'string' || !/^\d+\.\d+\.\d+$/.test(spec));
|
||||
|
||||
expect(loose).toEqual([]);
|
||||
});
|
||||
|
||||
it('resolves every installed copy to the pinned version', () => {
|
||||
/*
|
||||
* The manifest is the intent; the lockfile is what actually gets installed. npm nests
|
||||
* a second copy of a dependency whenever versions conflict, and a nested copy that
|
||||
* escaped the override would put the new pipeline back into the build while the
|
||||
* top-level entry still read correctly.
|
||||
*
|
||||
* So this walks EVERY path in the lockfile, not just `node_modules/<name>`.
|
||||
*/
|
||||
const wanted = Object.fromEntries(PINNED.map(name => [name, pkg.overrides[name]]));
|
||||
const mismatched = [];
|
||||
let copies = 0;
|
||||
|
||||
for (const [location, info] of Object.entries(lock.packages || {})) {
|
||||
for (const [name, version] of Object.entries(wanted)) {
|
||||
if (location === `node_modules/${name}` || location.endsWith(`/node_modules/${name}`)) {
|
||||
copies += 1;
|
||||
if (info.version !== version) {
|
||||
mismatched.push(`${location}: ${info.version} (pinned ${version})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The premise: a lockfile these names never matched would report nothing mismatched.
|
||||
expect(copies).toBeGreaterThan(100);
|
||||
expect(mismatched).toEqual([]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue