@bobfrankston/npmglobalize 1.0.104 → 1.0.106

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
@@ -169,6 +169,12 @@ For single-developer projects, this safely pulls remote changes before publishin
169
169
 
170
170
  ### 📂 Git Repository Setup
171
171
 
172
+ **Change visibility of an existing repo:**
173
+ ```bash
174
+ npmglobalize -git public # Makes the GitHub repo public (with confirmation)
175
+ npmglobalize -git private # Makes the GitHub repo private
176
+ ```
177
+
172
178
  When initializing a new repository with `--init`, npmglobalize automatically sets up:
173
179
 
174
180
  **File structure** (per programming.md standards):
@@ -266,8 +272,9 @@ Publishing requires being on a branch so commits and tags can be properly tracke
266
272
 
267
273
  ### Git/npm Visibility
268
274
  ```
269
- -git private Make git repo private (default)
270
- -git public Make git repo public (requires confirmation)
275
+ -git private Set GitHub repo to private (default for new repos)
276
+ -git public Set GitHub repo to public (requires confirmation)
277
+ Works on both new and existing repos
271
278
  -npm private Mark npm package private (skip publish) (default)
272
279
  -npm public Publish to npm
273
280
  ```
package/cli.js CHANGED
@@ -47,8 +47,9 @@ Mode Options:
47
47
  -nofiles Keep npm versions permanently
48
48
 
49
49
  Git/npm Visibility:
50
- -git private Make git repo private (default)
51
- -git public Make git repo public (requires confirmation)
50
+ -git private Set GitHub repo to private (default for new repos)
51
+ -git public Set GitHub repo to public (requires confirmation)
52
+ Works on both new and existing repos
52
53
  -npm private Mark npm package private (skip publish) (default)
53
54
  -npm public Publish to npm
54
55
 
package/lib.d.ts CHANGED
@@ -78,6 +78,8 @@ export interface GlobalizeOptions {
78
78
  _fromWorkspace?: boolean;
79
79
  /** Internal: signals this call is from CLI (version already printed) */
80
80
  _fromCli?: boolean;
81
+ /** Internal: tracks which options were explicitly set on the CLI */
82
+ explicitKeys?: Set<string>;
81
83
  }
82
84
  /** Result from a single package in workspace mode */
83
85
  interface WorkspacePackageResult {
@@ -186,6 +188,8 @@ export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: b
186
188
  /** Run a command and return success status */
187
189
  export declare function runCommand(cmd: string, args: string[], options?: {
188
190
  silent?: boolean;
191
+ verbose?: boolean;
192
+ showCommand?: boolean;
189
193
  cwd?: string;
190
194
  }): {
191
195
  success: boolean;
package/lib.js CHANGED
@@ -36,6 +36,7 @@ const colors = {
36
36
  green: (text) => styleText('green', text),
37
37
  italic: (text) => styleText('italic', text),
38
38
  blue: (text) => styleText('blue', text),
39
+ cyan: (text) => styleText('cyan', text),
39
40
  dim: (text) => styleText('dim', text),
40
41
  };
41
42
  /**
@@ -962,23 +963,23 @@ function waitForNpmVersion(pkgName, version, maxWaitMs = 90000) {
962
963
  }
963
964
  /** Run npm install -g with retries for registry propagation delay */
964
965
  function installGlobalWithRetry(pkgSpec, cwd, maxRetries = 3) {
965
- let result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false });
966
+ let result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
966
967
  for (let attempt = 1; attempt < maxRetries && !result.success; attempt++) {
967
968
  console.log(colors.yellow(` Retrying install (attempt ${attempt + 1}/${maxRetries}) in 10 seconds...`));
968
969
  spawnSafe(process.platform === 'win32' ? 'timeout' : 'sleep', process.platform === 'win32' ? ['/t', '10', '/nobreak'] : ['10'], { stdio: 'pipe', shell: process.platform === 'win32' });
969
- result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false });
970
+ result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
970
971
  }
971
972
  return result;
972
973
  }
973
974
  /** Run a command and return success status */
