@lavilas/codex 1.3.55 → 1.3.61

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.
package/bin/codex.js CHANGED
@@ -2,69 +2,31 @@
2
2
  // Unified entry point for the Codex CLI.
3
3
 
4
4
  import { spawn } from "node:child_process";
5
- import { chmodSync, existsSync, readdirSync, statSync } from "fs";
5
+ import { chmodSync, existsSync, readFileSync, readdirSync, statSync } from "fs";
6
6
  import { createRequire } from "node:module";
7
7
  import path from "path";
8
8
  import { fileURLToPath } from "url";
9
+ import {
10
+ PLATFORM_PACKAGE_BY_TARGET,
11
+ detectPackageManager,
12
+ getCodexBinaryName,
13
+ resolveTargetTriple,
14
+ resolveRuntimeCacheRoot,
15
+ selectVendorInstallation,
16
+ updateCommandForPackageManager,
17
+ } from "./platform-resolver.js";
9
18
 
10
19
  // __dirname equivalent in ESM
11
20
  const __filename = fileURLToPath(import.meta.url);
12
21
  const __dirname = path.dirname(__filename);
22
+ const packageDir = path.join(__dirname, "..");
13
23
  const require = createRequire(import.meta.url);
14
-
15
- const PLATFORM_PACKAGE_BY_TARGET = {
16
- "x86_64-unknown-linux-musl": "@lavilas/codex-linux-x64",
17
- "aarch64-unknown-linux-musl": "@lavilas/codex-linux-arm64",
18
- "x86_64-apple-darwin": "@lavilas/codex-darwin-x64",
19
- "aarch64-apple-darwin": "@lavilas/codex-darwin-arm64",
20
- "x86_64-pc-windows-msvc": "@lavilas/codex-win32-x64",
21
- "aarch64-pc-windows-msvc": "@lavilas/codex-win32-arm64",
22
- };
24
+ const rootPackageJson = JSON.parse(
25
+ readFileSync(path.join(packageDir, "package.json"), "utf8"),
26
+ );
23
27
 
24
28
  const { platform, arch } = process;
25
-
26
- let targetTriple = null;
27
- switch (platform) {
28
- case "linux":
29
- case "android":
30
- switch (arch) {
31
- case "x64":
32
- targetTriple = "x86_64-unknown-linux-musl";
33
- break;
34
- case "arm64":
35
- targetTriple = "aarch64-unknown-linux-musl";
36
- break;
37
- default:
38
- break;
39
- }
40
- break;
41
- case "darwin":
42
- switch (arch) {
43
- case "x64":
44
- targetTriple = "x86_64-apple-darwin";
45
- break;
46
- case "arm64":
47
- targetTriple = "aarch64-apple-darwin";
48
- break;
49
- default:
50
- break;
51
- }
52
- break;
53
- case "win32":
54
- switch (arch) {
55
- case "x64":
56
- targetTriple = "x86_64-pc-windows-msvc";
57
- break;
58
- case "arm64":
59
- targetTriple = "aarch64-pc-windows-msvc";
60
- break;
61
- default:
62
- break;
63
- }
64
- break;
65
- default:
66
- break;
67
- }
29
+ const targetTriple = resolveTargetTriple(platform, arch);
68
30
 
