@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.
Files changed (4) hide show
  1. package/cli.js +33 -2
  2. package/lib.d.ts +16 -0
  3. package/lib.js +197 -3
  4. 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
- const cwd = process.cwd();
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
- // Try to provide more context
1325
- if (error.message?.includes('unknown git error')) {
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",