@bobfrankston/npmglobalize 1.0.25 → 1.0.27

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/README.md CHANGED
@@ -19,6 +19,9 @@ cd your-package
19
19
  npmglobalize # Transform + publish (patch version)
20
20
  npmglobalize --minor # Bump minor version
21
21
  npmglobalize --major # Bump major version
22
+
23
+ # Or run from anywhere with a path
24
+ npmglobalize y:\path\to\your-package
22
25
  ```
23
26
 
24
27
  ## Key Features
@@ -41,7 +44,7 @@ lxtest
41
44
 
42
45
  It automatically:
43
46
  1. Publishes `lxland` (root dependency)
44
- 2. Publishes `lxlan-node` (depends on lxland)
47
+ 2. Publishes `lxlan-node` (depends on lxlan)
45
48
  3. Converts and publishes `lxtest`
46
49
 
47
50
  **Skip auto-publishing** (use with caution):
@@ -110,6 +113,24 @@ npmglobalize -np # --nopublish (formerly --apply)
110
113
  npmglobalize --cleanup # Restore original file: references
111
114
  ```
112
115
 
116
+ ### 🔧 Git Integration & Error Recovery
117
+
118
+ **Automatic tag conflict resolution**:
119
+ ```bash
120
+ npmglobalize --fix-tags # Auto-fix version/tag mismatches
121
+ ```
122
+
123
+ When a previous publish fails, git tags may conflict with package.json version. The `--fix-tags` option (or automatic detection) will clean up these conflicts.
124
+
125
+ **Automatic rebase** when local is behind remote:
126
+ ```bash
127
+ npmglobalize --rebase # Auto-rebase if behind remote
128
+ ```
129
+
130
+ For single-developer projects, this safely pulls remote changes before publishing.
131
+
132
+ **Note:** This tool is designed for single-developer, single-branch workflows where automatic rebase and tag cleanup are safe operations.
133
+
113
134
  ## Command Reference
114
135
 
115
136
  ### Release Options
@@ -160,6 +181,8 @@ npmglobalize --cleanup # Restore original file: references
160
181
  --verbose Show detailed output
161
182
  --conform Update .gitignore/.npmignore to best practices
162
183
  --asis Skip ignore file checks
184
+ --fix-tags Automatically fix version/tag mismatches
185
+ --rebase Automatically rebase if local is behind remote
163
186
  --help, -h Show help
164
187
  --version, -v Show version
