@bobfrankston/npmglobalize 1.0.213 → 1.0.215
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 +4 -4
- package/cli.js +14 -1
- package/lib.d.ts +18 -2
- package/lib.js +86 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -298,10 +298,10 @@ reachable `repository.url` in `package.json`. If found, it offers two paths:
|
|
|
298
298
|
```
|
|
299
299
|
How would you like to set up git?
|
|
300
300
|
1) Adopt history from existing remote (recommended)
|
|
301
|
-
2)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
2) Adopt for ALL remaining deps (don't ask again)
|
|
302
|
+
3) Initialize fresh git repository
|
|
303
|
+
4) Use local install only (skip git/publish)
|
|
304
|
+
5) Abort
|
|
305
305
|
```
|
|
306
306
|
|
|
307
307
|
**Adopt** runs:
|
package/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* npmglobalize CLI - Transform file: dependencies to npm versions for publishing
|
|
4
4
|
*/
|
|
5
|
-
import { globalize, globalizeWorkspace, installCleanupHandlers, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, getBuildIssues, clearBuildIssues, ensureFileDepModules, buildProject, buildFileDepsTopologically, reportTs7Deprecations, fixTs7Deprecations } from './lib.js';
|
|
5
|
+
import { globalize, globalizeWorkspace, installCleanupHandlers, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, getBuildIssues, clearBuildIssues, ensureFileDepModules, buildProject, buildFileDepsTopologically, reportTs7Deprecations, fixTs7Deprecations, canonicalCase } from './lib.js';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { colors } from './colors.js';
|
|
@@ -549,6 +549,19 @@ export async function main() {
|
|
|
549
549
|
process.exit(1);
|
|
550
550
|
}
|
|
551
551
|
}
|
|
552
|
+
// 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction.
|
|
553
|
+
// `process.cwd()` carries whatever letter case the shell was started with
|
|
554
|
+
// (`y:\dev\...` from a lowercase prompt). Every `file:` junction npm writes
|
|
555
|
+
// inherits that case, and mixed `y:`/`Y:` junction targets are exactly what
|
|
556
|
+
// makes arborist crash with "Cannot read properties of null (reading
|
|
557
|
+
// 'package')" — see `caseDiffers` in lib.ts. Canonicalizing once up front
|
|
558
|
+
// means every npm the cascade spawns writes junctions with the on-disk
|
|
559
|
+
// spelling, so the repair in verifyDepPathCase stops being needed again.
|
|
560
|
+
const canonicalCwd = canonicalCase(cwd);
|
|
561
|
+
if (canonicalCwd && canonicalCwd !== cwd) {
|
|
562
|
+
cwd = canonicalCwd;
|
|
563
|
+
process.chdir(cwd);
|
|
564
|
+
}
|
|
552
565
|
// Report-only TS7-readiness scan (no transform / build / publish).
|
|
553
566
|
if (cliOptions.ts7Report) {
|
|
554
567
|
printTs7Report(cwd);
|
package/lib.d.ts
CHANGED
|
@@ -287,6 +287,16 @@ export type UnpublishedDep = {
|
|
|
287
287
|
path: string;
|
|
288
288
|
reason: 'new' | 'update';
|
|
289
289
|
};
|
|
290
|
+
/** The canonical on-disk spelling of `target`, or null if it does not exist.
|
|
291
|
+
* Windows answers path queries case-insensitively, so the only way to learn how
|
|
292
|
+
* a directory is really spelled is to ask its parent to list it — a segment at a
|
|
293
|
+
* time, from the root down. The drive letter is always uppercased.
|
|
294
|
+
*
|
|
295
|
+
* 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction: exported so cli.ts can
|
|
296
|
+
* canonicalize the working directory at startup (see there for why). Deliberately
|
|
297
|
+
* NOT `fs.realpathSync.native`: that would also resolve a junction the user is
|
|
298
|
+
* standing inside, which is a behaviour change nobody asked for. */
|
|
299
|
+
export declare function canonicalCase(target: string): string;
|
|
290
300
|
/** Vet the casing of every `file:` dependency path, in BOTH places it is recorded:
|
|
291
301
|
* the spec in package.json and the junction npm created in node_modules.
|
|
292
302
|
*
|
|
@@ -589,8 +599,14 @@ export declare function validatePackageJson(pkg: any): string[];
|
|
|
589
599
|
export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
|
|
590
600
|
/** Prompt user for free text input */
|
|
591
601
|
export declare function promptText(message: string, defaultValue?: string): Promise<string>;
|
|
592
|
-
/**
|
|
593
|
-
|
|
602
|
+
/**
|
|
603
|
+
* Prompt user for multiple choice. Re-asks until the answer is one of `choices`.
|
|
604
|
+
*
|
|
605
|
+
* 2026-09-01 — Claude Code (Fable 5.1), at Bob's direction. Previously an unrecognized
|
|
606
|
+
* answer resolved null and every caller fell through to its default action, so a typo
|
|
607
|
+
* silently did something. Now it says what it didn't understand and asks again.
|
|
608
|
+
*/
|
|
609
|
+
export declare function promptChoice(message: string, choices: string[]): Promise<string>;
|
|
594
610
|
/** Initialize a local git repo by adopting history from an existing remote.
|
|
595
611
|
* Preserves the working tree — local files appear as uncommitted changes
|
|
596
612
|
* on top of the remote's HEAD. No force-push is required. */
|
package/lib.js
CHANGED
|
@@ -1323,8 +1323,13 @@ function hasLocalChanges(packageName, version, targetPath, verbose) {
|
|
|
1323
1323
|
/** The canonical on-disk spelling of `target`, or null if it does not exist.
|
|
1324
1324
|
* Windows answers path queries case-insensitively, so the only way to learn how
|
|
1325
1325
|
* a directory is really spelled is to ask its parent to list it — a segment at a
|
|
1326
|
-
* time, from the root down.
|
|
1327
|
-
|
|
1326
|
+
* time, from the root down. The drive letter is always uppercased.
|
|
1327
|
+
*
|
|
1328
|
+
* 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction: exported so cli.ts can
|
|
1329
|
+
* canonicalize the working directory at startup (see there for why). Deliberately
|
|
1330
|
+
* NOT `fs.realpathSync.native`: that would also resolve a junction the user is
|
|
1331
|
+
* standing inside, which is a behaviour change nobody asked for. */
|
|
1332
|
+
export function canonicalCase(target) {
|
|
1328
1333
|
const full = path.resolve(target);
|
|
1329
1334
|
if (!fs.existsSync(full))
|
|
1330
1335
|
return null;
|
|
@@ -1347,10 +1352,22 @@ function canonicalCase(target) {
|
|
|
1347
1352
|
return cur;
|
|
1348
1353
|
}
|
|
1349
1354
|
/** True when two paths name the same place but are spelled with different case
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1355
|
+
* anywhere — the drive letter included.
|
|
1356
|
+
*
|
|
1357
|
+
* 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction. This used to strip the
|
|
1358
|
+
* root before comparing, on the theory that `y:` vs `Y:` "resolves identically
|
|
1359
|
+
* everywhere". It does on the filesystem; it does NOT inside npm's arborist.
|
|
1360
|
+
* Traced with an instrumented `loadActual` on dbitemx / pings / itemw: arborist
|
|
1361
|
+
* keeps the junction target's case in `realpath` (an exact-string index) but
|
|
1362
|
+
* computes `location` with `path.win32.relative`, which folds drive-letter case.
|
|
1363
|
+
* One directory reached as `Y:\...` and `y:\...` becomes two target nodes at one
|
|
1364
|
+
* location; the second clobbers the first, every Link into the first is left with
|
|
1365
|
+
* `target === null`, and the next same-case Link crashes on `target.package` —
|
|
1366
|
+
* the "Cannot read properties of null (reading 'package')" failure. npm writes
|
|
1367
|
+
* the junction target with whatever drive case the cwd had, so a lowercase shell
|
|
1368
|
+
* is all it takes. Full trace: `.llm/arborist-trace.md`. */
|
|
1352
1369
|
function caseDiffers(a, b) {
|
|
1353
|
-
const strip = (p) => p.replace(/[\\/]+$/, '')
|
|
1370
|
+
const strip = (p) => p.replace(/[\\/]+$/, '');
|
|
1354
1371
|
return strip(a) !== strip(b);
|
|
1355
1372
|
}
|
|
1356
1373
|
/** Vet the casing of every `file:` dependency path, in BOTH places it is recorded:
|
|
@@ -4995,23 +5012,46 @@ export async function promptText(message, defaultValue) {
|
|
|
4995
5012
|
});
|
|
4996
5013
|
});
|
|
4997
5014
|
}
|
|
4998
|
-
/**
|
|
4999
|
-
|
|
5015
|
+
/**
|
|
5016
|
+
* Prompt user for multiple choice. Re-asks until the answer is one of `choices`.
|
|
5017
|
+
*
|
|
5018
|
+
* 2026-09-01 — Claude Code (Fable 5.1), at Bob's direction. Previously an unrecognized
|
|
5019
|
+
* answer resolved null and every caller fell through to its default action, so a typo
|
|
5020
|
+
* silently did something. Now it says what it didn't understand and asks again.
|
|
5021
|
+
*/
|
|
5022
|
+
export function promptChoice(message, choices) {
|
|
5000
5023
|
const rl = readline.createInterface({
|
|
5001
5024
|
input: process.stdin,
|
|
5002
5025
|
output: process.stdout
|
|
5003
5026
|
});
|
|
5027
|
+
const shown = choices.filter(c => c !== '').join(', ');
|
|
5028
|
+
// A 'line' listener rather than repeated rl.question(): when stdin is piped, several
|
|
5029
|
+
// lines can arrive in one chunk and a line landing between two question() calls is
|
|
5030
|
+
// dropped. The listener sees every line.
|
|
5004
5031
|
return new Promise((resolve) => {
|
|
5005
|
-
|
|
5032
|
+
let done = false;
|
|
5033
|
+
const finish = (value) => {
|
|
5034
|
+
done = true;
|
|
5006
5035
|
rl.close();
|
|
5036
|
+
resolve(value);
|
|
5037
|
+
};
|
|
5038
|
+
rl.setPrompt(`${message} `);
|
|
5039
|
+
rl.prompt();
|
|
5040
|
+
rl.on('line', (answer) => {
|
|
5041
|
+
if (done)
|
|
5042
|
+
return;
|
|
5007
5043
|
const a = answer.trim().toLowerCase();
|
|
5008
|
-
if (choices.includes(a))
|
|
5009
|
-
|
|
5010
|
-
}
|
|
5011
|
-
|
|
5012
|
-
resolve(null);
|
|
5013
|
-
}
|
|
5044
|
+
if (choices.includes(a))
|
|
5045
|
+
return finish(a);
|
|
5046
|
+
console.log(colors.yellow(`Unrecognized choice "${a}" — expected one of: ${shown}`));
|
|
5047
|
+
rl.prompt();
|
|
5014
5048
|
});
|
|
5049
|
+
// EOF with no valid answer: behave as "just pressed Enter" ('' is the default where
|
|
5050
|
+
// a default exists; callers without one treat '' as abort).
|
|
5051
|
+
rl.on('close', () => { if (!done) {
|
|
5052
|
+
done = true;
|
|
5053
|
+
resolve('');
|
|
5054
|
+
} });
|
|
5015
5055
|
});
|
|
5016
5056
|
}
|
|
5017
5057
|
/** Check npm authentication status */
|
|
@@ -6393,51 +6433,60 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6393
6433
|
justInitialized = true;
|
|
6394
6434
|
}
|
|
6395
6435
|
else if (!init && !options.autoInit && !publishDepsYes) {
|
|
6436
|
+
// 2026-09-01 — Claude Code (Fable 5.1), at Bob's direction. These menus were
|
|
6437
|
+
// numbered "1 a 2 3": the "ALL" option was lettered and sat between 1 and 2, so
|
|
6438
|
+
// a reader who wanted the second line typed 2 and got something else. Bob:
|
|
6439
|
+
// "I type 2 when I mean a because that's what humans do." Options are now
|
|
6440
|
+
// numbered in display order; the ALL variant is 2, directly under the single.
|
|
6396
6441
|
let choice;
|
|
6397
6442
|
if (adoptable) {
|
|
6398
6443
|
choice = await promptChoice('How would you like to set up git?\n'
|
|
6399
6444
|
+ ' 1) Adopt history from existing remote (recommended)\n'
|
|
6400
|
-
+ '
|
|
6401
|
-
+ '
|
|
6402
|
-
+ '
|
|
6403
|
-
+ '
|
|
6404
|
-
if (choice === '
|
|
6445
|
+
+ ' 2) Adopt for ALL remaining deps (don\'t ask again)\n'
|
|
6446
|
+
+ ' 3) Initialize fresh git repository\n'
|
|
6447
|
+
+ ' 4) Use local install only (skip git/publish)\n'
|
|
6448
|
+
+ ' 5) Abort\nChoice [1]:', ['1', '2', '3', '4', '5', '']);
|
|
6449
|
+
if (choice === '4') {
|
|
6405
6450
|
console.log(colors.dim('Switching to local-only mode...'));
|
|
6406
6451
|
writeConfig(cwd, { ...configOptions, local: true }, new Set(['local']));
|
|
6407
6452
|
return doLocalInstall(cwd, options);
|
|
6408
6453
|
}
|
|
6409
|
-
if (choice === '
|
|
6454
|
+
if (choice === '5') {
|
|
6410
6455
|
console.log('Aborted. Run with --init to initialize.');
|
|
6411
6456
|
return false;
|
|
6412
6457
|
}
|
|
6413
|
-
if (choice === '
|
|
6458
|
+
if (choice === '2')
|
|
6414
6459
|
options.autoInit = true;
|
|
6415
|
-
if (choice === '
|
|
6460
|
+
if (choice === '3') {
|
|
6416
6461
|
const success = await initGit(cwd, gitVisibility, dryRun, allowTs);
|
|
6417
6462
|
if (!success)
|
|
6418
6463
|
return false;
|
|
6419
6464
|
}
|
|
6420
6465
|
else {
|
|
6421
|
-
// '1', '
|
|
6466
|
+
// '1', '2', '' default → adopt
|
|
6422
6467
|
const success = await adoptExistingRemote(cwd, adoptable.url, adoptable.defaultBranch, dryRun);
|
|
6423
6468
|
if (!success)
|
|
6424
6469
|
return false;
|
|
6425
6470
|
}
|
|
6426
6471
|
}
|
|
6427
6472
|
else {
|
|
6428
|
-
choice = await promptChoice('No git repository found. What would you like to do?\n
|
|
6429
|
-
|
|
6473
|
+
choice = await promptChoice('No git repository found. What would you like to do?\n'
|
|
6474
|
+
+ ' 1) Initialize git repository\n'
|
|
6475
|
+
+ ' 2) Initialize for ALL remaining deps (don\'t ask again)\n'
|
|
6476
|
+
+ ' 3) Use local install only (skip git/publish)\n'
|
|
6477
|
+
+ ' 4) Abort\nChoice [1]:', ['1', '2', '3', '4', '']);
|
|
6478
|
+
if (choice === '3') {
|
|
6430
6479
|
console.log(colors.dim('Switching to local-only mode...'));
|
|
6431
6480
|
writeConfig(cwd, { ...configOptions, local: true }, new Set(['local']));
|
|
6432
6481
|
return doLocalInstall(cwd, options);
|
|
6433
6482
|
}
|
|
6434
|
-
if (choice === '
|
|
6483
|
+
if (choice === '4') {
|
|
6435
6484
|
console.log('Aborted. Run with --init to initialize.');
|
|
6436
6485
|
return false;
|
|
6437
6486
|
}
|
|
6438
|
-
if (choice === '
|
|
6487
|
+
if (choice === '2')
|
|
6439
6488
|
options.autoInit = true;
|
|
6440
|
-
// choice is '1', '
|
|
6489
|
+
// choice is '1', '2', or '' (default) → init
|
|
6441
6490
|
const success = await initGit(cwd, gitVisibility, dryRun, allowTs);
|
|
6442
6491
|
if (!success)
|
|
6443
6492
|
return false;
|
|
@@ -6463,17 +6512,22 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6463
6512
|
else if (!gitStatus.hasRemote) {
|
|
6464
6513
|
// Git repo exists but no remote - need to create GitHub repo
|
|
6465
6514
|
if (!init && !options.autoInit && !publishDepsYes) {
|
|
6466
|
-
|
|
6467
|
-
|
|
6515
|
+
// 2026-09-01 — Claude Code (Fable 5.1): renumbered from "1 a 2 3" (see note above).
|
|
6516
|
+
const choice = await promptChoice('No git remote configured. What would you like to do?\n'
|
|
6517
|
+
+ ' 1) Create GitHub repository\n'
|
|
6518
|
+
+ ' 2) Create for ALL remaining deps (don\'t ask again)\n'
|
|
6519
|
+
+ ' 3) Use local install only (skip git/publish)\n'
|
|
6520
|
+
+ ' 4) Abort\nChoice [1]:', ['1', '2', '3', '4', '']);
|
|
6521
|
+
if (choice === '3') {
|
|
6468
6522
|
console.log(colors.dim('Switching to local-only mode...'));
|
|
6469
6523
|
writeConfig(cwd, { ...configOptions, local: true }, new Set(['local']));
|
|
6470
6524
|
return doLocalInstall(cwd, options);
|
|
6471
6525
|
}
|
|
6472
|
-
if (choice === '
|
|
6526
|
+
if (choice === '4') {
|
|
6473
6527
|
console.log('Aborted. Run with --init to set up GitHub repository.');
|
|
6474
6528
|
return false;
|
|
6475
6529
|
}
|
|
6476
|
-
if (choice === '
|
|
6530
|
+
if (choice === '2') {
|
|
6477
6531
|
options.autoInit = true;
|
|
6478
6532
|
}
|
|
6479
6533
|
}
|