974
975
  export function runCommand(cmd, args, options = {}) {
975
- const { silent = false, cwd } = options;
976
+ const { silent = false, verbose = false, showCommand = false, cwd } = options;
976
977
  try {
977
978
  // Use shell:true for npm/gh commands which need it on Windows
978
979
  const needsShell = cmd === 'npm' || cmd === 'npm.cmd' || cmd === 'gh';
979
- // Debug: log the command being executed
980
- if (!silent) {
981
- console.log(colors.dim(`[DEBUG] Running: ${cmd} ${args.join(' ')}`));
980
+ // Show command for important actions (install, etc.) or in verbose mode
981
+ if (!silent && (showCommand || verbose)) {
982
+ console.log(colors.cyan(`> ${cmd} ${args.join(' ')}`));
982
983
  }
983
984
  const result = spawnSafe(cmd, args, {
984
985
  encoding: 'utf-8',
@@ -1865,7 +1866,7 @@ async function doLocalInstall(cwd, options) {
1865
1866
  console.log(' [dry-run] Would run: wsl npm install -g .');
1866
1867
  return true;
1867
1868
  }
1868
- const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false });
1869
+ const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
1869
1870
  if (result.success) {
1870
1871
  console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
1871
1872
  }
@@ -1876,7 +1877,7 @@ async function doLocalInstall(cwd, options) {
1876
1877
  }
1877
1878
  if (wsl) {
1878
1879
  console.log(`Installing ${pkgName} in WSL (local)...`);
1879
- const wslResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false });
1880
+ const wslResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false, showCommand: true });
1880
1881
  if (wslResult.success) {
1881
1882
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
1882
1883
  }
@@ -1971,7 +1972,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
1971
1972
  console.log(' [dry-run] Would run: wsl npm install -g .');
1972
1973
  return true;
1973
1974
  }
1974
- const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false });
1975
+ const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
1975
1976
  if (result.success) {
1976
1977
  console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
1977
1978
  }
@@ -1982,7 +1983,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
1982
1983
  }
1983
1984
  if (wsl) {
1984
1985
  console.log(`Installing ${pkgName} in WSL (local)...`);
1985
- const wslResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false });
1986
+ const wslResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false, showCommand: true });
1986
1987
  if (wslResult.success) {
1987
1988
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
1988
1989
  }
@@ -2115,6 +2116,33 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2115
2116
  return false;
2116
2117
  justInitialized = true;
2117
2118
  }
2119
+ // Change GitHub repo visibility if explicitly requested on an existing repo
2120
+ const explicitKeys = options.explicitKeys;
2121
+ if (!justInitialized && gitStatus.isRepo && gitStatus.hasRemote && explicitKeys?.has('gitVisibility')) {
2122
+ const targetVis = gitVisibility;
2123
+ if (targetVis === 'public') {
2124
+ const ok = await confirm(`Change GitHub repo visibility to PUBLIC? Anyone will be able to see your code.`, false);
2125
+ if (!ok) {
2126
+ console.log('Aborted visibility change.');
2127
+ return false;
2128
+ }
2129
+ }
2130
+ if (dryRun) {
2131
+ console.log(` [dry-run] Would change GitHub repo visibility to ${targetVis}`);
2132
+ }
2133
+ else {
2134
+ console.log(`Changing GitHub repo visibility to ${targetVis}...`);
2135
+ const visResult = runCommand('gh', ['repo', 'edit', '--visibility', targetVis], { cwd, silent: true });
2136
+ if (visResult.success) {
2137
+ console.log(colors.green(`✓ GitHub repo is now ${targetVis.toUpperCase()}`));
2138
+ }
2139
+ else {
2140
+ console.error(colors.red(`✗ Failed to change repo visibility: ${visResult.stderr}`));
2141
+ if (!force)
2142
+ return false;
2143
+ }
2144
+ }
2145
+ }
2118
2146
  // Re-check git status after potential init
2119
2147
  let currentGitStatus = getGitStatus(cwd);
2120
2148
  // Validate git state
@@ -2349,12 +2377,22 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2349
2377
  }
2350
2378
  }
2351
2379
  if (currentAccess === 'public') {
2352
- console.error(colors.red(`ERROR: Package '${pkg.name}' is currently PUBLIC on npm.`));
2353
- console.error(colors.red(`Cannot change to private. Options:`));
2354
- console.error(colors.yellow(` 1. Deprecate the public package: npm deprecate ${pkg.name} "moved to private"`));
2355
- console.error(colors.yellow(` 2. Continue as public: npmglobalize --npm public`));
2356
- console.error(colors.yellow(` 3. Use a different package name`));
2357
- return false;
2380
+ console.log(colors.yellow(`Package '${pkg.name}' is currently PUBLIC on npm. Converting to private...`));
2381
+ if (!dryRun) {
2382
+ const accessResult = runCommand('npm', ['access', 'set', 'status=restricted', pkg.name], { cwd, silent: false });
2383
+ if (accessResult.success) {
2384
+ console.log(colors.green(`✓ Changed ${pkg.name} to PRIVATE on npm`));
2385
+ currentAccess = 'restricted';
2386
+ }
2387
+ else {
2388
+ console.error(colors.red(`Failed to change access. Try manually: npm access set status=restricted ${pkg.name}`));
2389
+ if (!force)
2390
+ return false;
2391
+ }
2392
+ }
2393
+ else {
2394
+ console.log(colors.dim(` [dry-run] Would run: npm access set status=restricted ${pkg.name}`));
2395
+ }
2358
2396
  }
