@bobfrankston/npmglobalize 1.0.140 → 1.0.142

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 (4) hide show
  1. package/cli.js +19 -1
  2. package/lib.d.ts +12 -0
  3. package/lib.js +121 -0
  4. package/package.json +1 -1
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, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, confirm } from './lib.js';
5
+ import { globalize, globalizeWorkspace, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, confirm, getBuildIssues, clearBuildIssues } from './lib.js';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { styleText } from 'util';
@@ -328,6 +328,20 @@ function parseArgs(args) {
328
328
  }
329
329
  return options;
330
330
  }
331
+ /** Print accumulated build issues in warning color */
332
+ function printBuildSummary() {
333
+ const issues = getBuildIssues();
334
+ if (issues.length === 0)
335
+ return;
336
+ console.log('');
337
+ console.log(styleText('yellow', '━━━ Issues Summary ━━━━━━━━━━━━━━━━━━━━'));
338
+ for (const issue of issues) {
339
+ const icon = issue.severity === 'error' ? '✗' : '⚠';
340
+ console.log(styleText('yellow', ` ${icon} ${issue.module}: ${issue.message}`));
341
+ }
342
+ console.log(styleText('yellow', '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
343
+ console.log('');
344
+ }
331
345
  export async function main() {
332
346
  // Show version at the very start
333
347
  const ownPkgPath = path.join(__dirname, 'package.json');
@@ -444,6 +458,7 @@ export async function main() {
444
458
  }
445
459
  writeConfig(cwd, persistable, cliOptions.explicitKeys);
446
460
  }
461
+ clearBuildIssues();
447
462
  try {
448
463
  // Auto-detect workspace root: private package with workspaces[] field
449
464
  if (!options.noWorkspace) {
@@ -452,12 +467,14 @@ export async function main() {
452
467
  const rootPkg = readPackageJson(cwd);
453
468
  if (rootPkg.private && Array.isArray(rootPkg.workspaces)) {
454
469
  const result = await globalizeWorkspace(cwd, options, configOptions);
470
+ printBuildSummary();
455
471
  process.exit(result.success ? 0 : 1);
456
472
  }
457
473
  }
458
474
  }
459
475
  options._fromCli = true;
460
476
  const success = await globalize(cwd, options, configOptions);
477
+ printBuildSummary();
461
478
  process.exit(success ? 0 : 1);
462
479
  }
463
480
  catch (error) {
@@ -465,6 +482,7 @@ export async function main() {
465
482
  if (options.verbose) {
466
483
  console.error(error.stack);
467
484
  }
485
+ printBuildSummary();
468
486
  process.exit(1);
469
487
  }
470
488
  }
package/lib.d.ts CHANGED
@@ -10,6 +10,18 @@
10
10
  * Consider library-based approach if async operations or cross-platform issues arise.
11
11
  */
12
12
  import { type NpmCommonConfig } from '@bobfrankston/userconfig';
13
+ /** Issue recorded during a build/publish run for end-of-run summary */
14
+ export interface BuildIssue {
15
+ module: string;
16
+ severity: 'error' | 'warning';
17
+ message: string;
18
+ }
19
+ /** Record an issue for the end-of-run summary */
20
+ export declare function recordBuildIssue(module: string, severity: 'error' | 'warning', message: string): void;
21
+ /** Get all accumulated build issues */
22
+ export declare function getBuildIssues(): readonly BuildIssue[];
23
+ /** Clear accumulated issues (call at start of run) */
24
+ export declare function clearBuildIssues(): void;
13
25
  /** Options for the globalize operation */
14
26
  export interface GlobalizeOptions {
15
27
  /** Bump type: patch (default), minor, major */
package/lib.js CHANGED
@@ -33,6 +33,19 @@ import { fileURLToPath } from 'url';
33
33
  import { themeColors } from '@bobfrankston/themecolors';
34
34
  /** Semantic color functions — adapts to terminal light/dark theme */
35
35
  const colors = themeColors();
36
+ const _buildIssues = [];
37
+ /** Record an issue for the end-of-run summary */
38
+ export function recordBuildIssue(module, severity, message) {
39
+ _buildIssues.push({ module, severity, message });
40
+ }
41
+ /** Get all accumulated build issues */
42
+ export function getBuildIssues() {
43
+ return _buildIssues;
44
+ }
45
+ /** Clear accumulated issues (call at start of run) */
46
+ export function clearBuildIssues() {
47
+ _buildIssues.length = 0;
48
+ }
36
49
  /**
37
50
  * Remove 'nul' files from a directory tree (Windows reserved name issue).
38
51
  * These files break git and npm on Windows. Uses \\?\ prefix to bypass name validation.
@@ -1072,6 +1085,96 @@ export function runCommand(cmd, args, options = {}) {
1072
1085
  return { success: false, output: '', stderr: error.message };
1073
1086
  }
1074
1087
  }
1088
+ /** Diagnose common build/version failure patterns and print actionable hints.
1089
+ * Returns true if a diagnosis was printed. */
1090
+ function diagnoseBuildFailure(errorText, cwd) {
1091
+ let diagnosed = false;
1092
+ // Pattern: missing module / package not found
1093
+ const missingPkg = errorText.match(/Cannot find (?:package|module) ['"](@?[^'"\/]+(?:\/[^'"\/]+)?)/);
1094
+ if (missingPkg) {
1095
+ const pkgName = missingPkg[1];
1096
+ console.error(colors.yellow(`\n Hint: missing package "${pkgName}"`));
1097
+ // Check for broken symlinks in node_modules tree
1098
+ const searchDirs = [cwd];
1099
+ try {
1100
+ for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
1101
+ if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git' && entry.name !== 'prev') {
1102
+ searchDirs.push(path.join(cwd, entry.name));
1103
+ }
1104
+ }
1105
+ }
1106
+ catch { /* ignore */ }
1107
+ for (const dir of searchDirs) {
1108
+ const nmPath = path.join(dir, 'node_modules', ...pkgName.split('/'));
1109
+ try {
1110
+ const stat = fs.lstatSync(nmPath);
1111
+ if (stat.isSymbolicLink()) {
1112
+ const target = fs.readlinkSync(nmPath);
1113
+ const resolved = path.resolve(path.dirname(nmPath), target);
1114
+ if (!fs.existsSync(resolved)) {
1115
+ console.error(colors.yellow(` Broken symlink: ${nmPath}`));
1116
+ console.error(colors.yellow(` → ${resolved} (target missing)`));
1117
+ console.error(colors.yellow(` Fix: cd "${dir}" && npm install`));
1118
+ diagnosed = true;
1119
+ }
1120
+ else {
1121
+ console.error(colors.yellow(` Symlink exists at ${nmPath} but module still failed to load.`));
1122
+ console.error(colors.yellow(` Check that the target package has been built (has .js files).`));
1123
+ diagnosed = true;
1124
+ }
1125
+ }
1126
+ }
1127
+ catch {
1128
+ // No symlink found at this location — check if it should exist
1129
+ const subPkgPath = path.join(dir, 'package.json');
1130
+ if (fs.existsSync(subPkgPath)) {
1131
+ try {
1132
+ const subPkg = JSON.parse(fs.readFileSync(subPkgPath, 'utf-8'));
1133
+ const allDeps = { ...subPkg.dependencies, ...subPkg.devDependencies };
1134
+ if (allDeps[pkgName]) {
1135
+ const depValue = allDeps[pkgName];
1136
+ if (typeof depValue === 'string' && depValue.startsWith('file:')) {
1137
+ const target = path.resolve(dir, depValue.replace('file:', ''));
1138
+ console.error(colors.yellow(` "${pkgName}" is a file: dep in ${path.relative(cwd, subPkgPath) || 'package.json'}`));
1139
+ console.error(colors.yellow(` → ${target}`));
1140
+ if (!fs.existsSync(target)) {
1141
+ console.error(colors.yellow(` Target directory does not exist!`));
1142
+ }
1143
+ else {
1144
+ console.error(colors.yellow(` Fix: cd "${dir}" && npm install`));
1145
+ }
1146
+ diagnosed = true;
1147
+ }
1148
+ else {
1149
+ console.error(colors.yellow(` "${pkgName}" is listed in ${path.relative(cwd, subPkgPath) || 'package.json'} but not installed.`));
1150
+ console.error(colors.yellow(` Fix: cd "${dir}" && npm install`));
1151
+ diagnosed = true;
1152
+ }
1153
+ }
1154
+ }
1155
+ catch { /* ignore parse errors */ }
1156
+ }
1157
+ }
1158
+ }
1159
+ if (!diagnosed) {
1160
+ console.error(colors.yellow(` "${pkgName}" is not installed and not listed as a dependency.`));
1161
+ console.error(colors.yellow(` If needed, add it: npm install ${pkgName}`));
1162
+ diagnosed = true;
1163
+ }
1164
+ }
1165
+ // Pattern: permission denied
1166
+ if (!diagnosed && /permission denied|EPERM|EACCES/.test(errorText)) {
1167
+ console.error(colors.yellow('\n Hint: permission error during build.'));
1168
+ console.error(colors.yellow(' Check file ownership and that no other process has files locked.'));
1169
+ diagnosed = true;
1170
+ }
1171
+ // Pattern: TypeScript compilation error
1172
+ if (!diagnosed && /error TS\d+/.test(errorText)) {
1173
+ console.error(colors.yellow('\n Hint: TypeScript compilation errors. Fix the type errors above before publishing.'));
1174
+ diagnosed = true;
1175
+ }
1176
+ return diagnosed;
1177
+ }
1075
1178
  /** Run git commit silently and print a compact summary (no per-file create/delete mode lines). */
