@bobfrankston/npmglobalize 1.0.114 → 1.0.115

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 (3) hide show
  1. package/README.md +16 -11
  2. package/lib.js +111 -82
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -165,6 +165,10 @@ npmglobalize --rebase # Auto-rebase if behind remote
165
165
 
166
166
  For single-developer projects, this safely pulls remote changes before publishing.
167
167
 
168
+ **Failed publish recovery**: If a previous run bumped the version locally but the publish or push failed (e.g., network error, push protection), `npmglobalize` detects this automatically on the next run. It checks whether the current version actually exists on npm — if not, it republishes without re-bumping the version. Unpushed commits from a failed push are also pushed automatically.
169
+
170
+ **GitHub Push Protection (GH013)**: When GitHub's secret scanning blocks a push, `npmglobalize` detects the error and checks whether the flagged secrets are actually safe. For Google OAuth desktop/installed app credentials (where the "client_secret" is just a public app identifier, not a real secret), it automatically bypasses push protection via the GitHub API (`gh` CLI required) and retries the push. For other secret types, it displays the unblock URLs with guidance.
171
+
168
172
  **Note:** This tool is designed for single-developer, single-branch workflows where automatic rebase and tag cleanup are safe operations.
169
173
 
170
174
  ### 📂 Git Repository Setup
@@ -390,17 +394,18 @@ npmglobalize --dry-run # See what would happen
390
394
  ## How It Works
391
395
 
392
396
  1. **Validates** package.json and git status
393
- 2. **Updates dependencies** (if `--update-deps`)
394
- 3. **Publishes file: dependencies** (if needed)
395
- 4. **Backs up** original file: references to `.dependencies`
396
- 5. **Converts** `file:` npm version references
397
- 6. **Commits** changes
398
- 7. **Bumps** version (using npm version)
399
- 8. **Publishes** to npm
400
- 9. **Pushes** to git
401
- 10. **Installs** globally (if `--install`)
402
- 11. **Restores** file: references (if `--files`, default)
403
- 12. **Runs audit** (shows security status)
397
+ 2. **Checks** if current version is on npm (recovers from failed publishes)
398
+ 3. **Updates dependencies** (if `--update-deps`)
399
+ 4. **Publishes file: dependencies** (if needed)
400
+ 5. **Backs up** original file: references to `.dependencies`
401
+ 6. **Converts** `file:` → npm version references
402
+ 7. **Commits** changes
403
+ 8. **Bumps** version (using npm version) — skipped if recovering a failed publish
404
+ 9. **Publishes** to npm
405
+ 10. **Pushes** to git (with push-protection detection and auto-bypass)
406
+ 11. **Installs** globally (if `--install`)
407
+ 12. **Restores** file: references (if `--files`, default)
408
+ 13. **Runs audit** (shows security status)
404
409
 
405
410
  ## Operational Details
406
411
 
package/lib.js CHANGED
@@ -3012,103 +3012,127 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3012
3012
  // Check if there are changes to commit or a custom message
3013
3013
  // Skip this check for first publish (currentAccess null) or just-initialized repos
3014
3014
  const isFirstPublish = !currentAccess;
