@bobfrankston/npmglobalize 1.0.141 → 1.0.143

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 +32 -6
  2. package/lib.d.ts +15 -0
  3. package/lib.js +76 -12
  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, recordBuildIssue, extractFirstTscError } 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');
@@ -388,15 +402,23 @@ export async function main() {
388
402
  }
389
403
  }
390
404
  if (pkg.scripts?.build) {
391
- const { execSync } = await import('child_process');
392
- try {
393
- console.log(`Building ${cwd}...`);
394
- execSync('npm run build', { cwd, stdio: 'inherit' });
405
+ const { spawnSync } = await import('child_process');
406
+ console.log(`Building ${cwd}...`);
407
+ const buildResult = spawnSync('npm', ['run', 'build'], {
408
+ cwd, encoding: 'utf-8', stdio: 'pipe', shell: true
409
+ });
410
+ if (buildResult.status === 0) {
395
411
  console.log(styleText('green', '✓ Build succeeded'));
396
412
  }
397
- catch (error) {
413
+ else {
414
+ const buildOutput = (buildResult.stderr || '') + (buildResult.stdout || '');
415
+ if (buildOutput)
416
+ console.error(buildOutput);
398
417
  console.error(styleText('red', `Build failed in ${cwd}`));
418
+ const firstErr = extractFirstTscError(buildOutput);
419
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', firstErr || 'Build failed');
399
420
  if (!cliOptions.force) {
421
+ printBuildSummary();
400
422
  process.exit(1);
401
423
  }
402
424
  console.log(styleText('yellow', 'Continuing with --force...'));
@@ -444,6 +466,7 @@ export async function main() {
444
466
  }
445
467
  writeConfig(cwd, persistable, cliOptions.explicitKeys);
446
468
  }
469
+ clearBuildIssues();
447
470
  try {
448
471
  // Auto-detect workspace root: private package with workspaces[] field
449
472
  if (!options.noWorkspace) {
@@ -452,12 +475,14 @@ export async function main() {
452
475
  const rootPkg = readPackageJson(cwd);
453
476
  if (rootPkg.private && Array.isArray(rootPkg.workspaces)) {
454
477
  const result = await globalizeWorkspace(cwd, options, configOptions);
478
+ printBuildSummary();
455
479
  process.exit(result.success ? 0 : 1);
456
480
  }
457
481
  }
458
482
  }
459
483
  options._fromCli = true;
460
484
  const success = await globalize(cwd, options, configOptions);
485
+ printBuildSummary();
461
486
  process.exit(success ? 0 : 1);
462
487
  }
463
488
  catch (error) {
@@ -465,6 +490,7 @@ export async function main() {
465
490
  if (options.verbose) {
466
491
  console.error(error.stack);
467
492
  }
493
+ printBuildSummary();
468
494
  process.exit(1);
469
495
  }
470
496
  }
package/lib.d.ts CHANGED
@@ -10,6 +10,21 @@
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;
25
+ /** Extract the first TypeScript error line from build output for the summary.
26
+ * Returns a short string like "file.ts(42,5): error TS2339: Property 'foo' ..." */
27
+ export declare function extractFirstTscError(output: string): string | null;
13
28
  /** Options for the globalize operation */
14
29
  export interface GlobalizeOptions {
15
30
  /** Bump type: patch (default), minor, major */
package/lib.js CHANGED
@@ -33,6 +33,38 @@ 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
+ }
49
+ /** Extract the first TypeScript error line from build output for the summary.
50
+ * Returns a short string like "file.ts(42,5): error TS2339: Property 'foo' ..." */
51
+ export function extractFirstTscError(output) {
52
+ if (!output)
53
+ return null;
54
+ // tsc errors: src/file.ts(line,col): error TS1234: message
55
+ const tscMatch = output.match(/^(.+?\(\d+,\d+\): error TS\d+: .+)$/m);
56
+ if (tscMatch) {
57
+ const line = tscMatch[1];
58
+ return line.length > 120 ? line.slice(0, 117) + '...' : line;
59
+ }
60
+ // Generic "error" line
61
+ const errMatch = output.match(/^(error .+)$/m);
62
+ if (errMatch) {
63
+ const line = errMatch[1];
64
+ return line.length > 120 ? line.slice(0, 117) + '...' : line;
65
+ }
66
+ return null;
67
+ }
36
68
  /**
37
69
  * Remove 'nul' files from a directory tree (Windows reserved name issue).
38
70
  * These files break git and npm on Windows. Uses \\?\ prefix to bypass name validation.
@@ -2733,16 +2765,24 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2733
2765
  if (pkg.scripts?.build && !options._fromCli) {
2734
2766
  console.log(`${timestamp()} Running build...`);
2735
2767
  if (!dryRun) {
2736
- const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: !verbose });
2768
+ // Always capture output so we can extract tsc errors for the summary
2769
+ const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: true });
2737
2770
  if (!buildResult.success) {
2738
- console.error(colors.red('ERROR: Build failed:'), buildResult.stderr || buildResult.output);
2739
- diagnoseBuildFailure(buildResult.stderr || buildResult.output || '', cwd);
2771
+ const buildOutput = buildResult.stderr || buildResult.output;
2772
+ if (buildOutput)
2773
+ console.error(buildOutput);
2774
+ console.error(colors.red('ERROR: Build failed'));
2775
+ diagnoseBuildFailure(buildOutput || '', cwd);
2776
+ const firstErr = extractFirstTscError(buildOutput || '');
2777
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', firstErr || 'Build failed');
2740
2778
  if (!force) {
2741
2779
  return false;
2742
2780
  }
2743
2781
  console.log(colors.yellow('Continuing with --force despite build failure...'));
2744
2782
  }
2745
2783
  else {
2784
+ if (verbose && buildResult.output)
2785
+ process.stdout.write(buildResult.output);
2746
2786
  console.log(`${timestamp()} ${colors.green('✓ Build succeeded')}`);
2747
2787
  }
2748
2788
  }
@@ -3143,6 +3183,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3143
3183
  });
3144
3184
  if (!depSuccess) {
3145
3185
  console.error(colors.red(`Failed to publish ${name}`));
3186
+ recordBuildIssue(pkg.name || cwd, 'error', `Dependency failed: ${name}`);
3146
3187
  if (!force) {
3147
3188
  return false;
3148
3189
  }
@@ -3871,6 +3912,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3871
3912
  }
3872
3913
  console.error(colors.yellow('Consumers will get E404 when installing this package.'));
3873
3914
  console.error(colors.yellow('Fix: publish those deps as public, or make this package private.'));
3915
+ recordBuildIssue(pkg.name || path.basename(cwd), 'warning', `Public pkg depends on private: ${privateDeps.join(', ')}`);
3874
3916
  console.log('');
3875
3917
  }
3876
3918
  }
@@ -3882,6 +3924,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3882
3924
  const authStatus = checkNpmAuth();
3883
3925
  if (!authStatus.authenticated) {
3884
3926
  console.error(colors.red(`Not authenticated to npm (${authStatus.error}) — run: npm login`));
3927
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', `npm auth: ${authStatus.error}`);
3885
3928
  return false;
3886
3929
  }
3887
3930
  if (verbose) {
@@ -3893,6 +3936,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3893
3936
  console.error(colors.red('ERROR: Failed to create package tarball'));
3894
3937
  console.error(colors.yellow('Output:'), packResult.output);
3895
3938
  console.error(colors.yellow('Error:'), packResult.stderr);
3939
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm pack failed');
3896
3940
  return false;
3897
3941
  }
3898
3942
  // Get the tarball filename from npm pack output
@@ -3925,6 +3969,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3925
3969
  }
3926
3970
  }
3927
3971
  console.error(colors.yellow(' Check .npmignore — you may need to exclude large files or directories.'));
3972
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', `Tarball too large (${sizeMB.toFixed(0)}MB)`);
3928
3973
  // Clean up and abort
3929
3974
  try {
3930
3975
  fs.unlinkSync(tarballPath);
@@ -3984,21 +4029,17 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3984
4029
  // Ignore cleanup errors
3985
4030
  }
3986
4031
  if (!publishResult.success) {
3987
- console.error(colors.red('\nERROR: npm publish failed\n'));
3988
- // Check for specific error types
4032
+ // Check for specific error types before recording
3989
4033
  const output = (publishResult.output + '\n' + publishResult.stderr).toLowerCase();
3990
- if (output.includes('err_string_too_long') || output.includes('string longer than')) {
3991
- console.error(colors.red('Tarball too large check .npmignore. Run: npm pack --dry-run'));
3992
- }
3993
- else if (output.includes('e409') || output.includes('409 conflict')) {
3994
- console.error(colors.yellow('npm still processing previous version — wait and retry'));
3995
- }
3996
- else if (output.includes('cannot publish over') || output.includes('previously published')) {
4034
+ // "Already published" is benign — don't record as error
4035
+ if (output.includes('cannot publish over') || output.includes('previously published')) {
3997
4036
  const currentPkgVersion = readPackageJson(cwd).version;
3998
4037
  if (output.includes(currentPkgVersion)) {
3999
4038
  console.log(colors.green('✓ Already published — continuing'));
4000
4039
  }
4001
4040
  else {
4041
+ console.error(colors.red('\nERROR: npm publish failed\n'));
4042
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm publish failed');
4002
4043
  console.error(colors.yellow('Version conflict — run npmglobalize again'));
4003
4044
  if (transformResult.transformed) {
4004
4045
  const failPkg = readPackageJson(cwd);
@@ -4010,7 +4051,19 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4010
4051
  return false;
4011
4052
  }
4012
4053
  }
4054
+ else if (output.includes('err_string_too_long') || output.includes('string longer than')) {
4055
+ console.error(colors.red('\nERROR: npm publish failed\n'));
4056
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm publish failed');
4057
+ console.error(colors.red('Tarball too large — check .npmignore. Run: npm pack --dry-run'));
4058
+ }
4059
+ else if (output.includes('e409') || output.includes('409 conflict')) {
4060
+ console.error(colors.red('\nERROR: npm publish failed\n'));
4061
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm publish failed');
4062
+ console.error(colors.yellow('npm still processing previous version — wait and retry'));
4063
+ }
4013
4064
  else if (output.includes('403') || output.includes('forbidden')) {
4065
+ console.error(colors.red('\nERROR: npm publish failed\n'));
4066
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm publish failed');
4014
4067
  console.error(colors.yellow('Publish forbidden — check npm login and package access'));
4015
4068
  if (transformResult.transformed) {
4016
4069
  const failPkg = readPackageJson(cwd);
@@ -4022,9 +4075,13 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4022
4075
  return false;
4023
4076
  }
4024
4077
  else if (output.includes('402') || output.includes('payment required')) {
4078
+ console.error(colors.red('\nERROR: npm publish failed\n'));
4079
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm publish failed');
4025
4080
  console.error(colors.yellow('Private packages need paid npm account — use --npm public'));
4026
4081
  }
4027
4082
  else {
4083
+ console.error(colors.red('\nERROR: npm publish failed\n'));
4084
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm publish failed');
4028
4085
  console.error(colors.yellow('Publish failed — run npm login or check npm whoami'));
4029
4086
  }
4030
4087
  if (transformResult.transformed) {
@@ -4086,6 +4143,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4086
4143
  else {
4087
4144
  console.error(colors.red(`✗ Global link install failed`));
4088
4145
  console.error(colors.yellow(' Try running manually: npm install -g .'));
4146
+ recordBuildIssue(pkgName, 'warning', 'Global link install failed');
4089
4147
  }
4090
4148
  }
4091
4149
  else {
@@ -4105,6 +4163,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4105
4163
  else {
4106
4164
  console.error(colors.red(`✗ Global install failed`));
4107
4165
  console.error(colors.yellow(' Try running manually: npm install -g .'));
4166
+ recordBuildIssue(pkgName, 'warning', 'Global install failed');
4108
4167
  }
4109
4168
  }
4110
4169
  else {
@@ -4123,6 +4182,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4123
4182
  else {
4124
4183
  console.error(colors.red(`✗ Global install failed`));
4125
4184
  console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
4185
+ recordBuildIssue(pkgName, 'warning', 'Global install failed');
4126
4186
  }
4127
4187
  }
4128
4188
  else {
@@ -4144,6 +4204,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4144
4204
  }
4145
4205
  else {
4146
4206
  console.error(colors.yellow('✗ WSL install failed (is npm installed in WSL?)'));
4207
+ recordBuildIssue(pkgName, 'warning', 'WSL install failed');
4147
4208
  }
4148
4209
  }
4149
4210
  else {
@@ -4371,6 +4432,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4371
4432
  success: false,
4372
4433
  error: error.message,
4373
4434
  });
4435
+ recordBuildIssue(pkgName, 'error', error.message);
4374
4436
  console.error(colors.red(`\nError processing ${pkgName}: ${error.message}`));
4375
4437
  if (!options.continueOnError) {
4376
4438
  console.error(colors.red('Use --continue-on-error to continue with remaining packages.'));
@@ -4453,6 +4515,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4453
4515
  else {
4454
4516
  console.error(colors.red(`✗ Global install failed`));
4455
4517
  console.error(colors.yellow(' Try running manually: npm install -g .'));
4518
+ recordBuildIssue(pkgName, 'warning', 'Global install failed');
4456
4519
  }
4457
4520
  }
4458
4521
  else {
@@ -4468,6 +4531,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4468
4531
  }
4469
4532
  else {
4470
4533
  console.error(colors.yellow('✗ WSL install failed (is npm installed in WSL?)'));
4534
+ recordBuildIssue(pkgName, 'warning', 'WSL install failed');
4471
4535
  }
4472
4536
  }
4473
4537
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.141",
3
+ "version": "1.0.143",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",