@bobfrankston/npmglobalize 1.0.139 → 1.0.141

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/lib/types.d.ts CHANGED
@@ -6,16 +6,8 @@ import { type SpawnSyncOptions, type SpawnSyncReturns } from 'child_process';
6
6
  /** Wrapper for spawnSync that avoids DEP0190 (args + shell: true).
7
7
  * When shell is true, joins cmd+args into a single command string. */
8
8
  export declare function spawnSafe(cmd: string, args: string[], options?: SpawnSyncOptions): SpawnSyncReturns<string>;
9
- /** Color/style helpers using Node.js styleText (Node 24+) */
10
- export declare const colors: {
11
- red: (text: string) => string;
12
- yellow: (text: string) => string;
13
- green: (text: string) => string;
14
- italic: (text: string) => string;
15
- blue: (text: string) => string;
16
- cyan: (text: string) => string;
17
- dim: (text: string) => string;
18
- };
9
+ /** Semantic color functions adapts to terminal light/dark theme */
10
+ export declare const colors: import("@bobfrankston/themecolors").SemanticColors;
19
11
  /** Get npm command for current platform (npm.cmd on Windows, npm elsewhere) */
20
12
  export declare function getNpmCommand(): string;
21
13
  /** Options for the globalize operation */
package/lib/types.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Leaf module — no internal dependencies.
4
4
  */
5
5
  import { spawnSync } from 'child_process';
6
- import { styleText } from 'util';
6
+ import { themeColors } from '@bobfrankston/themecolors';
7
7
  import readline from 'readline';
8
8
  /** Wrapper for spawnSync that avoids DEP0190 (args + shell: true).
9
9
  * When shell is true, joins cmd+args into a single command string. */
@@ -16,16 +16,8 @@ export function spawnSafe(cmd, args, options = {}) {
16
16
  }
17
17
  return spawnSync(cmd, args, opts);
18
18
  }
19
- /** Color/style helpers using Node.js styleText (Node 24+) */
20
- export const colors = {
21
- red: (text) => styleText('red', text),
22
- yellow: (text) => styleText('yellow', text),
23
- green: (text) => styleText('green', text),
24
- italic: (text) => styleText('italic', text),
25
- blue: (text) => styleText('blue', text),
26
- cyan: (text) => styleText('cyan', text),
27
- dim: (text) => styleText('dim', text),
28
- };
19
+ /** Semantic color functions adapts to terminal light/dark theme */
20
+ export const colors = themeColors();
29
21
  /** Get npm command for current platform (npm.cmd on Windows, npm elsewhere) */
