@bobfrankston/npmglobalize 1.0.109 → 1.0.111
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 +3 -1
- package/cli.js +11 -0
- package/lib.d.ts +2 -0
- package/lib.js +100 -11
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -179,7 +179,8 @@ When initializing a new repository with `--init`, npmglobalize automatically set
|
|
|
179
179
|
|
|
180
180
|
**File structure** (per programming.md standards):
|
|
181
181
|
- `.gitignore` - Node.js best practices (node_modules, secrets, certificates, etc.)
|
|
182
|
-
- `.npmignore` - Publishing filters (excludes .git, tests, source files, etc.)
|
|
182
|
+
- `.npmignore` - Publishing filters (excludes .git, tests, source files, etc.)
|
|
183
|
+
- **noEmit projects:** `*.ts`, `*.map`, and `tsconfig.json` are kept (not ignored) since TS files are the runtime files
|
|
183
184
|
- `.gitattributes` - Forces LF line endings for all text files
|
|
184
185
|
|
|
185
186
|
**Git configuration** (ensures cross-platform compatibility):
|
|
@@ -298,6 +299,7 @@ Workspace mode is auto-detected when run from a root with `"private": true` and
|
|
|
298
299
|
-verbose Show detailed output
|
|
299
300
|
-conform Update .gitignore/.npmignore/.gitattributes to best practices
|
|
300
301
|
and configure git for LF line endings (fixes existing repos)
|
|
302
|
+
For noEmit projects: removes *.ts/*.map/tsconfig.json from .npmignore
|
|
301
303
|
-asis Skip ignore file checks (or set "asis": true in .globalize.json5)
|
|
302
304
|
-fix-tags Automatically fix version/tag mismatches
|
|
303
305
|
-rebase Automatically rebase if local is behind remote
|
package/cli.js
CHANGED
|
@@ -40,6 +40,8 @@ Install Options:
|
|
|
40
40
|
-link Global install via symlink (npm install -g .)
|
|
41
41
|
-local Local install only — skip transform/publish, just npm install -g .
|
|
42
42
|
-wsl Also install globally in WSL
|
|
43
|
+
-freeze Freeze node_modules (replace symlinks with real copies for network shares)
|
|
44
|
+
-nofreeze Disable freeze
|
|
43
45
|
-once Don't persist flags to .globalize.json5
|
|
44
46
|
|
|
45
47
|
Mode Options:
|
|
@@ -65,6 +67,7 @@ Other Options:
|
|
|
65
67
|
-quiet Suppress npm warnings (default)
|
|
66
68
|
-verbose Show detailed output
|
|
67
69
|
-conform Update .gitignore/.npmignore to best practices
|
|
70
|
+
(noEmit projects: removes TS ignores from .npmignore)
|
|
68
71
|
-asis Skip ignore file checks (or set "asis": true in .globalize.json5)
|
|
69
72
|
-rebase Automatically rebase if local is behind remote
|
|
70
73
|
-show Show package.json dependency changes
|
|
@@ -283,6 +286,14 @@ function parseArgs(args) {
|
|
|
283
286
|
case '-pkg':
|
|
284
287
|
options.package = true;
|
|
285
288
|
break;
|
|
289
|
+
case '-freeze':
|
|
290
|
+
options.freeze = true;
|
|
291
|
+
options.explicitKeys.add('freeze');
|
|
292
|
+
break;
|
|
293
|
+
case '-nofreeze':
|
|
294
|
+
options.freeze = false;
|
|
295
|
+
options.explicitKeys.add('freeze');
|
|
296
|
+
break;
|
|
286
297
|
case '-once':
|
|
287
298
|
options.once = true;
|
|
288
299
|
break;
|
package/lib.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export interface GlobalizeOptions {
|
|
|
74
74
|
once?: boolean;
|
|
75
75
|
/** Local install only — skip transform/publish, just npm install -g . */
|
|
76
76
|
local?: boolean;
|
|
77
|
+
/** Freeze node_modules: replace symlinks/junctions with real copies for network share use */
|
|
78
|
+
freeze?: boolean;
|
|
77
79
|
/** Internal: signals this call is from workspace orchestrator */
|
|
78
80
|
_fromWorkspace?: boolean;
|
|
79
81
|
/** Internal: signals this call is from CLI (version already printed) */
|
package/lib.js
CHANGED
|
@@ -13,6 +13,7 @@ import fs from 'fs';
|
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import { execSync, spawnSync } from 'child_process';
|
|
15
15
|
import { readConfig as readUserConfig, writeConfig as writeUserConfig, configDir } from '@bobfrankston/userconfig';
|
|
16
|
+
import { freezeDependencies } from '@bobfrankston/freezepak';
|
|
16
17
|
/** Wrapper for spawnSync that avoids DEP0190 (args + shell: true).
|
|
17
18
|
* When shell is true, joins cmd+args into a single command string. */
|
|
18
19
|
function spawnSafe(cmd, args, options = {}) {
|
|
@@ -198,7 +199,8 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
198
199
|
install: false,
|
|
199
200
|
wsl: false,
|
|
200
201
|
force: false,
|
|
201
|
-
verbose: false
|
|
202
|
+
verbose: false,
|
|
203
|
+
freeze: false
|
|
202
204
|
};
|
|
203
205
|
// Read existing config to preserve values
|
|
204
206
|
const existing = readConfig(dir);
|
|
@@ -257,6 +259,8 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
257
259
|
comment = ' // Local install only (skip transform/publish)';
|
|
258
260
|
else if (key === 'noPublish')
|
|
259
261
|
comment = ' // Transform but don\'t publish';
|
|
262
|
+
else if (key === 'freeze')
|
|
263
|
+
comment = ' // Freeze node_modules (replace symlinks with real copies)';
|
|
260
264
|
lines.push(` "${key}": ${jsonValue}${comma}${comment}`);
|
|
261
265
|
});
|
|
262
266
|
lines.push('');
|
|
@@ -276,6 +280,7 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
276
280
|
lines.push(' // "fix": false // Auto-run npm audit fix');
|
|
277
281
|
lines.push(' // "local": false // Local install only (skip transform/publish)');
|
|
278
282
|
lines.push(' // "noPublish": false // Transform but don\'t publish');
|
|
283
|
+
lines.push(' // "freeze": false // Freeze node_modules (replace symlinks with real copies)');
|
|
279
284
|
lines.push('}');
|
|
280
285
|
fs.writeFileSync(configPath, lines.join('\n') + '\n');
|
|
281
286
|
}
|
|
@@ -1333,6 +1338,29 @@ const IGNORE_PATTERNS = loadIgnorePatterns();
|
|
|
1333
1338
|
const ALL_GITIGNORE = [...IGNORE_PATTERNS.gitignore.security, ...IGNORE_PATTERNS.gitignore.recommended, ...(IGNORE_PATTERNS.gitignore.presenceOnly ?? [])];
|
|
1334
1339
|
/** All .npmignore patterns (security + recommended) */
|
|
1335
1340
|
const ALL_NPMIGNORE = [...IGNORE_PATTERNS.npmignore.security, ...IGNORE_PATTERNS.npmignore.recommended];
|
|
1341
|
+
/** Patterns that should NOT be in .npmignore for noEmit projects (TS files are the runtime files) */
|
|
1342
|
+
const TS_NPMIGNORE_PATTERNS = new Set(['*.ts', '!*.d.ts', '*.map', 'tsconfig.json']);
|
|
1343
|
+
/** Check if target project uses noEmit (TS files run directly, no compilation) */
|
|
1344
|
+
function isNoEmitProject(cwd) {
|
|
1345
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
1346
|
+
if (!fs.existsSync(tsconfigPath))
|
|
1347
|
+
return false;
|
|
1348
|
+
try {
|
|
1349
|
+
const content = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
1350
|
+
const tsconfig = JSON5.parse(content);
|
|
1351
|
+
return tsconfig.compilerOptions?.noEmit === true;
|
|
1352
|
+
}
|
|
1353
|
+
catch {
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
/** Get applicable npmignore patterns, excluding TS patterns for noEmit projects */
|
|
1358
|
+
function getApplicableNpmignorePatterns(cwd) {
|
|
1359
|
+
if (isNoEmitProject(cwd)) {
|
|
1360
|
+
return ALL_NPMIGNORE.filter(p => !TS_NPMIGNORE_PATTERNS.has(p));
|
|
1361
|
+
}
|
|
1362
|
+
return ALL_NPMIGNORE;
|
|
1363
|
+
}
|
|
1336
1364
|
/** Presence-only extensions derived from data file */
|
|
1337
1365
|
const PRESENCE_ONLY_EXTENSIONS = new Set((IGNORE_PATTERNS.gitignore.presenceOnly ?? [])
|
|
1338
1366
|
.map(p => p.match(/^\*(\.\w+)$/)?.[1])
|
|
@@ -1412,7 +1440,8 @@ function checkIgnoreFiles(cwd, options) {
|
|
|
1412
1440
|
if (fs.existsSync(npmignorePath)) {
|
|
1413
1441
|
const content = fs.readFileSync(npmignorePath, 'utf-8');
|
|
1414
1442
|
const lines = content.split('\n').map(l => l.trim());
|
|
1415
|
-
|
|
1443
|
+
const applicableNpm = getApplicableNpmignorePatterns(cwd);
|
|
1444
|
+
for (const pattern of applicableNpm) {
|
|
1416
1445
|
if (!lineHasPattern(lines, pattern)) {
|
|
1417
1446
|
if (securityNpm.has(pattern)) {
|
|
1418
1447
|
securityChanges.push(` .npmignore missing: ${pattern}`);
|
|
@@ -1447,7 +1476,8 @@ function conformIgnoreFiles(cwd, securityOnly = false) {
|
|
|
1447
1476
|
}
|
|
1448
1477
|
}
|
|
1449
1478
|
const patternsGit = securityOnly ? getSecurityGitignorePatterns() : getApplicableGitignorePatterns(cwd);
|
|
1450
|
-
const patternsNpm = securityOnly ? getSecurityNpmignorePatterns() :
|
|
1479
|
+
const patternsNpm = securityOnly ? getSecurityNpmignorePatterns() : getApplicableNpmignorePatterns(cwd);
|
|
1480
|
+
const noEmit = isNoEmitProject(cwd);
|
|
1451
1481
|
// Update .gitignore
|
|
1452
1482
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
1453
1483
|
if (fs.existsSync(gitignorePath)) {
|
|
@@ -1474,8 +1504,8 @@ function conformIgnoreFiles(cwd, securityOnly = false) {
|
|
|
1474
1504
|
const lines = content.split('\n').map(l => l.trim());
|
|
1475
1505
|
const newLines = new Set(lines.filter(l => l));
|
|
1476
1506
|
let updated = false;
|
|
1477
|
-
if (!securityOnly) {
|
|
1478
|
-
// Add TypeScript exclusions (only in full conform)
|
|
1507
|
+
if (!securityOnly && !noEmit) {
|
|
1508
|
+
// Add TypeScript exclusions (only in full conform, skip for noEmit projects)
|
|
1479
1509
|
for (const ts of ['*.ts', '!*.d.ts', '*.map']) {
|
|
1480
1510
|
if (!newLines.has(ts)) {
|
|
1481
1511
|
newLines.add(ts);
|
|
@@ -1483,6 +1513,15 @@ function conformIgnoreFiles(cwd, securityOnly = false) {
|
|
|
1483
1513
|
}
|
|
1484
1514
|
}
|
|
1485
1515
|
}
|
|
1516
|
+
// For noEmit projects, remove TS patterns that shouldn't be ignored
|
|
1517
|
+
if (noEmit) {
|
|
1518
|
+
for (const ts of TS_NPMIGNORE_PATTERNS) {
|
|
1519
|
+
if (newLines.has(ts)) {
|
|
1520
|
+
newLines.delete(ts);
|
|
1521
|
+
updated = true;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1486
1525
|
for (const pattern of patternsNpm) {
|
|
1487
1526
|
if (!newLines.has(pattern)) {
|
|
1488
1527
|
newLines.add(pattern);
|
|
@@ -1492,7 +1531,12 @@ function conformIgnoreFiles(cwd, securityOnly = false) {
|
|
|
1492
1531
|
if (updated) {
|
|
1493
1532
|
const newContent = Array.from(newLines).join('\n') + '\n';
|
|
1494
1533
|
fs.writeFileSync(npmignorePath, newContent);
|
|
1495
|
-
|
|
1534
|
+
if (noEmit) {
|
|
1535
|
+
console.log(colors.cyan(' ✓ .npmignore updated (noEmit: kept *.ts files)'));
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
console.log(colors.cyan(' ✓ Auto-added security patterns to .npmignore'));
|
|
1539
|
+
}
|
|
1496
1540
|
}
|
|
1497
1541
|
}
|
|
1498
1542
|
}
|
|
@@ -1542,7 +1586,8 @@ function ensureNpmignore(cwd) {
|
|
|
1542
1586
|
const npmignorePath = path.join(cwd, '.npmignore');
|
|
1543
1587
|
if (!fs.existsSync(npmignorePath)) {
|
|
1544
1588
|
console.log(' Creating .npmignore...');
|
|
1545
|
-
const
|
|
1589
|
+
const patterns = getApplicableNpmignorePatterns(cwd);
|
|
1590
|
+
const content = patterns.join('\n') + '\n';
|
|
1546
1591
|
fs.writeFileSync(npmignorePath, content);
|
|
1547
1592
|
console.log(colors.green(' ✓ .npmignore created'));
|
|
1548
1593
|
}
|
|
@@ -1870,7 +1915,7 @@ async function doLocalInstall(cwd, options) {
|
|
|
1870
1915
|
}
|
|
1871
1916
|
export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
1872
1917
|
const { bump = 'patch', noPublish = false, cleanup = false, install = false, link = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'private', message, conform = false, asis = false, updateDeps = false, updateMajor = false, publishDeps = true, // Default to publishing deps for safety
|
|
1873
|
-
forcePublish = false, fix = false, fixTags = false, rebase = false, show = false, local = false } = options;
|
|
1918
|
+
forcePublish = false, fix = false, fixTags = false, rebase = false, show = false, local = false, freeze = false } = options;
|
|
1874
1919
|
// Show tool version only for recursive dep calls (CLI already prints it at startup)
|
|
1875
1920
|
const toolVersion = getToolVersion();
|
|
1876
1921
|
if (!options._fromWorkspace && !options._fromCli) {
|
|
@@ -1927,6 +1972,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
1927
1972
|
settings.push('--show');
|
|
1928
1973
|
if (configOptions.local)
|
|
1929
1974
|
settings.push('-local');
|
|
1975
|
+
if (configOptions.freeze)
|
|
1976
|
+
settings.push('-freeze');
|
|
1930
1977
|
if (settings.length > 0) {
|
|
1931
1978
|
console.log(colors.dim(`Settings from .globalize.json5: ${settings.join(', ')}`));
|
|
1932
1979
|
}
|
|
@@ -2687,6 +2734,16 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2687
2734
|
}
|
|
2688
2735
|
if (noPublish) {
|
|
2689
2736
|
console.log('Transform complete (--nopublish mode).');
|
|
2737
|
+
if (freeze && !dryRun) {
|
|
2738
|
+
console.log('Freezing node_modules (replacing symlinks with real copies)...');
|
|
2739
|
+
try {
|
|
2740
|
+
freezeDependencies(cwd);
|
|
2741
|
+
console.log(colors.green('✓ node_modules frozen'));
|
|
2742
|
+
}
|
|
2743
|
+
catch (error) {
|
|
2744
|
+
console.error(colors.red(`✗ Freeze failed: ${error.message}`));
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2690
2747
|
if (files && transformResult.transformed) {
|
|
2691
2748
|
console.log('Restoring file: dependencies...');
|
|
2692
2749
|
const finalPkg = readPackageJson(cwd);
|
|
@@ -2705,6 +2762,16 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2705
2762
|
// Skip if private
|
|
2706
2763
|
if (pkg.private) {
|
|
2707
2764
|
console.log('Package is private - skipping npm publish.');
|
|
2765
|
+
if (freeze && !dryRun) {
|
|
2766
|
+
console.log('Freezing node_modules (replacing symlinks with real copies)...');
|
|
2767
|
+
try {
|
|
2768
|
+
freezeDependencies(cwd);
|
|
2769
|
+
console.log(colors.green('✓ node_modules frozen'));
|
|
2770
|
+
}
|
|
2771
|
+
catch (error) {
|
|
2772
|
+
console.error(colors.red(`✗ Freeze failed: ${error.message}`));
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2708
2775
|
if (files && transformResult.transformed) {
|
|
2709
2776
|
console.log('Restoring file: dependencies...');
|
|
2710
2777
|
const finalPkg = readPackageJson(cwd);
|
|
@@ -3283,6 +3350,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3283
3350
|
const updatedPkg = readPackageJson(cwd); // Re-read to get updated version
|
|
3284
3351
|
const pkgName = updatedPkg.name;
|
|
3285
3352
|
const pkgVersion = updatedPkg.version;
|
|
3353
|
+
let globalInstallOk = false;
|
|
3354
|
+
let wslInstallOk = false;
|
|
3286
3355
|
if (!updatedPkg.bin && (install || link || wsl)) {
|
|
3287
3356
|
const proceed = await offerAddBin(cwd, updatedPkg);
|
|
3288
3357
|
if (!proceed) {
|
|
@@ -3296,6 +3365,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3296
3365
|
if (!dryRun) {
|
|
3297
3366
|
const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
|
|
3298
3367
|
if (installResult.success) {
|
|
3368
|
+
globalInstallOk = true;
|
|
3299
3369
|
console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
|
|
3300
3370
|
}
|
|
3301
3371
|
else {
|
|
@@ -3313,6 +3383,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3313
3383
|
waitForNpmVersion(pkgName, pkgVersion);
|
|
3314
3384
|
const installResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
|
|
3315
3385
|
if (installResult.success) {
|
|
3386
|
+
globalInstallOk = true;
|
|
3316
3387
|
console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
|
|
3317
3388
|
}
|
|
3318
3389
|
else {
|
|
@@ -3332,6 +3403,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3332
3403
|
if (!dryRun) {
|
|
3333
3404
|
const wslResult = runCommand('wsl', wslArgs, { cwd, silent: false, showCommand: true });
|
|
3334
3405
|
if (wslResult.success) {
|
|
3406
|
+
wslInstallOk = true;
|
|
3335
3407
|
console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
|
|
3336
3408
|
}
|
|
3337
3409
|
else {
|
|
@@ -3343,6 +3415,20 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3343
3415
|
}
|
|
3344
3416
|
}
|
|
3345
3417
|
}
|
|
3418
|
+
// Freeze node_modules: replace symlinks/junctions with real copies
|
|
3419
|
+
if (freeze && !dryRun) {
|
|
3420
|
+
console.log('Freezing node_modules (replacing symlinks with real copies)...');
|
|
3421
|
+
try {
|
|
3422
|
+
freezeDependencies(cwd);
|
|
3423
|
+
console.log(colors.green('✓ node_modules frozen'));
|
|
3424
|
+
}
|
|
3425
|
+
catch (error) {
|
|
3426
|
+
console.error(colors.red(`✗ Freeze failed: ${error.message}`));
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
else if (freeze && dryRun) {
|
|
3430
|
+
console.log(' [dry-run] Would freeze node_modules');
|
|
3431
|
+
}
|
|
3346
3432
|
// Finalize - restore file: paths if --files mode (default)
|
|
3347
3433
|
if (files && transformResult.transformed) {
|
|
3348
3434
|
console.log('Restoring file: dependencies (--files mode)...');
|
|
@@ -3383,13 +3469,16 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3383
3469
|
console.log(` Git visibility: ${colors.green(gitVisibility.toUpperCase())}`);
|
|
3384
3470
|
}
|
|
3385
3471
|
if (link) {
|
|
3386
|
-
console.log(` Linked globally: ${colors.green('✓')}`);
|
|
3472
|
+
console.log(` Linked globally: ${globalInstallOk ? colors.green('✓') : colors.red('✗')}`);
|
|
3387
3473
|
}
|
|
3388
3474
|
else if (install) {
|
|
3389
|
-
console.log(` Installed globally: ${colors.green('✓')}`);
|
|
3475
|
+
console.log(` Installed globally: ${globalInstallOk ? colors.green('✓') : colors.red('✗')}`);
|
|
3390
3476
|
}
|
|
3391
3477
|
if (wsl) {
|
|
3392
|
-
console.log(` Installed in WSL: ${colors.green('✓')}`);
|
|
3478
|
+
console.log(` Installed in WSL: ${wslInstallOk ? colors.green('✓') : colors.red('✗')}`);
|
|
3479
|
+
}
|
|
3480
|
+
if (freeze) {
|
|
3481
|
+
console.log(` Frozen node_modules: ${colors.green('✓')}`);
|
|
3393
3482
|
}
|
|
3394
3483
|
if (files && transformResult.transformed) {
|
|
3395
3484
|
console.log(` Restored file: deps: ${colors.green('✓')}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/npmglobalize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.111",
|
|
4
4
|
"description": "Transform file: dependencies to npm versions for publishing",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"url": "https://github.com/BobFrankston/npmglobalize.git"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@bobfrankston/freezepak": "^0.1.4",
|
|
34
35
|
"@bobfrankston/userconfig": "^1.0.4",
|
|
35
36
|
"@npmcli/package-json": "^7.0.4",
|
|
36
37
|
"json5": "^2.2.3",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"access": "public"
|
|
44
45
|
},
|
|
45
46
|
".dependencies": {
|
|
47
|
+
"@bobfrankston/freezepak": "file:../freezepak",
|
|
46
48
|
"@bobfrankston/userconfig": "file:../userconfig",
|
|
47
49
|
"@npmcli/package-json": "^7.0.4",
|
|
48
50
|
"json5": "^2.2.3",
|