@bobfrankston/npmglobalize 1.0.203 → 1.0.205
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib.js +145 -6
- package/package.json +1 -1
package/lib.js
CHANGED
|
@@ -2383,23 +2383,43 @@ function migrateTsconfigDeprecations(cwd, snapshot) {
|
|
|
2383
2383
|
};
|
|
2384
2384
|
const note = (msg) => console.log(colors.cyan(` ${msg}`));
|
|
2385
2385
|
let changed = false;
|
|
2386
|
-
// moduleResolution: node/node10/classic →
|
|
2386
|
+
// moduleResolution: node/node10/classic → NodeNext (+ align module).
|
|
2387
2387
|
const mr = effectiveCompilerOption(chain, 'moduleResolution');
|
|
2388
2388
|
if (mr && typeof mr.value === 'string' && DEPRECATED_MODULE_RESOLUTION.has(mr.value.toLowerCase())) {
|
|
2389
2389
|
const tgt = targetFor(mr.definedIn);
|
|
2390
|
-
if (tgt && edit(tgt.path, t => upsertCompilerOption(t, 'moduleResolution',
|
|
2390
|
+
if (tgt && edit(tgt.path, t => upsertCompilerOption(t, 'moduleResolution', `"${CANONICAL_NODENEXT}"`))) {
|
|
2391
2391
|
changed = true;
|
|
2392
|
-
note(`Migrated moduleResolution "${mr.value}" → "
|
|
2392
|
+
note(`Migrated moduleResolution "${mr.value}" → "${CANONICAL_NODENEXT}" in ${path.relative(cwd, tgt.path) || 'tsconfig.json'}`);
|
|
2393
2393
|
// nodenext resolution requires a matching module setting.
|
|
2394
2394
|
const mod = effectiveCompilerOption(chain, 'module');
|
|
2395
2395
|
const modOk = mod && typeof mod.value === 'string' && /^(node16|nodenext|preserve)$/i.test(mod.value);
|
|
2396
2396
|
if (!modOk) {
|
|
2397
2397
|
const modTgt = mod ? targetFor(mod.definedIn) : mostLeafWritable;
|
|
2398
|
-
if (modTgt && edit(modTgt.path, t => upsertCompilerOption(t, 'module',
|
|
2399
|
-
note(`Aligned module → "
|
|
2398
|
+
if (modTgt && edit(modTgt.path, t => upsertCompilerOption(t, 'module', `"${CANONICAL_NODENEXT}"`))) {
|
|
2399
|
+
note(`Aligned module → "${CANONICAL_NODENEXT}" (required by moduleResolution "${CANONICAL_NODENEXT}")`);
|
|
2400
2400
|
}
|
|
2401
2401
|
}
|
|
2402
|
-
recordBuildIssue(name, 'warning', `Migrated moduleResolution "${mr.value}" → "
|
|
2402
|
+
recordBuildIssue(name, 'warning', `Migrated moduleResolution "${mr.value}" → "${CANONICAL_NODENEXT}". Verify relative imports resolve (NodeNext requires explicit file extensions on relative specifiers).`);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
// Canonical casing for values we write ourselves. TypeScript reads these
|
|
2406
|
+
// case-insensitively, but the JSON schema behind editor linting (webhint,
|
|
2407
|
+
// "Microsoft Edge Tools") only accepts the documented spelling, so an
|
|
2408
|
+
// all-lowercase "nodenext" we wrote earlier shows up as an error in the
|
|
2409
|
+
// editor even though the build is fine.
|
|
2410
|
+
for (const key of ['module', 'moduleResolution']) {
|
|
2411
|
+
const opt = effectiveCompilerOption(chain, key);
|
|
2412
|
+
if (!opt || typeof opt.value !== 'string')
|
|
2413
|
+
continue;
|
|
2414
|
+
const canonical = CANONICAL_CASING[opt.value.toLowerCase()];
|
|
2415
|
+
if (!canonical || canonical === opt.value)
|
|
2416
|
+
continue;
|
|
2417
|
+
const def = chain.find(c => c.path === opt.definedIn);
|
|
2418
|
+
if (!def?.writable)
|
|
2419
|
+
continue; // an override would be noise, not a fix
|
|
2420
|
+
if (edit(def.path, t => upsertCompilerOption(t, key, `"${canonical}"`))) {
|
|
2421
|
+
changed = true;
|
|
2422
|
+
note(`Normalized ${key} "${opt.value}" → "${canonical}" in ${path.relative(cwd, def.path) || 'tsconfig.json'}`);
|
|
2403
2423
|
}
|
|
2404
2424
|
}
|
|
2405
2425
|
// target: es3 → es2022 (es3 is removed in TS7).
|
|
@@ -2429,6 +2449,103 @@ function migrateTsconfigDeprecations(cwd, snapshot) {
|
|
|
2429
2449
|
}
|
|
2430
2450
|
return changed;
|
|
2431
2451
|
}
|
|
2452
|
+
/** Ordering of ES language levels as they appear in `target`/`lib`. Anything
|
|
2453
|
+
* unknown ranks 0 so it never looks newer than what the build asked for. */
|
|
2454
|
+
const ES_LEVEL_RANK = {
|
|
2455
|
+
es3: 3, es5: 5, es6: 6, es2015: 6, es7: 7, es2016: 7, es2017: 8, es2018: 9,
|
|
2456
|
+
es2019: 10, es2020: 11, es2021: 12, es2022: 13, es2023: 14, es2024: 15,
|
|
2457
|
+
es2025: 16, esnext: 99,
|
|
2458
|
+
};
|
|
2459
|
+
const esLevelRank = (value) => ES_LEVEL_RANK[value.toLowerCase().replace(/\.full$/, '')] ?? 0;
|
|
2460
|
+
/** tsc says "Try changing the 'lib' compiler option to 'es2023' or later"
|
|
2461
|
+
* (TS2550/TS2583/TS2584) when the code uses a standard-library API newer than
|
|
2462
|
+
* the configured level. That isn't a deprecation — it's a tsconfig the language
|
|
2463
|
+
* moved past — so raise the level and let the build try again, rather than
|
|
2464
|
+
* leaving the user to decode `replaceAll` and `toSorted` errors by hand. The
|
|
2465
|
+
* level asked for only decides *whether* to raise; what we write is
|
|
2466
|
+
* `RAISED_ES_LEVEL`, so the next new method used doesn't start this over.
|
|
2467
|
+
*
|
|
2468
|
+
* We raise `target`: its implied `lib` keeps the DOM declarations a bare
|
|
2469
|
+
* `lib: ["es2023"]` would silently drop. An explicit `lib` overrides `target`
|
|
2470
|
+
* though, so when one is set we raise its `es*` entry in place and keep the
|
|
2471
|
+
* rest (dom, webworker, …). Reads the required level from `buildOutput`;
|
|
2472
|
+
* returns true if it changed any file. */
|
|
2473
|
+
function raiseTsconfigLibLevel(cwd, buildOutput, snapshot) {
|
|
2474
|
+
let required = '';
|
|
2475
|
+
for (const m of buildOutput.matchAll(/'lib' compiler option to '([A-Za-z0-9.]+)'/g)) {
|
|
2476
|
+
if (esLevelRank(m[1]) > esLevelRank(required))
|
|
2477
|
+
required = m[1].toLowerCase();
|
|
2478
|
+
}
|
|
2479
|
+
if (!required)
|
|
2480
|
+
return false;
|
|
2481
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
2482
|
+
if (!fs.existsSync(tsconfigPath))
|
|
2483
|
+
return false;
|
|
2484
|
+
const { chain } = resolveTsconfigChain(tsconfigPath);
|
|
2485
|
+
if (!chain.length)
|
|
2486
|
+
return false;
|
|
2487
|
+
const mostLeafWritable = chain.find(c => c.writable);
|
|
2488
|
+
if (!mostLeafWritable)
|
|
2489
|
+
return false;
|
|
2490
|
+
const name = (() => { try {
|
|
2491
|
+
return readPackageJson(cwd).name;
|
|
2492
|
+
}
|
|
2493
|
+
catch {
|
|
2494
|
+
return null;
|
|
2495
|
+
} })() || path.basename(cwd);
|
|
2496
|
+
const edit = (filePath, fn) => {
|
|
2497
|
+
let text;
|
|
2498
|
+
try {
|
|
2499
|
+
text = fs.readFileSync(filePath, 'utf-8');
|
|
2500
|
+
}
|
|
2501
|
+
catch {
|
|
2502
|
+
return false;
|
|
2503
|
+
}
|
|
2504
|
+
const patched = fn(text);
|
|
2505
|
+
if (patched == null || patched === text)
|
|
2506
|
+
return false;
|
|
2507
|
+
try {
|
|
2508
|
+
fs.writeFileSync(filePath, patched);
|
|
2509
|
+
if (snapshot && !snapshot.has(filePath))
|
|
2510
|
+
snapshot.set(filePath, text);
|
|
2511
|
+
return true;
|
|
2512
|
+
}
|
|
2513
|
+
catch (error) {
|
|
2514
|
+
console.error(colors.yellow(` Could not write tsconfig patch (${filePath}): ${error.message}`));
|
|
2515
|
+
return false;
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
/** The file to edit for an option: where it's defined if that's writable,
|
|
2519
|
+
* else the most-leaf writable config, where an override wins. */
|
|
2520
|
+
const targetFor = (definedIn) => {
|
|
2521
|
+
const def = definedIn ? chain.find(c => c.path === definedIn) : null;
|
|
2522
|
+
return def?.writable ? def : mostLeafWritable;
|
|
2523
|
+
};
|
|
2524
|
+
// An explicit `lib` beats `target`, so it's the one that has to move.
|
|
2525
|
+
const lib = effectiveCompilerOption(chain, 'lib');
|
|
2526
|
+
if (lib && Array.isArray(lib.value)) {
|
|
2527
|
+
const entries = lib.value.filter((e) => typeof e === 'string');
|
|
2528
|
+
if (entries.some(e => esLevelRank(e) >= esLevelRank(required)))
|
|
2529
|
+
return false;
|
|
2530
|
+
const raised = [RAISED_ES_LEVEL, ...entries.filter(e => esLevelRank(e) === 0)];
|
|
2531
|
+
const tgt = targetFor(lib.definedIn);
|
|
2532
|
+
if (!edit(tgt.path, t => upsertCompilerOption(t, 'lib', JSON.stringify(raised).replace(/","/g, '", "'))))
|
|
2533
|
+
return false;
|
|
2534
|
+
console.log(colors.cyan(` Raised lib [${entries.join(', ')}] → [${raised.join(', ')}] in ${path.relative(cwd, tgt.path) || 'tsconfig.json'} (build needs ${required})`));
|
|
2535
|
+
recordBuildIssue(name, 'warning', `Raised "lib" to ${RAISED_ES_LEVEL} — the code uses standard-library APIs newer than the tsconfig allowed (needs ${required}).`);
|
|
2536
|
+
return true;
|
|
2537
|
+
}
|
|
2538
|
+
const tg = effectiveCompilerOption(chain, 'target');
|
|
2539
|
+
const current = tg && typeof tg.value === 'string' ? tg.value : 'es5'; // tsc's own default
|
|
2540
|
+
if (esLevelRank(current) >= esLevelRank(required))
|
|
2541
|
+
return false;
|
|
2542
|
+
const tgt = targetFor(tg?.definedIn);
|
|
2543
|
+
if (!edit(tgt.path, t => upsertCompilerOption(t, 'target', `"${RAISED_ES_LEVEL}"`)))
|
|
2544
|
+
return false;
|
|
2545
|
+
console.log(colors.cyan(` Raised target "${current}" → "${RAISED_ES_LEVEL}" in ${path.relative(cwd, tgt.path) || 'tsconfig.json'} (build needs ${required})`));
|
|
2546
|
+
recordBuildIssue(name, 'warning', `Raised "target" ${current} → ${RAISED_ES_LEVEL} — the code uses standard-library APIs newer than the tsconfig allowed (needs ${required}).`);
|
|
2547
|
+
return true;
|
|
2548
|
+
}
|
|
2432
2549
|
/** Write back the texts captured in a `migrateTsconfigDeprecations` snapshot
|
|
2433
2550
|
* (path → text). Used to undo a migration whose build turned out worse, and to
|
|
2434
2551
|
* re-apply it afterwards. Returns false if any write failed — the tree is then
|
|
@@ -3052,6 +3169,16 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
3052
3169
|
buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
3053
3170
|
}
|
|
3054
3171
|
}
|
|
3172
|
+
// "Try changing the 'lib' compiler option to 'es2023' or later" — a tsconfig
|
|
3173
|
+
// pinned below the standard-library APIs the code actually uses. Raise it and
|
|
3174
|
+
// rebuild. Looped because one level can uncover the need for a higher one,
|
|
3175
|
+
// and bounded because each pass must raise the level to run again.
|
|
3176
|
+
for (let pass = 0; pass < 3 && !buildResult.success; pass++) {
|
|
3177
|
+
const out = (buildResult.stderr || '') + (buildResult.output || '');
|
|
3178
|
+
if (!raiseTsconfigLibLevel(cwd, out, tsconfigSnapshot))
|
|
3179
|
+
break;
|
|
3180
|
+
buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
3181
|
+
}
|
|
3055
3182
|
if (!buildResult.success && migratedUpFront && tsconfigSnapshot.size) {
|
|
3056
3183
|
// The up-front migration is a semantic change: `nodenext` resolution can
|
|
3057
3184
|
// surface real errors (missing file extensions on relative imports) that
|
|
@@ -3143,6 +3270,18 @@ export async function buildFileDepsTopologically(cwd, opts = {}, visited = new S
|
|
|
3143
3270
|
}
|
|
3144
3271
|
/** `moduleResolution` values TypeScript 6 deprecated and TypeScript 7 removes. */
|
|
3145
3272
|
const DEPRECATED_MODULE_RESOLUTION = new Set(['node', 'node10', 'classic']);
|
|
3273
|
+
/** Spelling the tsconfig JSON schema — and so the editor linting built on it,
|
|
3274
|
+
* e.g. webhint via "Microsoft Edge Tools" — expects for the values we write.
|
|
3275
|
+
* TypeScript reads them case-insensitively; the schema does not. Deliberately
|
|
3276
|
+
* limited to the values npmglobalize writes itself: re-casing whatever the user
|
|
3277
|
+
* hand-typed would be churn, not a fix. */
|
|
3278
|
+
const CANONICAL_NODENEXT = 'NodeNext';
|
|
3279
|
+
const CANONICAL_CASING = { nodenext: CANONICAL_NODENEXT, node16: 'Node16' };
|
|
3280
|
+
/** Level we raise a too-old `target`/`lib` to. These are Node packages built and
|
|
3281
|
+
* run on the current Node, so tracking the newest language level costs nothing
|
|
3282
|
+
* and saves a re-raise every time the code picks up a newer standard-library
|
|
3283
|
+
* method. */
|
|
3284
|
+
const RAISED_ES_LEVEL = 'ESNext';
|
|
3146
3285
|
/** Compiler flags TypeScript deprecated (TS5101) and will remove in TS7. Their
|
|
3147
3286
|
* mere presence — at any value — is a migration item. */
|
|
3148
3287
|
const DEPRECATED_TS7_FLAGS = [
|