1076
1179
  function gitCommit(msg, cwd) {
1077
1180
  const result = runCommand('git', ['commit', '-m', msg], { cwd, silent: true });
@@ -2646,6 +2749,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2646
2749
  const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: !verbose });
2647
2750
  if (!buildResult.success) {
2648
2751
  console.error(colors.red('ERROR: Build failed:'), buildResult.stderr || buildResult.output);
2752
+ diagnoseBuildFailure(buildResult.stderr || buildResult.output || '', cwd);
2753
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'Build failed');
2649
2754
  if (!force) {
2650
2755
  return false;
2651
2756
  }
@@ -3052,6 +3157,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3052
3157
  });
3053
3158
  if (!depSuccess) {
3054
3159
  console.error(colors.red(`Failed to publish ${name}`));
3160
+ recordBuildIssue(pkg.name || cwd, 'error', `Dependency failed: ${name}`);
3055
3161
  if (!force) {
3056
3162
  return false;
3057
3163
  }
@@ -3551,6 +3657,9 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3551
3657
  if (error.code)
3552
3658
  console.error(' exit code:', error.code);
3553
3659
  }
3660
+ // Diagnose common failures (missing modules, permissions, TS errors)
3661
+ const versionErrorText = [error.message, error.stderr, error.stdout].filter(Boolean).join('\n');
3662
+ diagnoseBuildFailure(versionErrorText, cwd);
3554
3663
  // Show the full error stack in verbose mode
