@bobfrankston/npmglobalize 1.0.24 → 1.0.26
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 +29 -0
- package/cli.js +5 -0
- package/lib.d.ts +11 -0
- package/lib.js +165 -21
- package/package.json +1 -1
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
|
|
@@ -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;
|
|
@@ -99,6 +101,12 @@ export declare function getLatestGitTag(cwd: string): string | null;
|
|
|
99
101
|
export declare function gitTagExists(cwd: string, tag: string): boolean;
|
|
100
102
|
/** Delete a git tag */
|
|
101
103
|
export declare function deleteGitTag(cwd: string, tag: string): boolean;
|
|
104
|
+
/** Get all git tags */
|
|
105
|
+
export declare function getAllGitTags(cwd: string): string[];
|
|
106
|
+
/** Parse version from tag (e.g., 'v1.2.3' -> [1, 2, 3]) */
|
|
107
|
+
export declare function parseVersionTag(tag: string): number[] | null;
|
|
108
|
+
/** Compare two version arrays (returns -1 if a < b, 0 if equal, 1 if a > b) */
|
|
109
|
+
export declare function compareVersions(a: number[], b: number[]): number;
|
|
102
110
|
/** Fix version/tag mismatches */
|
|
103
111
|
export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean;
|
|
104
112
|
/** Run a command and return success status */
|
|
@@ -126,6 +134,7 @@ export interface GitStatus {
|
|
|
126
134
|
isDetachedHead: boolean;
|
|
127
135
|
currentBranch: string;
|
|
128
136
|
remoteBranch: string;
|
|
137
|
+
isBehindRemote: boolean;
|
|
129
138
|
}
|
|
130
139
|
export declare function getGitStatus(cwd: string): GitStatus;
|
|
131
140
|
/** Validate package.json for release */
|
|
@@ -141,6 +150,8 @@ export declare function runNpmAudit(cwd: string, fix?: boolean, verbose?: boolea
|
|
|
141
150
|
report: string;
|
|
142
151
|
hasVulnerabilities: boolean;
|
|
143
152
|
};
|
|
153
|
+
/** Get the version of npmglobalize itself */
|
|
154
|
+
export declare function getToolVersion(): string;
|
|
144
155
|
export declare function globalize(cwd: string, options?: GlobalizeOptions): Promise<boolean>;
|
|
145
156
|
declare const _default: {
|
|
146
157
|
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`,
|
|
@@ -388,38 +389,81 @@ export function deleteGitTag(cwd, tag) {
|
|
|
388
389
|
return false;
|
|
389
390
|
}
|
|
390
391
|
}
|
|
392
|
+
/** Get all git tags */
|
|
393
|
+
export function getAllGitTags(cwd) {
|
|
394
|
+
try {
|
|
395
|
+
const result = spawnSync('git', ['tag', '-l'], {
|
|
396
|
+
encoding: 'utf-8',
|
|
397
|
+
stdio: 'pipe',
|
|
398
|
+
cwd
|
|
399
|
+
});
|
|
400
|
+
if (result.status === 0 && result.stdout) {
|
|
401
|
+
return result.stdout.trim().split('\n').filter(t => t.trim());
|
|
402
|
+
}
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/** Parse version from tag (e.g., 'v1.2.3' -> [1, 2, 3]) */
|
|
410
|
+
export function parseVersionTag(tag) {
|
|
411
|
+
const match = tag.match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
412
|
+
if (!match)
|
|
413
|
+
return null;
|
|
414
|
+
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])];
|
|
415
|
+
}
|
|
416
|
+
/** Compare two version arrays (returns -1 if a < b, 0 if equal, 1 if a > b) */
|
|
417
|
+
export function compareVersions(a, b) {
|
|
418
|
+
for (let i = 0; i < 3; i++) {
|
|
419
|
+
if (a[i] < b[i])
|
|
420
|
+
return -1;
|
|
421
|
+
if (a[i] > b[i])
|
|
422
|
+
return 1;
|
|
423
|
+
}
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
391
426
|
/** Fix version/tag mismatches */
|
|
392
427
|
export function fixVersionTagMismatch(cwd, pkg, verbose = false) {
|
|
393
428
|
const pkgVersion = pkg.version;
|
|
394
429
|
if (!pkgVersion) {
|
|
395
430
|
return false; // No version in package.json
|
|
396
431
|
}
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
432
|
+
const currentVersion = parseVersionTag(pkgVersion);
|
|
433
|
+
if (!currentVersion) {
|
|
434
|
+
console.error(colors.yellow(`Warning: Could not parse package.json version: ${pkgVersion}`));
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
const allTags = getAllGitTags(cwd);
|
|
438
|
+
const tagsToDelete = [];
|
|
439
|
+
// Find all tags that are >= current package.json version
|
|
440
|
+
for (const tag of allTags) {
|
|
441
|
+
const tagVersion = parseVersionTag(tag);
|
|
442
|
+
if (tagVersion && compareVersions(tagVersion, currentVersion) >= 0) {
|
|
443
|
+
tagsToDelete.push(tag);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (tagsToDelete.length === 0) {
|
|
402
447
|
if (verbose) {
|
|
403
|
-
console.log(`
|
|
448
|
+
console.log(`No conflicting tags found (package.json is at v${pkgVersion})`);
|
|
404
449
|
}
|
|
405
450
|
return false;
|
|
406
451
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (deleteGitTag(cwd,
|
|
414
|
-
console.log(colors.green(
|
|
415
|
-
|
|
452
|
+
console.log(colors.yellow(`\nVersion/tag mismatch detected:`));
|
|
453
|
+
console.log(` package.json version: ${pkgVersion}`);
|
|
454
|
+
console.log(` Conflicting tags found: ${tagsToDelete.join(', ')}`);
|
|
455
|
+
console.log(colors.yellow('Deleting conflicting tags...'));
|
|
456
|
+
let deletedAny = false;
|
|
457
|
+
for (const tag of tagsToDelete) {
|
|
458
|
+
if (deleteGitTag(cwd, tag)) {
|
|
459
|
+
console.log(colors.green(` ✓ Deleted tag ${tag}`));
|
|
460
|
+
deletedAny = true;
|
|
416
461
|
}
|
|
417
462
|
else {
|
|
418
|
-
console.error(colors.red(
|
|
419
|
-
return false;
|
|
463
|
+
console.error(colors.red(` ✗ Failed to delete tag ${tag}`));
|
|
420
464
|
}
|
|
421
465
|
}
|
|
422
|
-
return
|
|
466
|
+
return deletedAny;
|
|
423
467
|
}
|
|
424
468
|
/** Run a command and return success status */
|
|
425
469
|
export function runCommand(cmd, args, options = {}) {
|
|
@@ -486,7 +530,8 @@ export function getGitStatus(cwd) {
|
|
|
486
530
|
hasMergeConflict: false,
|
|
487
531
|
isDetachedHead: false,
|
|
488
532
|
currentBranch: '',
|
|
489
|
-
remoteBranch: ''
|
|
533
|
+
remoteBranch: '',
|
|
534
|
+
isBehindRemote: false
|
|
490
535
|
};
|
|
491
536
|
// Check if git repo
|
|
492
537
|
const gitDir = path.join(cwd, '.git');
|
|
@@ -535,6 +580,18 @@ export function getGitStatus(cwd) {
|
|
|
535
580
|
catch (error) {
|
|
536
581
|
// Ignore - might not have tracking branch
|
|
537
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
|
+
}
|
|
538
595
|
}
|
|
539
596
|
return status;
|
|
540
597
|
}
|
|
@@ -995,9 +1052,25 @@ export function runNpmAudit(cwd, fix = false, verbose = false) {
|
|
|
995
1052
|
hasVulnerabilities
|
|
996
1053
|
};
|
|
997
1054
|
}
|
|
1055
|
+
/** Get the version of npmglobalize itself */
|
|
1056
|
+
export function getToolVersion() {
|
|
1057
|
+
try {
|
|
1058
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1059
|
+
const pkgPath = path.join(__dirname, 'package.json');
|
|
1060
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
1061
|
+
return pkg.version || 'unknown';
|
|
1062
|
+
}
|
|
1063
|
+
catch (error) {
|
|
1064
|
+
return 'unknown';
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
998
1067
|
export async function globalize(cwd, options = {}) {
|
|
999
1068
|
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
|
|
1000
|
-
forcePublish = false, fix = false, fixTags = false } = options;
|
|
1069
|
+
forcePublish = false, fix = false, fixTags = false, rebase = false } = options;
|
|
1070
|
+
// Show tool version
|
|
1071
|
+
const toolVersion = getToolVersion();
|
|
1072
|
+
console.log(colors.italic(`npmglobalize v${toolVersion}`));
|
|
1073
|
+
console.log('');
|
|
1001
1074
|
// Check ignore files first (unless cleanup mode)
|
|
1002
1075
|
if (!cleanup && !asis) {
|
|
1003
1076
|
const checkResult = checkIgnoreFiles(cwd, { conform, asis, verbose });
|
|
@@ -1120,6 +1193,38 @@ export async function globalize(cwd, options = {}) {
|
|
|
1120
1193
|
if (currentGitStatus.isDetachedHead && force) {
|
|
1121
1194
|
console.log(colors.yellow('Warning: Detached HEAD state - continuing with --force'));
|
|
1122
1195
|
}
|
|
1196
|
+
// Check if local branch is behind remote
|
|
1197
|
+
if (currentGitStatus.isBehindRemote && !dryRun) {
|
|
1198
|
+
console.log(colors.yellow('Local branch is behind remote.'));
|
|
1199
|
+
if (rebase) {
|
|
1200
|
+
console.log('Rebasing local changes (--rebase)...');
|
|
1201
|
+
const rebaseResult = runCommand('git', ['pull', '--rebase'], { cwd, silent: false });
|
|
1202
|
+
if (!rebaseResult.success) {
|
|
1203
|
+
console.error(colors.red('ERROR: Rebase failed.'));
|
|
1204
|
+
console.error('You may need to resolve conflicts manually.');
|
|
1205
|
+
if (!force) {
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
console.log(colors.yellow('Continuing with --force...'));
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
console.log(colors.green('✓ Successfully rebased'));
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
else {
|
|
1215
|
+
console.error(colors.red('ERROR: Local branch is behind remote.'));
|
|
1216
|
+
console.error(colors.yellow('Your local repository needs to be updated before publishing.'));
|
|
1217
|
+
console.error('');
|
|
1218
|
+
console.error('To fix this:');
|
|
1219
|
+
console.error(colors.yellow(' 1. Run: npmglobalize --rebase (automatic)'));
|
|
1220
|
+
console.error(colors.yellow(' 2. Or manually: git pull --rebase'));
|
|
1221
|
+
console.error('');
|
|
1222
|
+
if (!force) {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
console.log(colors.yellow('Continuing with --force (git push may fail)...'));
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1123
1228
|
// Read package.json
|
|
1124
1229
|
const pkg = readPackageJson(cwd);
|
|
1125
1230
|
// Pre-flight check: fix version/tag mismatches if requested or if we detect issues
|
|
@@ -1408,7 +1513,21 @@ export async function globalize(cwd, options = {}) {
|
|
|
1408
1513
|
}
|
|
1409
1514
|
// Check for specific error conditions
|
|
1410
1515
|
const stderrLower = (error.stderr || '').toLowerCase();
|
|
1411
|
-
|
|
1516
|
+
const stdoutLower = (error.stdout || '').toLowerCase();
|
|
1517
|
+
const combinedOutput = stderrLower + ' ' + stdoutLower;
|
|
1518
|
+
if (combinedOutput.includes('non-fast-forward') || combinedOutput.includes('rejected') && combinedOutput.includes('behind')) {
|
|
1519
|
+
console.error(colors.yellow('\nYour local branch is behind the remote.'));
|
|
1520
|
+
console.error(colors.yellow('This usually means changes were pushed from another location.'));
|
|
1521
|
+
console.error(colors.yellow('\nTo fix this:'));
|
|
1522
|
+
console.error(' 1. Pull remote changes: git pull --rebase');
|
|
1523
|
+
console.error(' 2. Then run npmglobalize again');
|
|
1524
|
+
console.error(colors.yellow('\nOr to force push (use with caution):'));
|
|
1525
|
+
console.error(' git push --force-with-lease');
|
|
1526
|
+
if (!force) {
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
else if (stderrLower.includes('tag') && stderrLower.includes('already exists')) {
|
|
1412
1531
|
// Extract tag name if possible
|
|
1413
1532
|
const tagMatch = error.stderr?.match(/tag '([^']+)' already exists/);
|
|
1414
1533
|
const tagName = tagMatch ? tagMatch[1] : null;
|
|
@@ -1431,6 +1550,31 @@ export async function globalize(cwd, options = {}) {
|
|
|
1431
1550
|
}
|
|
1432
1551
|
catch (retryError) {
|
|
1433
1552
|
console.error(colors.red('ERROR: Retry failed:'), retryError.message);
|
|
1553
|
+
// Show retry error details
|
|
1554
|
+
if (retryError.stderr || retryError.stdout || retryError.code) {
|
|
1555
|
+
if (retryError.stderr)
|
|
1556
|
+
console.error(' stderr:', retryError.stderr);
|
|
1557
|
+
if (retryError.stdout)
|
|
1558
|
+
console.error(' stdout:', retryError.stdout);
|
|
1559
|
+
if (retryError.code)
|
|
1560
|
+
console.error(' exit code:', retryError.code);
|
|
1561
|
+
}
|
|
1562
|
+
// Check if it's the same tag issue again
|
|
1563
|
+
const retryStderrLower = (retryError.stderr || '').toLowerCase();
|
|
1564
|
+
if (retryStderrLower.includes('tag') && retryStderrLower.includes('already exists')) {
|
|
1565
|
+
const retryTagMatch = retryError.stderr?.match(/tag '([^']+)' already exists/);
|
|
1566
|
+
const retryTagName = retryTagMatch ? retryTagMatch[1] : null;
|
|
1567
|
+
console.error(colors.red('\\nAnother tag conflict detected!'));
|
|
1568
|
+
console.error(colors.yellow('This suggests package.json version may have been bumped previously.'));
|
|
1569
|
+
console.error(colors.yellow('\\nSuggestions:'));
|
|
1570
|
+
if (retryTagName) {
|
|
1571
|
+
console.error(` 1. Delete the tag and reset package.json:`);
|
|
1572
|
+
console.error(` git tag -d ${retryTagName}`);
|
|
1573
|
+
console.error(` (Then manually adjust package.json version if needed)`);
|
|
1574
|
+
}
|
|
1575
|
+
console.error(' 2. Check git log and package.json history for version mismatches');
|
|
1576
|
+
console.error(' 3. Run: git tag -l | sort -V to see all version tags');
|
|
1577
|
+
}
|
|
1434
1578
|
if (!force) {
|
|
1435
1579
|
return false;
|
|
1436
1580
|
}
|