165
188
  ```
@@ -244,6 +267,12 @@ When publishing file: dependencies, checks if each version exists on npm:
244
267
  # Basic release
245
268
  npmglobalize
246
269
 
270
+ # Run on a different project
271
+ npmglobalize y:\dev\myproject
272
+
273
+ # Auto-fix tag conflicts and rebase
274
+ npmglobalize --fix-tags --rebase
275
+
247
276
  # Release with updates and security fixes
248
277
  npmglobalize --update-deps --fix
249
278
 
package/cli.js CHANGED
@@ -52,6 +52,7 @@ Other Options:
52
52
  --conform Update .gitignore/.npmignore to best practices
53
53
  --asis Skip ignore file checks (or set "asis": true in .globalize.json5)
54
54
  --fix-tags Automatically fix version/tag mismatches
55
+ --rebase Automatically rebase if local is behind remote
55
56
  --help, -h Show this help
56
57
  --version, -v Show version number
57
58
 
@@ -60,6 +61,7 @@ Examples:
60
61
  npmglobalize y:\\path\\to\\project Run on a different project directory
61
62
  npmglobalize --minor Release with minor version bump
62
63
  npmglobalize --fix-tags Fix version/tag mismatches before running
64
+ npmglobalize --rebase Auto-rebase if behind remote
63
65
  npmglobalize --update-deps Safe updates (minor/patch only)
64
66
  npmglobalize --update-major Allow breaking changes (major updates)
65
67
  npmglobalize -npd Skip auto-publishing file: deps (use with caution)
@@ -181,6 +183,9 @@ function parseArgs(args) {
181
183
  case '--fix-tags':
182
184
  options.fixTags = true;
183
185
  break;
186
+ case '--rebase':
187
+ options.rebase = true;
188
+ break;
184
189
  case '--update-deps':
185
190
  options.updateDeps = true;
186
191
  break;
package/lib.d.ts CHANGED
@@ -47,6 +47,8 @@ export interface GlobalizeOptions {
47
47
  fix?: boolean;
48
48
  /** Automatically fix version/tag mismatches */
49
49
  fixTags?: boolean;
50
+ /** Automatically rebase if local is behind remote */
51
+ rebase?: boolean;
50
52
  }
51
53
  /** Read and parse package.json from a directory */
52
54
  export declare function readPackageJson(dir: string): any;
@@ -132,12 +134,15 @@ export interface GitStatus {
132
134
  isDetachedHead: boolean;
133
135
  currentBranch: string;
134
136
  remoteBranch: string;
137
+ isBehindRemote: boolean;
135
138
  }
136
139
  export declare function getGitStatus(cwd: string): GitStatus;
137
140
  /** Validate package.json for release */
138
141
  export declare function validatePackageJson(pkg: any): string[];
139
142
  /** Prompt user for confirmation */
140
143
  export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
144
+ /** Prompt user for multiple choice */
145
+ export declare function promptChoice(message: string, choices: string[]): Promise<string | null>;
141
146
  /** Initialize git repository */
142
147
  export declare function initGit(cwd: string, visibility: 'private' | 'public', dryRun: boolean): Promise<boolean>;
143
148
  /** Main globalize function */
@@ -147,6 +152,8 @@ export declare function runNpmAudit(cwd: string, fix?: boolean, verbose?: boolea
147
152
  report: string;
148
153
  hasVulnerabilities: boolean;
149
154
  };
155
+ /** Get the version of npmglobalize itself */
156
+ export declare function getToolVersion(): string;
150
157
  export declare function globalize(cwd: string, options?: GlobalizeOptions): Promise<boolean>;
151
158
  declare const _default: {
152
159
  globalize: typeof globalize;
package/lib.js CHANGED
@@ -7,6 +7,7 @@ import { execSync, spawnSync } from 'child_process';
7
7
  import readline from 'readline';
8
8
  import libversion from 'libnpmversion';
9
9
  import JSON5 from 'json5';
10
+ import { fileURLToPath } from 'url';
10
11
  /** ANSI color codes */
11
12
  const colors = {
12
13
  red: (text) => `\x1b[31m${text}\x1b[0m`,
@@ -529,7 +530,8 @@ export function getGitStatus(cwd) {
529
530
  hasMergeConflict: false,
530
531
  isDetachedHead: false,
531
532
  currentBranch: '',
532
- remoteBranch: ''
533
+ remoteBranch: '',
534
+ isBehindRemote: false
533
535
  };
534
536
  // Check if git repo
535
537
  const gitDir = path.join(cwd, '.git');
@@ -578,6 +580,18 @@ export function getGitStatus(cwd) {
578
580
  catch (error) {
579
581
  // Ignore - might not have tracking branch
580
582
  }
583
+ // Check if local is behind remote
584
+ try {
585
+ const behind = execSync(`git log HEAD..origin/${status.currentBranch} --oneline 2>nul`, {
586
+ cwd,
587
+ encoding: 'utf-8',
588
+ stdio: ['pipe', 'pipe', 'ignore']
589
+ }).trim();
590
+ status.isBehindRemote = behind.length > 0;
591
+ }
592
+ catch (error) {
593
+ // Ignore - might not have tracking branch
594
+ }
581
595
  }
582
596
  return status;
583
597
  }
@@ -623,6 +637,25 @@ export async function confirm(message, defaultYes = false) {
623
637
  });
624
638
  });
625
639
  }
640
+ /** Prompt user for multiple choice */
641
+ export async function promptChoice(message, choices) {
642
+ const rl = readline.createInterface({
643
+ input: process.stdin,
644
+ output: process.stdout
645
+ });
646
+ return new Promise((resolve) => {
647
+ rl.question(`${message} `, (answer) => {
648
+ rl.close();
649
+ const a = answer.trim().toLowerCase();
650
+ if (choices.includes(a)) {
651
+ resolve(a);
652
+ }
653
+ else {
654
+ resolve(null);
655
+ }
656
+ });
657
+ });
658
+ }
626
659
  /** Check npm authentication status */
627
660
  function checkNpmAuth() {
628
661
  try {
@@ -1038,9 +1071,25 @@ export function runNpmAudit(cwd, fix = false, verbose = false) {
1038
1071
  hasVulnerabilities
1039
1072
  };
1040
1073
  }
1074
+ /** Get the version of npmglobalize itself */
1075
+ export function getToolVersion() {
1076
+ try {
1077
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1078
+ const pkgPath = path.join(__dirname, 'package.json');
1079
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
1080
+ return pkg.version || 'unknown';
1081
+ }
1082
+ catch (error) {
1083
+ return 'unknown';
1084
+ }
1085
+ }
1041
1086
  export async function globalize(cwd, options = {}) {
1042
1087
  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
1043
- forcePublish = false, fix = false, fixTags = false } = options;
1088
+ forcePublish = false, fix = false, fixTags = false, rebase = false } = options;
1089
+ // Show tool version
1090
+ const toolVersion = getToolVersion();
1091
+ console.log(colors.italic(`npmglobalize v${toolVersion}`));
1092
+ console.log('');
1044
1093
  // Check ignore files first (unless cleanup mode)
1045
1094
  if (!cleanup && !asis) {
1046
1095
  const checkResult = checkIgnoreFiles(cwd, { conform, asis, verbose });
@@ -1056,12 +1105,24 @@ export async function globalize(cwd, options = {}) {
1056
1105
  console.log('');
1057
1106
  }
1058
1107
  else {
1059
- console.log(colors.yellow('Run with --conform to apply these changes'));
1060
- console.log(colors.yellow('Or set "asis": true in .globalize.json5 to suppress checks'));
1061
- console.log(colors.yellow('Or use --asis flag to skip this check'));
1108
+ console.log('What would you like to do?');
1109
+ console.log(' ' + colors.green('conform') + ' - Apply these changes now');
1110
+ console.log(' ' + colors.yellow('asis') + ' - Skip checks and continue as-is');
1111
+ console.log(' ' + colors.red('abort') + ' - Stop and exit');
1062
1112
  console.log('');
1063
- const shouldContinue = await confirm('Continue anyway?', true);
1064
- if (!shouldContinue) {
1113
+ const choice = await promptChoice('Your choice [conform/asis/abort]:', ['conform', 'asis', 'abort']);
1114
+ if (choice === 'conform') {
1115
+ console.log('Applying changes...');
1116
+ conformIgnoreFiles(cwd);
1117
+ console.log(colors.green('✓ Ignore files updated'));
1118
+ console.log('');
1119
+ }
1120
+ else if (choice === 'asis') {
1121
+ console.log(colors.yellow('Continuing with existing ignore files...'));
1122
+ console.log('');
1123
+ }
1124
+ else {
1125
+ console.log('Aborted.');
1065
1126
  return false;
1066
1127
  }
1067
1128
  }
@@ -1163,6 +1224,38 @@ export async function globalize(cwd, options = {}) {
1163
1224
  if (currentGitStatus.isDetachedHead && force) {
1164
1225
  console.log(colors.yellow('Warning: Detached HEAD state - continuing with --force'));
1165
1226
  }
1227
+ // Check if local branch is behind remote
1228
+ if (currentGitStatus.isBehindRemote && !dryRun) {
1229
+ console.log(colors.yellow('Local branch is behind remote.'));
1230
+ if (rebase) {
1231
+ console.log('Rebasing local changes (--rebase)...');
1232
+ const rebaseResult = runCommand('git', ['pull', '--rebase'], { cwd, silent: false });
1233
+ if (!rebaseResult.success) {
1234
+ console.error(colors.red('ERROR: Rebase failed.'));
1235
+ console.error('You may need to resolve conflicts manually.');
1236
+ if (!force) {
1237
+ return false;
1238
+ }
1239
+ console.log(colors.yellow('Continuing with --force...'));
1240
+ }
1241
+ else {
1242
+ console.log(colors.green('✓ Successfully rebased'));
1243
+ }
1244
+ }
1245
+ else {
1246
+ console.error(colors.red('ERROR: Local branch is behind remote.'));
1247
+ console.error(colors.yellow('Your local repository needs to be updated before publishing.'));
1248
+ console.error('');
1249
+ console.error('To fix this:');
1250
+ console.error(colors.yellow(' 1. Run: npmglobalize --rebase (automatic)'));
1251
+ console.error(colors.yellow(' 2. Or manually: git pull --rebase'));
1252
+ console.error('');
1253
+ if (!force) {
1254
+ return false;
1255
+ }
1256
+ console.log(colors.yellow('Continuing with --force (git push may fail)...'));
1257
+ }
1258
+ }
1166
1259
  // Read package.json
1167
1260
  const pkg = readPackageJson(cwd);
1168
1261
  // Pre-flight check: fix version/tag mismatches if requested or if we detect issues
@@ -1451,7 +1544,21 @@ export async function globalize(cwd, options = {}) {
1451
1544
  }
1452
1545
  // Check for specific error conditions
1453
1546
  const stderrLower = (error.stderr || '').toLowerCase();
1454
- if (stderrLower.includes('tag') && stderrLower.includes('already exists')) {
1547
+ const stdoutLower = (error.stdout || '').toLowerCase();
1548
+ const combinedOutput = stderrLower + ' ' + stdoutLower;
1549
+ if (combinedOutput.includes('non-fast-forward') || combinedOutput.includes('rejected') && combinedOutput.includes('behind')) {
1550
+ console.error(colors.yellow('\nYour local branch is behind the remote.'));
1551
+ console.error(colors.yellow('This usually means changes were pushed from another location.'));
1552
+ console.error(colors.yellow('\nTo fix this:'));
1553
+ console.error(' 1. Pull remote changes: git pull --rebase');
1554
+ console.error(' 2. Then run npmglobalize again');
1555
+ console.error(colors.yellow('\nOr to force push (use with caution):'));
1556
+ console.error(' git push --force-with-lease');
1557
+ if (!force) {
1558
+ return false;
1559
+ }
1560
+ }
1561
+ else if (stderrLower.includes('tag') && stderrLower.includes('already exists')) {
1455
1562
  // Extract tag name if possible
1456
1563
  const tagMatch = error.stderr?.match(/tag '([^']+)' already exists/);
1457
1564
  const tagName = tagMatch ? tagMatch[1] : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",