@bobfrankston/npmglobalize 1.0.112 → 1.0.114
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/lib.js +302 -20
- package/package.json +1 -1
package/lib.js
CHANGED
|
@@ -1406,6 +1406,226 @@ function getSecurityNpmignorePatterns() {
|
|
|
1406
1406
|
function lineHasPattern(lines, pattern) {
|
|
1407
1407
|
return lines.some(line => line === pattern || line === pattern.replace('/', ''));
|
|
1408
1408
|
}
|
|
1409
|
+
/** Detect OAuth credentials type from a specific JSON file path.
|
|
1410
|
+
* Returns 'installed' (public app ID, safe) or 'web' (real secret) or null. */
|
|
1411
|
+
function detectCredentialsTypeFromFile(filePath) {
|
|
1412
|
+
if (!fs.existsSync(filePath))
|
|
1413
|
+
return null;
|
|
1414
|
+
try {
|
|
1415
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1416
|
+
const parsed = JSON.parse(content);
|
|
1417
|
+
if (parsed.installed)
|
|
1418
|
+
return 'installed';
|
|
1419
|
+
if (parsed.web)
|
|
1420
|
+
return 'web';
|
|
1421
|
+
return null; // Unknown format (e.g., Microsoft OAuth)
|
|
1422
|
+
}
|
|
1423
|
+
catch {
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
/** Detect OAuth credentials.json type: "installed" (public app ID, safe to include) or "web" (has real secret, must ignore).
|
|
1428
|
+
* Returns null if no credentials.json exists or it can't be parsed. */
|
|
1429
|
+
function detectCredentialsType(cwd) {
|
|
1430
|
+
return detectCredentialsTypeFromFile(path.join(cwd, 'credentials.json'));
|
|
1431
|
+
}
|
|
1432
|
+
/** Parse GitHub push protection (GH013) error output and extract secret details + unblock URLs. */
|
|
1433
|
+
function parsePushProtection(errorOutput, cwd) {
|
|
1434
|
+
const result = { detected: false, secrets: [], allInstalledOAuth: false };
|
|
1435
|
+
if (!errorOutput)
|
|
1436
|
+
return result;
|
|
1437
|
+
// Strip 'remote: ' prefixes for easier parsing
|
|
1438
|
+
const cleaned = errorOutput.replace(/^remote:\s*/gm, '');
|
|
1439
|
+
if (!cleaned.includes('GH013') && !cleaned.toLowerCase().includes('push protection')) {
|
|
1440
|
+
return result;
|
|
1441
|
+
}
|
|
1442
|
+
result.detected = true;
|
|
1443
|
+
// Split on secret-type headers: "—— Secret Type ————..."
|
|
1444
|
+
const blocks = cleaned.split(/\u2014\u2014\s+/).slice(1);
|
|
1445
|
+
for (const block of blocks) {
|
|
1446
|
+
const typeMatch = block.match(/^(.+?)[\s\u2014]+/);
|
|
1447
|
+
const type = typeMatch ? typeMatch[1].trim() : '';
|
|
1448
|
+
if (!type)
|
|
1449
|
+
continue;
|
|
1450
|
+
const pathMatch = block.match(/path:\s+(\S+?)(?::(\d+))?\s/);
|
|
1451
|
+
const file = pathMatch ? pathMatch[1].trim() : '';
|
|
1452
|
+
const urlMatch = block.match(/(https:\/\/github\.com\/\S+\/unblock-secret\/\S+)/);
|
|
1453
|
+
const unblockUrl = urlMatch ? urlMatch[1].trim() : '';
|
|
1454
|
+
if (type && (file || unblockUrl)) {
|
|
1455
|
+
result.secrets.push({ type, file, unblockUrl });
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
// Check if all detected secrets are from Google OAuth installed apps (safe to include)
|
|
1459
|
+
if (result.secrets.length > 0) {
|
|
1460
|
+
result.allInstalledOAuth = result.secrets.every(s => {
|
|
1461
|
+
if (!s.type.toLowerCase().includes('oauth'))
|
|
1462
|
+
return false;
|
|
1463
|
+
if (!s.file)
|
|
1464
|
+
return false;
|
|
1465
|
+
const credType = detectCredentialsTypeFromFile(path.join(cwd, s.file));
|
|
1466
|
+
return credType === 'installed';
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
return result;
|
|
1470
|
+
}
|
|
1471
|
+
/** Try to auto-bypass push protection for installed OAuth secrets via gh API.
|
|
1472
|
+
* Returns true if all bypasses succeeded and push should be retried. */
|
|
1473
|
+
function tryAutoBypassPushProtection(ppInfo, cwd) {
|
|
1474
|
+
if (!ppInfo.allInstalledOAuth || ppInfo.secrets.length === 0)
|
|
1475
|
+
return false;
|
|
1476
|
+
// Check if gh CLI is available
|
|
1477
|
+
const ghCheck = runCommand('gh', ['auth', 'status'], { cwd, silent: true });
|
|
1478
|
+
if (!ghCheck.success)
|
|
1479
|
+
return false;
|
|
1480
|
+
// Extract repo owner/name from unblock URLs or git remote
|
|
1481
|
+
let repo = '';
|
|
1482
|
+
for (const s of ppInfo.secrets) {
|
|
1483
|
+
if (s.unblockUrl) {
|
|
1484
|
+
const repoMatch = s.unblockUrl.match(/github\.com\/([^/]+\/[^/]+)\//);
|
|
1485
|
+
if (repoMatch) {
|
|
1486
|
+
repo = repoMatch[1];
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (!repo)
|
|
1492
|
+
return false;
|
|
1493
|
+
let allBypassed = true;
|
|
1494
|
+
for (const s of ppInfo.secrets) {
|
|
1495
|
+
if (!s.unblockUrl) {
|
|
1496
|
+
allBypassed = false;
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
// Extract placeholder_id from unblock URL (last path segment)
|
|
1500
|
+
const idMatch = s.unblockUrl.match(/\/unblock-secret\/(\S+)/);
|
|
1501
|
+
if (!idMatch) {
|
|
1502
|
+
allBypassed = false;
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
const placeholderId = idMatch[1];
|
|
1506
|
+
console.log(colors.cyan(` Bypassing push protection for: ${s.type} (false_positive — installed app ID)...`));
|
|
1507
|
+
const bypassResult = runCommand('gh', [
|
|
1508
|
+
'api', `repos/${repo}/secret-scanning/push-protection-bypasses`,
|
|
1509
|
+
'-X', 'POST',
|
|
1510
|
+
'-f', 'reason=false_positive',
|
|
1511
|
+
'-f', `placeholder_id=${placeholderId}`
|
|
1512
|
+
], { cwd, silent: true });
|
|
1513
|
+
if (!bypassResult.success) {
|
|
1514
|
+
allBypassed = false;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return allBypassed;
|
|
1518
|
+
}
|
|
1519
|
+
/** Display push protection guidance based on the type of secrets detected. */
|
|
1520
|
+
function showPushProtectionGuidance(ppInfo) {
|
|
1521
|
+
console.error('');
|
|
1522
|
+
console.error(colors.yellow('GitHub Push Protection blocked the push — secrets detected in commits.'));
|
|
1523
|
+
if (ppInfo.allInstalledOAuth) {
|
|
1524
|
+
console.error(colors.cyan('These are Google OAuth credentials for a desktop/installed app.'));
|
|
1525
|
+
console.error(colors.cyan('The "client_secret" is just a public app identifier, not a real secret.'));
|
|
1526
|
+
console.error(colors.cyan('(Google docs: "the client_secret is obviously not treated as a secret")'));
|
|
1527
|
+
console.error('');
|
|
1528
|
+
console.error('To unblock, visit each URL below and allow the secret:');
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
console.error('');
|
|
1532
|
+
console.error('To unblock, review each secret and visit the URL to allow or remove it:');
|
|
1533
|
+
}
|
|
1534
|
+
for (const s of ppInfo.secrets) {
|
|
1535
|
+
console.error(` ${s.type}: ${s.file}`);
|
|
1536
|
+
if (s.unblockUrl) {
|
|
1537
|
+
console.error(` ${colors.cyan(s.unblockUrl)}`);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
console.error('');
|
|
1541
|
+
console.error('After unblocking, re-run the push or re-run npmglobalize.');
|
|
1542
|
+
}
|
|
1543
|
+
/** Push to git with push-protection detection and auto-bypass for installed OAuth.
|
|
1544
|
+
* Returns true if push succeeded (possibly after auto-bypass). */
|
|
1545
|
+
function pushWithProtection(cwd, verbose) {
|
|
1546
|
+
const pushResult = runCommand('git', ['push'], { cwd, silent: true });
|
|
1547
|
+
if (pushResult.success) {
|
|
1548
|
+
if (verbose)
|
|
1549
|
+
console.log(colors.green(' ✓ Pushed to remote'));
|
|
1550
|
+
return true;
|
|
1551
|
+
}
|
|
1552
|
+
const ppInfo = parsePushProtection(pushResult.stderr, cwd);
|
|
1553
|
+
if (!ppInfo.detected) {
|
|
1554
|
+
console.error(colors.red('Git push failed:'));
|
|
1555
|
+
if (pushResult.stderr)
|
|
1556
|
+
console.error(pushResult.stderr);
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
// Try auto-bypass for installed OAuth credentials
|
|
1560
|
+
if (ppInfo.allInstalledOAuth && tryAutoBypassPushProtection(ppInfo, cwd)) {
|
|
1561
|
+
console.log(colors.green(' ✓ Auto-bypassed push protection (installed OAuth — not a real secret)'));
|
|
1562
|
+
const retryPush = runCommand('git', ['push'], { cwd, silent: true });
|
|
1563
|
+
if (retryPush.success) {
|
|
1564
|
+
if (verbose)
|
|
1565
|
+
console.log(colors.green(' ✓ Pushed to remote'));
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
console.error(colors.yellow('Push still failed after bypass:'));
|
|
1569
|
+
if (retryPush.stderr)
|
|
1570
|
+
console.error(retryPush.stderr);
|
|
1571
|
+
return false;
|
|
1572
|
+
}
|
|
1573
|
+
// Can't auto-bypass — show manual guidance
|
|
1574
|
+
showPushProtectionGuidance(ppInfo);
|
|
1575
|
+
return false;
|
|
1576
|
+
}
|
|
1577
|
+
/** Ensure credentials.json is handled correctly in ignore files based on OAuth type.
|
|
1578
|
+
* "installed" apps: client_secret is just a public app registration ID — must be INCLUDED.
|
|
1579
|
+
* "web" apps: client_secret is a real secret — must be IGNORED. */
|
|
1580
|
+
function conformCredentialsIgnore(cwd) {
|
|
1581
|
+
const credType = detectCredentialsType(cwd);
|
|
1582
|
+
if (!credType)
|
|
1583
|
+
return;
|
|
1584
|
+
for (const ignoreFile of ['.gitignore', '.npmignore']) {
|
|
1585
|
+
const ignorePath = path.join(cwd, ignoreFile);
|
|
1586
|
+
if (!fs.existsSync(ignorePath))
|
|
1587
|
+
continue;
|
|
1588
|
+
const content = fs.readFileSync(ignorePath, 'utf-8');
|
|
1589
|
+
const lines = content.split('\n');
|
|
1590
|
+
const trimmed = lines.map(l => l.trim());
|
|
1591
|
+
if (credType === 'installed') {
|
|
1592
|
+
// Ensure credentials.json is NOT ignored — add negation if it's being blocked
|
|
1593
|
+
const hasBlock = trimmed.some(l => l === 'credentials.json' || l === 'credentials.json/');
|
|
1594
|
+
const hasNegation = trimmed.some(l => l === '!credentials.json');
|
|
1595
|
+
if (hasBlock && !hasNegation) {
|
|
1596
|
+
// Add negation after the blocking line
|
|
1597
|
+
const newLines = [...lines];
|
|
1598
|
+
const blockIdx = trimmed.findIndex(l => l === 'credentials.json' || l === 'credentials.json/');
|
|
1599
|
+
newLines.splice(blockIdx + 1, 0, '!credentials.json');
|
|
1600
|
+
fs.writeFileSync(ignorePath, newLines.join('\n'));
|
|
1601
|
+
console.log(colors.cyan(` ✓ ${ignoreFile}: added !credentials.json (installed app — public OAuth app ID)`));
|
|
1602
|
+
}
|
|
1603
|
+
else if (!hasBlock && !hasNegation) {
|
|
1604
|
+
// No block exists, but add negation defensively in case a broader pattern catches it
|
|
1605
|
+
const newContent = content.trimEnd() + '\n!credentials.json\n';
|
|
1606
|
+
fs.writeFileSync(ignorePath, newContent);
|
|
1607
|
+
console.log(colors.cyan(` ✓ ${ignoreFile}: added !credentials.json (installed app — public OAuth app ID)`));
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
else if (credType === 'web') {
|
|
1611
|
+
// Ensure credentials.json IS ignored
|
|
1612
|
+
const hasBlock = trimmed.some(l => l === 'credentials.json');
|
|
1613
|
+
if (!hasBlock) {
|
|
1614
|
+
const newContent = content.trimEnd() + '\ncredentials.json\n';
|
|
1615
|
+
fs.writeFileSync(ignorePath, newContent);
|
|
1616
|
+
console.log(colors.cyan(` ✓ ${ignoreFile}: added credentials.json to ignore (web app — real secret)`));
|
|
1617
|
+
}
|
|
1618
|
+
// Remove any negation that would un-ignore it
|
|
1619
|
+
const negIdx = trimmed.findIndex(l => l === '!credentials.json');
|
|
1620
|
+
if (negIdx >= 0) {
|
|
1621
|
+
const newLines = [...lines];
|
|
1622
|
+
newLines.splice(negIdx, 1);
|
|
1623
|
+
fs.writeFileSync(ignorePath, newLines.join('\n'));
|
|
1624
|
+
console.log(colors.yellow(` ✓ ${ignoreFile}: removed !credentials.json (web app — must not be public)`));
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1409
1629
|
/** Check if ignore files need updates; separates security (auto-fix) from recommended (prompt) */
|
|
1410
1630
|
function checkIgnoreFiles(cwd, options) {
|
|
1411
1631
|
const changes = [];
|
|
@@ -2753,7 +2973,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2753
2973
|
runCommand('git', ['add', 'package.json'], { cwd });
|
|
2754
2974
|
runCommand('git', ['commit', '-m', 'Restore file: dependencies'], { cwd });
|
|
2755
2975
|
if (currentGitStatus.hasRemote) {
|
|
2756
|
-
|
|
2976
|
+
pushWithProtection(cwd, verbose);
|
|
2757
2977
|
}
|
|
2758
2978
|
}
|
|
2759
2979
|
}
|
|
@@ -2781,7 +3001,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2781
3001
|
runCommand('git', ['add', 'package.json'], { cwd });
|
|
2782
3002
|
runCommand('git', ['commit', '-m', 'Restore file: dependencies'], { cwd });
|
|
2783
3003
|
if (currentGitStatus.hasRemote) {
|
|
2784
|
-
|
|
3004
|
+
pushWithProtection(cwd, verbose);
|
|
2785
3005
|
}
|
|
2786
3006
|
}
|
|
2787
3007
|
}
|
|
@@ -2832,29 +3052,58 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2832
3052
|
}
|
|
2833
3053
|
}
|
|
2834
3054
|
else if (install) {
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3055
|
+
// Quick check: does this version actually exist on npm?
|
|
3056
|
+
const vCheck = spawnSafe('npm', ['view', `${pkgName}@${pkgVersion}`, 'version'], {
|
|
3057
|
+
shell: process.platform === 'win32',
|
|
3058
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3059
|
+
encoding: 'utf-8'
|
|
3060
|
+
});
|
|
3061
|
+
const versionOnNpm = vCheck.status === 0 && vCheck.stdout?.trim() === pkgVersion;
|
|
3062
|
+
if (versionOnNpm) {
|
|
3063
|
+
console.log(`Installing ${pkgName}@${pkgVersion} globally from registry...`);
|
|
3064
|
+
const registryInstallResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
|
|
3065
|
+
if (registryInstallResult.success) {
|
|
3066
|
+
console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
|
|
3067
|
+
}
|
|
3068
|
+
else {
|
|
3069
|
+
console.error(colors.red(`✗ Global install failed`));
|
|
3070
|
+
console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
|
|
3071
|
+
}
|
|
2840
3072
|
}
|
|
2841
3073
|
else {
|
|
2842
|
-
console.
|
|
2843
|
-
console.
|
|
3074
|
+
console.log(colors.yellow(`${pkgName}@${pkgVersion} not found on npm — installing from local directory.`));
|
|
3075
|
+
console.log(colors.dim(' Use -m "message" to publish this version to npm.'));
|
|
3076
|
+
const localResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
|
|
3077
|
+
if (localResult.success) {
|
|
3078
|
+
console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
|
|
3079
|
+
}
|
|
3080
|
+
else {
|
|
3081
|
+
console.error(colors.red(`✗ Local install failed`));
|
|
3082
|
+
console.error(colors.yellow(' Try running manually: npm install -g .'));
|
|
3083
|
+
}
|
|
2844
3084
|
}
|
|
2845
3085
|
}
|
|
2846
3086
|
if (wsl) {
|
|
2847
|
-
|
|
2848
|
-
|
|
3087
|
+
// Check if version is on npm for registry-based WSL install
|
|
3088
|
+
const useLocal = link || (() => {
|
|
3089
|
+
const vc = spawnSafe('npm', ['view', `${pkgName}@${pkgVersion}`, 'version'], {
|
|
3090
|
+
shell: process.platform === 'win32',
|
|
3091
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3092
|
+
encoding: 'utf-8'
|
|
3093
|
+
});
|
|
3094
|
+
return !(vc.status === 0 && vc.stdout?.trim() === pkgVersion);
|
|
3095
|
+
})();
|
|
3096
|
+
const wslArgs = useLocal ? ['npm', 'install', '-g', '.'] : ['npm', 'install', '-g', `${pkgName}@${pkgVersion}`];
|
|
3097
|
+
if (!useLocal)
|
|
2849
3098
|
waitForNpmVersion(pkgName, pkgVersion);
|
|
2850
|
-
console.log(`Installing ${pkgName} in WSL${
|
|
3099
|
+
console.log(`Installing ${pkgName} in WSL${useLocal ? ' (local)' : ' from registry'}...`);
|
|
2851
3100
|
const wslInstallResult = runCommand('wsl', wslArgs, { cwd, silent: false, showCommand: true });
|
|
2852
3101
|
if (wslInstallResult.success) {
|
|
2853
3102
|
console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
|
|
2854
3103
|
}
|
|
2855
3104
|
else {
|
|
2856
3105
|
console.error(colors.red(`✗ WSL install failed`));
|
|
2857
|
-
console.error(colors.yellow(' Try running manually in WSL: npm install -g ' + (
|
|
3106
|
+
console.error(colors.yellow(' Try running manually in WSL: npm install -g ' + (useLocal ? '.' : pkgName)));
|
|
2858
3107
|
}
|
|
2859
3108
|
}
|
|
2860
3109
|
}
|
|
@@ -2953,13 +3202,14 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2953
3202
|
// Version bump
|
|
2954
3203
|
console.log(`Bumping version (${bump})...`);
|
|
2955
3204
|
if (!dryRun) {
|
|
2956
|
-
// Temporarily disable postversion hook to prevent double-publishing
|
|
3205
|
+
// Temporarily disable postversion hook to prevent double-publishing or push
|
|
3206
|
+
// (push is handled separately with push-protection detection)
|
|
2957
3207
|
const pkg = readPackageJson(cwd);
|
|
2958
3208
|
const originalPostversion = pkg.scripts?.postversion;
|
|
2959
3209
|
let postversDisabled = false;
|
|
2960
|
-
if (originalPostversion && originalPostversion.includes('npm publish')) {
|
|
3210
|
+
if (originalPostversion && (originalPostversion.includes('npm publish') || originalPostversion.includes('git push'))) {
|
|
2961
3211
|
if (verbose) {
|
|
2962
|
-
console.log('Temporarily disabling postversion hook
|
|
3212
|
+
console.log('Temporarily disabling postversion hook (npmglobalize handles publish/push)...');
|
|
2963
3213
|
}
|
|
2964
3214
|
delete pkg.scripts.postversion;
|
|
2965
3215
|
writePackageJson(cwd, pkg);
|
|
@@ -3124,6 +3374,35 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3124
3374
|
console.error(' • Disk space or permissions issues');
|
|
3125
3375
|
console.error(colors.yellow('\nTry running with --verbose or check git status manually'));
|
|
3126
3376
|
}
|
|
3377
|
+
else if (combinedOutput.includes('gh013') || combinedOutput.includes('push protection') || combinedOutput.includes('push declined due to repository rule')) {
|
|
3378
|
+
// GitHub push protection blocked a push (from postversion script)
|
|
3379
|
+
const ppInfo = parsePushProtection(error.stderr || error.stdout || error.message || '', cwd);
|
|
3380
|
+
if (ppInfo.detected) {
|
|
3381
|
+
showPushProtectionGuidance(ppInfo);
|
|
3382
|
+
}
|
|
3383
|
+
else {
|
|
3384
|
+
console.error(colors.yellow('\nGitHub push protection blocked the push.'));
|
|
3385
|
+
console.error(colors.yellow('Check the output above for unblock URLs.'));
|
|
3386
|
+
}
|
|
3387
|
+
console.log(colors.yellow('Version and tag were created locally. Continuing with publish...'));
|
|
3388
|
+
autoFixed = true;
|
|
3389
|
+
}
|
|
3390
|
+
// If version/tag exist locally despite error, postversion script likely failed
|
|
3391
|
+
// (e.g., push protection with inherited stdio that we couldn't capture)
|
|
3392
|
+
if (!autoFixed) {
|
|
3393
|
+
try {
|
|
3394
|
+
const postPkg = readPackageJson(cwd);
|
|
3395
|
+
const tagCheck = runCommand('git', ['tag', '-l', `v${postPkg.version}`], { cwd, silent: true });
|
|
3396
|
+
if (tagCheck.success && tagCheck.output.trim() === `v${postPkg.version}`) {
|
|
3397
|
+
console.log(colors.yellow('\nVersion and tag created locally — postversion script may have failed.'));
|
|
3398
|
+
console.log(colors.yellow('Continuing — push will be attempted separately.'));
|
|
3399
|
+
autoFixed = true;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
catch {
|
|
3403
|
+
// ignore — fall through to normal error handling
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3127
3406
|
if (!autoFixed) {
|
|
3128
3407
|
if (!force) {
|
|
3129
3408
|
return false;
|
|
@@ -3333,14 +3612,17 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3333
3612
|
else {
|
|
3334
3613
|
console.log(` [dry-run] Would run: npm publish ${quiet ? '--quiet' : ''}`);
|
|
3335
3614
|
}
|
|
3336
|
-
// Push to git
|
|
3615
|
+
// Push to git (with push-protection detection and auto-bypass)
|
|
3337
3616
|
if (currentGitStatus.hasRemote) {
|
|
3338
3617
|
if (verbose) {
|
|
3339
3618
|
console.log('Pushing to git...');
|
|
3340
3619
|
}
|
|
3341
3620
|
if (!dryRun) {
|
|
3342
|
-
|
|
3343
|
-
|
|
3621
|
+
if (pushWithProtection(cwd, verbose)) {
|
|
3622
|
+
const tagResult = runCommand('git', ['push', '--tags'], { cwd, silent: true });
|
|
3623
|
+
if (verbose && tagResult.success)
|
|
3624
|
+
console.log(colors.green(' ✓ Pushed tags'));
|
|
3625
|
+
}
|
|
3344
3626
|
}
|
|
3345
3627
|
else {
|
|
3346
3628
|
console.log(' [dry-run] Would push to git');
|
|
@@ -3440,7 +3722,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3440
3722
|
runCommand('git', ['add', 'package.json'], { cwd });
|
|
3441
3723
|
runCommand('git', ['commit', '-m', 'Restore file: dependencies'], { cwd });
|
|
3442
3724
|
if (currentGitStatus.hasRemote) {
|
|
3443
|
-
|
|
3725
|
+
pushWithProtection(cwd, verbose);
|
|
3444
3726
|
}
|
|
3445
3727
|
}
|
|
3446
3728
|
}
|