3015
+ let skipVersionBump = false;
3015
3016
  if (!currentGitStatus.hasUncommitted && !message && !justInitialized && !isFirstPublish) {
3016
- console.log('');
3017
- if (currentAccess === 'public') {
3018
- console.log(`${pkg.name}@${pkg.version} is already PUBLIC on npm. No changes to publish.`);
3019
- }
3020
- else if (currentAccess === 'restricted') {
3021
- console.log(`${pkg.name}@${pkg.version} is PRIVATE on npm. No changes to publish.`);
3017
+ // Check if the current version is actually on npm (it might have failed to publish previously)
3018
+ const versionCheck = spawnSafe('npm', ['view', `${pkg.name}@${pkg.version}`, 'version'], {
3019
+ shell: process.platform === 'win32',
3020
+ stdio: ['pipe', 'pipe', 'pipe'],
3021
+ encoding: 'utf-8'
3022
+ });
3023
+ const versionIsOnNpm = versionCheck.status === 0 && versionCheck.stdout?.trim() === pkg.version;
3024
+ if (!versionIsOnNpm && currentAccess) {
3025
+ // Version not on npm — previous publish/push likely failed
3026
+ // Skip version bump but fall through to publish+push
3027
+ console.log('');
3028
+ console.log(colors.yellow(`${pkg.name}@${pkg.version} is NOT on npm (previous publish may have failed).`));
3029
+ console.log(colors.yellow('Republishing current version...'));
3030
+ skipVersionBump = true;
3022
3031
  }
3023
3032
  else {
3024
- console.log('No changes to commit and no custom message specified.');
3025
- }
3026
- console.log(colors.dim(' Use -m "message" to force a version bump and republish.'));
3027
- // If install/link flag is set, install globally
3028
- if (install || link || wsl) {
3029
- if (verbose) {
3030
- console.log('');
3031
- console.log(link ? 'Installing from local directory (link)...' : 'Installing from registry...');
3032
- }
3033
- const pkgName = pkg.name;
3034
- const pkgVersion = pkg.version;
3035
- if (!pkg.bin && (install || link || wsl)) {
3036
- const proceed = await offerAddBin(cwd, pkg);
3037
- if (!proceed) {
3038
- console.log(colors.yellow('Skipping global install — library packages should not be installed globally.'));
3039
- console.log(colors.yellow(`To use in projects: npm install ${pkgName}`));
3040
- }
3041
- }
3042
- if (pkg.bin && (install || link || wsl)) {
3043
- if (link) {
3044
- console.log(`Installing ${pkgName} globally from local directory (link)...`);
3045
- const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3046
- if (localInstallResult.success) {
3047
- console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
3048
- }
3049
- else {
3050
- console.error(colors.red(`✗ Global link install failed`));
3051
- console.error(colors.yellow(' Try running manually: npm install -g .'));
3033
+ // Version is on npm truly nothing to publish
3034
+ console.log('');
3035
+ if (currentAccess === 'public') {
3036
+ console.log(`${pkg.name}@${pkg.version} is already PUBLIC on npm. No changes to publish.`);
3037
+ }
3038
+ else if (currentAccess === 'restricted') {
3039
+ console.log(`${pkg.name}@${pkg.version} is PRIVATE on npm. No changes to publish.`);
3040
+ }
3041
+ else {
3042
+ console.log('No changes to commit and no custom message specified.');
3043
+ }
3044
+ console.log(colors.dim(' Use -m "message" to force a version bump and republish.'));
3045
+ // Push any unpushed commits (e.g., from a previous failed push)
3046
+ if (currentGitStatus.hasRemote && currentGitStatus.hasUnpushed && !dryRun) {
3047
+ console.log(colors.yellow('Pushing unpushed commits...'));
3048
+ pushWithProtection(cwd, verbose);
3049
+ }
3050
+ // If install/link flag is set, install globally
3051
+ if (install || link || wsl) {
3052
+ if (verbose) {
3053
+ console.log('');
3054
+ console.log(link ? 'Installing from local directory (link)...' : 'Installing from registry...');
3055
+ }
3056
+ const pkgName = pkg.name;
3057
+ const pkgVersion = pkg.version;
3058
+ if (!pkg.bin && (install || link || wsl)) {
3059
+ const proceed = await offerAddBin(cwd, pkg);
3060
+ if (!proceed) {
3061
+ console.log(colors.yellow('Skipping global install — library packages should not be installed globally.'));
3062
+ console.log(colors.yellow(`To use in projects: npm install ${pkgName}`));
3052
3063
  }
3053
3064
  }
3054
- else if (install) {
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
- }
3072
- }
3073
- else {
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}`));
3065
+ if (pkg.bin && (install || link || wsl)) {
3066
+ if (link) {
3067
+ console.log(`Installing ${pkgName} globally from local directory (link)...`);
3068
+ const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3069
+ if (localInstallResult.success) {
3070
+ console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
3079
3071
  }
3080
3072
  else {
3081
- console.error(colors.red(`✗ Local install failed`));
3073
+ console.error(colors.red(`✗ Global link install failed`));
3082
3074
  console.error(colors.yellow(' Try running manually: npm install -g .'));
3083
3075
  }
3084
3076
  }
3085
- }
3086
- if (wsl) {
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'], {
3077
+ else if (install) {
3078
+ // Quick check: does this version actually exist on npm?
3079
+ const vCheck = spawnSafe('npm', ['view', `${pkgName}@${pkgVersion}`, 'version'], {
3090
3080
  shell: process.platform === 'win32',
3091
3081
  stdio: ['pipe', 'pipe', 'pipe'],
3092
3082
  encoding: 'utf-8'
3093
3083
  });
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)
3098
- waitForNpmVersion(pkgName, pkgVersion);
3099
- console.log(`Installing ${pkgName} in WSL${useLocal ? ' (local)' : ' from registry'}...`);
3100
- const wslInstallResult = runCommand('wsl', wslArgs, { cwd, silent: false, showCommand: true });
3101
- if (wslInstallResult.success) {
3102
- console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
3084
+ const vOnNpm = vCheck.status === 0 && vCheck.stdout?.trim() === pkgVersion;
3085
+ if (vOnNpm) {
3086
+ console.log(`Installing ${pkgName}@${pkgVersion} globally from registry...`);
3087
+ const registryInstallResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
3088
+ if (registryInstallResult.success) {
3089
+ console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
3090
+ }
3091
+ else {
3092
+ console.error(colors.red(`✗ Global install failed`));
3093
+ console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
3094
+ }
3095
+ }
3096
+ else {
3097
+ console.log(colors.yellow(`${pkgName}@${pkgVersion} not found on npm — installing from local directory.`));
3098
+ console.log(colors.dim(' Use -m "message" to publish this version to npm.'));
3099
+ const localResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3100
+ if (localResult.success) {
3101
+ console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
3102
+ }
3103
+ else {
3104
+ console.error(colors.red(`✗ Local install failed`));
3105
+ console.error(colors.yellow(' Try running manually: npm install -g .'));
3106
+ }
3107
+ }
3103
3108
  }
3104
- else {
3105
- console.error(colors.red(`✗ WSL install failed`));
3106
- console.error(colors.yellow(' Try running manually in WSL: npm install -g ' + (useLocal ? '.' : pkgName)));
3109
+ if (wsl) {
3110
+ // Check if version is on npm for registry-based WSL install
3111
+ const useLocal = link || (() => {
3112
+ const vc = spawnSafe('npm', ['view', `${pkgName}@${pkgVersion}`, 'version'], {
3113
+ shell: process.platform === 'win32',
3114
+ stdio: ['pipe', 'pipe', 'pipe'],
3115
+ encoding: 'utf-8'
3116
+ });
3117
+ return !(vc.status === 0 && vc.stdout?.trim() === pkgVersion);
3118
+ })();
3119
+ const wslArgs = useLocal ? ['npm', 'install', '-g', '.'] : ['npm', 'install', '-g', `${pkgName}@${pkgVersion}`];
3120
+ if (!useLocal)
3121
+ waitForNpmVersion(pkgName, pkgVersion);
3122
+ console.log(`Installing ${pkgName} in WSL${useLocal ? ' (local)' : ' from registry'}...`);
3123
+ const wslInstallResult = runCommand('wsl', wslArgs, { cwd, silent: false, showCommand: true });
3124
+ if (wslInstallResult.success) {
3125
+ console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
3126
+ }
3127
+ else {
3128
+ console.error(colors.red(`✗ WSL install failed`));
3129
+ console.error(colors.yellow(' Try running manually in WSL: npm install -g ' + (useLocal ? '.' : pkgName)));
3130
+ }
3107
3131
  }
3108
3132
  }
3109
3133
  }
3134
+ return true;
3110
3135
  }
3111
- return true;
3112
3136
  }
3113
3137
  // Ensure node_modules is in .gitignore before any git operations
3114
3138
  if (currentGitStatus.isRepo && !dryRun) {
@@ -3199,9 +3223,14 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3199
3223
  console.log(' Pulled latest from remote');
3200
3224
  }
3201
3225
  }
3202
- // Version bump
3203
- console.log(`Bumping version (${bump})...`);
3204
- if (!dryRun) {
3226
+ // Version bump (skip if republishing a version that's already bumped locally)
3227
+ if (skipVersionBump) {
3228
+ console.log(`Skipping version bump — ${pkg.version} already set locally.`);
3229
+ }
3230
+ else {
3231
+ console.log(`Bumping version (${bump})...`);
3232
+ }
3233
+ if (!dryRun && !skipVersionBump) {
3205
3234
  // Temporarily disable postversion hook to prevent double-publishing or push
3206
3235
  // (push is handled separately with push-protection detection)
3207
3236
  const pkg = readPackageJson(cwd);
@@ -3411,7 +3440,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3411
3440
  }
3412
3441
  }
3413
3442
  }
3414
- else {
3443
+ else if (!skipVersionBump) {
3415
3444
  console.log(` [dry-run] Would run: npm version ${bump}`);
3416
3445
  }
3417
3446
  // Publish
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.114",
3
+ "version": "1.0.115",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",