Skip to content

Commit 9932f49

Browse files
authored
Merge pull request #388 from just-every/issue-369-postinstall
fix(cli/postinstall): rerun bootstrap when scripts skipped (#369)
2 parents b981bab + 8d842b8 commit 9932f49

3 files changed

Lines changed: 62 additions & 13 deletions

File tree

codex-cli/bin/coder.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { fileURLToPath } from "url";
66
import { platform as nodePlatform, arch as nodeArch } from "os";
77
import { execSync } from "child_process";
88
import { get as httpsGet } from "https";
9+
import { runPostinstall } from "../postinstall.js";
910

1011
// __dirname equivalent in ESM
1112
const __filename = fileURLToPath(import.meta.url);
@@ -270,12 +271,26 @@ const tryBootstrapBinary = async () => {
270271
};
271272

272273
// If missing, attempt to bootstrap into place (helps when Bun blocks postinstall)
273-
if (!existsSync(binaryPath) && !existsSync(legacyBinaryPath)) {
274-
const ok = await tryBootstrapBinary();
275-
if (!ok) {
276-
// retry legacy name in case archive provided coder-*
277-
if (existsSync(legacyBinaryPath) && !existsSync(binaryPath)) {
278-
binaryPath = legacyBinaryPath;
274+
let binaryReady = existsSync(binaryPath) || existsSync(legacyBinaryPath);
275+
if (!binaryReady) {
276+
let runtimePostinstallError = null;
277+
try {
278+
await runPostinstall({ invokedByRuntime: true, skipGlobalAlias: true });
279+
} catch (err) {
280+
runtimePostinstallError = err;
281+
}
282+
283+
binaryReady = existsSync(binaryPath) || existsSync(legacyBinaryPath);
284+
if (!binaryReady) {
285+
const ok = await tryBootstrapBinary();
286+
if (!ok) {
287+
if (runtimePostinstallError && !lastBootstrapError) {
288+
lastBootstrapError = runtimePostinstallError;
289+
}
290+
// retry legacy name in case archive provided coder-*
291+
if (existsSync(legacyBinaryPath) && !existsSync(binaryPath)) {
292+
binaryPath = legacyBinaryPath;
293+
}
279294
}
280295
}
281296
}

codex-cli/postinstall.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Non-functional change to trigger release workflow
33

44
import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync, unlinkSync, statSync, openSync, closeSync, copyFileSync, fsyncSync, renameSync, realpathSync } from 'fs';
5-
import { join, dirname } from 'path';
5+
import { join, dirname, resolve } from 'path';
66
import { fileURLToPath } from 'url';
77
import { get } from 'https';
88
import { platform, arch, tmpdir } from 'os';
@@ -288,13 +288,21 @@ function validateDownloadedBinary(p) {
288288
}
289289
}
290290

291-
async function main() {
291+
export async function runPostinstall(options = {}) {
292+
const { skipGlobalAlias = false, invokedByRuntime = false } = options;
293+
if (process.env.CODE_POSTINSTALL_DRY_RUN === '1') {
294+
return { skipped: true };
295+
}
296+
297+
if (invokedByRuntime) {
298+
process.env.CODE_RUNTIME_POSTINSTALL = process.env.CODE_RUNTIME_POSTINSTALL || '1';
299+
}
292300
// Detect potential PATH conflict with an existing `code` command (e.g., VS Code)
293301
// Only relevant for global installs; skip for npx/local installs to keep postinstall fast.
294302
const ua = process.env.npm_config_user_agent || '';
295303
const isNpx = ua.includes('npx');
296304
const isGlobal = process.env.npm_config_global === 'true';
297-
if (isGlobal && !isNpx) {
305+
if (!skipGlobalAlias && isGlobal && !isNpx) {
298306
try {
299307
const whichCmd = process.platform === 'win32' ? 'where code' : 'command -v code || which code || true';
300308
const resolved = execSync(whichCmd, { stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform !== 'win32' }).toString().split(/\r?\n/).filter(Boolean)[0];
@@ -760,7 +768,19 @@ async function main() {
760768
}
761769
}
762770

763-
main().catch(error => {
764-
console.error('Installation failed:', error);
765-
process.exit(1);
766-
});
771+
function isExecutedDirectly() {
772+
const entry = process.argv[1];
773+
if (!entry) return false;
774+
try {
775+
return resolve(entry) === fileURLToPath(import.meta.url);
776+
} catch {
777+
return false;
778+
}
779+
}
780+
781+
if (isExecutedDirectly()) {
782+
runPostinstall().catch(error => {
783+
console.error('Installation failed:', error);
784+
process.exit(1);
785+
});
786+
}

codex-cli/test/postinstall.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { test } from 'node:test';
2+
import assert from 'node:assert/strict';
3+
4+
test('runPostinstall resolves in dry-run mode', async () => {
5+
const { runPostinstall } = await import('../postinstall.js');
6+
process.env.CODE_POSTINSTALL_DRY_RUN = '1';
7+
try {
8+
const result = await runPostinstall({ invokedByRuntime: true, skipGlobalAlias: true });
9+
assert.ok(result && result.skipped === true);
10+
} finally {
11+
delete process.env.CODE_POSTINSTALL_DRY_RUN;
12+
delete process.env.CODE_RUNTIME_POSTINSTALL;
13+
}
14+
});

0 commit comments

Comments
 (0)