@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.
Files changed (5) hide show
  1. package/README.md +3 -1
  2. package/cli.js +11 -0
  3. package/lib.d.ts +2 -0
  4. package/lib.js +100 -11
  5. 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
- for (const pattern of ALL_NPMIGNORE) {
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() : ALL_NPMIGNORE;
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
- console.log(colors.cyan(' ✓ Auto-added security patterns to .npmignore'));
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 content = ALL_NPMIGNORE.join('\n') + '\n';
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.109",
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",