@bobfrankston/npmglobalize 1.0.201 → 1.0.203
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 +162 -60
- package/package.json +1 -1
package/lib.js
CHANGED
|
@@ -42,6 +42,16 @@ const _buildIssues = [];
|
|
|
42
42
|
export function recordBuildIssue(module, severity, message) {
|
|
43
43
|
_buildIssues.push({ module, severity, message });
|
|
44
44
|
}
|
|
45
|
+
/** How many issues have been recorded so far — pair with `dropBuildIssuesFrom`
|
|
46
|
+
* to un-record issues for an action that was subsequently undone. */
|
|
47
|
+
function markBuildIssues() {
|
|
48
|
+
return _buildIssues.length;
|
|
49
|
+
}
|
|
50
|
+
/** Drop every issue recorded after `mark` (see `markBuildIssues`). */
|
|
51
|
+
function dropBuildIssuesFrom(mark) {
|
|
52
|
+
if (mark >= 0 && mark < _buildIssues.length)
|
|
53
|
+
_buildIssues.length = mark;
|
|
54
|
+
}
|
|
45
55
|
/** Get all accumulated build issues */
|
|
46
56
|
export function getBuildIssues() {
|
|
47
57
|
return _buildIssues;
|
|
@@ -219,6 +229,33 @@ export function readConfig(dir) {
|
|
|
219
229
|
return {};
|
|
220
230
|
}
|
|
221
231
|
}
|
|
232
|
+
/** Inline comments for boolean settings in .globalize.json5. Value-aware: a
|
|
233
|
+
* negated setting must not read as though it still does the thing (a
|
|
234
|
+
* `"quiet": false` line saying "Suppress npm warnings" describes the
|
|
235
|
+
* opposite of what it does). */
|
|
236
|
+
const BOOL_COMMENTS = {
|
|
237
|
+
install: { on: 'Auto-install globally after publish (from registry)', off: 'Do NOT auto-install globally after publish' },
|
|
238
|
+
link: { on: 'Install globally via symlink (npm install -g .)', off: 'Do NOT install globally via symlink' },
|
|
239
|
+
wsl: { on: 'Also install in WSL', off: 'Do NOT install in WSL' },
|
|
240
|
+
files: { on: 'Restore file: paths after publish', off: 'Leave npm versions in package.json after publish' },
|
|
241
|
+
force: { on: 'Continue despite git errors', off: 'Stop on git errors' },
|
|
242
|
+
quiet: { on: 'Suppress npm warnings', off: 'Show npm warnings' },
|
|
243
|
+
verbose: { on: 'Show detailed output', off: 'Normal output detail' },
|
|
244
|
+
fix: { on: 'Auto-run npm audit fix', off: 'Do NOT run npm audit fix' },
|
|
245
|
+
local: { on: 'Local install only (skip transform/publish)', off: 'Normal transform/publish flow' },
|
|
246
|
+
noPublish: { on: 'Transform but don\'t publish', off: 'Publish normally' },
|
|
247
|
+
freeze: { on: 'Freeze node_modules (replace symlinks with real copies)', off: 'Leave node_modules symlinks as-is' },
|
|
248
|
+
usePaths: { on: 'Resolve file: deps from sibling checkouts', off: 'Resolve file: deps from npm, not siblings (standalone package)' },
|
|
249
|
+
allowTs: { on: 'Keep .ts source (and *.map, tsconfig.json) in the published npm tarball', off: 'Strip .ts source from the published npm tarball' },
|
|
250
|
+
importgen: { on: 'Regenerate browser import maps', off: 'Never ask about importgen here' }
|
|
251
|
+
};
|
|
252
|
+
/** Inline comments for settings whose value isn't a plain boolean. */
|
|
253
|
+
const VALUE_COMMENTS = {
|
|
254
|
+
gitVisibility: 'private (default) or public',
|
|
255
|
+
npmVisibility: 'private (default) or public',
|
|
256
|
+
bump: 'patch (default), minor, or major',
|
|
257
|
+
subProjects: 'Sub-dirs with their own tsconfig that build must compile (false = never ask)'
|
|
258
|
+
};
|
|
222
259
|
/** Write .globalize.json5 config file */
|
|
223
260
|
export function writeConfig(dir, config, explicitKeys) {
|
|
224
261
|
const configPath = path.join(dir, '.globalize.json5');
|
|
@@ -284,68 +321,51 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
284
321
|
}
|
|
285
322
|
const jsonValue = typeof value === 'string' ? `"${value}"` : JSON.stringify(value);
|
|
286
323
|
const comma = ','; // JSON5 allows trailing commas
|
|
287
|
-
// Add inline comment for clarity
|
|
324
|
+
// Add inline comment for clarity, keyed off the actual value so a
|
|
325
|
+
// negated boolean doesn't describe the behaviour it turns off.
|
|
288
326
|
let comment = '';
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
else if (key
|
|
294
|
-
comment =
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
comment =
|
|
299
|
-
|
|
300
|
-
comment = ' // Suppress npm warnings';
|
|
301
|
-
else if (key === 'verbose')
|
|
302
|
-
comment = ' // Show detailed output';
|
|
303
|
-
else if (key === 'gitVisibility')
|
|
304
|
-
comment = ' // private (default) or public';
|
|
305
|
-
else if (key === 'npmVisibility')
|
|
306
|
-
comment = ' // private (default) or public';
|
|
307
|
-
else if (key === 'bump')
|
|
308
|
-
comment = ' // patch (default), minor, or major';
|
|
309
|
-
else if (key === 'fix')
|
|
310
|
-
comment = ' // Auto-run npm audit fix';
|
|
311
|
-
else if (key === 'local')
|
|
312
|
-
comment = ' // Local install only (skip transform/publish)';
|
|
313
|
-
else if (key === 'noPublish')
|
|
314
|
-
comment = ' // Transform but don\'t publish';
|
|
315
|
-
else if (key === 'freeze')
|
|
316
|
-
comment = ' // Freeze node_modules (replace symlinks with real copies)';
|
|
317
|
-
else if (key === 'usePaths')
|
|
318
|
-
comment = ' // Resolve file: deps from sibling checkouts (set false for standalone packages)';
|
|
319
|
-
else if (key === 'allowTs')
|
|
320
|
-
comment = ' // Keep .ts source (and *.map, tsconfig.json) in the published npm tarball';
|
|
321
|
-
else if (key === 'importgen')
|
|
322
|
-
comment = ' // Regenerate browser import maps (false = never ask about importgen here)';
|
|
323
|
-
else if (key === 'subProjects')
|
|
324
|
-
comment = ' // Sub-dirs with their own tsconfig that build must compile (false = never ask)';
|
|
327
|
+
const boolComment = BOOL_COMMENTS[key];
|
|
328
|
+
if (boolComment && typeof value === 'boolean') {
|
|
329
|
+
comment = ` // ${value ? boolComment.on : boolComment.off}`;
|
|
330
|
+
}
|
|
331
|
+
else if (VALUE_COMMENTS[key]) {
|
|
332
|
+
comment = ` // ${VALUE_COMMENTS[key]}`;
|
|
333
|
+
}
|
|
334
|
+
else if (boolComment) {
|
|
335
|
+
// Non-boolean value for a normally-boolean key (e.g. subProjects: ["Sw"])
|
|
336
|
+
comment = ` // ${boolComment.on}`;
|
|
337
|
+
}
|
|
325
338
|
lines.push(` "${key}": ${jsonValue}${comma}${comment}`);
|
|
326
339
|
});
|
|
327
340
|
lines.push('');
|
|
328
341
|
}
|
|
329
|
-
// Add commented reference for all options
|
|
342
|
+
// Add commented reference for all options. Booleans are phrased "true = ..."
|
|
343
|
+
// so the description reads correctly next to a default of false.
|
|
330
344
|
lines.push(' // Defaults (omitted above):');
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
345
|
+
const reference = [
|
|
346
|
+
['"bump": "patch"', 'Version bump: patch, minor, major'],
|
|
347
|
+
['"install": false', 'true = auto-install globally after publish (from registry)'],
|
|
348
|
+
['"link": false', 'true = install globally via symlink (npm install -g .)'],
|
|
349
|
+
['"wsl": false', 'true = also install in WSL'],
|
|
350
|
+
['"files": true', 'true = restore file: paths after publish; false = leave npm versions in package.json'],
|
|
351
|
+
['"force": false', 'true = continue despite git errors'],
|
|
352
|
+
['"quiet": true', 'true = suppress npm warnings'],
|
|
353
|
+
['"verbose": false', 'true = show detailed output'],
|
|
354
|
+
['"gitVisibility": "private"', 'Git repo: private or public'],
|
|
355
|
+
['"npmVisibility": "private"', 'npm package: private or public'],
|
|
356
|
+
['"fix": false', 'true = auto-run npm audit fix'],
|
|
357
|
+
['"local": false', 'true = local install only (skip transform/publish)'],
|
|
358
|
+
['"noPublish": false', 'true = transform but don\'t publish'],
|
|
359
|
+
['"freeze": false', 'true = freeze node_modules (replace symlinks with real copies)'],
|
|
360
|
+
['"usePaths": true', 'true = resolve file: deps from siblings; false = use latest npm version (standalone)'],
|
|
361
|
+
['"allowTs": false', 'true = include .ts source in npm tarball (auto-true for noEmit projects)'],
|
|
362
|
+
['"importgen": auto', 'Detected from .vscode/tasks.json / import map in HTML; false = never ask'],
|
|
363
|
+
['"subProjects": auto', 'Sub-dirs with their own tsconfig (e.g. ["Sw"]); detected from .vscode/tasks.json; false = never ask']
|
|
364
|
+
];
|
|
365
|
+
const refWidth = Math.max(...reference.map(([decl]) => decl.length));
|
|
366
|
+
for (const [decl, note] of reference) {
|
|
367
|
+
lines.push(` // ${decl.padEnd(refWidth)} // ${note}`);
|
|
368
|
+
}
|
|
349
369
|
lines.push('}');
|
|
350
370
|
fs.writeFileSync(configPath, lines.join('\n') + '\n');
|
|
351
371
|
}
|
|
@@ -2313,8 +2333,12 @@ function ensureTsconfigNodeTypes(cwd) {
|
|
|
2313
2333
|
* most-leaf writable config so it wins. This is a *semantic* change: the rebuild
|
|
2314
2334
|
* may now surface genuine resolution errors — that is the point. Those are real
|
|
2315
2335
|
* bugs the `node10` resolver was hiding, not deprecations. Returns true if it
|
|
2316
|
-
* changed any file.
|
|
2317
|
-
|
|
2336
|
+
* changed any file.
|
|
2337
|
+
*
|
|
2338
|
+
* Pass `snapshot` to capture each touched file's original text (path → text)
|
|
2339
|
+
* before it is rewritten, so the caller can put the tsconfigs back if the
|
|
2340
|
+
* migrated build turns out to be worse than the un-migrated one. */
|
|
2341
|
+
function migrateTsconfigDeprecations(cwd, snapshot) {
|
|
2318
2342
|
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
2319
2343
|
if (!fs.existsSync(tsconfigPath))
|
|
2320
2344
|
return false;
|
|
@@ -2348,6 +2372,8 @@ function migrateTsconfigDeprecations(cwd) {
|
|
|
2348
2372
|
return false;
|
|
2349
2373
|
try {
|
|
2350
2374
|
fs.writeFileSync(filePath, patched);
|
|
2375
|
+
if (snapshot && !snapshot.has(filePath))
|
|
2376
|
+
snapshot.set(filePath, text);
|
|
2351
2377
|
return true;
|
|
2352
2378
|
}
|
|
2353
2379
|
catch (error) {
|
|
@@ -2403,6 +2429,35 @@ function migrateTsconfigDeprecations(cwd) {
|
|
|
2403
2429
|
}
|
|
2404
2430
|
return changed;
|
|
2405
2431
|
}
|
|
2432
|
+
/** Write back the texts captured in a `migrateTsconfigDeprecations` snapshot
|
|
2433
|
+
* (path → text). Used to undo a migration whose build turned out worse, and to
|
|
2434
|
+
* re-apply it afterwards. Returns false if any write failed — the tree is then
|
|
2435
|
+
* in a mixed state, which the caller reports rather than hides. */
|
|
2436
|
+
function restoreTsconfigTexts(texts) {
|
|
2437
|
+
let ok = true;
|
|
2438
|
+
for (const [filePath, text] of texts) {
|
|
2439
|
+
try {
|
|
2440
|
+
fs.writeFileSync(filePath, text);
|
|
2441
|
+
}
|
|
2442
|
+
catch (error) {
|
|
2443
|
+
ok = false;
|
|
2444
|
+
console.error(colors.yellow(` Could not restore tsconfig (${filePath}): ${error.message}`));
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
return ok;
|
|
2448
|
+
}
|
|
2449
|
+
/** Current text of every file in a snapshot, so a migration can be re-applied
|
|
2450
|
+
* after being tried in reverse. */
|
|
2451
|
+
function readTsconfigTexts(paths) {
|
|
2452
|
+
const out = new Map();
|
|
2453
|
+
for (const p of paths) {
|
|
2454
|
+
try {
|
|
2455
|
+
out.set(p, fs.readFileSync(p, 'utf-8'));
|
|
2456
|
+
}
|
|
2457
|
+
catch { /* gone — nothing to re-apply */ }
|
|
2458
|
+
}
|
|
2459
|
+
return out;
|
|
2460
|
+
}
|
|
2406
2461
|
/** Newest mtime of any regular file under `dir` (recursive). Symlinks/junctions
|
|
2407
2462
|
* are not followed — the file: dep webs here link back into sibling checkouts. */
|
|
2408
2463
|
function newestMtimeUnder(dir) {
|
|
@@ -2903,15 +2958,29 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
2903
2958
|
// with no tsconfig at all, and still need its import map regenerated.
|
|
2904
2959
|
await ensureImportgenInBuild(cwd);
|
|
2905
2960
|
let shouldBuild = false;
|
|
2961
|
+
let hasTsconfig = false;
|
|
2906
2962
|
try {
|
|
2907
2963
|
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
2908
2964
|
const content = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
2909
2965
|
const tsconfig = JSON5.parse(content);
|
|
2966
|
+
hasTsconfig = true;
|
|
2910
2967
|
shouldBuild = tsconfig.compilerOptions?.noEmit !== true;
|
|
2911
2968
|
}
|
|
2912
2969
|
catch {
|
|
2913
2970
|
// No tsconfig — skip
|
|
2914
2971
|
}
|
|
2972
|
+
// Fix removed-in-TS7 tsconfig settings up front rather than waiting for a
|
|
2973
|
+
// build to fail on them. A deprecated `moduleResolution` is only a *hard*
|
|
2974
|
+
// error under the tsc that flags it (TS6+, and the editor's bundled
|
|
2975
|
+
// compiler), so a package pinned to an older tsc — or one that never gets
|
|
2976
|
+
// to the build at all (noEmit, no build script, output already fresh) —
|
|
2977
|
+
// would otherwise keep the deprecation until TS7 removes it outright.
|
|
2978
|
+
// Deliberately ahead of every early return below: the tsconfig is worth
|
|
2979
|
+
// fixing whether or not this package builds. Migrating also touches
|
|
2980
|
+
// tsconfig.json, which makes the freshness check correctly read as stale.
|
|
2981
|
+
const tsconfigSnapshot = new Map();
|
|
2982
|
+
const issueMark = markBuildIssues();
|
|
2983
|
+
const migratedUpFront = hasTsconfig && migrateTsconfigDeprecations(cwd, tsconfigSnapshot);
|
|
2915
2984
|
let earlyPkg;
|
|
2916
2985
|
try {
|
|
2917
2986
|
earlyPkg = readPackageJson(cwd);
|
|
@@ -2964,7 +3033,9 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
2964
3033
|
// freshness — one the user declined to wire in isn't this build's business.
|
|
2965
3034
|
const finalBuild = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '';
|
|
2966
3035
|
const wiredSubs = detectSubProjects(cwd).filter(s => scriptBuildsSubProject(finalBuild, s.dir));
|
|
2967
|
-
|
|
3036
|
+
// A migration just changed how modules resolve — never report that as fresh;
|
|
3037
|
+
// the rebuild is what validates it (and what the revert path above needs).
|
|
3038
|
+
if (!opts.forceBuild && !migratedUpFront && isBuildUpToDate(cwd) && areSubProjectsUpToDate(cwd, wiredSubs)
|
|
2968
3039
|
&& (!runsImportgen || isImportMapUpToDate(cwd))) {
|
|
2969
3040
|
console.log(colors.dim(`– Build up to date (${pkg.name || path.basename(cwd)})`));
|
|
2970
3041
|
return true;
|
|
@@ -2977,10 +3048,41 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
2977
3048
|
// We don't silence with ignoreDeprecations — the retry may now surface real
|
|
2978
3049
|
// resolution errors, which are genuine bugs to fix, not deprecations.
|
|
2979
3050
|
const out = (buildResult.stderr || '') + (buildResult.output || '');
|
|
2980
|
-
if (/error TS510[17]\b/.test(out) && migrateTsconfigDeprecations(cwd)) {
|
|
3051
|
+
if (/error TS510[17]\b/.test(out) && migrateTsconfigDeprecations(cwd, tsconfigSnapshot)) {
|
|
2981
3052
|
buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
2982
3053
|
}
|
|
2983
3054
|
}
|
|
3055
|
+
if (!buildResult.success && migratedUpFront && tsconfigSnapshot.size) {
|
|
3056
|
+
// The up-front migration is a semantic change: `nodenext` resolution can
|
|
3057
|
+
// surface real errors (missing file extensions on relative imports) that
|
|
3058
|
+
// `node10` was hiding. Those are genuine bugs — but they are not this
|
|
3059
|
+
// publish's business, and silently breaking a build that worked a moment
|
|
3060
|
+
// ago is worse than leaving the deprecation in place. So put the
|
|
3061
|
+
// tsconfigs back, and if that restores a working build, hand the user
|
|
3062
|
+
// the errors plus the command that migrates for real.
|
|
3063
|
+
const migratedOutput = (buildResult.stderr || '') + (buildResult.output || '');
|
|
3064
|
+
const migratedTexts = readTsconfigTexts(tsconfigSnapshot.keys());
|
|
3065
|
+
if (restoreTsconfigTexts(tsconfigSnapshot)) {
|
|
3066
|
+
const retry = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
3067
|
+
if (retry.success) {
|
|
3068
|
+
dropBuildIssuesFrom(issueMark); // the migration was undone; its warnings no longer apply
|
|
3069
|
+
const firstErr = extractFirstTscError(migratedOutput);
|
|
3070
|
+
const label = pkg.name || path.basename(cwd);
|
|
3071
|
+
console.log(colors.yellow(` Reverted the tsconfig TS7 migration in ${label} — it broke the build:`));
|
|
3072
|
+
if (firstErr)
|
|
3073
|
+
console.log(colors.yellow(` ${firstErr}`));
|
|
3074
|
+
console.log(colors.yellow(` Deprecated settings left in place. Fix with: npmglobalize ${cwd} -tsfix`));
|
|
3075
|
+
recordBuildIssue(label, 'warning', `tsconfig still uses settings TypeScript 7 removes: migrating to "nodenext" broke the build (${firstErr || 'see build output'}). Run \`npmglobalize ${cwd} -tsfix\` and fix the surfaced imports.`);
|
|
3076
|
+
buildResult = retry;
|
|
3077
|
+
}
|
|
3078
|
+
else {
|
|
3079
|
+
// The build was already failing for its own reasons — the migration
|
|
3080
|
+
// isn't what broke it, so keep the migrated (TS7-ready) tsconfigs
|
|
3081
|
+
// and report the migrated build's failure.
|
|
3082
|
+
restoreTsconfigTexts(migratedTexts);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
2984
3086
|
if (buildResult.success) {
|
|
2985
3087
|
console.log(colors.green(`✓ Build succeeded (${pkg.name || path.basename(cwd)})`));
|
|
2986
3088
|
return true;
|