@bobfrankston/npmglobalize 1.0.101 → 1.0.103

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 (2) hide show
  1. package/lib.js +95 -12
  2. package/package.json +1 -1
package/lib.js CHANGED
@@ -752,6 +752,47 @@ export function transformDeps(pkg, baseDir, verbose = false, forcePublish = fals
752
752
  }
753
753
  return { transformed, unpublished };
754
754
  }
755
+ /** Build and print a dependency tree of file: references.
756
+ * Recursively walks file: deps showing the full hierarchy with indentation. */
757
+ function printDepTree(baseDir, indent = 0, visited = new Set()) {
758
+ const realDir = fs.realpathSync(baseDir);
759
+ if (visited.has(realDir))
760
+ return;
761
+ visited.add(realDir);
762
+ let pkg;
763
+ try {
764
+ pkg = readPackageJson(baseDir);
765
+ }
766
+ catch {
767
+ return;
768
+ }
769
+ for (const key of DEP_KEYS) {
770
+ if (!pkg[key])
771
+ continue;
772
+ // Use the backup (.dependencies) if present — it has the original file: refs
773
+ const deps = pkg['.' + key] || pkg[key];
774
+ for (const [name, value] of Object.entries(deps)) {
775
+ if (!isFileRef(value))
776
+ continue;
777
+ const prefix = ' '.repeat(indent * 2);
778
+ let targetPath;
779
+ let version = '?';
780
+ try {
781
+ targetPath = resolveFilePath(value, baseDir);
782
+ const targetPkg = readPackageJson(targetPath);
783
+ version = targetPkg.version || '?';
784
+ }
785
+ catch {
786
+ console.log(`${prefix} ${name} → ${value} ${colors.red('(unresolvable)')}`);
787
+ continue;
788
+ }
789
+ const exists = checkVersionExists(name, version);
790
+ const marker = exists ? colors.green('✓') : colors.red('✗');
791
+ console.log(`${prefix} ${marker} ${name}@${version} → ${value}`);
792
+ printDepTree(targetPath, indent + 1, visited);
793
+ }
794
+ }
795
+ }
755
796
  /** Restore file: dependencies from .dependencies */
756
797
  export function restoreDeps(pkg, verbose = false) {
757
798
  let restored = false;
@@ -1301,6 +1342,37 @@ const RECOMMENDED_GITIGNORE = [
1301
1342
  '*.ldf',
1302
1343
  '*.ndf'
1303
1344
  ];
1345
+ /** Extensions only recommended for .gitignore when matching files exist in the project */
1346
+ const PRESENCE_ONLY_EXTENSIONS = new Set(['.mdf', '.ldf', '.ndf']);
1347
+ /** Check if any files with the given extension exist in dir (skips node_modules, .git, prev) */
1348
+ function hasFilesWithExtension(dir, ext) {
1349
+ const skip = new Set(['node_modules', '.git', 'prev']);
1350
+ function check(d) {
1351
+ try {
1352
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
1353
+ if (skip.has(entry.name))
1354
+ continue;
1355
+ if (entry.isFile() && entry.name.endsWith(ext))
1356
+ return true;
1357
+ if (entry.isDirectory() && check(path.join(d, entry.name)))
1358
+ return true;
1359
+ }
1360
+ }
1361
+ catch { }
1362
+ return false;
1363
+ }
1364
+ return check(dir);
1365
+ }
1366
+ /** Filter RECOMMENDED_GITIGNORE to skip presence-only patterns when no matching files exist */
1367
+ function getApplicableGitignorePatterns(cwd) {
1368
+ return RECOMMENDED_GITIGNORE.filter(pattern => {
1369
+ const extMatch = pattern.match(/^\*(\.\w+)$/);
1370
+ if (extMatch && PRESENCE_ONLY_EXTENSIONS.has(extMatch[1])) {
1371
+ return hasFilesWithExtension(cwd, extMatch[1]);
1372
+ }
1373
+ return true;
1374
+ });
1375
+ }
1304
1376
  /** Recommended .npmignore patterns */
