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