@bobfrankston/npmglobalize 1.0.23 → 1.0.24
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 +10 -0
- package/lib.js +129 -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,14 @@ 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
|
+
/** Fix version/tag mismatches */
|
|
103
|
+
export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean;
|
|
94
104
|
/** Run a command and return success status */
|
|
95
105
|
export declare function runCommand(cmd: string, args: string[], options?: {
|
|
96
106
|
silent?: boolean;
|
package/lib.js
CHANGED
|
@@ -343,6 +343,84 @@ 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
|
+
/** Fix version/tag mismatches */
|
|
392
|
+
export function fixVersionTagMismatch(cwd, pkg, verbose = false) {
|
|
393
|
+
const pkgVersion = pkg.version;
|
|
394
|
+
if (!pkgVersion) {
|
|
395
|
+
return false; // No version in package.json
|
|
396
|
+
}
|
|
397
|
+
const expectedTag = `v${pkgVersion}`;
|
|
398
|
+
const latestTag = getLatestGitTag(cwd);
|
|
399
|
+
// Check if the expected tag exists but doesn't match package.json
|
|
400
|
+
if (gitTagExists(cwd, expectedTag)) {
|
|
401
|
+
// Tag exists and matches - no issue
|
|
402
|
+
if (verbose) {
|
|
403
|
+
console.log(`Tag ${expectedTag} exists and matches package.json version`);
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
// Check if there's a tag that's ahead of package.json version
|
|
408
|
+
if (latestTag && latestTag !== expectedTag) {
|
|
409
|
+
console.log(colors.yellow(`\nVersion mismatch detected:`));
|
|
410
|
+
console.log(` package.json version: ${pkgVersion}`);
|
|
411
|
+
console.log(` Latest git tag: ${latestTag}`);
|
|
412
|
+
console.log(colors.yellow('Deleting conflicting tag...'));
|
|
413
|
+
if (deleteGitTag(cwd, latestTag)) {
|
|
414
|
+
console.log(colors.green(`✓ Deleted tag ${latestTag}`));
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
console.error(colors.red(`✗ Failed to delete tag ${latestTag}`));
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
346
424
|
/** Run a command and return success status */
|
|
347
425
|
export function runCommand(cmd, args, options = {}) {
|
|
348
426
|
const { silent = false, cwd, shell = false } = options;
|
|
@@ -919,7 +997,7 @@ export function runNpmAudit(cwd, fix = false, verbose = false) {
|
|
|
919
997
|
}
|
|
920
998
|
export async function globalize(cwd, options = {}) {
|
|
921
999
|
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;
|
|
1000
|
+
forcePublish = false, fix = false, fixTags = false } = options;
|
|
923
1001
|
// Check ignore files first (unless cleanup mode)
|
|
924
1002
|
if (!cleanup && !asis) {
|
|
925
1003
|
const checkResult = checkIgnoreFiles(cwd, { conform, asis, verbose });
|
|
@@ -1044,6 +1122,13 @@ export async function globalize(cwd, options = {}) {
|
|
|
1044
1122
|
}
|
|
1045
1123
|
// Read package.json
|
|
1046
1124
|
const pkg = readPackageJson(cwd);
|
|
1125
|
+
// Pre-flight check: fix version/tag mismatches if requested or if we detect issues
|
|
1126
|
+
if (fixTags || !dryRun) {
|
|
1127
|
+
const hadMismatch = fixVersionTagMismatch(cwd, pkg, verbose);
|
|
1128
|
+
if (hadMismatch && !fixTags) {
|
|
1129
|
+
console.log(colors.yellow('(Use --fix-tags to automatically fix tag issues in the future)'));
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1047
1132
|
// Auto-add repository field if git exists but field is missing
|
|
1048
1133
|
if (currentGitStatus.isRepo && currentGitStatus.hasRemote && !pkg.repository) {
|
|
1049
1134
|
try {
|
|
@@ -1321,8 +1406,49 @@ export async function globalize(cwd, options = {}) {
|
|
|
1321
1406
|
if (verbose && error.stack) {
|
|
1322
1407
|
console.error(' Stack trace:', error.stack);
|
|
1323
1408
|
}
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1409
|
+
// Check for specific error conditions
|
|
1410
|
+
const stderrLower = (error.stderr || '').toLowerCase();
|
|
1411
|
+
if (stderrLower.includes('tag') && stderrLower.includes('already exists')) {
|
|
1412
|
+
// Extract tag name if possible
|
|
1413
|
+
const tagMatch = error.stderr?.match(/tag '([^']+)' already exists/);
|
|
1414
|
+
const tagName = tagMatch ? tagMatch[1] : null;
|
|
1415
|
+
console.error(colors.yellow('\nThe version tag already exists in git.'));
|
|
1416
|
+
console.error(colors.yellow('This usually means a previous version bump succeeded but publish failed.'));
|
|
1417
|
+
// Attempt automatic fix
|
|
1418
|
+
if (tagName) {
|
|
1419
|
+
console.log(colors.yellow('Attempting automatic fix...'));
|
|
1420
|
+
if (deleteGitTag(cwd, tagName)) {
|
|
1421
|
+
console.log(colors.green(`✓ Deleted conflicting tag ${tagName}`));
|
|
1422
|
+
console.log('Retrying version bump...');
|
|
1423
|
+
// Retry the version bump
|
|
1424
|
+
try {
|
|
1425
|
+
const newVersion = await libversion(bump, {
|
|
1426
|
+
path: cwd
|
|
1427
|
+
});
|
|
1428
|
+
console.log(colors.green(`Version bumped to ${newVersion}`));
|
|
1429
|
+
// Success! Continue with the rest of the process
|
|
1430
|
+
// Don't return here - let it fall through to publish
|
|
1431
|
+
}
|
|
1432
|
+
catch (retryError) {
|
|
1433
|
+
console.error(colors.red('ERROR: Retry failed:'), retryError.message);
|
|
1434
|
+
if (!force) {
|
|
1435
|
+
return false;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
console.error(colors.red(`✗ Failed to delete tag ${tagName}`));
|
|
1441
|
+
console.error(colors.yellow('\nManual fix required:'));
|
|
1442
|
+
console.error(` 1. Delete the tag: git tag -d ${tagName}`);
|
|
1443
|
+
console.error(` 2. Or push and reset: git push origin ${tagName} && git tag -d ${tagName}`);
|
|
1444
|
+
console.error(' 3. Or manually edit package.json to match the existing tag version');
|
|
1445
|
+
if (!force) {
|
|
1446
|
+
return false;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
else if (error.message?.includes('unknown git error')) {
|
|
1326
1452
|
console.error(colors.yellow('\nPossible causes:'));
|
|
1327
1453
|
console.error(' • Git hooks (pre-commit, commit-msg) might be failing');
|
|
1328
1454
|
console.error(' • GPG signing might be required but not configured');
|