@bobfrankston/npmglobalize 1.0.207 → 1.0.208

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 +61 -4
  2. package/package.json +1 -1
package/lib.js CHANGED
@@ -4299,6 +4299,56 @@ async function waitForNpmVersionInWsl(spec, maxWaitMs = 180000) {
4299
4299
  process.stdout.write(' timed out\n');
4300
4300
  return false;
4301
4301
  }
4302
+ /** After a global WSL install, confirm a NON-interactive shell actually resolves
4303
+ * the binaries we just installed, rather than older copies of them.
4304
+ *
4305
+ * WSL's npm prefix is per-user (~/.npm-global), and that directory reaches PATH
4306
+ * only via ~/.bashrc — which returns immediately when the shell is not
4307
+ * interactive. Every `wsl <cmd>` is non-interactive, so it searches the default
4308
+ * PATH from /etc/environment instead, where a stale /usr/local/bin entry left by
4309
+ * an earlier root-prefix install keeps winning. The install genuinely succeeds
4310
+ * and the version reported by `wsl <tool>` is genuinely stale, which is exactly
4311
+ * why this went unnoticed for four months. Repair with a symlink rather than a
4312
+ * copy, so it tracks later installs on its own. */
4313
+ async function verifyWslGlobalBins(cwd) {
4314
+ let names = [];
4315
+ try {
4316
+ const pkg = readPackageJson(cwd);
4317
+ if (typeof pkg?.bin === 'string')
4318
+ names = [String(pkg.name || '').split('/').pop()].filter(Boolean);
4319
+ else if (pkg?.bin && typeof pkg.bin === 'object')
4320
+ names = Object.keys(pkg.bin);
4321
+ }
4322
+ catch {
4323
+ return;
4324
+ }
4325
+ if (!names.length)
4326
+ return;
4327
+ const prefixResult = await runCommandAsync('wsl', ['npm', 'config', 'get', 'prefix'], { silent: true });
4328
+ const prefix = (prefixResult.output || '').trim();
4329
+ if (!prefixResult.success || !prefix.startsWith('/'))
4330
+ return;
4331
+ for (const name of names) {
4332
+ const expected = `${prefix}/bin/${name}`;
4333
+ // `bash -c` is non-interactive — the same shape as a bare `wsl <cmd>`,
4334
+ // which is the case that breaks. A login shell would mask the problem.
4335
+ const lookup = await runCommandAsync('wsl', ['bash', '-c', `command -v ${name} || true`], { silent: true });
4336
+ const found = (lookup.output || '').trim();
4337
+ if (!found || found === expected)
4338
+ continue;
4339
+ console.log(colors.yellow(` WSL resolves ${name} to ${found}, not the copy just installed at ${expected}`));
4340
+ if (!found.startsWith('/usr/local/bin/')) {
4341
+ recordBuildIssue(name, 'warning', `WSL resolves ${name} to ${found} instead of ${expected}, so scripts run a different version than an interactive shell does. Left alone: only /usr/local/bin shadows are repaired automatically.`);
4342
+ continue;
4343
+ }
4344
+ const repair = await runCommandAsync('wsl', ['bash', '-c', `sudo -n rm -f ${found} && sudo -n ln -s ${expected} ${found}`], { silent: true });
4345
+ if (repair.success) {
4346
+ console.log(colors.green(` ✓ Repaired WSL shadow: ${found} -> ${expected}`));
4347
+ continue;
4348
+ }
4349
+ recordBuildIssue(name, 'warning', `WSL resolves ${name} to a stale ${found}. Repair with: wsl sudo rm ${found} && wsl sudo ln -s ${expected} ${found}`);
4350
+ }
4351
+ }
4302
4352
  export async function installInWsl(wslArgs, opts = {}) {
4303
4353
  // Same trust rule as the Windows installs: allow our own packages'
4304
4354
  // install scripts, leave third-party ones gated. Probed against WSL's
@@ -4317,9 +4367,16 @@ export async function installInWsl(wslArgs, opts = {}) {
4317
4367
  process.stderr.write(r.stderr);
4318
4368
  return r;
4319
4369
  };
4370
+ // Every success path leaves through here, so the shadow check cannot be
4371
+ // skipped by whichever retry happened to be the one that worked.
4372
+ const succeed = async (fixed) => {
4373
+ if (opts.cwd && wslArgs.includes('-g'))
4374
+ await verifyWslGlobalBins(opts.cwd);
4375
+ return { success: true, fixed };
4376
+ };
4320
4377
  let result = await runOnce();
4321
4378
  if (result.success)
4322
- return { success: true, fixed: false };
4379
+ return await succeed(false);
4323
4380
  let combined = (result.output || '') + '\n' + (result.stderr || '');
4324
4381
  // EACCES on a root-owned npm prefix → switch to a user prefix and retry.
4325
4382
  if (/EACCES/.test(combined) && /\/usr\/(?:local\/)?lib\/node_modules/.test(combined)) {
@@ -4335,7 +4392,7 @@ export async function installInWsl(wslArgs, opts = {}) {
4335
4392
  console.log(colors.green('✓ WSL npm prefix set to ~/.npm-global; PATH appended to ~/.bashrc'));
4336
4393
  result = await runOnce();
4337
4394
  if (result.success)
4338
- return { success: true, fixed: true };
4395
+ return await succeed(true);
4339
4396
  combined = (result.output || '') + '\n' + (result.stderr || '');
4340
4397
  }
4341
4398
  // E404 on a scoped package has TWO causes, and npm gives the same error for
@@ -4352,7 +4409,7 @@ export async function installInWsl(wslArgs, opts = {}) {
4352
4409
  console.log(colors.green('✓ Synced npm token to WSL'));
4353
4410
  result = await runOnce();
4354
4411
  if (result.success)
4355
- return { success: true, fixed: true };
4412
+ return await succeed(true);
4356
4413
  }
4357
4414
  else {
4358
4415
  console.error(colors.yellow(' Could not authenticate WSL npm. Run `wsl npm login` (or sync your ~/.npmrc token) and retry.'));
@@ -4364,7 +4421,7 @@ export async function installInWsl(wslArgs, opts = {}) {
4364
4421
  if (await waitForNpmVersionInWsl(spec)) {
4365
4422
  result = await runOnce();
4366
4423
  if (result.success)
4367
- return { success: true, fixed: true };
4424
+ return await succeed(true);
4368
4425
  }
4369
4426
  else {
4370
4427
  console.error(colors.yellow(` ${spec} still not visible to WSL's npm after waiting — try the WSL install again shortly.`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.207",
3
+ "version": "1.0.208",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",