@bobfrankston/npmglobalize 1.0.150 → 1.0.151

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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Diagnose failures of npm commands run by npmglobalize.
3
+ *
4
+ * Pulls out common error shapes — especially ones whose root cause lives in
5
+ * a referenced (file:) module, not the current package — and turns them into
6
+ * a short summary + actionable hint instead of a raw arborist stack trace.
7
+ *
8
+ * Leaf module: no dependencies on lib.ts or other internal modules. Keeps
9
+ * the main publish flow in lib.ts readable.
10
+ */
11
+ export interface DiagnosedError {
12
+ /** Short one-liner suitable for the Issues Summary. */
13
+ summary: string;
14
+ /** Multi-line block to print to console.error (without color). */
15
+ details: string[];
16
+ /** If the root cause is a file: sibling, its package name. */
17
+ referencedModule?: string;
18
+ /** One concrete next step the user can take. */
19
+ hint?: string;
20
+ }
21
+ /** Diagnose an `npm pack` failure. */
22
+ export declare function diagnoseNpmPackFailure(cwd: string, output: string, stderr: string, pkg: any): DiagnosedError;
23
+ /** Given a nested-node_modules path like
24
+ * `node_modules/@scope/sibling/node_modules/transitive`
25
+ * or `../sibling/node_modules/transitive`,
26
+ * find the outermost dep name and resolve it to a file: sibling declared
27
+ * in pkg.dependencies / pkg['.dependencies'] / etc. */
28
+ declare function resolveSiblingFromNestedPath(nestedPath: string, cwd: string, pkg: any): {
29
+ name: string;
30
+ filePath: string;
31
+ } | undefined;
32
+ /** Scan pkg deps for a file: entry whose resolved absolute path equals absTarget. */
33
+ declare function findFileDepByPath(pkg: any, cwd: string, absTarget: string): {
34
+ name: string;
35
+ filePath: string;
36
+ } | undefined;
37
+ /** Exposed for ad-hoc testing — not used by the main flow. */
38
+ export declare const _test: {
39
+ resolveSiblingFromNestedPath: typeof resolveSiblingFromNestedPath;
40
+ findFileDepByPath: typeof findFileDepByPath;
41
+ };
42
+ export {};
43
+ //# sourceMappingURL=diagnose.d.ts.map
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Diagnose failures of npm commands run by npmglobalize.
3
+ *
4
+ * Pulls out common error shapes — especially ones whose root cause lives in
5
+ * a referenced (file:) module, not the current package — and turns them into
6
+ * a short summary + actionable hint instead of a raw arborist stack trace.
7
+ *
8
+ * Leaf module: no dependencies on lib.ts or other internal modules. Keeps
9
+ * the main publish flow in lib.ts readable.
10
+ */
11
+ import path from 'path';
12
+ /** Diagnose an `npm pack` failure. */
13
+ export function diagnoseNpmPackFailure(cwd, output, stderr, pkg) {
14
+ const blob = `${output}\n${stderr}`;
15
+ const lower = blob.toLowerCase();
16
+ // Pattern 1: arborist "missing from lockfile: <path>" + null-`package` TypeError.
17
+ // Root cause: a file: sibling has its own populated node_modules/<transitive>
18
+ // that is not in the current package's lockfile. See notes.md §TODO:
19
+ // "Isolate npm pack from sibling file: dep junctions".
20
+ const missingMatch = blob.match(/missing from lockfile:\s*(\S+)/i);
21
+ const hasNullPackage = /cannot read propert(y|ies) of null \(reading 'package'\)/i.test(blob);
22
+ if (missingMatch && (hasNullPackage || /shrinkwrap failed to load/i.test(blob))) {
23
+ const nestedPath = missingMatch[1];
24
+ const sibling = resolveSiblingFromNestedPath(nestedPath, cwd, pkg);
25
+ const details = [
26
+ `npm arborist crashed walking a nested node_modules tree.`,
27
+ `Missing from lockfile: ${nestedPath}`,
28
+ ];
29
+ if (sibling) {
30
+ return {
31
+ summary: `npm pack failed — arborist crashed on sibling's node_modules`,
32
+ details,
33
+ referencedModule: sibling.name,
34
+ hint: `sibling ${sibling.name} has its own populated node_modules; see notes.md §TODO: Isolate npm pack from sibling file: dep junctions`,
35
+ };
36
+ }
37
+ return {
38
+ summary: `npm pack failed — arborist crashed on a nested node_modules`,
39
+ details,
40
+ hint: `likely a file: dep whose target has its own node_modules; see notes.md §TODO: Isolate npm pack from sibling file: dep junctions`,
41
+ };
42
+ }
43
+ // Pattern 2: arborist null-`package` without an obvious path — same class,
44
+ // no identifiable referenced module.
45
+ if (hasNullPackage) {
46
+ return {
47
+ summary: `npm pack failed — arborist internal error`,
48
+ details: [extractArboristFrame(blob) || 'Cannot read properties of null (reading \'package\')'],
49
+ hint: `likely a symlinked file: dep with its own node_modules; try renaming node_modules/ and running \`npm pack --dry-run\``,
50
+ };
51
+ }
52
+ // Pattern 3: shrinkwrap / lockfile parsing without the arborist crash.
53
+ if (/enolock|shrinkwrap failed to load|eresolve/i.test(lower)) {
54
+ return {
55
+ summary: `npm pack failed — lockfile problem`,
56
+ details: [extractFirstNpmError(blob) || blob.trim().slice(0, 400)],
57
+ hint: `run \`npm install\` in ${cwd}, then retry`,
58
+ };
59
+ }
60
+ // Pattern 4: file-locking on the tarball (AV, editor, explorer preview).
61
+ if (/\be(acces|busy|perm)\b/i.test(blob) && /\.tgz\b/i.test(blob)) {
62
+ return {
63
+ summary: `npm pack failed — tarball file locked`,
64
+ details: [extractFirstNpmError(blob) || blob.trim().slice(0, 400)],
65
+ hint: `close editors / antivirus holding the .tgz, then retry`,
66
+ };
67
+ }
68
+ // Fallthrough: raw output.
69
+ return {
70
+ summary: `npm pack failed`,
71
+ details: [
72
+ output.trim() ? `Output: ${output.trim()}` : '',
73
+ stderr.trim() ? `Error: ${stderr.trim()}` : '',
74
+ ].filter(Boolean),
75
+ };
76
+ }
77
+ /** Given a nested-node_modules path like
78
+ * `node_modules/@scope/sibling/node_modules/transitive`
79
+ * or `../sibling/node_modules/transitive`,
80
+ * find the outermost dep name and resolve it to a file: sibling declared
81
+ * in pkg.dependencies / pkg['.dependencies'] / etc. */
82
+ function resolveSiblingFromNestedPath(nestedPath, cwd, pkg) {
83
+ const normalized = nestedPath.replace(/\\/g, '/');
84
+ // Prefer the sibling-relative shape first: `../iflow-direct/node_modules/undici-types`.
85
+ // The *outermost* thing is the sibling dir, not the nested transitive.
86
+ const siblingMatch = normalized.match(/^(?:\.\.\/)+((?:@[^/]+\/)?[^/]+)\/node_modules\//);
87
+ if (siblingMatch) {
88
+ const absSibling = path.resolve(cwd, normalized.split('/node_modules/')[0]);
89
+ const resolved = findFileDepByPath(pkg, cwd, absSibling);
90
+ if (resolved)
91
+ return resolved;
92
+ return { name: siblingMatch[1], filePath: absSibling };
93
+ }
94
+ // Otherwise use the first `node_modules/<name>` segment.
95
+ const nmMatch = normalized.match(/(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)/);
96
+ const candidateName = nmMatch?.[1];
97
+ if (!candidateName)
98
+ return undefined;
99
+ // Try to confirm it's a file: dep by checking pkg.dependencies and .dependencies.
100
+ const allDeps = {};
101
+ for (const key of ['dependencies', '.dependencies', 'devDependencies', '.devDependencies']) {
102
+ if (pkg && pkg[key] && typeof pkg[key] === 'object')
103
+ Object.assign(allDeps, pkg[key]);
104
+ }
105
+ const spec = allDeps[candidateName];
106
+ if (spec && spec.startsWith('file:')) {
107
+ return { name: candidateName, filePath: path.resolve(cwd, spec.slice('file:'.length)) };
108
+ }
109
+ // Even if not explicitly file:, still useful to name it.
110
+ return { name: candidateName, filePath: path.resolve(cwd, 'node_modules', candidateName) };
111
+ }
112
+ /** Scan pkg deps for a file: entry whose resolved absolute path equals absTarget. */
113
+ function findFileDepByPath(pkg, cwd, absTarget) {
114
+ const target = path.resolve(absTarget).toLowerCase();
115
+ for (const key of ['dependencies', '.dependencies', 'devDependencies', '.devDependencies']) {
116
+ const deps = pkg && pkg[key];
117
+ if (!deps || typeof deps !== 'object')
118
+ continue;
119
+ for (const [name, spec] of Object.entries(deps)) {
120
+ if (typeof spec !== 'string' || !spec.startsWith('file:'))
121
+ continue;
122
+ const abs = path.resolve(cwd, spec.slice('file:'.length)).toLowerCase();
123
+ if (abs === target)
124
+ return { name, filePath: abs };
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+ /** Pull out the first arborist stack frame for display. */
130
+ function extractArboristFrame(blob) {
131
+ const m = blob.match(/at [^\n]*arborist[^\n]*/i);
132
+ return m ? m[0].trim() : undefined;
133
+ }
134
+ /** Pull out the first `npm error <message>` line. */
135
+ function extractFirstNpmError(blob) {
136
+ const m = blob.match(/npm error [^\n]+/i);
137
+ return m ? m[0].trim() : undefined;
138
+ }
139
+ /** Exposed for ad-hoc testing — not used by the main flow. */
140
+ export const _test = { resolveSiblingFromNestedPath, findFileDepByPath };
141
+ //# sourceMappingURL=diagnose.js.map
package/lib.js CHANGED
@@ -31,6 +31,7 @@ import libversion from 'libnpmversion';
31
31
  import JSON5 from 'json5';
32
32
  import { fileURLToPath } from 'url';
33
33
  import { themeColors } from '@bobfrankston/themecolors';
34
+ import { diagnoseNpmPackFailure } from './lib/diagnose.js';
34
35
  /** Semantic color functions — adapts to terminal light/dark theme */
35
36
  const colors = themeColors();
36
37
  const _buildIssues = [];
@@ -2976,7 +2977,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2976
2977
  console.log(colors.yellow('Local branch is behind remote.'));
2977
2978
  if (rebase) {
2978
2979
  console.log('Rebasing local changes (--rebase)...');
2979
- const rebaseResult = runCommand('git', ['pull', '--rebase'], { cwd, silent: false });
2980
+ const rebaseResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: false });
2980
2981
  if (!rebaseResult.success) {
2981
2982
  console.error(colors.red('ERROR: Rebase failed.'));
2982
2983
  console.error('You may need to resolve conflicts manually.');
@@ -3872,7 +3873,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3872
3873
  }
3873
3874
  // Pull latest from remote before version bump to avoid push rejection
3874
3875
  if (currentGitStatus.hasRemote && !dryRun) {
3875
- const pullResult = runCommand('git', ['pull', '--rebase'], { cwd, silent: true });
3876
+ const pullResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: true });
3876
3877
  if (!pullResult.success) {
3877
3878
  console.error(colors.yellow('Warning: git pull --rebase failed before version bump'));
3878
3879
  if (verbose) {
@@ -3973,7 +3974,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3973
3974
  // Version bump + tag succeeded locally; only the push failed.
3974
3975
  // Auto-pull --rebase and retry the push.
3975
3976
  console.log(colors.yellow('\nLocal branch is behind remote — pulling with rebase...'));
3976
- const pullResult = runCommand('git', ['pull', '--rebase'], { cwd });
3977
+ const pullResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd });
3977
3978
  if (pullResult.success) {
3978
3979
  console.log(colors.green(' ✓ Rebased onto remote'));
3979
3980
  const pushResult = runCommand('git', ['push'], { cwd });
@@ -4239,10 +4240,15 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4239
4240
  // Create tarball first
4240
4241
  const packResult = runCommand('npm', ['pack'], { cwd, silent: true });
4241
4242
  if (!packResult.success) {
4242
- console.error(colors.red('ERROR: Failed to create package tarball'));
4243
- console.error(colors.yellow('Output:'), packResult.output);
4244
- console.error(colors.yellow('Error:'), packResult.stderr);
4245
- recordBuildIssue(pkg.name || path.basename(cwd), 'error', 'npm pack failed');
4243
+ const d = diagnoseNpmPackFailure(cwd, packResult.output, packResult.stderr, pkg);
4244
+ console.error(colors.red(`ERROR: ${d.summary}`));
4245
+ for (const line of d.details)
4246
+ console.error(colors.yellow(' ' + line));
4247
+ if (d.referencedModule)
4248
+ console.error(colors.yellow(` Caused by referenced module: ${d.referencedModule}`));
4249
+ if (d.hint)
4250
+ console.error(colors.yellow(` Hint: ${d.hint}`));
4251
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', d.referencedModule ? `${d.summary} (via ${d.referencedModule})` : d.summary);
4246
4252
  return false;
4247
4253
  }
4248
4254
  // Get the tarball filename from npm pack output
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.150",
3
+ "version": "1.0.151",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",