@bobfrankston/npmglobalize 1.0.12 → 1.0.18
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/cli.js +8 -0
- package/lib.d.ts +8 -3
- package/lib.js +427 -94
- package/package.json +2 -1
- package/.gitattributes +0 -10
- package/.globalize.jsonc +0 -8
- package/.vscode/settings.json +0 -22
- package/.vscode/spellright.dict +0 -2
- package/.vscode/tasks.json +0 -20
- package/cli.d.ts.map +0 -1
- package/cli.js.map +0 -1
- package/cli.ts +0 -197
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/index.ts +0 -12
- package/lib.d.ts.map +0 -1
- package/lib.js.map +0 -1
- package/lib.ts +0 -993
- package/token.env +0 -10
- package/token.env.example +0 -9
- package/tsconfig.json +0 -28
package/lib.js
CHANGED
|
@@ -6,11 +6,13 @@ import path from 'path';
|
|
|
6
6
|
import { execSync, spawnSync } from 'child_process';
|
|
7
7
|
import readline from 'readline';
|
|
8
8
|
import libversion from 'libnpmversion';
|
|
9
|
+
import JSON5 from 'json5';
|
|
9
10
|
/** ANSI color codes */
|
|
10
11
|
const colors = {
|
|
11
12
|
red: (text) => `\x1b[31m${text}\x1b[0m`,
|
|
12
13
|
yellow: (text) => `\x1b[33m${text}\x1b[0m`,
|
|
13
14
|
green: (text) => `\x1b[32m${text}\x1b[0m`,
|
|
15
|
+
italic: (text) => `\x1b[3m${text}\x1b[0m`,
|
|
14
16
|
};
|
|
15
17
|
const DEP_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
16
18
|
/** Read and parse package.json from a directory */
|
|
@@ -21,54 +23,100 @@ export function readPackageJson(dir) {
|
|
|
21
23
|
}
|
|
22
24
|
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
23
25
|
}
|
|
24
|
-
/** Read .globalize.
|
|
26
|
+
/** Read .globalize.json5 config file */
|
|
25
27
|
export function readConfig(dir) {
|
|
26
|
-
const configPath = path.join(dir, '.globalize.
|
|
28
|
+
const configPath = path.join(dir, '.globalize.json5');
|
|
27
29
|
if (!fs.existsSync(configPath)) {
|
|
28
30
|
return {};
|
|
29
31
|
}
|
|
30
32
|
try {
|
|
31
33
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
32
|
-
|
|
33
|
-
const jsonContent = content
|
|
34
|
-
.split('\n')
|
|
35
|
-
.map(line => line.replace(/\/\/.*$/, '').trim())
|
|
36
|
-
.filter(line => line.length > 0)
|
|
37
|
-
.join('\n');
|
|
38
|
-
return JSON.parse(jsonContent);
|
|
34
|
+
return JSON5.parse(content);
|
|
39
35
|
}
|
|
40
36
|
catch (error) {
|
|
41
|
-
console.warn(`Warning: Could not parse .globalize.
|
|
37
|
+
console.warn(`Warning: Could not parse .globalize.json5 config: ${error.message}`);
|
|
42
38
|
return {};
|
|
43
39
|
}
|
|
44
40
|
}
|
|
45
|
-
/** Write .globalize.
|
|
46
|
-
export function writeConfig(dir, config) {
|
|
47
|
-
const configPath = path.join(dir, '.globalize.
|
|
41
|
+
/** Write .globalize.json5 config file */
|
|
42
|
+
export function writeConfig(dir, config, explicitKeys) {
|
|
43
|
+
const configPath = path.join(dir, '.globalize.json5');
|
|
44
|
+
// Define defaults to omit
|
|
45
|
+
const defaults = {
|
|
46
|
+
bump: 'patch',
|
|
47
|
+
files: true,
|
|
48
|
+
quiet: true,
|
|
49
|
+
gitVisibility: 'private',
|
|
50
|
+
npmVisibility: 'public',
|
|
51
|
+
install: false,
|
|
52
|
+
wsl: false,
|
|
53
|
+
force: false,
|
|
54
|
+
verbose: false
|
|
55
|
+
};
|
|
56
|
+
// Read existing config to preserve values
|
|
57
|
+
const existing = readConfig(dir);
|
|
58
|
+
// Filter out temporary flags and default values (unless explicitly set)
|
|
59
|
+
const filtered = {};
|
|
60
|
+
const omitKeys = new Set(['applyOnly', 'cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error']);
|
|
61
|
+
for (const [key, value] of Object.entries(config)) {
|
|
62
|
+
if (omitKeys.has(key))
|
|
63
|
+
continue;
|
|
64
|
+
// Keep if: already in config, explicitly set via CLI, or differs from default
|
|
65
|
+
const isExplicit = explicitKeys?.has(key);
|
|
66
|
+
const wasInConfig = key in existing;
|
|
67
|
+
const differsFromDefault = defaults[key] !== value;
|
|
68
|
+
if (isExplicit || wasInConfig || differsFromDefault) {
|
|
69
|
+
filtered[key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
48
72
|
// Build content with comments
|
|
49
73
|
const lines = [
|
|
50
74
|
'{',
|
|
51
|
-
' //
|
|
52
|
-
' //
|
|
75
|
+
' // npmglobalize configuration (JSON5 format - trailing commas OK)',
|
|
76
|
+
' // Explicitly set values are preserved here',
|
|
53
77
|
''
|
|
54
78
|
];
|
|
55
79
|
// Add configured values
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
const entries = Object.entries(filtered);
|
|
81
|
+
if (entries.length > 0) {
|
|
82
|
+
entries.forEach(([key, value]) => {
|
|
83
|
+
const jsonValue = typeof value === 'string' ? `"${value}"` : JSON.stringify(value);
|
|
84
|
+
const comma = ','; // JSON5 allows trailing commas
|
|
85
|
+
// Add inline comment for clarity
|
|
86
|
+
let comment = '';
|
|
87
|
+
if (key === 'install')
|
|
88
|
+
comment = ' // Auto-install globally after publish';
|
|
89
|
+
else if (key === 'wsl')
|
|
90
|
+
comment = ' // Also install in WSL';
|
|
91
|
+
else if (key === 'files')
|
|
92
|
+
comment = ' // Keep file: paths after publish';
|
|
93
|
+
else if (key === 'force')
|
|
94
|
+
comment = ' // Continue despite git errors';
|
|
95
|
+
else if (key === 'quiet')
|
|
96
|
+
comment = ' // Suppress npm warnings';
|
|
97
|
+
else if (key === 'verbose')
|
|
98
|
+
comment = ' // Show detailed output';
|
|
99
|
+
else if (key === 'gitVisibility')
|
|
100
|
+
comment = ' // private (default) or public';
|
|
101
|
+
else if (key === 'npmVisibility')
|
|
102
|
+
comment = ' // private or public (default)';
|
|
103
|
+
else if (key === 'bump')
|
|
104
|
+
comment = ' // patch (default), minor, or major';
|
|
105
|
+
lines.push(` "${key}": ${jsonValue}${comma}${comment}`);
|
|
106
|
+
});
|
|
107
|
+
lines.push('');
|
|
108
|
+
}
|
|
109
|
+
// Add commented reference for all options
|
|
110
|
+
lines.push(' // Defaults (omitted above):');
|
|
111
|
+
lines.push(' // "bump": "patch" // Version bump: patch, minor, major');
|
|
112
|
+
lines.push(' // "install": false // Auto-install globally after publish');
|
|
113
|
+
lines.push(' // "wsl": false // Also install in WSL');
|
|
114
|
+
lines.push(' // "files": true // Keep file: paths after publish');
|
|
115
|
+
lines.push(' // "force": false // Continue despite git errors');
|
|
116
|
+
lines.push(' // "quiet": true // Suppress npm warnings');
|
|
117
|
+
lines.push(' // "verbose": false // Show detailed output');
|
|
118
|
+
lines.push(' // "gitVisibility": "private" // Git repo: private or public');
|
|
119
|
+
lines.push(' // "npmVisibility": "public" // npm package: private or public');
|
|
72
120
|
lines.push('}');
|
|
73
121
|
fs.writeFileSync(configPath, lines.join('\n') + '\n');
|
|
74
122
|
}
|
|
@@ -165,13 +213,13 @@ export function hasBackup(pkg) {
|
|
|
165
213
|
}
|
|
166
214
|
/** Run a command and return success status */
|
|
167
215
|
export function runCommand(cmd, args, options = {}) {
|
|
168
|
-
const { silent = false, cwd } = options;
|
|
216
|
+
const { silent = false, cwd, shell = false } = options;
|
|
169
217
|
try {
|
|
170
218
|
const result = spawnSync(cmd, args, {
|
|
171
219
|
encoding: 'utf-8',
|
|
172
220
|
stdio: silent ? 'pipe' : 'inherit',
|
|
173
221
|
cwd,
|
|
174
|
-
shell
|
|
222
|
+
shell // Use shell when explicitly requested (e.g., for npm publish on Windows)
|
|
175
223
|
});
|
|
176
224
|
// For non-silent commands, we can't capture output when using 'inherit'
|
|
177
225
|
// So we return empty string for output, but the user sees it in the terminal
|
|
@@ -379,9 +427,155 @@ Authentication Options:
|
|
|
379
427
|
- Run: ${colors.green('npm login')}
|
|
380
428
|
- Follow interactive prompts
|
|
381
429
|
|
|
430
|
+
${colors.italic('Note: y:\\dev\\utils\\npmglobalize has set-npm-token.ps1 that may help fix token')}
|
|
431
|
+
${colors.italic(' problems, but no promises.')}
|
|
432
|
+
|
|
382
433
|
Note: npm now requires either 2FA or a granular token with bypass enabled.
|
|
383
434
|
`;
|
|
384
435
|
}
|
|
436
|
+
/** Recommended .gitignore patterns */
|
|
437
|
+
const RECOMMENDED_GITIGNORE = [
|
|
438
|
+
'node_modules/',
|
|
439
|
+
'.env*',
|
|
440
|
+
'*cert*/',
|
|
441
|
+
'*.pem',
|
|
442
|
+
'*.key',
|
|
443
|
+
'*.p12',
|
|
444
|
+
'*.pfx',
|
|
445
|
+
'token',
|
|
446
|
+
'tokens',
|
|
447
|
+
'*.token',
|
|
448
|
+
'.globalize.json5',
|
|
449
|
+
'*.log',
|
|
450
|
+
'.DS_Store',
|
|
451
|
+
'Thumbs.db'
|
|
452
|
+
];
|
|
453
|
+
/** Recommended .npmignore patterns */
|
|
454
|
+
const RECOMMENDED_NPMIGNORE = [
|
|
455
|
+
'.git/',
|
|
456
|
+
'.gitignore',
|
|
457
|
+
'.gitattributes',
|
|
458
|
+
'certs/',
|
|
459
|
+
'*cert*/',
|
|
460
|
+
'.env*',
|
|
461
|
+
'token*',
|
|
462
|
+
'cruft/',
|
|
463
|
+
'prev/',
|
|
464
|
+
'tests/',
|
|
465
|
+
'*.md',
|
|
466
|
+
'!README.md',
|
|
467
|
+
'*.log',
|
|
468
|
+
'.DS_Store',
|
|
469
|
+
'Thumbs.db',
|
|
470
|
+
'package-lock.json',
|
|
471
|
+
'*.ts',
|
|
472
|
+
'!*.d.ts',
|
|
473
|
+
'*.map',
|
|
474
|
+
'tsconfig.json',
|
|
475
|
+
'.vscode/',
|
|
476
|
+
'.globalize.json5'
|
|
477
|
+
];
|
|
478
|
+
/** Check if ignore files need updates */
|
|
479
|
+
function checkIgnoreFiles(cwd, options) {
|
|
480
|
+
const changes = [];
|
|
481
|
+
// Check if asis is set in config or passed as option
|
|
482
|
+
if (options.asis) {
|
|
483
|
+
if (options.verbose) {
|
|
484
|
+
console.log(colors.yellow(' Skipping ignore file checks (--asis or asis in config)'));
|
|
485
|
+
}
|
|
486
|
+
return { needsUpdate: false, changes: [] };
|
|
487
|
+
}
|
|
488
|
+
// Check .gitignore
|
|
489
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
490
|
+
if (fs.existsSync(gitignorePath)) {
|
|
491
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
492
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
493
|
+
for (const pattern of RECOMMENDED_GITIGNORE) {
|
|
494
|
+
if (!lines.some(line => line === pattern || line === pattern.replace('/', ''))) {
|
|
495
|
+
changes.push(` .gitignore missing: ${pattern}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Check .npmignore
|
|
500
|
+
const npmignorePath = path.join(cwd, '.npmignore');
|
|
501
|
+
if (fs.existsSync(npmignorePath)) {
|
|
502
|
+
const content = fs.readFileSync(npmignorePath, 'utf-8');
|
|
503
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
504
|
+
// Check for missing TypeScript exclusions
|
|
505
|
+
const hasMapExclusion = lines.some(line => line === '*.map');
|
|
506
|
+
const hasTsExclusion = lines.some(line => line === '*.ts');
|
|
507
|
+
const hasDtsInclude = lines.some(line => line === '!*.d.ts');
|
|
508
|
+
if (!hasMapExclusion) {
|
|
509
|
+
changes.push(' .npmignore missing: *.map');
|
|
510
|
+
}
|
|
511
|
+
if (!hasTsExclusion) {
|
|
512
|
+
changes.push(' .npmignore missing: *.ts');
|
|
513
|
+
}
|
|
514
|
+
if (!hasDtsInclude && hasTsExclusion) {
|
|
515
|
+
changes.push(' .npmignore missing: !*.d.ts (to keep type definitions)');
|
|
516
|
+
}
|
|
517
|
+
// Check for token files
|
|
518
|
+
if (!lines.some(line => line.includes('token'))) {
|
|
519
|
+
changes.push(' .npmignore missing: token* (security)');
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return { needsUpdate: changes.length > 0, changes };
|
|
523
|
+
}
|
|
524
|
+
/** Update ignore files to conform to best practices */
|
|
525
|
+
function conformIgnoreFiles(cwd) {
|
|
526
|
+
// Update .gitignore
|
|
527
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
528
|
+
if (fs.existsSync(gitignorePath)) {
|
|
529
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
530
|
+
const lines = new Set(content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#')));
|
|
531
|
+
let updated = false;
|
|
532
|
+
for (const pattern of RECOMMENDED_GITIGNORE) {
|
|
533
|
+
const normalized = pattern.replace('/', '');
|
|
534
|
+
if (!lines.has(pattern) && !lines.has(normalized)) {
|
|
535
|
+
lines.add(pattern);
|
|
536
|
+
updated = true;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (updated) {
|
|
540
|
+
const newContent = Array.from(lines).sort().join('\n') + '\n';
|
|
541
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
542
|
+
console.log(colors.green(' ✓ Updated .gitignore'));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// Update .npmignore
|
|
546
|
+
const npmignorePath = path.join(cwd, '.npmignore');
|
|
547
|
+
if (fs.existsSync(npmignorePath)) {
|
|
548
|
+
const content = fs.readFileSync(npmignorePath, 'utf-8');
|
|
549
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
550
|
+
const newLines = new Set(lines.filter(l => l));
|
|
551
|
+
let updated = false;
|
|
552
|
+
// Add TypeScript exclusions
|
|
553
|
+
if (!newLines.has('*.ts')) {
|
|
554
|
+
newLines.add('*.ts');
|
|
555
|
+
updated = true;
|
|
556
|
+
}
|
|
557
|
+
if (!newLines.has('!*.d.ts')) {
|
|
558
|
+
newLines.add('!*.d.ts');
|
|
559
|
+
updated = true;
|
|
560
|
+
}
|
|
561
|
+
if (!newLines.has('*.map')) {
|
|
562
|
+
newLines.add('*.map');
|
|
563
|
+
updated = true;
|
|
564
|
+
}
|
|
565
|
+
// Add other missing patterns
|
|
566
|
+
for (const pattern of RECOMMENDED_NPMIGNORE) {
|
|
567
|
+
if (!newLines.has(pattern)) {
|
|
568
|
+
newLines.add(pattern);
|
|
569
|
+
updated = true;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (updated) {
|
|
573
|
+
const newContent = Array.from(newLines).join('\n') + '\n';
|
|
574
|
+
fs.writeFileSync(npmignorePath, newContent);
|
|
575
|
+
console.log(colors.green(' ✓ Updated .npmignore'));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
385
579
|
/** Ensure .gitignore exists and includes node_modules */
|
|
386
580
|
function ensureGitignore(cwd) {
|
|
387
581
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
@@ -487,7 +681,37 @@ export async function initGit(cwd, visibility, dryRun) {
|
|
|
487
681
|
}
|
|
488
682
|
/** Main globalize function */
|
|
489
683
|
export async function globalize(cwd, options = {}) {
|
|
490
|
-
const { bump = 'patch', applyOnly = false, cleanup = false, install = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'public', message } = options;
|
|
684
|
+
const { bump = 'patch', applyOnly = false, cleanup = false, install = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'public', message, conform = false, asis = false } = options;
|
|
685
|
+
// Check ignore files first (unless cleanup mode)
|
|
686
|
+
if (!cleanup && !asis) {
|
|
687
|
+
const checkResult = checkIgnoreFiles(cwd, { conform, asis, verbose });
|
|
688
|
+
if (checkResult.needsUpdate) {
|
|
689
|
+
console.log(colors.yellow('\nIgnore file recommendations:'));
|
|
690
|
+
for (const change of checkResult.changes) {
|
|
691
|
+
console.log(colors.yellow(change));
|
|
692
|
+
}
|
|
693
|
+
console.log('');
|
|
694
|
+
if (conform) {
|
|
695
|
+
console.log('Updating ignore files (--conform)...');
|
|
696
|
+
conformIgnoreFiles(cwd);
|
|
697
|
+
console.log('');
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
console.log(colors.yellow('Run with --conform to apply these changes'));
|
|
701
|
+
console.log(colors.yellow('Or set "asis": true in .globalize.json5 to suppress checks'));
|
|
702
|
+
console.log(colors.yellow('Or use --asis flag to skip this check'));
|
|
703
|
+
console.log('');
|
|
704
|
+
const shouldContinue = await confirm('Continue anyway?', true);
|
|
705
|
+
if (!shouldContinue) {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else if (verbose) {
|
|
711
|
+
console.log(colors.green('✓ Ignore files look good'));
|
|
712
|
+
console.log('');
|
|
713
|
+
}
|
|
714
|
+
}
|
|
491
715
|
// Handle cleanup mode
|
|
492
716
|
if (cleanup) {
|
|
493
717
|
console.log('Restoring dependencies from backup...');
|
|
@@ -659,6 +883,60 @@ export async function globalize(cwd, options = {}) {
|
|
|
659
883
|
if (!currentGitStatus.hasUncommitted && !message) {
|
|
660
884
|
console.log('');
|
|
661
885
|
console.log('No changes to commit and no custom message specified.');
|
|
886
|
+
// If install flag is set, verify/install even without publishing
|
|
887
|
+
if (install || wsl) {
|
|
888
|
+
console.log('');
|
|
889
|
+
console.log('Checking global installation...');
|
|
890
|
+
const pkgName = pkg.name;
|
|
891
|
+
if (install) {
|
|
892
|
+
const verifyResult = runCommand('npm', ['list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
893
|
+
if (verifyResult.success) {
|
|
894
|
+
console.log(colors.green(`✓ Already installed globally: ${pkgName}`));
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
console.log(colors.yellow(`Package not installed globally. Installing latest version...`));
|
|
898
|
+
const installResult = runCommand('npm', ['install', '-g', `${pkgName}@latest`], { cwd, silent: false });
|
|
899
|
+
if (installResult.success) {
|
|
900
|
+
const reVerify = runCommand('npm', ['list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
901
|
+
if (reVerify.success) {
|
|
902
|
+
const version = pkg.version;
|
|
903
|
+
console.log(colors.green(`✓ Installed and verified globally: ${pkgName}@${version}`));
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
console.log(colors.yellow(`⚠ Install appeared successful but verification failed`));
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
console.error(colors.red(`✗ Global install failed`));
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (wsl) {
|
|
915
|
+
const verifyResult = runCommand('wsl', ['npm', 'list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
916
|
+
if (verifyResult.success) {
|
|
917
|
+
console.log(colors.green(`✓ Already installed in WSL: ${pkgName}`));
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
console.log(colors.yellow(`Package not installed in WSL. Installing latest version...`));
|
|
921
|
+
const wslInstallResult = runCommand('wsl', ['npm', 'install', '-g', `${pkgName}@latest`], { cwd, silent: false });
|
|
922
|
+
if (wslInstallResult.success) {
|
|
923
|
+
const reVerify = runCommand('wsl', ['npm', 'list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
924
|
+
if (reVerify.success) {
|
|
925
|
+
const version = pkg.version;
|
|
926
|
+
console.log(colors.green(`✓ Installed and verified in WSL: ${pkgName}@${version}`));
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
console.log(colors.yellow(`⚠ WSL install appeared successful but verification failed`));
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
console.error(colors.yellow('✗ WSL install failed (is npm installed in WSL?)'));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
console.log('');
|
|
938
|
+
return true;
|
|
939
|
+
}
|
|
662
940
|
console.log('Nothing to release. Use -m "message" to force a release.');
|
|
663
941
|
return true;
|
|
664
942
|
}
|
|
@@ -671,8 +949,22 @@ export async function globalize(cwd, options = {}) {
|
|
|
671
949
|
const commitMsg = message || 'Pre-release commit';
|
|
672
950
|
console.log(`Committing changes: ${commitMsg}`);
|
|
673
951
|
if (!dryRun) {
|
|
674
|
-
runCommand('git', ['add', '-A'], { cwd });
|
|
675
|
-
|
|
952
|
+
const addResult = runCommand('git', ['add', '-A'], { cwd });
|
|
953
|
+
if (!addResult.success) {
|
|
954
|
+
console.error(colors.red('ERROR: Failed to add files to git:'), addResult.stderr);
|
|
955
|
+
if (!force) {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
console.log(colors.yellow('Continuing with --force...'));
|
|
959
|
+
}
|
|
960
|
+
const commitResult = runCommand('git', ['commit', '-m', commitMsg], { cwd });
|
|
961
|
+
if (!commitResult.success) {
|
|
962
|
+
console.error(colors.red('ERROR: Failed to commit changes:'), commitResult.stderr);
|
|
963
|
+
if (!force) {
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
console.log(colors.yellow('Continuing with --force...'));
|
|
967
|
+
}
|
|
676
968
|
}
|
|
677
969
|
else {
|
|
678
970
|
console.log(' [dry-run] Would commit changes');
|
|
@@ -701,69 +993,85 @@ export async function globalize(cwd, options = {}) {
|
|
|
701
993
|
}
|
|
702
994
|
// Publish
|
|
703
995
|
console.log('Publishing to npm...');
|
|
704
|
-
const npmArgs = ['publish'];
|
|
705
|
-
if (quiet) {
|
|
706
|
-
npmArgs.push('--quiet');
|
|
707
|
-
}
|
|
708
996
|
if (!dryRun) {
|
|
709
|
-
//
|
|
710
|
-
const
|
|
711
|
-
if (!
|
|
712
|
-
console.error(colors.red('
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
997
|
+
// Create tarball first
|
|
998
|
+
const packResult = runCommand('npm', ['pack'], { cwd, silent: true, shell: true });
|
|
999
|
+
if (!packResult.success) {
|
|
1000
|
+
console.error(colors.red('ERROR: Failed to create package tarball'));
|
|
1001
|
+
if (packResult.stderr)
|
|
1002
|
+
console.error(packResult.stderr);
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
// Get the tarball filename from npm pack output
|
|
1006
|
+
const tarballName = packResult.output.trim().split('\n').pop()?.trim();
|
|
1007
|
+
if (!tarballName) {
|
|
1008
|
+
console.error(colors.red('ERROR: Could not determine tarball filename'));
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
const tarballPath = path.join(cwd, tarballName);
|
|
1012
|
+
if (verbose) {
|
|
1013
|
+
console.log(`Created tarball: ${tarballName}`);
|
|
1014
|
+
}
|
|
1015
|
+
// Publish the tarball
|
|
1016
|
+
const npmArgs = ['publish', tarballName];
|
|
1017
|
+
if (npmVisibility === 'public') {
|
|
1018
|
+
npmArgs.push('--access', 'public');
|
|
1019
|
+
}
|
|
1020
|
+
if (quiet) {
|
|
1021
|
+
npmArgs.push('--quiet');
|
|
1022
|
+
}
|
|
1023
|
+
// Retry logic for E409 conflicts (publishing too fast)
|
|
1024
|
+
const maxRetries = 12; // 12 attempts * 5 seconds = 60 seconds total
|
|
1025
|
+
const retryDelay = 5; // 5 seconds between attempts
|
|
1026
|
+
let publishResult;
|
|
1027
|
+
let attempt = 0;
|
|
1028
|
+
while (attempt < maxRetries) {
|
|
1029
|
+
if (attempt > 0) {
|
|
1030
|
+
console.log(colors.yellow(`⏱ Waiting ${retryDelay} seconds before retry (attempt ${attempt + 1}/${maxRetries})...`));
|
|
1031
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay * 1000));
|
|
737
1032
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1033
|
+
publishResult = runCommand('npm', npmArgs, { cwd, silent: true, shell: true });
|
|
1034
|
+
if (publishResult.success) {
|
|
1035
|
+
break; // Success!
|
|
1036
|
+
}
|
|
1037
|
+
// Check if it's an E409 error
|
|
1038
|
+
const output = (publishResult.output + '\n' + publishResult.stderr).toLowerCase();
|
|
1039
|
+
const isE409 = output.includes('e409') || output.includes('409 conflict');
|
|
1040
|
+
if (!isE409) {
|
|
1041
|
+
break; // Not an E409, don't retry
|
|
1042
|
+
}
|
|
1043
|
+
if (attempt === 0) {
|
|
1044
|
+
console.log(colors.yellow('⚠ npm is still processing the previous version, retrying...'));
|
|
746
1045
|
}
|
|
747
|
-
|
|
748
|
-
|
|
1046
|
+
attempt++;
|
|
1047
|
+
}
|
|
1048
|
+
// Clean up tarball
|
|
1049
|
+
try {
|
|
1050
|
+
fs.unlinkSync(tarballPath);
|
|
1051
|
+
if (verbose) {
|
|
1052
|
+
console.log(`Cleaned up tarball: ${tarballName}`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
catch (e) {
|
|
1056
|
+
// Ignore cleanup errors
|
|
1057
|
+
}
|
|
1058
|
+
if (!publishResult.success) {
|
|
1059
|
+
console.error(colors.red('\nERROR: npm publish failed\n'));
|
|
1060
|
+
// Check for E409 conflict (publishing too fast)
|
|
1061
|
+
const output = (publishResult.output + '\n' + publishResult.stderr).toLowerCase();
|
|
1062
|
+
if (output.includes('e409') || output.includes('409 conflict')) {
|
|
1063
|
+
console.error(colors.yellow('⚠ Publishing too quickly'));
|
|
749
1064
|
console.error('');
|
|
750
|
-
console.error('
|
|
751
|
-
console.error('
|
|
1065
|
+
console.error('npm is still processing the previous version after multiple retries.');
|
|
1066
|
+
console.error(colors.green('Solution: Wait a few more minutes and try again'));
|
|
752
1067
|
console.error('');
|
|
753
1068
|
}
|
|
754
1069
|
else {
|
|
755
|
-
// Show the actual error output
|
|
756
|
-
if (publishResult.stderr) {
|
|
757
|
-
console.error(publishResult.stderr);
|
|
758
|
-
}
|
|
759
|
-
if (publishResult.output) {
|
|
760
|
-
console.error(publishResult.output);
|
|
761
|
-
}
|
|
762
|
-
console.error('');
|
|
763
1070
|
console.error(colors.yellow('Common causes:'));
|
|
764
1071
|
console.error(colors.yellow(' 1. Not logged in - run: npm login'));
|
|
765
1072
|
console.error(colors.yellow(' 2. Version already published'));
|
|
766
1073
|
console.error(colors.yellow(' 3. Authentication token expired'));
|
|
1074
|
+
console.error('');
|
|
767
1075
|
}
|
|
768
1076
|
if (transformed) {
|
|
769
1077
|
console.log('Run --cleanup to restore file: dependencies');
|
|
@@ -788,21 +1096,46 @@ export async function globalize(cwd, options = {}) {
|
|
|
788
1096
|
}
|
|
789
1097
|
// Global install
|
|
790
1098
|
const pkgName = pkg.name;
|
|
1099
|
+
const pkgVersion = pkg.version;
|
|
791
1100
|
if (install) {
|
|
792
|
-
console.log(`Installing globally: ${pkgName}...`);
|
|
1101
|
+
console.log(`Installing globally: ${pkgName}@${pkgVersion}...`);
|
|
793
1102
|
if (!dryRun) {
|
|
794
|
-
runCommand('npm', ['install', '-g', pkgName], { cwd });
|
|
1103
|
+
const installResult = runCommand('npm', ['install', '-g', `${pkgName}@latest`], { cwd, silent: false });
|
|
1104
|
+
if (installResult.success) {
|
|
1105
|
+
// Verify installation by checking if command exists
|
|
1106
|
+
const verifyResult = runCommand('npm', ['list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
1107
|
+
if (verifyResult.success) {
|
|
1108
|
+
console.log(colors.green(`✓ Installed and verified globally: ${pkgName}@${pkgVersion}`));
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
console.log(colors.yellow(`⚠ Install appeared successful but verification failed`));
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
console.error(colors.red(`✗ Global install failed`));
|
|
1116
|
+
console.error(colors.yellow(' Try running manually: npm install -g ' + pkgName));
|
|
1117
|
+
}
|
|
795
1118
|
}
|
|
796
1119
|
else {
|
|
797
1120
|
console.log(` [dry-run] Would run: npm install -g ${pkgName}`);
|
|
798
1121
|
}
|
|
799
1122
|
}
|
|
800
1123
|
if (wsl) {
|
|
801
|
-
console.log(`Installing in WSL: ${pkgName}...`);
|
|
1124
|
+
console.log(`Installing in WSL: ${pkgName}@${pkgVersion}...`);
|
|
802
1125
|
if (!dryRun) {
|
|
803
|
-
const wslResult = runCommand('wsl', ['npm', 'install', '-g', pkgName], { cwd });
|
|
804
|
-
if (
|
|
805
|
-
|
|
1126
|
+
const wslResult = runCommand('wsl', ['npm', 'install', '-g', `${pkgName}@latest`], { cwd, silent: false });
|
|
1127
|
+
if (wslResult.success) {
|
|
1128
|
+
// Verify WSL installation
|
|
1129
|
+
const verifyResult = runCommand('wsl', ['npm', 'list', '-g', '--depth=0', pkgName], { cwd, silent: true });
|
|
1130
|
+
if (verifyResult.success) {
|
|
1131
|
+
console.log(colors.green(`✓ Installed and verified in WSL: ${pkgName}@${pkgVersion}`));
|
|
1132
|
+
}
|
|
1133
|
+
else {
|
|
1134
|
+
console.log(colors.yellow(`⚠ WSL install appeared successful but verification failed`));
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
console.error(colors.yellow(' ✗ WSL install failed (is npm installed in WSL?)'));
|
|
806
1139
|
}
|
|
807
1140
|
}
|
|
808
1141
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/npmglobalize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "Transform file: dependencies to npm versions for publishing",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"url": "https://github.com/BobFrankston/npmglobalize.git"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"json5": "^2.2.3",
|
|
30
31
|
"libnpmversion": "^8.0.3"
|
|
31
32
|
}
|
|
32
33
|
}
|