@bobfrankston/npmglobalize 1.0.203 → 1.0.204

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.
Files changed (2) hide show
  1. package/lib.js +106 -0
  2. package/package.json +1 -1
package/lib.js CHANGED
@@ -2429,6 +2429,102 @@ function migrateTsconfigDeprecations(cwd, snapshot) {
2429
2429
  }
2430
2430
  return changed;
2431
2431
  }
2432
+ /** Ordering of ES language levels as they appear in `target`/`lib`. Anything
2433
+ * unknown ranks 0 so it never looks newer than what the build asked for. */
2434
+ const ES_LEVEL_RANK = {
2435
+ es3: 3, es5: 5, es6: 6, es2015: 6, es7: 7, es2016: 7, es2017: 8, es2018: 9,
2436
+ es2019: 10, es2020: 11, es2021: 12, es2022: 13, es2023: 14, es2024: 15,
2437
+ es2025: 16, esnext: 99,
2438
+ };
2439
+ const esLevelRank = (value) => ES_LEVEL_RANK[value.toLowerCase().replace(/\.full$/, '')] ?? 0;
2440
+ /** tsc says "Try changing the 'lib' compiler option to 'es2023' or later"
2441
+ * (TS2550/TS2583/TS2584) when the code uses a standard-library API newer than
2442
+ * the configured level. That isn't a deprecation — it's a tsconfig the language
2443
+ * moved past — so raise the level to whatever the build asked for and let it
2444
+ * try again, rather than leaving the user to decode `replaceAll` and `toSorted`
2445
+ * errors by hand.
2446
+ *
2447
+ * We raise `target`: its implied `lib` keeps the DOM declarations a bare
2448
+ * `lib: ["es2023"]` would silently drop. An explicit `lib` overrides `target`
2449
+ * though, so when one is set we raise its `es*` entry in place and keep the
2450
+ * rest (dom, webworker, …). Reads the required level from `buildOutput`;
2451
+ * returns true if it changed any file. */
2452
+ function raiseTsconfigLibLevel(cwd, buildOutput, snapshot) {
2453
+ let required = '';
2454
+ for (const m of buildOutput.matchAll(/'lib' compiler option to '([A-Za-z0-9.]+)'/g)) {
2455
+ if (esLevelRank(m[1]) > esLevelRank(required))
2456
+ required = m[1].toLowerCase();
2457
+ }
2458
+ if (!required)
2459
+ return false;
2460
+ const tsconfigPath = path.join(cwd, 'tsconfig.json');
2461
+ if (!fs.existsSync(tsconfigPath))
2462
+ return false;
2463
+ const { chain } = resolveTsconfigChain(tsconfigPath);
2464
+ if (!chain.length)
2465
+ return false;
2466
+ const mostLeafWritable = chain.find(c => c.writable);
2467
+ if (!mostLeafWritable)
2468
+ return false;
2469
+ const name = (() => { try {
2470
+ return readPackageJson(cwd).name;
2471
+ }
2472
+ catch {
2473
+ return null;
2474
+ } })() || path.basename(cwd);
2475
+ const edit = (filePath, fn) => {
2476
+ let text;
2477
+ try {
2478
+ text = fs.readFileSync(filePath, 'utf-8');
2479
+ }
2480
+ catch {
2481
+ return false;
2482
+ }
2483
+ const patched = fn(text);
2484
+ if (patched == null || patched === text)
2485
+ return false;
2486
+ try {
2487
+ fs.writeFileSync(filePath, patched);
2488
+ if (snapshot && !snapshot.has(filePath))
2489
+ snapshot.set(filePath, text);
2490
+ return true;
2491
+ }
2492
+ catch (error) {
2493
+ console.error(colors.yellow(` Could not write tsconfig patch (${filePath}): ${error.message}`));
2494
+ return false;
2495
+ }
2496
+ };
2497
+ /** The file to edit for an option: where it's defined if that's writable,
2498
+ * else the most-leaf writable config, where an override wins. */
2499
+ const targetFor = (definedIn) => {
2500
+ const def = definedIn ? chain.find(c => c.path === definedIn) : null;
2501
+ return def?.writable ? def : mostLeafWritable;
2502
+ };
2503
+ // An explicit `lib` beats `target`, so it's the one that has to move.
2504
+ const lib = effectiveCompilerOption(chain, 'lib');
2505
+ if (lib && Array.isArray(lib.value)) {
2506
+ const entries = lib.value.filter((e) => typeof e === 'string');
2507
+ if (entries.some(e => esLevelRank(e) >= esLevelRank(required)))
2508
+ return false;
2509
+ const raised = [required, ...entries.filter(e => esLevelRank(e) === 0)];
2510
+ const tgt = targetFor(lib.definedIn);
2511
+ if (!edit(tgt.path, t => upsertCompilerOption(t, 'lib', JSON.stringify(raised).replace(/","/g, '", "'))))
2512
+ return false;
2513
+ console.log(colors.cyan(` Raised lib [${entries.join(', ')}] → [${raised.join(', ')}] in ${path.relative(cwd, tgt.path) || 'tsconfig.json'} (build needs ${required})`));
2514
+ recordBuildIssue(name, 'warning', `Raised "lib" to ${required} — the code uses standard-library APIs newer than the tsconfig allowed.`);
2515
+ return true;
2516
+ }
2517
+ const tg = effectiveCompilerOption(chain, 'target');
2518
+ const current = tg && typeof tg.value === 'string' ? tg.value : 'es5'; // tsc's own default
2519
+ if (esLevelRank(current) >= esLevelRank(required))
2520
+ return false;
2521
+ const tgt = targetFor(tg?.definedIn);
2522
+ if (!edit(tgt.path, t => upsertCompilerOption(t, 'target', `"${required}"`)))
2523
+ return false;
2524
+ console.log(colors.cyan(` Raised target "${current}" → "${required}" in ${path.relative(cwd, tgt.path) || 'tsconfig.json'} (build needs it)`));
2525
+ recordBuildIssue(name, 'warning', `Raised "target" ${current} → ${required} — the code uses standard-library APIs newer than the tsconfig allowed.`);
2526
+ return true;
2527
+ }
2432
2528
  /** Write back the texts captured in a `migrateTsconfigDeprecations` snapshot
2433
2529
  * (path → text). Used to undo a migration whose build turned out worse, and to
2434
2530
  * re-apply it afterwards. Returns false if any write failed — the tree is then
@@ -3052,6 +3148,16 @@ export async function buildProject(cwd, opts = {}) {
3052
3148
  buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
3053
3149
  }
3054
3150
  }
3151
+ // "Try changing the 'lib' compiler option to 'es2023' or later" — a tsconfig
3152
+ // pinned below the standard-library APIs the code actually uses. Raise it and
3153
+ // rebuild. Looped because one level can uncover the need for a higher one,
3154
+ // and bounded because each pass must raise the level to run again.
3155
+ for (let pass = 0; pass < 3 && !buildResult.success; pass++) {
3156
+ const out = (buildResult.stderr || '') + (buildResult.output || '');
3157
+ if (!raiseTsconfigLibLevel(cwd, out, tsconfigSnapshot))
3158
+ break;
3159
+ buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
3160
+ }
3055
3161
  if (!buildResult.success && migratedUpFront && tsconfigSnapshot.size) {
3056
3162
  // The up-front migration is a semantic change: `nodenext` resolution can
3057
3163
  // surface real errors (missing file extensions on relative imports) that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.203",
3
+ "version": "1.0.204",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",