@bobfrankston/npmglobalize 1.0.23 → 1.0.25
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 +33 -2
- package/lib.d.ts +16 -0
- package/lib.js +197 -3
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -10,7 +10,10 @@ function printHelp() {
|
|
|
10
10
|
console.log(`
|
|
11
11
|
npmglobalize - Transform file: dependencies to npm versions for publishing
|
|
12
12
|
|
|
13
|
-
Usage: npmglobalize [options]
|
|
13
|
+
Usage: npmglobalize [path] [options]
|
|
14
|
+
|
|
15
|
+
Arguments:
|
|
16
|
+
path Path to the project directory (default: current directory)
|
|
14
17
|
|
|
15
18
|
Release Options:
|
|
16
19
|
--patch Bump patch version (default)
|
|
@@ -48,12 +51,15 @@ Other Options:
|
|
|
48
51
|
--verbose Show detailed output
|
|
49
52
|
--conform Update .gitignore/.npmignore to best practices
|
|
50
53
|
--asis Skip ignore file checks (or set "asis": true in .globalize.json5)
|
|
54
|
+
--fix-tags Automatically fix version/tag mismatches
|
|
51
55
|
--help, -h Show this help
|
|
52
56
|
--version, -v Show version number
|
|
53
57
|
|
|
54
58
|
Examples:
|
|
55
59
|
npmglobalize Transform + publish (auto-publishes file: deps)
|
|
60
|
+
npmglobalize y:\\path\\to\\project Run on a different project directory
|
|
56
61
|
npmglobalize --minor Release with minor version bump
|
|
62
|
+
npmglobalize --fix-tags Fix version/tag mismatches before running
|
|
57
63
|
npmglobalize --update-deps Safe updates (minor/patch only)
|
|
58
64
|
npmglobalize --update-major Allow breaking changes (major updates)
|
|
59
65
|
npmglobalize -npd Skip auto-publishing file: deps (use with caution)
|
|
@@ -73,6 +79,7 @@ function parseArgs(args) {
|
|
|
73
79
|
error: ''
|
|
74
80
|
};
|
|
75
81
|
const unrecognized = [];
|
|
82
|
+
const positional = [];
|
|
76
83
|
for (let i = 0; i < args.length; i++) {
|
|
77
84
|
const arg = args[i];
|
|
78
85
|
switch (arg) {
|
|
@@ -171,6 +178,9 @@ function parseArgs(args) {
|
|
|
171
178
|
case '--asis':
|
|
172
179
|
options.asis = true;
|
|
173
180
|
break;
|
|
181
|
+
case '--fix-tags':
|
|
182
|
+
options.fixTags = true;
|
|
183
|
+
break;
|
|
174
184
|
case '--update-deps':
|
|
175
185
|
options.updateDeps = true;
|
|
176
186
|
break;
|
|
@@ -198,12 +208,21 @@ function parseArgs(args) {
|
|
|
198
208
|
if (arg.startsWith('-')) {
|
|
199
209
|
unrecognized.push(arg);
|
|
200
210
|
}
|
|
211
|
+
else {
|
|
212
|
+
positional.push(arg);
|
|
213
|
+
}
|
|
201
214
|
break;
|
|
202
215
|
}
|
|
203
216
|
}
|
|
204
217
|
if (unrecognized.length > 0) {
|
|
205
218
|
options.error = `Unrecognized arguments: ${unrecognized.join(', ')}`;
|
|
206
219
|
}
|
|
220
|
+
if (positional.length > 1) {
|
|
221
|
+
options.error = `Too many positional arguments. Expected at most 1 (path), got ${positional.length}`;
|
|
222
|
+
}
|
|
223
|
+
else if (positional.length === 1) {
|
|
224
|
+
options.targetPath = positional[0];
|
|
225
|
+
}
|
|
207
226
|
return options;
|
|
208
227
|
}
|
|
209
228
|
export async function main() {
|
|
@@ -226,7 +245,19 @@ export async function main() {
|
|
|
226
245
|
console.error('Run with --help for usage.');
|
|
227
246
|
process.exit(1);
|
|
228
247
|
}
|
|
229
|
-
|
|
248
|
+
let cwd = process.cwd();
|
|
249
|
+
// Use target path if provided
|
|
250
|
+
if (cliOptions.targetPath) {
|
|
251
|
+
cwd = path.resolve(cwd, cliOptions.targetPath);
|
|
252
|
+
if (!fs.existsSync(cwd)) {
|
|
253
|
+
console.error(`Error: Path does not exist: ${cwd}`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
if (!fs.statSync(cwd).isDirectory()) {
|
|
257
|
+
console.error(`Error: Not a directory: ${cwd}`);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
230
261
|
// Load config file and merge with CLI options (CLI takes precedence)
|
|
231
262
|
const configOptions = readConfig(cwd);
|
|
232
263
|
const options = { ...configOptions, ...cliOptions };
|
package/lib.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface GlobalizeOptions {
|
|
|
45
45
|
forcePublish?: boolean;
|
|
46
46
|
/** Run npm audit and fix vulnerabilities */
|
|
47
47
|
fix?: boolean;
|
|
48
|
+
/** Automatically fix version/tag mismatches */
|
|
49
|
+
fixTags?: boolean;
|
|
48
50
|
}
|
|
49
51
|
/** Read and parse package.json from a directory */
|
|
50
52
|
export declare function readPackageJson(dir: string): any;
|
|
@@ -91,6 +93,20 @@ export declare function transformDeps(pkg: any, baseDir: string, verbose?: boole
|
|
|
91
93
|
export declare function restoreDeps(pkg: any, verbose?: boolean): boolean;
|
|
92
94
|
/** Check if .dependencies exist (already transformed) */
|
|
93
95
|
export declare function hasBackup(pkg: any): boolean;
|
|
96
|
+
/** Get the latest git tag (if any) */
|
|
97
|
+
export declare function getLatestGitTag(cwd: string): string | null;
|
|
98
|
+
/** Check if a git tag exists */
|
|
99
|
+
export declare function gitTagExists(cwd: string, tag: string): boolean;
|
|
100
|
+
/** Delete a git tag */
|
|
101
|
+
export declare function deleteGitTag(cwd: string, tag: string): boolean;
|
|
102
|
+
/** Get all git tags */
|
|
103
|
+
export declare function getAllGitTags(cwd: string): string[];
|
|
104
|
+
/** Parse version from tag (e.g., 'v1.2.3' -> [1, 2, 3]) */
|
|
105
|
+
export declare function parseVersionTag(tag: string): number[] | null;
|
|
106
|
+
/** Compare two version arrays (returns -1 if a < b, 0 if equal, 1 if a > b) */
|
|
107
|
+
export declare function compareVersions(a: number[], b: number[]): number;
|
|
108
|
+
/** Fix version/tag mismatches */
|
|
109
|
+
export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean;
|
|
94
110
|
/** Run a command and return success status */
|
|
95
111
|
export declare function runCommand(cmd: string, args: string[], options?: {
|
|
96
112
|
silent?: boolean;
|
package/lib.js
CHANGED
|
@@ -343,6 +343,127 @@ export function restoreDeps(pkg, verbose = false) {
|
|
|
343
343
|
export function hasBackup(pkg) {
|
|
344
344
|
return DEP_KEYS.some(key => pkg['.' + key]);
|
|
345
345
|
}
|
|
346
|
+
/** Get the latest git tag (if any) */
|
|
347
|
+
export function getLatestGitTag(cwd) {
|
|
348
|
+
try {
|
|
349
|
+
const result = spawnSync('git', ['describe', '--tags', '--abbrev=0'], {
|
|
350
|
+
encoding: 'utf-8',
|
|
351
|
+
stdio: 'pipe',
|
|
352
|
+
cwd
|
|
353
|
+
});
|
|
354
|
+
if (result.status === 0 && result.stdout) {
|
|
355
|
+
return result.stdout.trim();
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Check if a git tag exists */
|
|
364
|
+
export function gitTagExists(cwd, tag) {
|
|
365
|
+
try {
|
|
366
|
+
const result = spawnSync('git', ['tag', '-l', tag], {
|
|
367
|
+
encoding: 'utf-8',
|
|
368
|
+
stdio: 'pipe',
|
|
369
|
+
cwd
|
|
370
|
+
});
|
|
371
|
+
return result.status === 0 && result.stdout.trim() === tag;
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/** Delete a git tag */
|
|
378
|
+
export function deleteGitTag(cwd, tag) {
|
|
379
|
+
try {
|
|
380
|
+
const result = spawnSync('git', ['tag', '-d', tag], {
|
|
381
|
+
encoding: 'utf-8',
|
|
382
|
+
stdio: 'pipe',
|
|
383
|
+
cwd
|
|
384
|
+
});
|
|
385
|
+
return result.status === 0;
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/** Get all git tags */
|
|
392
|
+
export function getAllGitTags(cwd) {
|
|
393
|
+
try {
|
|
394
|
+
const result = spawnSync('git', ['tag', '-l'], {
|
|
395
|
+
encoding: 'utf-8',
|
|
396
|
+
stdio: 'pipe',
|
|
397
|
+
cwd
|
|
398
|
+
});
|
|
399
|
+
if (result.status === 0 && result.stdout) {
|
|
400
|
+
return result.stdout.trim().split('\n').filter(t => t.trim());
|
|
401
|
+
}
|
|
402
|
+
return [];
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/** Parse version from tag (e.g., 'v1.2.3' -> [1, 2, 3]) */
|
|
409
|
+
export function parseVersionTag(tag) {
|
|
410
|
+
const match = tag.match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
411
|
+
if (!match)
|
|
412
|
+
return null;
|
|
413
|
+
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])];
|
|
414
|
+
}
|
|
415
|
+
/** Compare two version arrays (returns -1 if a < b, 0 if equal, 1 if a > b) */
|
|
416
|
+
export function compareVersions(a, b) {
|
|
417
|
+
for (let i = 0; i < 3; i++) {
|
|
418
|
+
if (a[i] < b[i])
|
|
419
|
+
return -1;
|
|
420
|
+
if (a[i] > b[i])
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
425
|
+
/** Fix version/tag mismatches */
|
|
426
|
+
export function fixVersionTagMismatch(cwd, pkg, verbose = false) {
|
|
427
|
+
const pkgVersion = pkg.version;
|
|
428
|
+
if (!pkgVersion) {
|
|
429
|
+
return false; // No version in package.json
|
|
430
|
+
}
|
|
431
|
+
const currentVersion = parseVersionTag(pkgVersion);
|
|
432
|
+
if (!currentVersion) {
|
|
433
|
+
console.error(colors.yellow(`Warning: Could not parse package.json version: ${pkgVersion}`));
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const allTags = getAllGitTags(cwd);
|
|
437
|
+
const tagsToDelete = [];
|
|
438
|
+
// Find all tags that are >= current package.json version
|
|
439
|
+
for (const tag of allTags) {
|
|
440
|
+
const tagVersion = parseVersionTag(tag);
|
|
441
|
+
if (tagVersion && compareVersions(tagVersion, currentVersion) >= 0) {
|
|
442
|
+
tagsToDelete.push(tag);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (tagsToDelete.length === 0) {
|
|
446
|
+
if (verbose) {
|
|
447
|
+
console.log(`No conflicting tags found (package.json is at v${pkgVersion})`);
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
console.log(colors.yellow(`\nVersion/tag mismatch detected:`));
|
|
452
|
+
console.log(` package.json version: ${pkgVersion}`);
|
|
453
|
+
console.log(` Conflicting tags found: ${tagsToDelete.join(', ')}`);
|
|
454
|
+
console.log(colors.yellow('Deleting conflicting tags...'));
|
|
455
|
+
let deletedAny = false;
|
|
456
|
+
for (const tag of tagsToDelete) {
|
|
457
|
+
if (deleteGitTag(cwd, tag)) {
|
|
458
|
+
console.log(colors.green(` ✓ Deleted tag ${tag}`));
|
|
459
|
+
deletedAny = true;
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.error(colors.red(` ✗ Failed to delete tag ${tag}`));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return deletedAny;
|
|
466
|
+
}
|
|
346
467
|
/** Run a command and return success status */
|
|
347
468
|
export function runCommand(cmd, args, options = {}) {
|
|
348
469
|
const { silent = false, cwd, shell = false } = options;
|
|
@@ -919,7 +1040,7 @@ export function runNpmAudit(cwd, fix = false, verbose = false) {
|
|
|
919
1040
|
}
|
|
920
1041
|
export async function globalize(cwd, options = {}) {
|
|
921
1042
|
const { bump = 'patch', noPublish = 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, updateDeps = false, updateMajor = false, publishDeps = true, // Default to publishing deps for safety
|
|
922
|
-
forcePublish = false, fix = false } = options;
|
|
1043
|
+
forcePublish = false, fix = false, fixTags = false } = options;
|
|
923
1044
|
// Check ignore files first (unless cleanup mode)
|
|
924
1045
|
if (!cleanup && !asis) {
|
|
925
1046
|
const checkResult = checkIgnoreFiles(cwd, { conform, asis, verbose });
|
|
@@ -1044,6 +1165,13 @@ export async function globalize(cwd, options = {}) {
|
|
|
1044
1165
|
}
|
|
1045
1166
|
// Read package.json
|
|
1046
1167
|
const pkg = readPackageJson(cwd);
|
|
1168
|
+
// Pre-flight check: fix version/tag mismatches if requested or if we detect issues
|
|
1169
|
+
if (fixTags || !dryRun) {
|
|
1170
|
+
const hadMismatch = fixVersionTagMismatch(cwd, pkg, verbose);
|
|
1171
|
+
if (hadMismatch && !fixTags) {
|
|
1172
|
+
console.log(colors.yellow('(Use --fix-tags to automatically fix tag issues in the future)'));
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1047
1175
|
// Auto-add repository field if git exists but field is missing
|
|
1048
1176
|
if (currentGitStatus.isRepo && currentGitStatus.hasRemote && !pkg.repository) {
|
|
1049
1177
|
try {
|
|
@@ -1321,8 +1449,74 @@ export async function globalize(cwd, options = {}) {
|
|
|
1321
1449
|
if (verbose && error.stack) {
|
|
1322
1450
|
console.error(' Stack trace:', error.stack);
|
|
1323
1451
|
}
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1452
|
+
// Check for specific error conditions
|
|
1453
|
+
const stderrLower = (error.stderr || '').toLowerCase();
|
|
1454
|
+
if (stderrLower.includes('tag') && stderrLower.includes('already exists')) {
|
|
1455
|
+
// Extract tag name if possible
|
|
1456
|
+
const tagMatch = error.stderr?.match(/tag '([^']+)' already exists/);
|
|
1457
|
+
const tagName = tagMatch ? tagMatch[1] : null;
|
|
1458
|
+
console.error(colors.yellow('\nThe version tag already exists in git.'));
|
|
1459
|
+
console.error(colors.yellow('This usually means a previous version bump succeeded but publish failed.'));
|
|
1460
|
+
// Attempt automatic fix
|
|
1461
|
+
if (tagName) {
|
|
1462
|
+
console.log(colors.yellow('Attempting automatic fix...'));
|
|
1463
|
+
if (deleteGitTag(cwd, tagName)) {
|
|
1464
|
+
console.log(colors.green(`✓ Deleted conflicting tag ${tagName}`));
|
|
1465
|
+
console.log('Retrying version bump...');
|
|
1466
|
+
// Retry the version bump
|
|
1467
|
+
try {
|
|
1468
|
+
const newVersion = await libversion(bump, {
|
|
1469
|
+
path: cwd
|
|
1470
|
+
});
|
|
1471
|
+
console.log(colors.green(`Version bumped to ${newVersion}`));
|
|
1472
|
+
// Success! Continue with the rest of the process
|
|
1473
|
+
// Don't return here - let it fall through to publish
|
|
1474
|
+
}
|
|
1475
|
+
catch (retryError) {
|
|
1476
|
+
console.error(colors.red('ERROR: Retry failed:'), retryError.message);
|
|
1477
|
+
// Show retry error details
|
|
1478
|
+
if (retryError.stderr || retryError.stdout || retryError.code) {
|
|
1479
|
+
if (retryError.stderr)
|
|
1480
|
+
console.error(' stderr:', retryError.stderr);
|
|
1481
|
+
if (retryError.stdout)
|
|
1482
|
+
console.error(' stdout:', retryError.stdout);
|
|
1483
|
+
if (retryError.code)
|
|
1484
|
+
console.error(' exit code:', retryError.code);
|
|
1485
|
+
}
|
|
1486
|
+
// Check if it's the same tag issue again
|
|
1487
|
+
const retryStderrLower = (retryError.stderr || '').toLowerCase();
|
|
1488
|
+
if (retryStderrLower.includes('tag') && retryStderrLower.includes('already exists')) {
|
|
1489
|
+
const retryTagMatch = retryError.stderr?.match(/tag '([^']+)' already exists/);
|
|
1490
|
+
const retryTagName = retryTagMatch ? retryTagMatch[1] : null;
|
|
1491
|
+
console.error(colors.red('\\nAnother tag conflict detected!'));
|
|
1492
|
+
console.error(colors.yellow('This suggests package.json version may have been bumped previously.'));
|
|
1493
|
+
console.error(colors.yellow('\\nSuggestions:'));
|
|
1494
|
+
if (retryTagName) {
|
|
1495
|
+
console.error(` 1. Delete the tag and reset package.json:`);
|
|
1496
|
+
console.error(` git tag -d ${retryTagName}`);
|
|
1497
|
+
console.error(` (Then manually adjust package.json version if needed)`);
|
|
1498
|
+
}
|
|
1499
|
+
console.error(' 2. Check git log and package.json history for version mismatches');
|
|
1500
|
+
console.error(' 3. Run: git tag -l | sort -V to see all version tags');
|
|
1501
|
+
}
|
|
1502
|
+
if (!force) {
|
|
1503
|
+
return false;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
else {
|
|
1508
|
+
console.error(colors.red(`✗ Failed to delete tag ${tagName}`));
|
|
1509
|
+
console.error(colors.yellow('\nManual fix required:'));
|
|
1510
|
+
console.error(` 1. Delete the tag: git tag -d ${tagName}`);
|
|
1511
|
+
console.error(` 2. Or push and reset: git push origin ${tagName} && git tag -d ${tagName}`);
|
|
1512
|
+
console.error(' 3. Or manually edit package.json to match the existing tag version');
|
|
1513
|
+
if (!force) {
|
|
1514
|
+
return false;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
else if (error.message?.includes('unknown git error')) {
|
|
1326
1520
|
console.error(colors.yellow('\nPossible causes:'));
|
|
1327
1521
|
console.error(' • Git hooks (pre-commit, commit-msg) might be failing');
|
|
1328
1522
|
console.error(' • GPG signing might be required but not configured');
|