3555
3664
  if (verbose && error.stack) {
3556
3665
  console.error(' Stack trace:', error.stack);
@@ -3777,6 +3886,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3777
3886
  }
3778
3887
  console.error(colors.yellow('Consumers will get E404 when installing this package.'));
3779
3888
  console.error(colors.yellow('Fix: publish those deps as public, or make this package private.'));
3889
+ recordBuildIssue(pkg.name || path.basename(cwd), 'warning', `Public pkg depends on private: ${privateDeps.join(', ')}`);
3780
3890
  console.log('');
3781
3891
  }
3782
3892
  }
@@ -3788,6 +3898,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3788
3898
  const authStatus = checkNpmAuth();
3789
3899
  if (!authStatus.authenticated) {
3790
3900
  console.error(colors.red(`Not authenticated to npm (${authStatus.error}) — run: npm login`));
3901
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', `npm auth: ${authStatus.error}`);
3791
3902
  return false;
3792
3903
  }
3793
3904
  if (verbose) {
@@ -3799,6 +3910,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3799
3910
  console.error(colors.red('ERROR: Failed to create package tarball'));
3800
3911
  console.error(colors.yellow('Output:'), packResult.output);
3801
3912
  console.error(colors.yellow('Error:'), packResult.stderr);
3913
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm pack failed');
3802
3914
  return false;
3803
3915
  }
