fix(install): chmod dist/cli.js 0o755 after npm install -g; add regression test (closes #2525) (#2536)
Use process.platform !== 'win32' guard in catch instead of a comment, and add regression test for bug #2525 (gsd-sdk bin symlink points at non-executable file). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6812,7 +6812,7 @@ function installSdkIfNeeded() {
|
||||
? path.join(npmPrefix, 'node_modules')
|
||||
: path.join(npmPrefix, 'lib', 'node_modules');
|
||||
const cliPath = path.join(globalModulesDir, sdkName, 'dist', 'cli.js');
|
||||
try { fs.chmodSync(cliPath, 0o755); } catch (e) { /* Windows / path not found */ }
|
||||
try { fs.chmodSync(cliPath, 0o755); } catch (e) { if (process.platform !== 'win32') throw e; }
|
||||
}
|
||||
} catch (e) { /* Non-fatal: PATH verification in step 4 will catch any real failure */ }
|
||||
|
||||
|
||||
82
tests/bug-2525-sdk-executable-bit.test.cjs
Normal file
82
tests/bug-2525-sdk-executable-bit.test.cjs
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Regression test for bug #2525
|
||||
*
|
||||
* After running the GSD installer on macOS with a Homebrew npm prefix,
|
||||
* `gsd-sdk` is installed but `command -v gsd-sdk` returns nothing because
|
||||
* `dist/cli.js` is installed with mode 644 (no executable bit). tsc emits
|
||||
* .js files as 644, and `npm install -g .` creates the bin symlink without
|
||||
* chmod-ing the target. The kernel then refuses to exec the file.
|
||||
*
|
||||
* Fix: between the `npm run build` step and `npm install -g .`, chmod
|
||||
* dist/cli.js to 0o755. This mirrors the pattern already used for hook
|
||||
* files at lines 5838, 5846, 5959, and 5965.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const INSTALL_PATH = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
|
||||
describe('bug #2525: dist/cli.js chmod 0o755 after tsc build', () => {
|
||||
test('install.js exists', () => {
|
||||
assert.ok(fs.existsSync(INSTALL_PATH), 'bin/install.js should exist');
|
||||
});
|
||||
|
||||
test('chmodSync is called for dist/cli.js after the build step', () => {
|
||||
const content = fs.readFileSync(INSTALL_PATH, 'utf-8');
|
||||
// Find the installSdkIfNeeded function body
|
||||
const fnStart = content.indexOf('function installSdkIfNeeded()');
|
||||
assert.ok(fnStart !== -1, 'installSdkIfNeeded function must exist in bin/install.js');
|
||||
|
||||
// Find the closing brace of the function (next top-level function definition)
|
||||
const nextFnIdx = content.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnEnd = nextFnIdx === -1 ? content.length : nextFnIdx;
|
||||
const fnBody = content.slice(fnStart, fnEnd);
|
||||
|
||||
// Locate the build step
|
||||
const buildStep = fnBody.indexOf("'run', 'build'");
|
||||
assert.ok(buildStep !== -1, "installSdkIfNeeded must contain the 'run', 'build' spawn call");
|
||||
|
||||
// Locate the global install step
|
||||
const globalStep = fnBody.indexOf("'install', '-g', '.'");
|
||||
assert.ok(globalStep !== -1, "installSdkIfNeeded must contain the 'install', '-g', '.' spawn call");
|
||||
|
||||
// Locate chmodSync for dist/cli.js
|
||||
const chmodIdx = fnBody.indexOf("chmodSync");
|
||||
assert.ok(chmodIdx !== -1, "installSdkIfNeeded must call chmodSync to set the executable bit on dist/cli.js");
|
||||
|
||||
// The path may be assembled via path.join(sdkDir, 'dist', 'cli.js') so check
|
||||
// for the component strings rather than the joined slash form.
|
||||
const cliPathIdx = fnBody.indexOf("'cli.js'");
|
||||
assert.ok(cliPathIdx !== -1, "installSdkIfNeeded must reference 'cli.js' (via path.join or literal) for the chmod call");
|
||||
|
||||
// chmodSync must appear AFTER the build step
|
||||
assert.ok(
|
||||
chmodIdx > buildStep,
|
||||
'chmodSync for dist/cli.js must appear AFTER the npm run build step'
|
||||
);
|
||||
|
||||
// chmodSync must appear AFTER the global install step
|
||||
assert.ok(
|
||||
chmodIdx > globalStep,
|
||||
'chmodSync for dist/cli.js must appear AFTER the npm install -g . step'
|
||||
);
|
||||
});
|
||||
|
||||
test('chmod mode is 0o755', () => {
|
||||
const content = fs.readFileSync(INSTALL_PATH, 'utf-8');
|
||||
const fnStart = content.indexOf('function installSdkIfNeeded()');
|
||||
const nextFnIdx = content.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnEnd = nextFnIdx === -1 ? content.length : nextFnIdx;
|
||||
const fnBody = content.slice(fnStart, fnEnd);
|
||||
|
||||
assert.ok(
|
||||
fnBody.includes('0o755'),
|
||||
"chmodSync call in installSdkIfNeeded must use mode 0o755 (not 0o644 or a bare number)"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user