2359
2397
  // Don't set "private": true in package.json - that blocks all publishing
2360
2398
  console.log(`Package '${pkg.name}' will publish as PRIVATE (restricted access).`);
@@ -2365,12 +2403,22 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2365
2403
  else if (effectiveNpmVisibility === 'public') {
2366
2404
  // User explicitly wants public (or confirmed via prompt)
2367
2405
  if (currentAccess === 'restricted') {
2368
- console.error(colors.red(`ERROR: Package '${pkg.name}' is currently PRIVATE on npm.`));
2369
- console.error(colors.red(`Cannot change to public. Options:`));
2370
- console.error(colors.yellow(` 1. Unpublish and republish: npm unpublish ${pkg.name} --force`));
2371
- console.error(colors.yellow(` 2. Continue as private: npmglobalize --npm private`));
2372
- console.error(colors.yellow(` 3. Use a different package name`));
2373
- return false;
2406
+ console.log(colors.yellow(`Package '${pkg.name}' is currently PRIVATE on npm. Converting to public...`));
2407
+ if (!dryRun) {
2408
+ const accessResult = runCommand('npm', ['access', 'set', 'status=public', pkg.name], { cwd, silent: false });
2409
+ if (accessResult.success) {
2410
+ console.log(colors.green(`✓ Changed ${pkg.name} to PUBLIC on npm`));
2411
+ currentAccess = 'public';
2412
+ }
2413
+ else {
2414
+ console.error(colors.red(`Failed to change access. Try manually: npm access set status=public ${pkg.name}`));
2415
+ if (!force)
2416
+ return false;
2417
+ }
2418
+ }
2419
+ else {
2420
+ console.log(colors.dim(` [dry-run] Would run: npm access set status=public ${pkg.name}`));
2421
+ }
2374
2422
  }
2375
2423
  if (!publicConfirmed) {
2376
2424
  console.log(`Will publish '${pkg.name}' to PUBLIC npm registry.`);
@@ -2677,7 +2725,16 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2677
2725
  const isFirstPublish = !currentAccess;
2678
2726
  if (!currentGitStatus.hasUncommitted && !message && !justInitialized && !isFirstPublish) {
2679
2727
  console.log('');
2680
- console.log('No changes to commit and no custom message specified.');
2728
+ if (currentAccess === 'public') {
2729
+ console.log(`${pkg.name}@${pkg.version} is already PUBLIC on npm. No changes to publish.`);
2730
+ }
2731
+ else if (currentAccess === 'restricted') {
2732
+ console.log(`${pkg.name}@${pkg.version} is PRIVATE on npm. No changes to publish.`);
2733
+ }
2734
+ else {
2735
+ console.log('No changes to commit and no custom message specified.');
2736
+ }
2737
+ console.log(colors.dim(' Use -m "message" to force a version bump and republish.'));
2681
2738
  // If install/link flag is set, install globally
2682
2739
  if (install || link || wsl) {
2683
2740
  if (verbose) {
@@ -2696,7 +2753,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2696
2753
  if (pkg.bin && (install || link || wsl)) {
2697
2754
  if (link) {
2698
2755
  console.log(`Installing ${pkgName} globally from local directory (link)...`);
2699
- const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false });
2756
+ const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
2700
2757
  if (localInstallResult.success) {
2701
2758
  console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
2702
2759
  }
@@ -2722,7 +2779,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2722
2779
  if (!link && !install)
2723
2780
  waitForNpmVersion(pkgName, pkgVersion);
2724
2781
  console.log(`Installing ${pkgName} in WSL${link ? ' (link)' : ' from registry'}...`);
2725
- const wslInstallResult = runCommand('wsl', wslArgs, { cwd, silent: false });
2782
+ const wslInstallResult = runCommand('wsl', wslArgs, { cwd, silent: false, showCommand: true });
2726
2783
  if (wslInstallResult.success) {
2727
2784
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
2728
2785
  }
@@ -3194,7 +3251,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3194
3251
  if (link) {
3195
3252
  console.log(`Linking globally: ${pkgName}@${pkgVersion}...`);
3196
3253
  if (!dryRun) {
3197
- const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false });
3254
+ const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3198
3255
  if (installResult.success) {
3199
3256
  console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
3200
3257
  }
@@ -3230,7 +3287,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3230
3287
  waitForNpmVersion(pkgName, pkgVersion);
3231
3288
  console.log(`Installing in WSL${link ? ' (link)' : ' from registry'}: ${pkgName}@${pkgVersion}...`);
3232
3289
  if (!dryRun) {
3233
- const wslResult = runCommand('wsl', wslArgs, { cwd, silent: false });
3290
+ const wslResult = runCommand('wsl', wslArgs, { cwd, silent: false, showCommand: true });
3234
3291
  if (wslResult.success) {
3235
3292
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
3236
3293
  }
@@ -3279,6 +3336,9 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3279
3336
  console.log(` Version: ${colors.green('v' + finalPkg.version)}`);
3280
3337
  console.log(` Published: ${colors.green('✓')} (${accessLabel})`);
3281
3338
  console.log(` Git pushed: ${colors.green('✓')}`);
3339
+ if (explicitKeys?.has('gitVisibility')) {
3340
+ console.log(` Git visibility: ${colors.green(gitVisibility.toUpperCase())}`);
3341
+ }
3282
3342
  if (link) {
3283
3343
  console.log(` Linked globally: ${colors.green('✓')}`);
3284
3344
  }
@@ -3357,6 +3417,52 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
3357
3417
  console.log(`Filtered to: ${filteredOrder.join(', ')}`);
3358
3418
  console.log('');
3359
3419
  }
3420
+ // Check for visibility mismatches: public packages depending on would-be-private siblings
3421
+ const visibilityMap = new Map();
3422
+ for (const pkgInfo of packages) {
3423
+ const pkgConfig = readConfig(pkgInfo.dir);
3424
+ const vis = pkgConfig.npmVisibility
3425
+ || pkgInfo.pkg.publishConfig?.access
3426
+ || options.npmVisibility; // CLI-level default
3427
+ if (vis === 'public') {
3428
+ visibilityMap.set(pkgInfo.name, 'public');
3429
+ }
3430
+ else if (pkgInfo.pkg.private) {
3431
+ visibilityMap.set(pkgInfo.name, 'private');
3432
+ }
3433
+ else {
3434
+ visibilityMap.set(pkgInfo.name, 'unknown');
3435
+ }
3436
+ }
3437
+ const wsNameSet = new Set(packages.map(p => p.name));
3438
+ for (const pkgInfo of packages) {
3439
+ if (visibilityMap.get(pkgInfo.name) !== 'public')
3440
+ continue;
3441
+ const deps = { ...pkgInfo.pkg.dependencies, ...pkgInfo.pkg.devDependencies };
3442
+ for (const depName of Object.keys(deps || {})) {
3443
+ if (!wsNameSet.has(depName))
3444
+ continue;
3445
+ if (visibilityMap.get(depName) === 'public')
3446
+ continue;
3447
+ const depInfo = packages.find(p => p.name === depName);
3448
+ if (!depInfo)
3449
+ continue;
3450
+ console.log(colors.yellow(`⚠ Public package ${pkgInfo.name} depends on ${depName} which has no public visibility set.`));
3451
+ if (!options.dryRun) {
3452
+ const makePublic = await confirm(`Make ${depName} public too?`, true);
3453
+ if (makePublic) {
3454
+ const depConfig = readConfig(depInfo.dir);
3455
+ depConfig.npmVisibility = 'public';
3456
+ writeConfig(depInfo.dir, depConfig, new Set(['npmVisibility']));
3457
+ visibilityMap.set(depName, 'public');
3458
+ console.log(colors.green(`✓ Set ${depName} to public in .globalize.json5`));
3459
+ }
3460
+ }
3461
+ else {
3462
+ console.log(colors.dim(` [dry-run] Would ask to make ${depName} public`));
3463
+ }
3464
+ }
3465
+ }
3360
3466
  // Process each package in dependency order
3361
3467
  const results = [];
3362
3468
  for (const pkgName of filteredOrder) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.104",
3
+ "version": "1.0.106",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",