3804
3916
  // Get the tarball filename from npm pack output
@@ -3831,6 +3943,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3831
3943
  }
3832
3944
  }
3833
3945
  console.error(colors.yellow(' Check .npmignore — you may need to exclude large files or directories.'));
3946
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', `Tarball too large (${sizeMB.toFixed(0)}MB)`);
3834
3947
  // Clean up and abort
3835
3948
  try {
3836
3949
  fs.unlinkSync(tarballPath);
@@ -3891,6 +4004,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3891
4004
  }
3892
4005
  if (!publishResult.success) {
3893
4006
  console.error(colors.red('\nERROR: npm publish failed\n'));
4007
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm publish failed');
3894
4008
  // Check for specific error types
3895
4009
  const output = (publishResult.output + '\n' + publishResult.stderr).toLowerCase();
3896
4010
  if (output.includes('err_string_too_long') || output.includes('string longer than')) {
@@ -3992,6 +4106,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3992
4106
  else {
3993
4107
  console.error(colors.red(`✗ Global link install failed`));
3994
4108
  console.error(colors.yellow(' Try running manually: npm install -g .'));
4109
+ recordBuildIssue(pkgName, 'warning', 'Global link install failed');
3995
4110
  }
3996
4111
  }
3997
4112
  else {
@@ -4011,6 +4126,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4011
4126
  else {
4012
4127
  console.error(colors.red(`✗ Global install failed`));
4013
4128
  console.error(colors.yellow(' Try running manually: npm install -g .'));
4129
+ recordBuildIssue(pkgName, 'warning', 'Global install failed');
4014
4130
  }
4015
4131
  }
4016
4132
  else {
@@ -4029,6 +4145,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4029
4145
  else {
4030
4146
  console.error(colors.red(`✗ Global install failed`));
4031
4147
  console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
4148
+ recordBuildIssue(pkgName, 'warning', 'Global install failed');
4032
4149
  }
4033
4150
  }
4034
4151
  else {
@@ -4050,6 +4167,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4050
4167
  }
4051
4168
  else {
4052
4169
  console.error(colors.yellow('✗ WSL install failed (is npm installed in WSL?)'));
4170
+ recordBuildIssue(pkgName, 'warning', 'WSL install failed');
4053
4171
  }
4054
4172
  }
4055
4173
  else {
@@ -4277,6 +4395,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4277
4395
  success: false,
4278
4396
  error: error.message,
4279
4397
  });
4398
+ recordBuildIssue(pkgName, 'error', error.message);
4280
4399
  console.error(colors.red(`\nError processing ${pkgName}: ${error.message}`));
4281
4400
  if (!options.continueOnError) {
4282
4401
  console.error(colors.red('Use --continue-on-error to continue with remaining packages.'));
@@ -4359,6 +4478,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4359
4478
  else {
4360
4479
  console.error(colors.red(`✗ Global install failed`));
4361
4480
  console.error(colors.yellow(' Try running manually: npm install -g .'));
4481
+ recordBuildIssue(pkgName, 'warning', 'Global install failed');
4362
4482
  }
4363
4483
  }
4364
4484
  else {
@@ -4374,6 +4494,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4374
4494
  }
4375
4495
  else {
4376
4496
  console.error(colors.yellow('✗ WSL install failed (is npm installed in WSL?)'));
4497
+ recordBuildIssue(pkgName, 'warning', 'WSL install failed');
4377
4498
  }
4378
4499
  }
4379
4500
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.140",
3
+ "version": "1.0.142",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",