30
22
  export function getNpmCommand() {
31
23
  return process.platform === 'win32' ? 'npm.cmd' : 'npm';
package/lib.js CHANGED
@@ -137,6 +137,10 @@ function getNpmCommand() {
137
137
  return process.platform === 'win32' ? 'npm.cmd' : 'npm';
138
138
  }
139
139
  const DEP_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
140
+ /** Format current time as HH:MM:SS for progress timestamps */
141
+ function timestamp() {
142
+ return colors.dim(new Date().toLocaleTimeString('en-US', { hour12: false }));
143
+ }
140
144
  /** Read and parse package.json from a directory */
141
145
  export function readPackageJson(dir) {
142
146
  const pkgPath = path.join(dir, 'package.json');
@@ -1003,7 +1007,7 @@ export function fixVersionTagMismatch(cwd, pkg, verbose = false) {
1003
1007
  function waitForNpmVersion(pkgName, version, maxWaitMs = 90000) {
1004
1008
  const interval = 3000;
1005
1009
  const maxAttempts = Math.ceil(maxWaitMs / interval);
1006
- process.stdout.write(`Waiting for ${pkgName}@${version} on npm registry`);
1010
+ process.stdout.write(`${timestamp()} Waiting for ${pkgName}@${version} on npm registry`);
1007
1011
  for (let i = 0; i < maxAttempts; i++) {
1008
1012
  const result = spawnSafe('npm', ['view', `${pkgName}@${version}`, 'version'], {
1009
1013
  shell: process.platform === 'win32',
@@ -1068,6 +1072,96 @@ export function runCommand(cmd, args, options = {}) {
1068
1072
  return { success: false, output: '', stderr: error.message };
1069
1073
  }
1070
1074
  }
1075
+ /** Diagnose common build/version failure patterns and print actionable hints.
1076
+ * Returns true if a diagnosis was printed. */
1077
+ function diagnoseBuildFailure(errorText, cwd) {
1078
+ let diagnosed = false;
1079
+ // Pattern: missing module / package not found
1080
+ const missingPkg = errorText.match(/Cannot find (?:package|module) ['"](@?[^'"\/]+(?:\/[^'"\/]+)?)/);
1081
+ if (missingPkg) {
1082
+ const pkgName = missingPkg[1];
1083
+ console.error(colors.yellow(`\n Hint: missing package "${pkgName}"`));
1084
+ // Check for broken symlinks in node_modules tree
1085
+ const searchDirs = [cwd];
1086
+ try {
1087
+ for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
1088
+ if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git' && entry.name !== 'prev') {
1089
+ searchDirs.push(path.join(cwd, entry.name));
1090
+ }
1091
+ }
1092
+ }
1093
+ catch { /* ignore */ }
1094
+ for (const dir of searchDirs) {
1095
+ const nmPath = path.join(dir, 'node_modules', ...pkgName.split('/'));
1096
+ try {
1097
+ const stat = fs.lstatSync(nmPath);
1098
+ if (stat.isSymbolicLink()) {
1099
+ const target = fs.readlinkSync(nmPath);
1100
+ const resolved = path.resolve(path.dirname(nmPath), target);
1101
+ if (!fs.existsSync(resolved)) {
1102
+ console.error(colors.yellow(` Broken symlink: ${nmPath}`));
1103
+ console.error(colors.yellow(` → ${resolved} (target missing)`));
1104
+ console.error(colors.yellow(` Fix: cd "${dir}" && npm install`));
1105
+ diagnosed = true;
1106
+ }
1107
+ else {
1108
+ console.error(colors.yellow(` Symlink exists at ${nmPath} but module still failed to load.`));
1109
+ console.error(colors.yellow(` Check that the target package has been built (has .js files).`));
1110
+ diagnosed = true;
1111
+ }
1112
+ }
1113
+ }
1114
+ catch {
1115
+ // No symlink found at this location — check if it should exist
1116
+ const subPkgPath = path.join(dir, 'package.json');
1117
+ if (fs.existsSync(subPkgPath)) {
1118
+ try {
1119
+ const subPkg = JSON.parse(fs.readFileSync(subPkgPath, 'utf-8'));
1120
+ const allDeps = { ...subPkg.dependencies, ...subPkg.devDependencies };
1121
+ if (allDeps[pkgName]) {
1122
+ const depValue = allDeps[pkgName];
1123
+ if (typeof depValue === 'string' && depValue.startsWith('file:')) {
1124
+ const target = path.resolve(dir, depValue.replace('file:', ''));
1125
+ console.error(colors.yellow(` "${pkgName}" is a file: dep in ${path.relative(cwd, subPkgPath) || 'package.json'}`));
1126
+ console.error(colors.yellow(` → ${target}`));
1127
+ if (!fs.existsSync(target)) {
1128
+ console.error(colors.yellow(` Target directory does not exist!`));
1129
+ }
1130
+ else {
1131
+ console.error(colors.yellow(` Fix: cd "${dir}" && npm install`));
1132
+ }
1133
+ diagnosed = true;
1134
+ }
1135
+ else {
1136
+ console.error(colors.yellow(` "${pkgName}" is listed in ${path.relative(cwd, subPkgPath) || 'package.json'} but not installed.`));
1137
+ console.error(colors.yellow(` Fix: cd "${dir}" && npm install`));
1138
+ diagnosed = true;
1139
+ }
1140
+ }
1141
+ }
1142
+ catch { /* ignore parse errors */ }
1143
+ }
1144
+ }
1145
+ }
1146
+ if (!diagnosed) {
1147
+ console.error(colors.yellow(` "${pkgName}" is not installed and not listed as a dependency.`));
1148
+ console.error(colors.yellow(` If needed, add it: npm install ${pkgName}`));
1149
+ diagnosed = true;
1150
+ }
1151
+ }
1152
+ // Pattern: permission denied
1153
+ if (!diagnosed && /permission denied|EPERM|EACCES/.test(errorText)) {
1154
+ console.error(colors.yellow('\n Hint: permission error during build.'));
1155
+ console.error(colors.yellow(' Check file ownership and that no other process has files locked.'));
1156
+ diagnosed = true;
1157
+ }
1158
+ // Pattern: TypeScript compilation error
1159
+ if (!diagnosed && /error TS\d+/.test(errorText)) {
1160
+ console.error(colors.yellow('\n Hint: TypeScript compilation errors. Fix the type errors above before publishing.'));
1161
+ diagnosed = true;
1162
+ }
1163
+ return diagnosed;
1164
+ }
1071
1165
  /** Run git commit silently and print a compact summary (no per-file create/delete mode lines). */
1072
1166
  function gitCommit(msg, cwd) {
1073
1167
  const result = runCommand('git', ['commit', '-m', msg], { cwd, silent: true });
@@ -2637,18 +2731,19 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2637
2731
  const pkg = readPackageJson(cwd);
2638
2732
  // Run build step if package.json has a build script (skip if CLI already built)
2639
2733
  if (pkg.scripts?.build && !options._fromCli) {
2640
- console.log('Running build...');
2734
+ console.log(`${timestamp()} Running build...`);
2641
2735
  if (!dryRun) {
2642
2736
  const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: !verbose });
2643
2737
  if (!buildResult.success) {
2644
2738
  console.error(colors.red('ERROR: Build failed:'), buildResult.stderr || buildResult.output);
2739
+ diagnoseBuildFailure(buildResult.stderr || buildResult.output || '', cwd);
2645
2740
  if (!force) {
2646
2741
  return false;
2647
2742
  }
2648
2743
  console.log(colors.yellow('Continuing with --force despite build failure...'));
2649
2744
  }
2650
2745
  else {
2651
- console.log(colors.green('✓ Build succeeded'));
2746
+ console.log(`${timestamp()} ${colors.green('✓ Build succeeded')}`);
2652
2747
  }
2653
2748
  }
2654
2749
  else {
@@ -3392,7 +3487,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3392
3487
  // Git operations
3393
3488
  if (currentGitStatus.hasUncommitted) {
3394
3489
  const commitMsg = message || 'Pre-release commit';
3395
- console.log(`Committing changes: ${commitMsg}`);
3490
+ console.log(`${timestamp()} Committing changes: ${commitMsg}`);
3396
3491
  if (!dryRun) {
3397
3492
  // Remove 'nul' files that break git on Windows
3398
3493
  const nulCount = removeNulFiles(cwd);
@@ -3490,7 +3585,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3490
3585
  console.log(`Skipping version bump — ${pkg.version} already set locally.`);
3491
3586
  }
3492
3587
  else {
3493
- console.log(`Bumping version (${bump})...`);
3588
+ console.log(`${timestamp()} Bumping version (${bump})...`);
3494
3589
  }
3495
3590
  if (!dryRun && !skipVersionBump) {
3496
3591
  // Temporarily disable postversion hook to prevent double-publishing or push
@@ -3547,6 +3642,9 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3547
3642
  if (error.code)
3548
3643
  console.error(' exit code:', error.code);
3549
3644
  }
3645
+ // Diagnose common failures (missing modules, permissions, TS errors)
3646
+ const versionErrorText = [error.message, error.stderr, error.stdout].filter(Boolean).join('\n');
3647
+ diagnoseBuildFailure(versionErrorText, cwd);
3550
3648
  // Show the full error stack in verbose mode
3551
3649
  if (verbose && error.stack) {
3552
3650
  console.error(' Stack trace:', error.stack);
@@ -3734,38 +3832,51 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3734
3832
  else if (!skipVersionBump) {
3735
3833
  console.log(` [dry-run] Would run: npm version ${bump}`);
3736
3834
  }
3737
- // Check for public package depending on private dependencies
3835
+ // Check for public package depending on private/unpublished scoped dependencies
3836
+ // Only scoped deps can be private — unscoped are always public on npm.
3837
+ // Use a single fast `npm view` per dep instead of the full checkNpmAccess.
3738
3838
  const publishAccess = effectiveNpmVisibility || currentAccess || (isScoped ? 'restricted' : 'public');
3739
3839
  if (publishAccess === 'public') {
3740
- const privateDeps = [];
3840
+ const scopedDeps = [];
3741
3841
  for (const depKey of ['dependencies', 'peerDependencies']) {
3742
3842
  const deps = pkg[depKey];
3743
3843
  if (!deps)
3744
3844
  continue;
3745
3845
  for (const depName of Object.keys(deps)) {
3746
- const depAccess = checkNpmAccess(depName);
3747
- if (depAccess === 'restricted') {
3748
- privateDeps.push(depName);
3749
- }
3750
- else if (depAccess === null && depName.startsWith('@')) {
3751
- // Scoped package not on npm — will be private by default
3752
- privateDeps.push(`${depName} (not yet published)`);
3753
- }
3846
+ if (depName.startsWith('@'))
3847
+ scopedDeps.push(depName);
3754
3848
  }
3755
3849
  }
3756
- if (privateDeps.length > 0) {
3757
- console.log('');
3758
- console.error(colors.red('WARNING: Public package depends on PRIVATE dependencies:'));
3759
- for (const d of privateDeps) {
3760
- console.error(colors.red(` ${d}`));
3850
+ if (scopedDeps.length > 0) {
3851
+ const privateDeps = [];
3852
+ for (const depName of scopedDeps) {
3853
+ const viewResult = spawnSafe('npm', ['view', depName, 'name'], {
3854
+ encoding: 'utf-8', stdio: 'pipe', shell: true
3855
+ });
3856
+ if (viewResult.status !== 0) {
3857
+ const stderr = viewResult.stderr || '';
3858
+ if (stderr.includes('404') || stderr.includes('not found')) {
3859
+ privateDeps.push(`${depName} (not on npm)`);
3860
+ }
3861
+ else {
3862
+ privateDeps.push(`${depName} (restricted)`);
3863
+ }
3864
+ }
3865
+ }
3866
+ if (privateDeps.length > 0) {
3867
+ console.log('');
3868
+ console.error(colors.red('WARNING: Public package depends on PRIVATE/missing dependencies:'));
3869
+ for (const d of privateDeps) {
3870
+ console.error(colors.red(` ${d}`));
3871
+ }
3872
+ console.error(colors.yellow('Consumers will get E404 when installing this package.'));
3873
+ console.error(colors.yellow('Fix: publish those deps as public, or make this package private.'));
3874
+ console.log('');
3761
3875
  }
3762
- console.error(colors.yellow('Consumers will get E404 when installing this package.'));
3763
- console.error(colors.yellow('Fix: publish those deps as public, or make this package private.'));
3764
- console.log('');
3765
3876
  }
3766
3877
  }
3767
3878
  // Publish
3768
- console.log('Publishing to npm...');
3879
+ console.log(`${timestamp()} Publishing to npm...`);
3769
3880
  if (!dryRun) {
3770
3881
  // Check npm authentication right before publishing
3771
3882
  const authStatus = checkNpmAuth();
@@ -3929,7 +4040,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3929
4040
  // Determine what was published
3930
4041
  const finalAccess = effectiveNpmVisibility || currentAccess || (isScoped ? 'restricted' : 'public');
3931
4042
  const accessLabel = (finalAccess === 'restricted' || finalAccess === 'private') ? 'PRIVATE' : 'PUBLIC';
3932
- console.log(colors.green(`✓ Published to npm as ${accessLabel}`));
4043
+ console.log(`${timestamp()} ${colors.green(`✓ Published to npm as ${accessLabel}`)}`);
3933
4044
  }
3934
4045
  else {
3935
4046
  console.log(` [dry-run] Would run: npm publish ${quiet ? '--quiet' : ''}`);
@@ -3937,7 +4048,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3937
4048
  // Push to git (with push-protection detection and auto-bypass)
3938
4049
  if (currentGitStatus.hasRemote) {
3939
4050
  if (verbose) {
3940
- console.log('Pushing to git...');
4051
+ console.log(`${timestamp()} Pushing to git...`);
3941
4052
  }
3942
4053
  if (!dryRun) {
3943
4054
  if (pushWithProtection(cwd, verbose)) {
@@ -4001,7 +4112,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4001
4112
  }
4002
4113
  }
4003
4114
  else {
4004
- console.log(`Installing globally from registry: ${pkgName}@${pkgVersion}...`);
4115
+ console.log(`${timestamp()} Installing globally from registry: ${pkgName}@${pkgVersion}...`);
4005
4116
  if (!dryRun) {
4006
4117
  waitForNpmVersion(pkgName, pkgVersion);
4007
4118
  const installResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
@@ -4072,7 +4183,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4072
4183
  else if (!files) {
4073
4184
  console.log('Keeping npm versions (--nofiles mode).');
4074
4185
  }
4075
- console.log('Done!');
4186
+ console.log(`${timestamp()} Done!`);
4076
4187
  // Run final audit report if not already run
4077
4188
  const auditAlreadyRun = (fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps);
4078
4189
  if (!auditAlreadyRun && (fix || updateDeps || transformResult.transformed) && !dryRun) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.139",
3
+ "version": "1.0.141",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@bobfrankston/freezepak": "^0.1.6",
35
35
  "@bobfrankston/importgen": "^0.1.32",
36
- "@bobfrankston/themecolors": "^0.1.2",
36
+ "@bobfrankston/themecolors": "^0.1.4",
37
37
  "@bobfrankston/userconfig": "^1.0.6",
38
38
  "@npmcli/package-json": "^7.0.4",
39
39
  "json5": "^2.2.3",