@bobfrankston/npmglobalize 1.0.111 → 1.0.113

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 (2) hide show
  1. package/lib.js +111 -11
  2. package/package.json +1 -1
package/lib.js CHANGED
@@ -1406,6 +1406,77 @@ 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.json type: "installed" (public app ID, safe to include) or "web" (has real secret, must ignore).
1410
+ * Returns null if no credentials.json exists or it can't be parsed. */
1411
+ function detectCredentialsType(cwd) {
1412
+ const credPath = path.join(cwd, 'credentials.json');
1413
+ if (!fs.existsSync(credPath))
1414
+ return null;
1415
+ try {
1416
+ const content = fs.readFileSync(credPath, 'utf-8');
1417
+ const parsed = JSON.parse(content);
1418
+ if (parsed.installed)
1419
+ return 'installed';
1420
+ if (parsed.web)
1421
+ return 'web';
1422
+ return null; // Unknown format (e.g., Microsoft OAuth)
1423
+ }
1424
+ catch {
1425
+ return null;
1426
+ }
1427
+ }
1428
+ /** Ensure credentials.json is handled correctly in ignore files based on OAuth type.
1429
+ * "installed" apps: client_secret is just a public app registration ID — must be INCLUDED.
1430
+ * "web" apps: client_secret is a real secret — must be IGNORED. */
1431
+ function conformCredentialsIgnore(cwd) {
1432
+ const credType = detectCredentialsType(cwd);
1433
+ if (!credType)
1434
+ return;
1435
+ for (const ignoreFile of ['.gitignore', '.npmignore']) {
1436
+ const ignorePath = path.join(cwd, ignoreFile);
1437
+ if (!fs.existsSync(ignorePath))
1438
+ continue;
1439
+ const content = fs.readFileSync(ignorePath, 'utf-8');
1440
+ const lines = content.split('\n');
1441
+ const trimmed = lines.map(l => l.trim());
1442
+ if (credType === 'installed') {
1443
+ // Ensure credentials.json is NOT ignored — add negation if it's being blocked
1444
+ const hasBlock = trimmed.some(l => l === 'credentials.json' || l === 'credentials.json/');
1445
+ const hasNegation = trimmed.some(l => l === '!credentials.json');
1446
+ if (hasBlock && !hasNegation) {
1447
+ // Add negation after the blocking line
1448
+ const newLines = [...lines];
1449
+ const blockIdx = trimmed.findIndex(l => l === 'credentials.json' || l === 'credentials.json/');
1450
+ newLines.splice(blockIdx + 1, 0, '!credentials.json');
1451
+ fs.writeFileSync(ignorePath, newLines.join('\n'));
1452
+ console.log(colors.cyan(` ✓ ${ignoreFile}: added !credentials.json (installed app — public OAuth app ID)`));
1453
+ }
1454
+ else if (!hasBlock && !hasNegation) {
1455
+ // No block exists, but add negation defensively in case a broader pattern catches it
1456
+ const newContent = content.trimEnd() + '\n!credentials.json\n';
1457
+ fs.writeFileSync(ignorePath, newContent);
1458
+ console.log(colors.cyan(` ✓ ${ignoreFile}: added !credentials.json (installed app — public OAuth app ID)`));
1459
+ }
1460
+ }
1461
+ else if (credType === 'web') {
1462
+ // Ensure credentials.json IS ignored
1463
+ const hasBlock = trimmed.some(l => l === 'credentials.json');
1464
+ if (!hasBlock) {
1465
+ const newContent = content.trimEnd() + '\ncredentials.json\n';
1466
+ fs.writeFileSync(ignorePath, newContent);
1467
+ console.log(colors.cyan(` ✓ ${ignoreFile}: added credentials.json to ignore (web app — real secret)`));
1468
+ }
1469
+ // Remove any negation that would un-ignore it
1470
+ const negIdx = trimmed.findIndex(l => l === '!credentials.json');
1471
+ if (negIdx >= 0) {
1472
+ const newLines = [...lines];
1473
+ newLines.splice(negIdx, 1);
1474
+ fs.writeFileSync(ignorePath, newLines.join('\n'));
1475
+ console.log(colors.yellow(` ✓ ${ignoreFile}: removed !credentials.json (web app — must not be public)`));
1476
+ }
1477
+ }
1478
+ }
1479
+ }
1409
1480
  /** Check if ignore files need updates; separates security (auto-fix) from recommended (prompt) */
