@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 +2 -10
- package/lib/types.js +3 -11
- package/lib.js +139 -28
- package/package.json +2 -2
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
|
-
/**
|
|
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 {
|
|
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
|
-
/**
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
3747
|
-
|
|
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 (
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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",
|