1305
1377
  const RECOMMENDED_NPMIGNORE = [
1306
1378
  '.git/',
@@ -1341,7 +1413,7 @@ function checkIgnoreFiles(cwd, options) {
1341
1413
  if (fs.existsSync(gitignorePath)) {
1342
1414
  const content = fs.readFileSync(gitignorePath, 'utf-8');
1343
1415
  const lines = content.split('\n').map(l => l.trim());
1344
- for (const pattern of RECOMMENDED_GITIGNORE) {
1416
+ for (const pattern of getApplicableGitignorePatterns(cwd)) {
1345
1417
  if (!lines.some(line => line === pattern || line === pattern.replace('/', ''))) {
1346
1418
  changes.push(` .gitignore missing: ${pattern}`);
1347
1419
  }
@@ -1395,7 +1467,7 @@ function conformIgnoreFiles(cwd) {
1395
1467
  const content = fs.readFileSync(gitignorePath, 'utf-8');
1396
1468
  const lines = new Set(content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#')));
1397
1469
  let updated = false;
1398
- for (const pattern of RECOMMENDED_GITIGNORE) {
1470
+ for (const pattern of getApplicableGitignorePatterns(cwd)) {
1399
1471
  const normalized = pattern.replace('/', '');
1400
1472
  if (!lines.has(pattern) && !lines.has(normalized)) {
1401
1473
  lines.add(pattern);
@@ -1468,9 +1540,9 @@ function ensureGitignore(cwd) {
1468
1540
  // Update .gitignore if needed
1469
1541
  if (needsUpdate) {
1470
1542
  if (!content || content.trim() === '') {
1471
- // Create new .gitignore from RECOMMENDED_GITIGNORE plus extras
1543
+ // Create new .gitignore from applicable patterns plus extras
1472
1544
  const extras = ['*certs*/', 'configuration.json', 'cruft/', 'prev/', 'tests/'];
1473
- content = [...RECOMMENDED_GITIGNORE, ...extras].join('\n') + '\n';
1545
+ content = [...getApplicableGitignorePatterns(cwd), ...extras].join('\n') + '\n';
1474
1546
  }
1475
1547
  else {
1476
1548
  // Add node_modules to existing .gitignore
@@ -2477,13 +2549,16 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2477
2549
  console.log('');
2478
2550
  }
2479
2551
  // Check if target packages need to be published
2480
- if (transformResult.unpublished.length > 0) {
2552
+ if (transformResult.unpublished.length > 0 && !noPublish) {
2481
2553
  console.log('');
2482
2554
  console.log(colors.red('⚠ WARNING: Some file: dependencies are not published on npm:'));
2483
2555
  for (const { name, version, path } of transformResult.unpublished) {
2484
2556
  console.log(colors.yellow(` ${name}@${version} (${path})`));
2485
2557
  }
2486
2558
  console.log('');
2559
+ console.log('Dependency tree (✓ = on npm, ✗ = needs publishing):');
2560
+ printDepTree(cwd);
2561
+ console.log('');
2487
2562
  if (publishDeps) {
2488
2563
  const action = forcePublish ? 'Publishing/updating' : 'Publishing';
2489
2564
  console.log(`${action} file: dependencies first (--publish-deps)...`);
@@ -2508,7 +2583,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2508
2583
  fix,
2509
2584
  conform, // Propagate conform to dependencies
2510
2585
  publishDeps, // Propagate so transitive deps also get published
2511
- forcePublish // Propagate so transitive deps get force-published too
2586
+ forcePublish, // Propagate so transitive deps get force-published too
2587
+ rebase // Propagate so behind-remote deps get rebased automatically
2512
2588
  });
2513
2589
  if (!depSuccess) {
2514
2590
  console.error(colors.red(`Failed to publish ${name}`));
@@ -2529,6 +2605,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2529
2605
  console.log(colors.yellow(' 1. Publish them manually first'));
2530
2606
  console.log(colors.yellow(' 2. Use --publish-deps to publish them automatically'));
2531
2607
  console.log(colors.yellow(' 3. Use --force to continue anyway (NOT RECOMMENDED)'));
2608
+ console.log(colors.yellow(' 4. Use -npd (--no-publish-deps) to skip dependency publishing'));
2532
2609
  console.log('');
2533
2610
  if (!force) {
2534
2611
  const shouldContinue = await confirm('Continue with unpublished dependencies?', false);
@@ -2610,10 +2687,13 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2610
2687
  const pkgName = pkg.name;
2611
2688
  const pkgVersion = pkg.version;
2612
2689
  if (!pkg.bin && (install || link || wsl)) {
2613
- console.log(colors.yellow('Note: This is a library (no bin field), skipping global installation.'));
2614
- console.log(colors.yellow('To use in projects: npm install ' + pkgName));
2690
+ const proceed = await offerAddBin(cwd, pkg);
2691
+ if (!proceed) {
2692
+ console.log(colors.yellow('Skipping global install — library packages should not be installed globally.'));
2693
+ console.log(colors.yellow(`To use in projects: npm install ${pkgName}`));
2694
+ }
2615
2695
  }
2616
- else {
2696
+ if (pkg.bin && (install || link || wsl)) {
2617
2697
  if (link) {
2618
2698
  console.log(`Installing ${pkgName} globally from local directory (link)...`);
2619
2699
  const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false });
@@ -3104,10 +3184,13 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3104
3184
  const pkgName = updatedPkg.name;
3105
3185
  const pkgVersion = updatedPkg.version;
3106
3186
  if (!updatedPkg.bin && (install || link || wsl)) {
3107
- console.log(colors.yellow('Note: This is a library (no bin field), skipping global installation.'));
3108
- console.log(colors.yellow('To use in projects: npm install ' + pkgName));
3187
+ const proceed = await offerAddBin(cwd, updatedPkg);
3188
+ if (!proceed) {
3189
+ console.log(colors.yellow('Skipping global install — library packages should not be installed globally.'));
3190
+ console.log(colors.yellow(`To use in projects: npm install ${pkgName}`));
3191
+ }
3109
3192
  }
3110
- else {
3193
+ if (updatedPkg.bin && (install || link || wsl)) {
3111
3194
  if (link) {
3112
3195
  console.log(`Linking globally: ${pkgName}@${pkgVersion}...`);
3113
3196
  if (!dryRun) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.101",
3
+ "version": "1.0.103",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",