1410
1481
  function checkIgnoreFiles(cwd, options) {
1411
1482
  const changes = [];
@@ -2832,29 +2903,58 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2832
2903
  }
2833
2904
  }
2834
2905
  else if (install) {
2835
- waitForNpmVersion(pkgName, pkgVersion);
2836
- console.log(`Installing ${pkgName}@${pkgVersion} globally from registry...`);
2837
- const registryInstallResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
2838
- if (registryInstallResult.success) {
2839
- console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
2906
+ // Quick check: does this version actually exist on npm?
2907
+ const vCheck = spawnSafe('npm', ['view', `${pkgName}@${pkgVersion}`, 'version'], {
2908
+ shell: process.platform === 'win32',
2909
+ stdio: ['pipe', 'pipe', 'pipe'],
2910
+ encoding: 'utf-8'
2911
+ });
2912
+ const versionOnNpm = vCheck.status === 0 && vCheck.stdout?.trim() === pkgVersion;
2913
+ if (versionOnNpm) {
2914
+ console.log(`Installing ${pkgName}@${pkgVersion} globally from registry...`);
2915
+ const registryInstallResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
2916
+ if (registryInstallResult.success) {
2917
+ console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
2918
+ }
2919
+ else {
2920
+ console.error(colors.red(`✗ Global install failed`));
2921
+ console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
2922
+ }
2840
2923
  }
2841
2924
  else {
2842
- console.error(colors.red(`✗ Global install failed`));
2843
- console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
2925
+ console.log(colors.yellow(`${pkgName}@${pkgVersion} not found on npm — installing from local directory.`));
2926
+ console.log(colors.dim(' Use -m "message" to publish this version to npm.'));
2927
+ const localResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
2928
+ if (localResult.success) {
2929
+ console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
2930
+ }
2931
+ else {
2932
+ console.error(colors.red(`✗ Local install failed`));
2933
+ console.error(colors.yellow(' Try running manually: npm install -g .'));
2934
+ }
2844
2935
  }
2845
2936
  }
2846
2937
  if (wsl) {
2847
- const wslArgs = link ? ['npm', 'install', '-g', '.'] : ['npm', 'install', '-g', `${pkgName}@${pkgVersion}`];
2848
- if (!link && !install)
2938
+ // Check if version is on npm for registry-based WSL install
2939
+ const useLocal = link || (() => {
2940
+ const vc = spawnSafe('npm', ['view', `${pkgName}@${pkgVersion}`, 'version'], {
2941
+ shell: process.platform === 'win32',
2942
+ stdio: ['pipe', 'pipe', 'pipe'],
2943
+ encoding: 'utf-8'
2944
+ });
2945
+ return !(vc.status === 0 && vc.stdout?.trim() === pkgVersion);
2946
+ })();
2947
+ const wslArgs = useLocal ? ['npm', 'install', '-g', '.'] : ['npm', 'install', '-g', `${pkgName}@${pkgVersion}`];
2948
+ if (!useLocal)
2849
2949
  waitForNpmVersion(pkgName, pkgVersion);
2850
- console.log(`Installing ${pkgName} in WSL${link ? ' (link)' : ' from registry'}...`);
2950
+ console.log(`Installing ${pkgName} in WSL${useLocal ? ' (local)' : ' from registry'}...`);
2851
2951
  const wslInstallResult = runCommand('wsl', wslArgs, { cwd, silent: false, showCommand: true });
2852
2952
  if (wslInstallResult.success) {
2853
2953
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
2854
2954
  }
2855
2955
  else {
2856
2956
  console.error(colors.red(`✗ WSL install failed`));
2857
- console.error(colors.yellow(' Try running manually in WSL: npm install -g ' + (link ? '.' : pkgName)));
2957
+ console.error(colors.yellow(' Try running manually in WSL: npm install -g ' + (useLocal ? '.' : pkgName)));
2858
2958
  }
2859
2959
  }
2860
2960
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.111",
3
+ "version": "1.0.113",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",