mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-06 17:57:52 +02:00
fix: resolve pnpm in Git Bash pre-push hook
This commit is contained in:
@@ -60,11 +60,21 @@ has_node_script() {
|
||||
node -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); process.exit(p.scripts && p.scripts[process.argv[1]] ? 0 : 1)' "$script_name" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
run_pnpm() {
|
||||
if command -v corepack >/dev/null 2>&1; then
|
||||
corepack pnpm "$@"
|
||||
elif command -v pnpm >/dev/null 2>&1; then
|
||||
pnpm "$@"
|
||||
else
|
||||
fail "pnpm could not be resolved from PATH or Corepack"
|
||||
fi
|
||||
}
|
||||
|
||||
run_node_script() {
|
||||
local pm="$1"
|
||||
local script_name="$2"
|
||||
case "$pm" in
|
||||
pnpm) pnpm run "$script_name" ;;
|
||||
pnpm) run_pnpm run "$script_name" ;;
|
||||
bun) bun run "$script_name" ;;
|
||||
yarn) yarn "$script_name" ;;
|
||||
npm) npm run "$script_name" ;;
|
||||
@@ -90,7 +100,7 @@ if [[ -f "package.json" ]]; then
|
||||
ran_any_check=1
|
||||
log "Running dependency audit (ECC_PREPUSH_AUDIT=1)"
|
||||
case "$pm" in
|
||||
pnpm) pnpm audit --prod || fail "pnpm audit failed" ;;
|
||||
pnpm) run_pnpm audit --prod || fail "pnpm audit failed" ;;
|
||||
bun) bun audit || fail "bun audit failed" ;;
|
||||
yarn) yarn npm audit --recursive || fail "yarn audit failed" ;;
|
||||
npm) npm audit --omit=dev || fail "npm audit failed" ;;
|
||||
|
||||
@@ -11,6 +11,7 @@ const TOML = require('@iarna/toml');
|
||||
|
||||
const repoRoot = path.join(__dirname, '..', '..');
|
||||
const installScript = path.join(repoRoot, 'scripts', 'codex', 'install-global-git-hooks.sh');
|
||||
const prePushHook = path.join(repoRoot, 'scripts', 'codex-git-hooks', 'pre-push');
|
||||
const pluginCacheCheckScript = path.join(repoRoot, 'scripts', 'codex', 'check-plugin-cache.js');
|
||||
const mergeCodexConfigScript = path.join(repoRoot, 'scripts', 'codex', 'merge-codex-config.js');
|
||||
const mergeMcpConfigScript = path.join(repoRoot, 'scripts', 'codex', 'merge-mcp-config.js');
|
||||
@@ -42,18 +43,30 @@ function cleanup(dirPath) {
|
||||
fs.rmSync(dirPath, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function runBash(scriptPath, args = [], env = {}, cwd = repoRoot) {
|
||||
return spawnSync('bash', [scriptPath, ...args], {
|
||||
function runBash(scriptPath, args = [], env = {}, cwd = repoRoot, input = undefined, preservePath = true) {
|
||||
const bash = process.platform === 'win32' && fs.existsSync('C:\\Program Files\\Git\\bin\\bash.exe')
|
||||
? 'C:\\Program Files\\Git\\bin\\bash.exe'
|
||||
: fs.existsSync('/bin/bash')
|
||||
? '/bin/bash'
|
||||
: 'bash';
|
||||
return spawnSync(bash, [scriptPath, ...args], {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
...(preservePath ? process.env : {}),
|
||||
...env,
|
||||
},
|
||||
encoding: 'utf8',
|
||||
input,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
}
|
||||
|
||||
function toBashPath(filePath) {
|
||||
return process.platform === 'win32'
|
||||
? `/${filePath[0].toLowerCase()}${filePath.slice(2).replaceAll('\\', '/')}`
|
||||
: filePath;
|
||||
}
|
||||
|
||||
function runNode(scriptPath, args = [], env = {}, cwd = repoRoot) {
|
||||
return spawnSync('node', [scriptPath, ...args], {
|
||||
cwd,
|
||||
@@ -116,6 +129,116 @@ const cacheManifestWithLocalRefs = {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function makeExecutable(filePath, content) {
|
||||
fs.writeFileSync(filePath, content, { mode: 0o755 });
|
||||
fs.chmodSync(filePath, 0o755);
|
||||
}
|
||||
|
||||
function runHermeticPrePush({ failScript = null, includeCorepack = true, includePnpm = false } = {}) {
|
||||
const tempDir = createTempDir('codex-pre-push-');
|
||||
const binDir = path.join(tempDir, 'bin');
|
||||
const projectDir = path.join(tempDir, 'project');
|
||||
const callsPath = path.join(tempDir, 'calls.txt');
|
||||
const bashEnv = path.join(tempDir, 'bash-env');
|
||||
fs.mkdirSync(binDir);
|
||||
fs.mkdirSync(projectDir);
|
||||
const functionStub = (name, corepack) => `${name}() {
|
||||
printf '%s\\n' "${corepack ? '' : 'pnpm '}$*" >> "${toBashPath(callsPath)}"
|
||||
${corepack ? 'shift' : ':'}
|
||||
shift
|
||||
test "$1" != "${failScript || '__never__'}"
|
||||
}`;
|
||||
fs.writeFileSync(
|
||||
bashEnv,
|
||||
`git() { return 0; }
|
||||
node() { "${toBashPath(process.execPath)}" "$@"; }
|
||||
${includeCorepack ? functionStub('corepack', true) : ''}
|
||||
${includePnpm ? functionStub('pnpm', false) : ''}
|
||||
`,
|
||||
);
|
||||
fs.writeFileSync(path.join(projectDir, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n');
|
||||
const initialized = spawnSync('git', ['init', '--quiet'], { cwd: projectDir });
|
||||
assert.strictEqual(initialized.status, 0, initialized.stderr?.toString());
|
||||
writeJson(path.join(projectDir, 'package.json'), {
|
||||
packageManager: 'pnpm@11.9.0',
|
||||
scripts: { lint: 'x', typecheck: 'x', test: 'x', build: 'x' },
|
||||
});
|
||||
const result = runBash(
|
||||
prePushHook,
|
||||
[],
|
||||
{
|
||||
PATH: toBashPath(binDir),
|
||||
BASH_ENV: toBashPath(bashEnv),
|
||||
ECC_PREPUSH_AUDIT: '0',
|
||||
ECC_SKIP_GIT_HOOKS: '0',
|
||||
ECC_SKIP_PREPUSH: '0',
|
||||
MSYS_NO_PATHCONV: '1',
|
||||
},
|
||||
projectDir,
|
||||
Buffer.from('refs/heads/main 1111111111111111111111111111111111111111 refs/heads/main 0000000000000000000000000000000000000000\n'),
|
||||
false,
|
||||
);
|
||||
const calls = fs.existsSync(callsPath)
|
||||
? fs.readFileSync(callsPath, 'utf8').trim().split(/\r?\n/)
|
||||
: [];
|
||||
cleanup(tempDir);
|
||||
return { result, calls };
|
||||
}
|
||||
|
||||
if (
|
||||
test('pre-push uses Corepack pinned pnpm and runs every required verification script', () => {
|
||||
const { result, calls } = runHermeticPrePush();
|
||||
assert.strictEqual(result.status, 0, JSON.stringify(result, null, 2));
|
||||
assert.deepStrictEqual(calls, [
|
||||
'pnpm run lint',
|
||||
'pnpm run typecheck',
|
||||
'pnpm run test',
|
||||
'pnpm run build',
|
||||
], JSON.stringify(result, null, 2));
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('pre-push falls back to direct pnpm when Corepack is absent', () => {
|
||||
const { result, calls } = runHermeticPrePush({
|
||||
includeCorepack: false,
|
||||
includePnpm: true,
|
||||
});
|
||||
assert.strictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
||||
assert.deepStrictEqual(calls, [
|
||||
'pnpm run lint',
|
||||
'pnpm run typecheck',
|
||||
'pnpm run test',
|
||||
'pnpm run build',
|
||||
]);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('pre-push fails closed when pnpm and Corepack cannot resolve', () => {
|
||||
const { result } = runHermeticPrePush({ includeCorepack: false });
|
||||
assert.notStrictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
||||
assert.match(result.stderr, /pnpm.*(?:resolve|found)/i);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('pre-push stops immediately when a required verification script fails', () => {
|
||||
const { result, calls } = runHermeticPrePush({ failScript: 'typecheck' });
|
||||
assert.notStrictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
|
||||
assert.deepStrictEqual(calls, ['pnpm run lint', 'pnpm run typecheck']);
|
||||
assert.match(result.stderr, /typecheck failed/);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('check-plugin-cache fails when the installed cache is missing manifest-referenced files', () => {
|
||||
const homeDir = createTempDir('codex-plugin-cache-home-');
|
||||
|
||||
Reference in New Issue
Block a user