@bobfrankston/npmglobalize 1.0.218 → 1.0.220
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/README.md +1 -1
- package/lib.d.ts +15 -16
- package/lib.js +134 -69
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -756,7 +756,7 @@ As with import maps, these have historically been built only by a second `.vscod
|
|
|
756
756
|
Before building, `npmglobalize` looks for sub-projects, in this order:
|
|
757
757
|
|
|
758
758
|
1. `.vscode/tasks.json` has a task that runs `tsc` or `importgen` with `"options": { "cwd": "${workspaceFolder}/client" }`, or runs `tsc` with `-p`/`--project` naming a sub-directory (authoritative — it's how the project is actually built today; the task's `label` is quoted back in the prompt).
|
|
759
|
-
2. An immediate sub-directory
|
|
759
|
+
2. An immediate sub-directory that is a build of its own: a `package.json` with a `build` script or an import map to regenerate (a sub-package gets its build script set up the same way the root does — importgen, `tsc`, its own sub-projects — before the root delegates to it), or a `tsconfig.json` the root `tsconfig.json` does not already compile. A `package.json` that only holds dependencies for files the root `tsc` compiles (`whts/pdfToImg`) is not a sub-project. Build output and vendored trees (`node_modules`, `prev`, `dist`, `built`, `out`, `wwwroot`, `coverage`, `temp`, `preflight`, dot-directories) are never scanned.
|
|
760
760
|
|
|
761
761
|
Nothing is detected when the root `tsconfig.json` uses project `references` — that build graph belongs to `tsc -b` and isn't second-guessed.
|
|
762
762
|
|
package/lib.d.ts
CHANGED
|
@@ -397,16 +397,6 @@ export declare function missingDeps(pkgDir: string, pkg: any): string[];
|
|
|
397
397
|
* re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
|
|
398
398
|
* Cycle-safe via the shared `visited` set. */
|
|
399
399
|
export declare function ensureFileDepModules(cwd: string, verbose?: boolean, visited?: Set<string>): Promise<boolean>;
|
|
400
|
-
/** Cheap freshness check so the build cascade can skip packages whose output is
|
|
401
|
-
* already current. Returns true only when provably up to date:
|
|
402
|
-
* - outDir projects: newest source (.ts/.tsx/.mts/.cts + tsconfig*.json) must
|
|
403
|
-
* not be newer than the newest file under outDir.
|
|
404
|
-
* - side-by-side projects: every source must have an emitted sibling
|
|
405
|
-
* (.js/.jsx/.mjs/.cjs or declaration) at least as new.
|
|
406
|
-
* Conservative: project references, allowJs, missing outputs, or unreadable
|
|
407
|
-
* tsconfig all report stale (→ build). package.json mtime is deliberately
|
|
408
|
-
* ignored — npmglobalize itself rewrites it around every publish, which would
|
|
409
|
-
* otherwise force a rebuild on every run. */
|
|
410
400
|
export declare function isBuildUpToDate(cwd: string): boolean;
|
|
411
401
|
/** Detect whether a package is an importgen project — a browser app whose HTML
|
|
412
402
|
* carries a generated `<script type="importmap">`. Signals, in order:
|
|
@@ -430,7 +420,7 @@ export declare function ensureImportgenInBuild(cwd: string): Promise<void>;
|
|
|
430
420
|
/** A sub-directory the root build must build, and how. */
|
|
431
421
|
interface SubProject {
|
|
432
422
|
dir: string; /** Relative directory with its on-disk casing */
|
|
433
|
-
kind: 'package' | 'tsconfig'; /** package: delegate to its build script; tsconfig: `tsc -p` */
|
|
423
|
+
kind: 'package' | 'tsconfig'; /** package: has its own package.json, delegate to its build script; tsconfig: `tsc -p` */
|
|
434
424
|
reason: string; /** The signal that found it, quoted in prompts */
|
|
435
425
|
}
|
|
436
426
|
/** Sub-directories the root build must build itself. A package can hold more
|
|
@@ -452,11 +442,20 @@ export declare function detectSubProjects(cwd: string): SubProject[];
|
|
|
452
442
|
* own idea of what to compile. Declining records `subProjects: false` in
|
|
453
443
|
* .globalize.json5. */
|
|
454
444
|
export declare function ensureSubProjectsInBuild(cwd: string): Promise<void>;
|
|
455
|
-
/**
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
445
|
+
/** Make sure `cwd`'s package.json has the build script it needs: importgen
|
|
446
|
+
* for an importgen project, `tsc` for a TypeScript project (prompted), and a
|
|
447
|
+
* step per sub-project. Returns the package plus whether the script runs
|
|
448
|
+
* importgen, or null when there is nothing to build here (no package.json, no
|
|
449
|
+
* tsconfig or `noEmit` and no importgen, or the user declined the prompt). */
|
|
450
|
+
export declare function ensureBuildScript(cwd: string): Promise<{
|
|
451
|
+
pkg: any;
|
|
452
|
+
runsImportgen: boolean;
|
|
453
|
+
} | null>;
|
|
454
|
+
/** Build a single project: set up its build script (see `ensureBuildScript`),
|
|
455
|
+
* run `npm run build`, record failures. Returns true if build succeeded (or
|
|
456
|
+
* was skipped because there was nothing to build / the user declined the
|
|
457
|
+
* build-script prompt / output already up to date). Pass `forceBuild` to
|
|
458
|
+
* skip the freshness check. */
|
|
460
459
|
export declare function buildProject(cwd: string, opts?: {
|
|
461
460
|
verbose?: boolean;
|
|
462
461
|
force?: boolean;
|
package/lib.js
CHANGED
|
@@ -2888,6 +2888,35 @@ function newestMtimeUnder(dir) {
|
|
|
2888
2888
|
* tsconfig all report stale (→ build). package.json mtime is deliberately
|
|
2889
2889
|
* ignored — npmglobalize itself rewrites it around every publish, which would
|
|
2890
2890
|
* otherwise force a rebuild on every run. */
|
|
2891
|
+
/** The directories a parsed tsconfig's `exclude` keeps out of the compile, as
|
|
2892
|
+
* lower-cased relative paths. Matches on the literal path prefix (glob tails
|
|
2893
|
+
* like "tests/**" reduce to "tests"). */
|
|
2894
|
+
function tsconfigExcludedDirs(tsconfig) {
|
|
2895
|
+
return (Array.isArray(tsconfig?.exclude) ? tsconfig.exclude : [])
|
|
2896
|
+
.filter((x) => typeof x === 'string')
|
|
2897
|
+
.map((x) => x.replace(/^\.\//, '').replace(/[\\/]?\*.*$/, '').replace(/[\\/]+$/, '').toLowerCase())
|
|
2898
|
+
.filter((x) => x.length > 0 && !x.includes('*'));
|
|
2899
|
+
}
|
|
2900
|
+
/** True when the root tsconfig in `cwd` already compiles the sources under
|
|
2901
|
+
* `rel`: it exists, doesn't narrow the compile with `files`/`include`, and
|
|
2902
|
+
* doesn't `exclude` the directory. Conservative — a root that uses `include`
|
|
2903
|
+
* is treated as not covering the sub-directory. */
|
|
2904
|
+
function rootTsconfigCompiles(cwd, rel) {
|
|
2905
|
+
let tsconfig;
|
|
2906
|
+
try {
|
|
2907
|
+
tsconfig = JSON5.parse(fs.readFileSync(path.join(cwd, 'tsconfig.json'), 'utf-8'));
|
|
2908
|
+
}
|
|
2909
|
+
catch {
|
|
2910
|
+
return false;
|
|
2911
|
+
}
|
|
2912
|
+
if (Array.isArray(tsconfig.files) || Array.isArray(tsconfig.include))
|
|
2913
|
+
return false;
|
|
2914
|
+
const target = normalizeProjectPath(rel).toLowerCase();
|
|
2915
|
+
const parts = target.split('/');
|
|
2916
|
+
const excludes = tsconfigExcludedDirs(tsconfig);
|
|
2917
|
+
// Excluding a parent excludes everything under it.
|
|
2918
|
+
return !parts.some((_, i) => excludes.includes(parts.slice(0, i + 1).join('/')));
|
|
2919
|
+
}
|
|
2891
2920
|
export function isBuildUpToDate(cwd) {
|
|
2892
2921
|
let tsconfig;
|
|
2893
2922
|
try {
|
|
@@ -2901,12 +2930,8 @@ export function isBuildUpToDate(cwd) {
|
|
|
2901
2930
|
return false;
|
|
2902
2931
|
const outDir = typeof co.outDir === 'string' ? path.resolve(cwd, co.outDir) : null;
|
|
2903
2932
|
// tsconfig "exclude" dirs aren't compiled, so their .ts files never get
|
|
2904
|
-
// outputs — don't count them as sources.
|
|
2905
|
-
|
|
2906
|
-
const excludes = (Array.isArray(tsconfig.exclude) ? tsconfig.exclude : [])
|
|
2907
|
-
.filter((x) => typeof x === 'string')
|
|
2908
|
-
.map((x) => x.replace(/^\.\//, '').replace(/[\\/]?\*.*$/, '').replace(/[\\/]+$/, '').toLowerCase())
|
|
2909
|
-
.filter((x) => x.length > 0 && !x.includes('*'));
|
|
2933
|
+
// outputs — don't count them as sources.
|
|
2934
|
+
const excludes = tsconfigExcludedDirs(tsconfig);
|
|
2910
2935
|
const sources = [];
|
|
2911
2936
|
const collect = (dir, rel) => {
|
|
2912
2937
|
let entries;
|
|
@@ -3198,18 +3223,31 @@ function resolveOnDiskCase(base, rel) {
|
|
|
3198
3223
|
}
|
|
3199
3224
|
return out.length ? out.join('/') : null;
|
|
3200
3225
|
}
|
|
3201
|
-
/** Classify `rel` under `cwd`: a package
|
|
3202
|
-
*
|
|
3226
|
+
/** Classify `rel` under `cwd`: a package (its own package.json with a build
|
|
3227
|
+
* script, or an importgen project — `ensureBuildScript` sets its script up),
|
|
3228
|
+
* a plain tsconfig sub-project the root tsc doesn't compile, or null when it
|
|
3229
|
+
* is neither.
|
|
3230
|
+
* 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction. whts/pdfToImg has
|
|
3231
|
+
* a package.json that only holds puppeteer/sharp for a file the root tsc
|
|
3232
|
+
* already compiles; treating every package.json as a sub-package gave it a
|
|
3233
|
+
* `build: tsc` it never needed and a second compile under its stale tsconfig.
|
|
3234
|
+
* A package.json is a build of its own only when it has something the root
|
|
3235
|
+
* can't do — its own build script, or an import map to regenerate. */
|
|
3203
3236
|
function subProjectKind(cwd, rel) {
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3237
|
+
const dir = path.join(cwd, rel);
|
|
3238
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
3239
|
+
let pkg = {};
|
|
3240
|
+
try {
|
|
3241
|
+
pkg = readPackageJson(dir);
|
|
3242
|
+
}
|
|
3243
|
+
catch { /* unreadable — treat as having no build script */ }
|
|
3244
|
+
const hasBuild = typeof pkg?.scripts?.build === 'string' && pkg.scripts.build.trim();
|
|
3245
|
+
if (hasBuild || detectImportgen(dir))
|
|
3207
3246
|
return 'package';
|
|
3208
3247
|
}
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
return fs.existsSync(path.join(cwd, rel, 'tsconfig.json')) ? 'tsconfig' : null;
|
|
3248
|
+
if (!fs.existsSync(path.join(dir, 'tsconfig.json')))
|
|
3249
|
+
return null;
|
|
3250
|
+
return rootTsconfigCompiles(cwd, rel) ? null : 'tsconfig';
|
|
3213
3251
|
}
|
|
3214
3252
|
/** The command the root build script runs for a sub-project. */
|
|
3215
3253
|
function subProjectBuildCommand(s) {
|
|
@@ -3303,7 +3341,7 @@ export function detectSubProjects(cwd) {
|
|
|
3303
3341
|
continue;
|
|
3304
3342
|
const kind = subProjectKind(cwd, e.name);
|
|
3305
3343
|
if (kind)
|
|
3306
|
-
add(e.name, kind === 'package' ? `${e.name}/package.json
|
|
3344
|
+
add(e.name, kind === 'package' ? `${e.name}/package.json` : `${e.name}/tsconfig.json`);
|
|
3307
3345
|
}
|
|
3308
3346
|
}
|
|
3309
3347
|
catch {
|
|
@@ -3328,6 +3366,20 @@ function configuredSubProjects(cwd, config) {
|
|
|
3328
3366
|
}
|
|
3329
3367
|
return detectSubProjects(cwd);
|
|
3330
3368
|
}
|
|
3369
|
+
/** The sub-projects the root build should run, each ready to be run: a
|
|
3370
|
+
* sub-package gets its own build script set up first (importgen, tsc, its own
|
|
3371
|
+
* sub-projects — the same setup the root gets), and one that still has
|
|
3372
|
+
* nothing to build afterwards is left out, since delegating to it would only
|
|
3373
|
+
* fail with "missing script: build". */
|
|
3374
|
+
async function readySubProjects(cwd, config) {
|
|
3375
|
+
const ready = [];
|
|
3376
|
+
for (const s of configuredSubProjects(cwd, config)) {
|
|
3377
|
+
if (s.kind === 'package' && !(await ensureBuildScript(path.join(cwd, s.dir))))
|
|
3378
|
+
continue;
|
|
3379
|
+
ready.push(s);
|
|
3380
|
+
}
|
|
3381
|
+
return ready;
|
|
3382
|
+
}
|
|
3331
3383
|
/** Quote a path for a package.json script only when it needs it. */
|
|
3332
3384
|
function quoteScriptPath(p) {
|
|
3333
3385
|
return /[\s&|<>]/.test(p) ? `"${p}"` : p;
|
|
@@ -3351,14 +3403,8 @@ export async function ensureSubProjectsInBuild(cwd) {
|
|
|
3351
3403
|
const buildScript = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build.trim() : '';
|
|
3352
3404
|
if (!buildScript || !scriptRunsTsc(buildScript))
|
|
3353
3405
|
return;
|
|
3354
|
-
const
|
|
3355
|
-
|
|
3356
|
-
// importgen check the root gets, so delegating to it is enough.
|
|
3357
|
-
for (const s of subs) {
|
|
3358
|
-
if (s.kind === 'package')
|
|
3359
|
-
await ensureImportgenInBuild(path.join(cwd, s.dir));
|
|
3360
|
-
}
|
|
3361
|
-
const missing = subs.filter(s => !scriptBuildsSubProject(buildScript, s.dir));
|
|
3406
|
+
const missing = (await readySubProjects(cwd, config))
|
|
3407
|
+
.filter(s => !scriptBuildsSubProject(buildScript, s.dir));
|
|
3362
3408
|
if (missing.length === 0)
|
|
3363
3409
|
return;
|
|
3364
3410
|
const name = pkg.name || path.basename(cwd);
|
|
@@ -3367,7 +3413,7 @@ export async function ensureSubProjectsInBuild(cwd) {
|
|
|
3367
3413
|
// — a bare nested tsconfig (tests, examples) is a weaker signal, and this
|
|
3368
3414
|
// prompt rewrites a build script that currently works.
|
|
3369
3415
|
const strong = missing.every(s => s.reason.startsWith('.vscode/tasks.json') || s.reason.startsWith('listed in'));
|
|
3370
|
-
console.log(colors.yellow(`${name}'s build script doesn't
|
|
3416
|
+
console.log(colors.yellow(`${name}'s build script doesn't build ${missing.length === 1 ? 'a sub-project' : 'its sub-projects'}:`));
|
|
3371
3417
|
for (const s of missing)
|
|
3372
3418
|
console.log(colors.yellow(` ${s.dir}/ — ${s.reason}`));
|
|
3373
3419
|
const addIt = await confirm(`Set "build": "${newScript}" in ${name}'s package.json?`, strong);
|
|
@@ -3417,76 +3463,61 @@ function areSubProjectsUpToDate(cwd, subs) {
|
|
|
3417
3463
|
}
|
|
3418
3464
|
return true;
|
|
3419
3465
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3466
|
+
// 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction ("when you add
|
|
3467
|
+
// importgen for a client dir you should set up the proper package.json").
|
|
3468
|
+
// Factored out of buildProject so a sub-package (itemw/client) gets the SAME
|
|
3469
|
+
// build-script setup as the root — importgen wired in, `tsc` added when it has
|
|
3470
|
+
// a tsconfig, its own sub-projects appended — instead of a second recipe.
|
|
3471
|
+
/** Make sure `cwd`'s package.json has the build script it needs: importgen
|
|
3472
|
+
* for an importgen project, `tsc` for a TypeScript project (prompted), and a
|
|
3473
|
+
* step per sub-project. Returns the package plus whether the script runs
|
|
3474
|
+
* importgen, or null when there is nothing to build here (no package.json, no
|
|
3475
|
+
* tsconfig or `noEmit` and no importgen, or the user declined the prompt). */
|
|
3476
|
+
export async function ensureBuildScript(cwd) {
|
|
3426
3477
|
// Do this before the tsconfig check: an importgen project may be plain JS
|
|
3427
3478
|
// with no tsconfig at all, and still need its import map regenerated.
|
|
3428
3479
|
await ensureImportgenInBuild(cwd);
|
|
3429
3480
|
let shouldBuild = false;
|
|
3430
|
-
let hasTsconfig = false;
|
|
3431
3481
|
try {
|
|
3432
|
-
const
|
|
3433
|
-
const content = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
3434
|
-
const tsconfig = JSON5.parse(content);
|
|
3435
|
-
hasTsconfig = true;
|
|
3482
|
+
const tsconfig = JSON5.parse(fs.readFileSync(path.join(cwd, 'tsconfig.json'), 'utf-8'));
|
|
3436
3483
|
shouldBuild = tsconfig.compilerOptions?.noEmit !== true;
|
|
3437
3484
|
}
|
|
3438
3485
|
catch {
|
|
3439
|
-
// No tsconfig —
|
|
3486
|
+
// No tsconfig — nothing for tsc to do
|
|
3440
3487
|
}
|
|
3441
|
-
|
|
3442
|
-
// build to fail on them. A deprecated `moduleResolution` is only a *hard*
|
|
3443
|
-
// error under the tsc that flags it (TS6+, and the editor's bundled
|
|
3444
|
-
// compiler), so a package pinned to an older tsc — or one that never gets
|
|
3445
|
-
// to the build at all (noEmit, no build script, output already fresh) —
|
|
3446
|
-
// would otherwise keep the deprecation until TS7 removes it outright.
|
|
3447
|
-
// Deliberately ahead of every early return below: the tsconfig is worth
|
|
3448
|
-
// fixing whether or not this package builds. Migrating also touches
|
|
3449
|
-
// tsconfig.json, which makes the freshness check correctly read as stale.
|
|
3450
|
-
const tsconfigSnapshot = new Map();
|
|
3451
|
-
const issueMark = markBuildIssues();
|
|
3452
|
-
const migratedUpFront = hasTsconfig && migrateTsconfigDeprecations(cwd, tsconfigSnapshot);
|
|
3453
|
-
let earlyPkg;
|
|
3488
|
+
let pkg;
|
|
3454
3489
|
try {
|
|
3455
|
-
|
|
3490
|
+
pkg = readPackageJson(cwd);
|
|
3456
3491
|
}
|
|
3457
3492
|
catch {
|
|
3458
|
-
return
|
|
3493
|
+
return null;
|
|
3459
3494
|
}
|
|
3460
|
-
const runsImportgen = scriptRunsImportgen(typeof
|
|
3495
|
+
const runsImportgen = scriptRunsImportgen(typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '');
|
|
3461
3496
|
// A build script that regenerates the import map is worth running even when
|
|
3462
3497
|
// there's nothing for tsc to emit.
|
|
3463
3498
|
if (!shouldBuild && !runsImportgen)
|
|
3464
|
-
return
|
|
3465
|
-
const pkg = earlyPkg;
|
|
3499
|
+
return null;
|
|
3466
3500
|
const existingBuild = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build.trim() : '';
|
|
3467
3501
|
// A build script ensureImportgenInBuild just created for a package that had
|
|
3468
3502
|
// none still needs its tsc pass — treat importgen-only as "no build yet".
|
|
3469
3503
|
const importgenOnly = !!existingBuild && existingBuild.split(/\s*&&\s*/).every(scriptRunsImportgen);
|
|
3470
3504
|
if (shouldBuild && (!existingBuild || importgenOnly)) {
|
|
3471
|
-
//
|
|
3472
|
-
// root tsc excludes them, so a bare "tsc" would leave them unbuilt.
|
|
3473
|
-
const subs =
|
|
3505
|
+
// Build the sub-projects (service worker, client package, …) in the same
|
|
3506
|
+
// breath: the root tsc excludes them, so a bare "tsc" would leave them unbuilt.
|
|
3507
|
+
const subs = await readySubProjects(cwd, readConfig(cwd));
|
|
3474
3508
|
const tscParts = ['tsc', ...subs.map(subProjectBuildCommand)];
|
|
3475
3509
|
const newScript = [...(existingBuild ? [existingBuild] : []), ...tscParts].join(' && ');
|
|
3476
3510
|
console.log(colors.yellow(`TypeScript project has no "build" script in ${pkg.name || cwd}`));
|
|
3477
3511
|
for (const s of subs)
|
|
3478
3512
|
console.log(colors.yellow(` sub-project ${s.dir}/ — ${s.reason}`));
|
|
3479
3513
|
const addIt = await confirm(`Add "build": "${newScript}" to ${pkg.name || path.basename(cwd)}'s package.json?`, true);
|
|
3480
|
-
if (addIt)
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
pkg.scripts
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
}
|
|
3487
|
-
else {
|
|
3488
|
-
return true;
|
|
3489
|
-
}
|
|
3514
|
+
if (!addIt)
|
|
3515
|
+
return null;
|
|
3516
|
+
if (!pkg.scripts)
|
|
3517
|
+
pkg.scripts = {};
|
|
3518
|
+
pkg.scripts.build = newScript;
|
|
3519
|
+
writePackageJson(cwd, pkg);
|
|
3520
|
+
console.log(colors.green(`✓ Added "build": "${newScript}" to ${pkg.name || path.basename(cwd)}`));
|
|
3490
3521
|
}
|
|
3491
3522
|
else if (existingBuild) {
|
|
3492
3523
|
await ensureSubProjectsInBuild(cwd);
|
|
@@ -3498,10 +3529,44 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
3498
3529
|
catch { /* keep the script we already have */ }
|
|
3499
3530
|
}
|
|
3500
3531
|
ensureTsconfigNodeTypes(cwd);
|
|
3532
|
+
return { pkg, runsImportgen };
|
|
3533
|
+
}
|
|
3534
|
+
/** Build a single project: set up its build script (see `ensureBuildScript`),
|
|
3535
|
+
* run `npm run build`, record failures. Returns true if build succeeded (or
|
|
3536
|
+
* was skipped because there was nothing to build / the user declined the
|
|
3537
|
+
* build-script prompt / output already up to date). Pass `forceBuild` to
|
|
3538
|
+
* skip the freshness check. */
|
|
3539
|
+
export async function buildProject(cwd, opts = {}) {
|
|
3540
|
+
// Fix removed-in-TS7 tsconfig settings up front rather than waiting for a
|
|
3541
|
+
// build to fail on them. A deprecated `moduleResolution` is only a *hard*
|
|
3542
|
+
// error under the tsc that flags it (TS6+, and the editor's bundled
|
|
3543
|
+
// compiler), so a package pinned to an older tsc — or one that never gets
|
|
3544
|
+
// to the build at all (noEmit, no build script, output already fresh) —
|
|
3545
|
+
// would otherwise keep the deprecation until TS7 removes it outright.
|
|
3546
|
+
// Deliberately ahead of every early return below: the tsconfig is worth
|
|
3547
|
+
// fixing whether or not this package builds. Migrating also touches
|
|
3548
|
+
// tsconfig.json, which makes the freshness check correctly read as stale.
|
|
3549
|
+
const tsconfigSnapshot = new Map();
|
|
3550
|
+
const issueMark = markBuildIssues();
|
|
3551
|
+
let migratedUpFront = fs.existsSync(path.join(cwd, 'tsconfig.json')) && migrateTsconfigDeprecations(cwd, tsconfigSnapshot);
|
|
3552
|
+
const setup = await ensureBuildScript(cwd);
|
|
3553
|
+
if (!setup)
|
|
3554
|
+
return true;
|
|
3555
|
+
const { pkg, runsImportgen } = setup;
|
|
3501
3556
|
// Only the sub-projects the final build script really compiles count toward
|
|
3502
3557
|
// freshness — one the user declined to wire in isn't this build's business.
|
|
3503
3558
|
const finalBuild = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '';
|
|
3504
3559
|
const wiredSubs = detectSubProjects(cwd).filter(s => scriptBuildsSubProject(finalBuild, s.dir));
|
|
3560
|
+
// 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction.
|
|
3561
|
+
// The sub-projects this build runs get the same TS7 migration as the root.
|
|
3562
|
+
// whts: `tsc -p pdfToImg` failed on pdfToImg/tsconfig.json's
|
|
3563
|
+
// `moduleResolution: node10` (TS5107) and the retry below re-migrated only
|
|
3564
|
+
// the root chain, which had nothing left to fix — so no retry ever happened.
|
|
3565
|
+
const migrateSubProjects = () => wiredSubs
|
|
3566
|
+
.map(s => migrateTsconfigDeprecations(path.join(cwd, s.dir), tsconfigSnapshot))
|
|
3567
|
+
.some(Boolean);
|
|
3568
|
+
if (migrateSubProjects())
|
|
3569
|
+
migratedUpFront = true;
|
|
3505
3570
|
// A migration just changed how modules resolve — never report that as fresh;
|
|
3506
3571
|
// the rebuild is what validates it (and what the revert path above needs).
|
|
3507
3572
|
if (!opts.forceBuild && !migratedUpFront && isBuildUpToDate(cwd) && areSubProjectsUpToDate(cwd, wiredSubs)
|
|
@@ -3517,7 +3582,7 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
3517
3582
|
// We don't silence with ignoreDeprecations — the retry may now surface real
|
|
3518
3583
|
// resolution errors, which are genuine bugs to fix, not deprecations.
|
|
3519
3584
|
const out = (buildResult.stderr || '') + (buildResult.output || '');
|
|
3520
|
-
if (/error TS510[17]\b/.test(out) && migrateTsconfigDeprecations(cwd, tsconfigSnapshot)) {
|
|
3585
|
+
if (/error TS510[17]\b/.test(out) && [migrateTsconfigDeprecations(cwd, tsconfigSnapshot), migrateSubProjects()].some(Boolean)) {
|
|
3521
3586
|
buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
3522
3587
|
}
|
|
3523
3588
|
}
|