69
31
  if (!targetTriple) {
70
32
  throw new Error(`Unsupported platform: ${platform} (${arch})`);
@@ -75,47 +37,29 @@ if (!platformPackage) {
75
37
  throw new Error(`Unsupported target triple: ${targetTriple}`);
76
38
  }
77
39
 
78
- const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
79
- const localVendorRoot = path.join(__dirname, "..", "vendor");
80
- const localBinaryPath = path.join(
81
- localVendorRoot,
82
- targetTriple,
83
- "codex",
84
- codexBinaryName,
85
- );
40
+ const codexBinaryName = getCodexBinaryName(process.platform);
41
+ const localVendorRoot = path.join(packageDir, "vendor");
86
42
 
87
- let vendorRoot;
88
- try {
89
- const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
90
- vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
91
- } catch {
92
- if (existsSync(localBinaryPath)) {
93
- vendorRoot = localVendorRoot;
94
- } else {
95
- const packageManager = detectPackageManager();
96
- const updateCommand =
97
- packageManager === "bun"
98
- ? "bun install -g @lavilas/codex@latest"
99
- : "npm install -g @lavilas/codex@latest";
100
- throw new Error(
101
- `Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
102
- );
103
- }
104
- }
43
+ const selectedInstallation = selectVendorInstallation({
44
+ packageDir,
45
+ platformPackage,
46
+ targetTriple,
47
+ binaryName: codexBinaryName,
48
+ packageVersion: rootPackageJson.version ?? null,
49
+ runtimeCacheRoot: resolveRuntimeCacheRoot(),
50
+ localVendorRoot,
51
+ requireResolve: (specifier) => require.resolve(specifier),
52
+ });
105
53
 
106
- if (!vendorRoot) {
107
- const packageManager = detectPackageManager();
108
- const updateCommand =
109
- packageManager === "bun"
110
- ? "bun install -g @lavilas/codex@latest"
111
- : "npm install -g @lavilas/codex@latest";
54
+ if (!selectedInstallation) {
55
+ const packageManager = detectPackageManager({ installDir: __dirname });
56
+ const updateCommand = updateCommandForPackageManager(packageManager);
112
57
  throw new Error(
113
58
  `Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
114
59
  );
115
60
  }
116
61
 
117
- const archRoot = path.join(vendorRoot, targetTriple);
118
- const binaryPath = path.join(archRoot, "codex", codexBinaryName);
62
+ const binaryPath = selectedInstallation.binaryPath;
119
63
 
120
64
  // Use an asynchronous spawn instead of spawnSync so that Node is able to
121
65
  // respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
@@ -133,33 +77,8 @@ function getUpdatedPath(newDirs) {
133
77
  return updatedPath;
134
78
  }
135
79
 
136
- /**
137
- * Use heuristics to detect the package manager that was used to install Codex
138
- * in order to give the user a hint about how to update it.
139
- */
140
- function detectPackageManager() {
141
- const userAgent = process.env.npm_config_user_agent || "";
142
- if (/\bbun\//.test(userAgent)) {
143
- return "bun";
144
- }
145
-
146
- const execPath = process.env.npm_execpath || "";
147
- if (execPath.includes("bun")) {
148
- return "bun";
149
- }
150
-
151
- if (
152
- __dirname.includes(".bun/install/global") ||
153
- __dirname.includes(".bun\\install\\global")
154
- ) {
155
- return "bun";
156
- }
157
-
158
- return userAgent ? "npm" : null;
159
- }
160
-
161
80
  const additionalDirs = [];
162
- const pathDir = path.join(archRoot, "path");
81
+ const pathDir = selectedInstallation.pathDir;
163
82
  if (existsSync(pathDir)) {
164
83
  additionalDirs.push(pathDir);
165
84
  }
@@ -167,7 +86,7 @@ const updatedPath = getUpdatedPath(additionalDirs);
167
86
 
168
87
  const env = { ...process.env, PATH: updatedPath };
169
88
  const packageManagerEnvVar =
170
- detectPackageManager() === "bun"
89
+ detectPackageManager({ installDir: __dirname }) === "bun"
171
90
  ? "CODEX_MANAGED_BY_BUN"
172
91
  : "CODEX_MANAGED_BY_NPM";
173
92
  env[packageManagerEnvVar] = "1";
@@ -0,0 +1,680 @@
1
+ import {
2
+ chmodSync,
3
+ copyFileSync,
4
+ existsSync,
5
+ lstatSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ realpathSync,
10
+ renameSync,
11
+ rmSync,
12
+ statSync,
13
+ writeFileSync,
14
+ } from "node:fs";
15
+ import { createHash } from "node:crypto";
16
+ import os from "node:os";
17
+ import path from "node:path";
18
+
19
+ export const PLATFORM_PACKAGE_BY_TARGET = {
20
+ "x86_64-unknown-linux-musl": "@lavilas/codex-linux-x64",
21
+ "aarch64-unknown-linux-musl": "@lavilas/codex-linux-arm64",
22
+ "x86_64-apple-darwin": "@lavilas/codex-darwin-x64",
23
+ "aarch64-apple-darwin": "@lavilas/codex-darwin-arm64",
24
+ "x86_64-pc-windows-msvc": "@lavilas/codex-win32-x64",
25
+ "aarch64-pc-windows-msvc": "@lavilas/codex-win32-arm64",
26
+ };
27
+
28
+ export function resolveTargetTriple(
29
+ platformName = process.platform,
30
+ archName = process.arch,
31
+ ) {
32
+ switch (platformName) {
33
+ case "linux":
34
+ case "android":
35
+ switch (archName) {
36
+ case "x64":
37
+ return "x86_64-unknown-linux-musl";
38
+ case "arm64":
39
+ return "aarch64-unknown-linux-musl";
40
+ default:
41
+ return null;
42
+ }
43
+ case "darwin":
44
+ switch (archName) {
45
+ case "x64":
46
+ return "x86_64-apple-darwin";
47
+ case "arm64":
48
+ return "aarch64-apple-darwin";
49
+ default:
50
+ return null;
51
+ }
52
+ case "win32":
53
+ switch (archName) {
54
+ case "x64":
55
+ return "x86_64-pc-windows-msvc";
56
+ case "arm64":
57
+ return "aarch64-pc-windows-msvc";
58
+ default:
59
+ return null;
60
+ }
61
+ default:
62
+ return null;
63
+ }
64
+ }
65
+
66
+ export function getCodexBinaryName(platformName = process.platform) {
67
+ return platformName === "win32" ? "codex.exe" : "codex";
68
+ }
69
+
70
+ export function detectPackageManager(options = {}) {
71
+ const userAgent = options.userAgent ?? process.env.npm_config_user_agent ?? "";
72
+ if (/\bbun\//.test(userAgent)) {
73
+ return "bun";
74
+ }
75
+
76
+ const execPath = options.execPath ?? process.env.npm_execpath ?? "";
77
+ if (execPath.includes("bun")) {
78
+ return "bun";
79
+ }
80
+
81
+ const installDir = options.installDir ?? "";
82
+ if (
83
+ installDir.includes(".bun/install/global") ||
84
+ installDir.includes(".bun\\install\\global")
85
+ ) {
86
+ return "bun";
87
+ }
88
+
89
+ return userAgent ? "npm" : null;
90
+ }
91
+
92
+ export function updateCommandForPackageManager(packageManager) {
93
+ return packageManager === "bun"
94
+ ? "bun install -g @lavilas/codex@latest"
95
+ : "npm install -g @lavilas/codex@latest";
96
+ }
97
+
98
+ function packageNameSegments(packageName) {
99
+ return packageName.split("/");
100
+ }
101
+
102
+ function packageInstallDir(packageDir, platformPackage) {
103
+ return path.join(packageDir, "node_modules", ...packageNameSegments(platformPackage));
104
+ }
105
+
106
+ function packageCacheBaseDir(runtimeCacheRoot, platformPackage) {
107
+ if (!runtimeCacheRoot) {
108
+ return null;
109
+ }
110
+ return path.join(runtimeCacheRoot, ...packageNameSegments(platformPackage));
111
+ }
112
+
113
+ function packageCacheDir(
114
+ runtimeCacheRoot,
115
+ platformPackage,
116
+ packageVersion,
117
+ manifestDigest = null,
118
+ ) {
119
+ if (!runtimeCacheRoot || !packageVersion) {
120
+ return null;
121
+ }
122
+ const cacheBaseDir = packageCacheBaseDir(runtimeCacheRoot, platformPackage);
123
+ if (!cacheBaseDir) {
124
+ return null;
125
+ }
126
+ const cacheKey = manifestDigest
127
+ ? `${packageVersion}-${manifestDigest.slice(0, 12)}`
128
+ : packageVersion;
129
+ return path.join(cacheBaseDir, cacheKey);
130
+ }
131
+
132
+ export function resolveRuntimeCacheRoot({
133
+ env = process.env,
134
+ homeDir = (() => {
135
+ try {
136
+ return os.homedir();
137
+ } catch {
138
+ return null;
139
+ }
140
+ })(),
141
+ platformName = process.platform,
142
+ } = {}) {
143
+ const explicitCacheDir =
144
+ env.LAVILAS_CODEX_VENDOR_CACHE_DIR ?? env.CODEX_VENDOR_CACHE_DIR ?? null;
145
+ if (explicitCacheDir) {
146
+ return path.resolve(explicitCacheDir);
147
+ }
148
+
149
+ const codexHome =
150
+ env.CODEX_HOME ?? (homeDir ? path.join(homeDir, ".codex") : null);
151
+ if (codexHome) {
152
+ return path.join(codexHome, "runtime", "npm");
153
+ }
154
+
155
+ if (!homeDir) {
156
+ return null;
157
+ }
158
+
159
+ switch (platformName) {
160
+ case "win32": {
161
+ const localAppData = env.LOCALAPPDATA || path.join(homeDir, "AppData", "Local");
162
+ return path.join(localAppData, "Lavilas", "Codex", "runtime", "npm");
163
+ }
164
+ case "darwin":
165
+ return path.join(homeDir, "Library", "Caches", "Lavilas", "Codex", "runtime", "npm");
166
+ default: {
167
+ const xdgCacheHome = env.XDG_CACHE_HOME || path.join(homeDir, ".cache");
168
+ return path.join(xdgCacheHome, "lavilas-codex", "runtime", "npm");
169
+ }
170
+ }
171
+ }
172
+
173
+ function readJsonFile(filePath) {
174
+ try {
175
+ if (!existsSync(filePath)) {
176
+ return null;
177
+ }
178
+ return JSON.parse(readFileSync(filePath, "utf8"));
179
+ } catch {
180
+ return null;
181
+ }
182
+ }
183
+
184
+ function vendorManifestFor(vendorRoot) {
185
+ return readJsonFile(path.join(vendorRoot, "manifest.json"));
186
+ }
187
+
188
+ function vendorManifestDigest(vendorRoot) {
189
+ try {
190
+ const manifestContents = readFileSync(path.join(vendorRoot, "manifest.json"));
191
+ return createHash("sha256").update(manifestContents).digest("hex");
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ function readyMarkerPathFor(vendorRoot) {
198
+ return path.join(path.dirname(vendorRoot), ".ready.json");
199
+ }
200
+
201
+ function readyMarkerFor(vendorRoot) {
202
+ return readJsonFile(readyMarkerPathFor(vendorRoot));
203
+ }
204
+
205
+ function validateVendorManifest(manifest, vendorRoot) {
206
+ if (!manifest || typeof manifest !== "object") {
207
+ return { valid: false, reason: "missing manifest.json" };
208
+ }
209
+
210
+ const files = manifest.files;
211
+ if (!files || typeof files !== "object") {
212
+ return { valid: false, reason: "invalid manifest.json files map" };
213
+ }
214
+
215
+ for (const [relativePath, metadata] of Object.entries(files)) {
216
+ const candidatePath = path.join(vendorRoot, relativePath);
217
+ if (!existsSync(candidatePath)) {
218
+ return { valid: false, reason: `missing ${relativePath}` };
219
+ }
220
+
221
+ let candidateStat;
222
+ try {
223
+ candidateStat = statSync(candidatePath);
224
+ } catch {
225
+ return { valid: false, reason: `unable to stat ${relativePath}` };
226
+ }
227
+
228
+ if (!candidateStat.isFile() || candidateStat.size <= 0) {
229
+ return { valid: false, reason: `invalid ${relativePath}` };
230
+ }
231
+
232
+ if (
233
+ metadata &&
234
+ typeof metadata === "object" &&
235
+ typeof metadata.size === "number" &&
236
+ metadata.size !== candidateStat.size
237
+ ) {
238
+ return { valid: false, reason: `size mismatch for ${relativePath}` };
239
+ }
240
+ }
241
+
242
+ return { valid: true };
243
+ }
244
+
245
+ function validateVendorRoot(
246
+ candidate,
247
+ targetTriple,
248
+ binaryName,
249
+ { requireReadyMarker = false } = {},
250
+ ) {
251
+ const manifest = vendorManifestFor(candidate.vendorRoot);
252
+ const manifestValidation = validateVendorManifest(manifest, candidate.vendorRoot);
253
+ if (!manifestValidation.valid) {
254
+ return manifestValidation;
255
+ }
256
+
257
+ if (requireReadyMarker) {
258
+ const readyMarker = readyMarkerFor(candidate.vendorRoot);
259
+ if (!readyMarker || typeof readyMarker !== "object") {
260
+ return { valid: false, reason: "missing .ready.json" };
261
+ }
262
+ if (
263
+ candidate.cacheKey &&
264
+ typeof readyMarker.cacheKey === "string" &&
265
+ readyMarker.cacheKey !== candidate.cacheKey
266
+ ) {
267
+ return { valid: false, reason: "runtime cache marker mismatch" };
268
+ }
269
+ }
270
+
271
+ const binaryRelativePath = `${targetTriple}/codex/${binaryName}`;
272
+ const binaryPath = path.join(candidate.vendorRoot, binaryRelativePath);
273
+ if (!existsSync(binaryPath)) {
274
+ return { valid: false, reason: `missing ${binaryRelativePath}` };
275
+ }
276
+
277
+ let binaryStat;
278
+ try {
279
+ binaryStat = statSync(binaryPath);
280
+ } catch {
281
+ return { valid: false, reason: `unable to stat ${binaryRelativePath}` };
282
+ }
283
+ if (!binaryStat.isFile() || binaryStat.size <= 0) {
284
+ return { valid: false, reason: `invalid ${binaryRelativePath}` };
285
+ }
286
+
287
+ const expectedBinary = manifest?.files?.[binaryRelativePath];
288
+ if (
289
+ expectedBinary &&
290
+ typeof expectedBinary.size === "number" &&
291
+ expectedBinary.size !== binaryStat.size
292
+ ) {
293
+ return {
294
+ valid: false,
295
+ reason: `size mismatch for ${binaryRelativePath}`,
296
+ };
297
+ }
298
+
299
+ return {
300
+ valid: true,
301
+ binaryPath,
302
+ pathDir: path.join(candidate.vendorRoot, targetTriple, "path"),
303
+ vendorRoot: candidate.vendorRoot,
304
+ source: candidate.source,
305
+ };
306
+ }
307
+
308
+ function pushCandidate(candidates, seen, vendorRoot, source) {
309
+ if (!existsSync(vendorRoot)) {
310
+ return;
311
+ }
312
+ const resolvedRoot = path.resolve(vendorRoot);
313
+ if (seen.has(resolvedRoot)) {
314
+ return;
315
+ }
316
+ seen.add(resolvedRoot);
317
+ candidates.push({ vendorRoot: resolvedRoot, source });
318
+ }
319
+
320
+ function copyDirectoryRecursive(sourceDir, destinationDir) {
321
+ mkdirSync(destinationDir, { recursive: true });
322
+
323
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
324
+ const sourcePath = path.join(sourceDir, entry.name);
325
+ const destinationPath = path.join(destinationDir, entry.name);
326
+
327
+ if (entry.isDirectory()) {
328
+ copyDirectoryRecursive(sourcePath, destinationPath);
329
+ continue;
330
+ }
331
+
332
+ if (entry.isSymbolicLink()) {
333
+ const resolvedPath = realpathSync(sourcePath);
334
+ const resolvedStat = statSync(resolvedPath);
335
+ if (resolvedStat.isDirectory()) {
336
+ copyDirectoryRecursive(resolvedPath, destinationPath);
337
+ } else {
338
+ copyFileSync(resolvedPath, destinationPath);
339
+ chmodSync(destinationPath, resolvedStat.mode);
340
+ }
341
+ continue;
342
+ }
343
+
344
+ const sourceStat = lstatSync(sourcePath);
345
+ if (sourceStat.isDirectory()) {
346
+ copyDirectoryRecursive(sourcePath, destinationPath);
347
+ continue;
348
+ }
349
+
350
+ copyFileSync(sourcePath, destinationPath);
351
+ chmodSync(destinationPath, sourceStat.mode);
352
+ }
353
+ }
354
+
355
+ function runtimeCacheCandidate({
356
+ runtimeCacheRoot,
357
+ platformPackage,
358
+ packageVersion,
359
+ manifestDigest = null,
360
+ source = "runtime-cache",
361
+ }) {
362
+ const cacheDir = packageCacheDir(
363
+ runtimeCacheRoot,
364
+ platformPackage,
365
+ packageVersion,
366
+ manifestDigest,
367
+ );
368
+ if (!cacheDir) {
369
+ return null;
370
+ }
371
+ return {
372
+ vendorRoot: path.join(cacheDir, "vendor"),
373
+ cacheKey: path.basename(cacheDir),
374
+ source,
375
+ };
376
+ }
377
+
378
+ function collectRuntimeCacheCandidates({
379
+ runtimeCacheRoot,
380
+ platformPackage,
381
+ packageVersion,
382
+ }) {
383
+ const cacheBaseDir = packageCacheBaseDir(runtimeCacheRoot, platformPackage);
384
+ if (!cacheBaseDir || !packageVersion || !existsSync(cacheBaseDir)) {
385
+ return [];
386
+ }
387
+
388
+ const versionPrefix = `${packageVersion}-`;
389
+ return readdirSync(cacheBaseDir, { withFileTypes: true })
390
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith(versionPrefix))
391
+ .map((entry) => ({
392
+ vendorRoot: path.join(cacheBaseDir, entry.name, "vendor"),
393
+ cacheKey: entry.name,
394
+ source: "runtime-cache",
395
+ }));
396
+ }
397
+
398
+ function materializeRuntimeCache({
399
+ runtimeCacheRoot,
400
+ platformPackage,
401
+ packageVersion,
402
+ targetTriple,
403
+ binaryName,
404
+ sourceInstallation,
405
+ }) {
406
+ const manifestDigest = vendorManifestDigest(sourceInstallation.vendorRoot);
407
+ const cacheDir = packageCacheDir(
408
+ runtimeCacheRoot,
409
+ platformPackage,
410
+ packageVersion,
411
+ manifestDigest,
412
+ );
413
+ if (!cacheDir || !manifestDigest) {
414
+ return null;
415
+ }
416
+
417
+ const cachedCandidate = runtimeCacheCandidate({
418
+ runtimeCacheRoot,
419
+ platformPackage,
420
+ packageVersion,
421
+ manifestDigest,
422
+ source: `runtime-cache:${sourceInstallation.source}`,
423
+ });
424
+ if (!cachedCandidate) {
425
+ return null;
426
+ }
427
+
428
+ const cachedInstallation = validateVendorRoot(
429
+ cachedCandidate,
430
+ targetTriple,
431
+ binaryName,
432
+ { requireReadyMarker: true },
433
+ );
434
+ if (cachedInstallation.valid) {
435
+ return cachedInstallation;
436
+ }
437
+
438
+ const cacheParentDir = path.dirname(cacheDir);
439
+ mkdirSync(cacheParentDir, { recursive: true });
440
+
441
+ const tempCacheDir = path.join(
442
+ cacheParentDir,
443
+ `.tmp-${packageVersion}-${process.pid}-${Date.now()}-${Math.random()
444
+ .toString(16)
445
+ .slice(2)}`,
446
+ );
447
+
448
+ try {
449
+ copyDirectoryRecursive(sourceInstallation.vendorRoot, path.join(tempCacheDir, "vendor"));
450
+
451
+ const stagedInstallation = validateVendorRoot(
452
+ {
453
+ vendorRoot: path.join(tempCacheDir, "vendor"),
454
+ cacheKey: path.basename(cacheDir),
455
+ source: `runtime-cache-staging:${sourceInstallation.source}`,
456
+ },
457
+ targetTriple,
458
+ binaryName,
459
+ );
460
+ if (!stagedInstallation.valid) {
461
+ return null;
462
+ }
463
+
464
+ const readyMarker = {
465
+ cacheKey: path.basename(cacheDir),
466
+ platformPackage,
467
+ packageVersion,
468
+ targetTriple,
469
+ manifestSha256: manifestDigest,
470
+ createdAt: new Date().toISOString(),
471
+ };
472
+ writeFileSync(
473
+ readyMarkerPathFor(path.join(tempCacheDir, "vendor")),
474
+ `${JSON.stringify(readyMarker, null, 2)}\n`,
475
+ );
476
+
477
+ try {
478
+ renameSync(tempCacheDir, cacheDir);
479
+ } catch {
480
+ const currentInstallation = validateVendorRoot(
481
+ cachedCandidate,
482
+ targetTriple,
483
+ binaryName,
484
+ { requireReadyMarker: true },
485
+ );
486
+ if (currentInstallation.valid) {
487
+ return currentInstallation;
488
+ }
489
+
490
+ const currentReadyMarker = readyMarkerFor(cachedCandidate.vendorRoot);
491
+ if (!currentReadyMarker && existsSync(cacheDir)) {
492
+ rmSync(cacheDir, { recursive: true, force: true });
493
+ renameSync(tempCacheDir, cacheDir);
494
+ } else {
495
+ return null;
496
+ }
497
+ }
498
+
499
+ const finalInstallation = validateVendorRoot(
500
+ cachedCandidate,
501
+ targetTriple,
502
+ binaryName,
503
+ { requireReadyMarker: true },
504
+ );
505
+ return finalInstallation.valid ? finalInstallation : null;
506
+ } catch {
507
+ return null;
508
+ } finally {
509
+ rmSync(tempCacheDir, { recursive: true, force: true });
510
+ }
511
+ }
512
+
513
+ export function collectVendorCandidates({
514
+ packageDir,
515
+ platformPackage,
516
+ localVendorRoot = path.join(packageDir, "vendor"),
517
+ requireResolve = null,
518
+ }) {
519
+ const candidates = [];
520
+ const seen = new Set();
521
+
522
+ if (typeof requireResolve === "function") {
523
+ try {
524
+ const packageJsonPath = requireResolve(`${platformPackage}/package.json`);
525
+ pushCandidate(
526
+ candidates,
527
+ seen,
528
+ path.join(path.dirname(packageJsonPath), "vendor"),
529
+ "resolved-platform-package",
530
+ );
531
+ } catch {
532
+ // Ignore and continue with direct filesystem fallbacks.
533
+ }
534
+ }
535
+
536
+ pushCandidate(
537
+ candidates,
538
+ seen,
539
+ path.join(packageInstallDir(packageDir, platformPackage), "vendor"),
540
+ "nested-platform-package",
541
+ );
542
+
543
+ const scopeDir = path.resolve(packageDir, "..");
544
+ if (existsSync(scopeDir)) {
545
+ for (const entry of readdirSync(scopeDir, { withFileTypes: true })) {
546
+ if (!entry.isDirectory() || !entry.name.startsWith(".codex-")) {
547
+ continue;
548
+ }
549
+ pushCandidate(
550
+ candidates,
551
+ seen,
552
+ path.join(scopeDir, entry.name, "node_modules", ...packageNameSegments(platformPackage), "vendor"),
553
+ `staged-platform-package:${entry.name}`,
554
+ );
555
+ }
556
+ }
557
+
558
+ pushCandidate(candidates, seen, localVendorRoot, "local-vendor-root");
559
+
560
+ return candidates;
561
+ }
562
+
563
+ export function selectVendorInstallation({
564
+ packageDir,
565
+ platformPackage,
566
+ targetTriple,
567
+ binaryName,
568
+ packageVersion = null,
569
+ runtimeCacheRoot = null,
570
+ localVendorRoot = path.join(packageDir, "vendor"),
571
+ requireResolve = null,
572
+ }) {
573
+ const candidates = collectVendorCandidates({
574
+ packageDir,
575
+ platformPackage,
576
+ localVendorRoot,
577
+ requireResolve,
578
+ });
579
+ for (const candidate of candidates) {
580
+ const result = validateVendorRoot(candidate, targetTriple, binaryName);
581
+ if (result.valid) {
582
+ return (
583
+ materializeRuntimeCache({
584
+ runtimeCacheRoot,
585
+ platformPackage,
586
+ packageVersion,
587
+ targetTriple,
588
+ binaryName,
589
+ sourceInstallation: result,
590
+ }) ?? result
591
+ );
592
+ }
593
+ }
594
+
595
+ for (const cachedCandidate of collectRuntimeCacheCandidates({
596
+ runtimeCacheRoot,
597
+ platformPackage,
598
+ packageVersion,
599
+ })) {
600
+ const cachedInstallation = validateVendorRoot(
601
+ cachedCandidate,
602
+ targetTriple,
603
+ binaryName,
604
+ { requireReadyMarker: true },
605
+ );
606
+ if (cachedInstallation.valid) {
607
+ return cachedInstallation;
608
+ }
609
+ }
610
+
611
+ return null;
612
+ }
613
+
614
+ export function buildPlatformPackageMetadata({
615
+ platformPackage,
616
+ version,
617
+ license = "Apache-2.0",
618
+ repository = null,
619
+ packageManager = null,
620
+ }) {
621
+ const metadata = {
622
+ name: platformPackage,
623
+ version,
624
+ license,
625
+ files: ["vendor"],
626
+ };
627
+ if (repository) {
628
+ metadata.repository = repository;
629
+ }
630
+ if (packageManager) {
631
+ metadata.packageManager = packageManager;
632
+ }
633
+
634
+ if (platformPackage.includes("linux")) {
635
+ metadata.os = ["linux"];
636
+ } else if (platformPackage.includes("darwin")) {
637
+ metadata.os = ["darwin"];
638
+ } else if (platformPackage.includes("win32")) {
639
+ metadata.os = ["win32"];
640
+ }
641
+
642
+ if (platformPackage.endsWith("x64")) {
643
+ metadata.cpu = ["x64"];
644
+ } else if (platformPackage.endsWith("arm64")) {
645
+ metadata.cpu = ["arm64"];
646
+ }
647
+
648
+ metadata.engines = { node: ">=16" };
649
+ return metadata;
650
+ }
651
+
652
+ export function ensurePlatformPackageMetadata({
653
+ packageDir,
654
+ platformPackage,
655
+ version,
656
+ license = "Apache-2.0",
657
+ repository = null,
658
+ packageManager = null,
659
+ }) {
660
+ const vendorRoot = path.join(packageDir, "vendor");
661
+ if (!existsSync(vendorRoot)) {
662
+ return null;
663
+ }
664
+
665
+ const packageJsonPath = path.join(packageDir, "package.json");
666
+ if (existsSync(packageJsonPath)) {
667
+ return packageJsonPath;
668
+ }
669
+
670
+ mkdirSync(packageDir, { recursive: true });
671
+ const metadata = buildPlatformPackageMetadata({
672
+ platformPackage,
673
+ version,
674
+ license,
675
+ repository,
676
+ packageManager,
677
+ });
678
+ writeFileSync(packageJsonPath, `${JSON.stringify(metadata, null, 2)}\n`);
679
+ return packageJsonPath;
680
+ }
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { readFileSync } from "node:fs";
5
+ import { createRequire } from "node:module";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import {
9
+ PLATFORM_PACKAGE_BY_TARGET,
10
+ detectPackageManager,
11
+ ensurePlatformPackageMetadata,
12
+ getCodexBinaryName,
13
+ resolveTargetTriple,
14
+ resolveRuntimeCacheRoot,
15
+ selectVendorInstallation,
16
+ updateCommandForPackageManager,
17
+ } from "./platform-resolver.js";
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+ const packageDir = path.join(__dirname, "..");
22
+ const require = createRequire(import.meta.url);
23
+
24
+ if (!packageDir.includes(`${path.sep}node_modules${path.sep}`)) {
25
+ process.exit(0);
26
+ }
27
+
28
+ const targetTriple = resolveTargetTriple();
29
+ if (!targetTriple) {
30
+ process.exit(0);
31
+ }
32
+
33
+ const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
34
+ if (!platformPackage) {
35
+ process.exit(0);
36
+ }
37
+
38
+ const rootPackageJson = JSON.parse(
39
+ readFileSync(path.join(packageDir, "package.json"), "utf8"),
40
+ );
41
+ ensurePlatformPackageMetadata({
42
+ packageDir: path.join(packageDir, "node_modules", ...platformPackage.split("/")),
43
+ platformPackage,
44
+ version: rootPackageJson.optionalDependencies?.[platformPackage] ?? rootPackageJson.version,
45
+ license: rootPackageJson.license ?? "Apache-2.0",
46
+ repository: rootPackageJson.repository ?? null,
47
+ packageManager: rootPackageJson.packageManager ?? null,
48
+ });
49
+
50
+ const selectedInstallation = selectVendorInstallation({
51
+ packageDir,
52
+ platformPackage,
53
+ targetTriple,
54
+ binaryName: getCodexBinaryName(),
55
+ packageVersion: rootPackageJson.version ?? null,
56
+ runtimeCacheRoot: resolveRuntimeCacheRoot(),
57
+ requireResolve: (specifier) => require.resolve(specifier),
58
+ });
59
+
60
+ if (!selectedInstallation) {
61
+ const packageManager = detectPackageManager({ installDir: __dirname });
62
+ const updateCommand = updateCommandForPackageManager(packageManager);
63
+ console.error(
64
+ `[lavilas/codex] Failed to validate ${platformPackage}. Reinstall Codex: ${updateCommand}`,
65
+ );
66
+ process.exit(1);
67
+ }
68
+
69
+ const result = spawnSync(selectedInstallation.binaryPath, ["--version"], {
70
+ stdio: "pipe",
71
+ encoding: "utf8",
72
+ timeout: 15000,
73
+ });
74
+
75
+ if (result.error) {
76
+ console.error(
77
+ `[lavilas/codex] Native binary validation failed: ${result.error.message}`,
78
+ );
79
+ process.exit(1);
80
+ }
81
+
82
+ if (result.signal) {
83
+ console.error(
84
+ `[lavilas/codex] Native binary exited with signal ${result.signal} during install validation.`,
85
+ );
86
+ process.exit(1);
87
+ }
88
+
89
+ if (result.status !== 0) {
90
+ const details = (result.stderr || result.stdout || "").trim();
91
+ if (details) {
92
+ console.error(details);
93
+ }
94
+ process.exit(result.status ?? 1);
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lavilas/codex",
3
- "version": "1.3.55",
3
+ "version": "1.3.61",
4
4
  "license": "Apache-2.0",
5
5
  "bin": {
6
6
  "codex": "bin/codex.js",
@@ -12,6 +12,10 @@
12
12
  "engines": {
13
13
  "node": ">=16"
14
14
  },
15
+ "scripts": {
16
+ "postinstall": "node bin/postinstall.js",
17
+ "test:bin": "node --test bin/platform-resolver.test.js"
18
+ },
15
19
  "files": [
16
20
  "bin"
17
21
  ],
@@ -22,11 +26,11 @@
22
26
  },
23
27
  "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
24
28
  "optionalDependencies": {
25
- "@lavilas/codex-linux-x64": "1.3.55",
26
- "@lavilas/codex-linux-arm64": "1.3.55",
27
- "@lavilas/codex-darwin-x64": "1.3.55",
28
- "@lavilas/codex-darwin-arm64": "1.3.55",
29
- "@lavilas/codex-win32-x64": "1.3.55",
30
- "@lavilas/codex-win32-arm64": "1.3.55"
29
+ "@lavilas/codex-linux-x64": "1.3.61",
30
+ "@lavilas/codex-linux-arm64": "1.3.61",
31
+ "@lavilas/codex-darwin-x64": "1.3.61",
32
+ "@lavilas/codex-darwin-arm64": "1.3.61",
33
+ "@lavilas/codex-win32-x64": "1.3.61",
34
+ "@lavilas/codex-win32-arm64": "1.3.61"
31
35
  }
32
36
  }