@hanzo/dev 0.6.73 → 0.6.75
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/README.md +254 -365
- package/bin/{coder.js → dev.js} +38 -95
- package/package.json +11 -11
- package/postinstall.js +442 -501
- package/scripts/preinstall.js +46 -24
- package/scripts/windows-cleanup.ps1 +6 -7
- package/bin/codex.js +0 -197
package/scripts/preinstall.js
CHANGED
|
@@ -2,68 +2,90 @@
|
|
|
2
2
|
// Windows-friendly preinstall: proactively free file locks from prior installs
|
|
3
3
|
// so npm/yarn/pnpm can stage the new package. No-ops on non-Windows.
|
|
4
4
|
|
|
5
|
-
import { platform } from
|
|
6
|
-
import { execSync } from
|
|
7
|
-
import { existsSync, readdirSync, rmSync, readFileSync, statSync } from
|
|
8
|
-
import path from
|
|
9
|
-
import { fileURLToPath } from
|
|
5
|
+
import { platform } from "os";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import { existsSync, readdirSync, rmSync, readFileSync, statSync } from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
10
|
|
|
11
11
|
function isWSL() {
|
|
12
|
-
if (platform() !==
|
|
12
|
+
if (platform() !== "linux") return false;
|
|
13
13
|
try {
|
|
14
|
-
const rel = readFileSync(
|
|
15
|
-
return rel.includes(
|
|
16
|
-
} catch {
|
|
14
|
+
const rel = readFileSync("/proc/version", "utf8").toLowerCase();
|
|
15
|
+
return rel.includes("microsoft") || !!process.env.WSL_DISTRO_NAME;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const isWin = platform() ===
|
|
21
|
+
const isWin = platform() === "win32";
|
|
20
22
|
const wsl = isWSL();
|
|
21
23
|
const isWinLike = isWin || wsl;
|
|
22
24
|
|
|
23
25
|
// Scope: only run for global installs, unless explicitly forced. Allow opt-out.
|
|
24
|
-
const isGlobal = process.env.npm_config_global ===
|
|
25
|
-
const force = process.env.
|
|
26
|
-
const skip = process.env.
|
|
26
|
+
const isGlobal = process.env.npm_config_global === "true";
|
|
27
|
+
const force = process.env.DEV_FORCE_PREINSTALL === "1";
|
|
28
|
+
const skip = process.env.DEV_SKIP_PREINSTALL === "1";
|
|
27
29
|
if (!isWinLike || skip || (!isGlobal && !force)) process.exit(0);
|
|
28
30
|
|
|
29
31
|
function tryExec(cmd, opts = {}) {
|
|
30
|
-
try {
|
|
32
|
+
try {
|
|
33
|
+
execSync(cmd, {
|
|
34
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
35
|
+
shell: true,
|
|
36
|
+
...opts,
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
/* ignore */
|
|
40
|
+
}
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
// 1) Stop our native binary if it is holding locks. Avoid killing unrelated tools.
|
|
34
44
|
// Only available on native Windows; skip entirely on WSL to avoid noise.
|
|
35
45
|
if (isWin) {
|
|
36
|
-
tryExec(
|
|
46
|
+
tryExec("taskkill /IM dev-x86_64-pc-windows-msvc.exe /F");
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
// 2) Remove stale staging dirs from previous failed installs under the global
|
|
40
|
-
// @hanzo scope, which npm will reuse (e.g., .
|
|
50
|
+
// @hanzo scope, which npm will reuse (e.g., .dev-XXXXX). Remove only
|
|
41
51
|
// old entries and never the current staging or live package.
|
|
42
52
|
try {
|
|
43
|
-
let scopeDir =
|
|
53
|
+
let scopeDir = "";
|
|
44
54
|
try {
|
|
45
|
-
const root = execSync(
|
|
46
|
-
|
|
55
|
+
const root = execSync("npm root -g", {
|
|
56
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
57
|
+
shell: true,
|
|
58
|
+
})
|
|
59
|
+
.toString()
|
|
60
|
+
.trim();
|
|
61
|
+
scopeDir = path.join(root, "@hanzo");
|
|
47
62
|
} catch {
|
|
48
63
|
// Fall back to guessing from this script location: <staging>\..\..\
|
|
49
64
|
const here = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
50
|
-
scopeDir = path.resolve(here,
|
|
65
|
+
scopeDir = path.resolve(here, "..");
|
|
51
66
|
}
|
|
52
67
|
if (existsSync(scopeDir)) {
|
|
53
68
|
const now = Date.now();
|
|
54
69
|
const maxAgeMs = 2 * 60 * 60 * 1000; // 2 hours
|
|
55
|
-
const currentDir = path.resolve(
|
|
70
|
+
const currentDir = path.resolve(
|
|
71
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
72
|
+
"..",
|
|
73
|
+
);
|
|
56
74
|
for (const name of readdirSync(scopeDir)) {
|
|
57
|
-
if (!name.startsWith(
|
|
75
|
+
if (!name.startsWith(".dev-")) continue;
|
|
58
76
|
const p = path.join(scopeDir, name);
|
|
59
77
|
if (path.resolve(p) === currentDir) continue; // never remove our current dir
|
|
60
78
|
try {
|
|
61
79
|
const st = statSync(p);
|
|
62
80
|
const age = now - st.mtimeMs;
|
|
63
81
|
if (age > maxAgeMs) rmSync(p, { recursive: true, force: true });
|
|
64
|
-
} catch {
|
|
82
|
+
} catch {
|
|
83
|
+
/* ignore */
|
|
84
|
+
}
|
|
65
85
|
}
|
|
66
86
|
}
|
|
67
|
-
} catch {
|
|
87
|
+
} catch {
|
|
88
|
+
/* ignore */
|
|
89
|
+
}
|
|
68
90
|
|
|
69
91
|
process.exit(0);
|
|
@@ -4,15 +4,15 @@ Closes running processes and removes stale package folders.
|
|
|
4
4
|
|
|
5
5
|
Usage (PowerShell):
|
|
6
6
|
Set-ExecutionPolicy -Scope Process Bypass -Force
|
|
7
|
-
./
|
|
7
|
+
./dev-cli/scripts/windows-cleanup.ps1
|
|
8
8
|
#>
|
|
9
9
|
|
|
10
10
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
11
11
|
|
|
12
|
-
Write-Host "Stopping running
|
|
13
|
-
taskkill /IM
|
|
14
|
-
taskkill /IM
|
|
15
|
-
taskkill /IM
|
|
12
|
+
Write-Host "Stopping running Hanzo Dev processes..."
|
|
13
|
+
taskkill /IM dev-x86_64-pc-windows-msvc.exe /F 2>$null | Out-Null
|
|
14
|
+
taskkill /IM dev.exe /F 2>$null | Out-Null
|
|
15
|
+
taskkill /IM hanzo.exe /F 2>$null | Out-Null
|
|
16
16
|
|
|
17
17
|
Write-Host "Removing old global package (if present)..."
|
|
18
18
|
$npmRoot = (& npm root -g).Trim()
|
|
@@ -23,10 +23,9 @@ if (Test-Path $pkgPath) {
|
|
|
23
23
|
|
|
24
24
|
Write-Host "Removing temp staging directories (if present)..."
|
|
25
25
|
Get-ChildItem -LiteralPath (Join-Path $npmRoot "@hanzo") -Force -ErrorAction SilentlyContinue |
|
|
26
|
-
Where-Object { $_.Name -like '.
|
|
26
|
+
Where-Object { $_.Name -like '.dev-*' } |
|
|
27
27
|
ForEach-Object {
|
|
28
28
|
try { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction Stop } catch {}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
Write-Host "Cleanup complete. You can now run: npm install -g @hanzo/dev@latest"
|
|
32
|
-
|
package/bin/codex.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Unified entry point for the Codex CLI.
|
|
3
|
-
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
import { existsSync } from "fs";
|
|
6
|
-
import { createRequire } from "node:module";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
9
|
-
|
|
10
|
-
// __dirname equivalent in ESM
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
|
|
15
|
-
const PLATFORM_PACKAGE_BY_TARGET = {
|
|
16
|
-
"x86_64-unknown-linux-musl": "@openai/codex-linux-x64",
|
|
17
|
-
"aarch64-unknown-linux-musl": "@openai/codex-linux-arm64",
|
|
18
|
-
"x86_64-apple-darwin": "@openai/codex-darwin-x64",
|
|
19
|
-
"aarch64-apple-darwin": "@openai/codex-darwin-arm64",
|
|
20
|
-
"x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
|
|
21
|
-
"aarch64-pc-windows-msvc": "@openai/codex-win32-arm64",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
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
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!targetTriple) {
|
|
70
|
-
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
|
|
74
|
-
if (!platformPackage) {
|
|
75
|
-
throw new Error(`Unsupported target triple: ${targetTriple}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
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
|
-
);
|
|
86
|
-
|
|
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 @openai/codex@latest"
|
|
99
|
-
: "npm install -g @openai/codex@latest";
|
|
100
|
-
throw new Error(
|
|
101
|
-
`Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!vendorRoot) {
|
|
107
|
-
const packageManager = detectPackageManager();
|
|
108
|
-
const updateCommand =
|
|
109
|
-
packageManager === "bun"
|
|
110
|
-
? "bun install -g @openai/codex@latest"
|
|
111
|
-
: "npm install -g @openai/codex@latest";
|
|
112
|
-
throw new Error(
|
|
113
|
-
`Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const archRoot = path.join(vendorRoot, targetTriple);
|
|
118
|
-
const binaryPath = path.join(archRoot, "codex", codexBinaryName);
|
|
119
|
-
|
|
120
|
-
// Use an asynchronous spawn instead of spawnSync so that Node is able to
|
|
121
|
-
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
|
|
122
|
-
// executing. This allows us to forward those signals to the child process
|
|
123
|
-
// and guarantees that when either the child terminates or the parent
|
|
124
|
-
// receives a fatal signal, both processes exit in a predictable manner.
|
|
125
|
-
|
|
126
|
-
function getUpdatedPath(newDirs) {
|
|
127
|
-
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
128
|
-
const existingPath = process.env.PATH || "";
|
|
129
|
-
const updatedPath = [
|
|
130
|
-
...newDirs,
|
|
131
|
-
...existingPath.split(pathSep).filter(Boolean),
|
|
132
|
-
].join(pathSep);
|
|
133
|
-
return updatedPath;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const additionalDirs = [];
|
|
137
|
-
const pathDir = path.join(archRoot, "path");
|
|
138
|
-
if (existsSync(pathDir)) {
|
|
139
|
-
additionalDirs.push(pathDir);
|
|
140
|
-
}
|
|
141
|
-
const updatedPath = getUpdatedPath(additionalDirs);
|
|
142
|
-
|
|
143
|
-
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
144
|
-
stdio: "inherit",
|
|
145
|
-
env: { ...process.env, PATH: updatedPath, CODEX_MANAGED_BY_NPM: "1" },
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
child.on("error", (err) => {
|
|
149
|
-
// Typically triggered when the binary is missing or not executable.
|
|
150
|
-
// Re-throwing here will terminate the parent with a non-zero exit code
|
|
151
|
-
// while still printing a helpful stack trace.
|
|
152
|
-
// eslint-disable-next-line no-console
|
|
153
|
-
console.error(err);
|
|
154
|
-
process.exit(1);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Forward common termination signals to the child so that it shuts down
|
|
158
|
-
// gracefully. In the handler we temporarily disable the default behavior of
|
|
159
|
-
// exiting immediately; once the child has been signaled we simply wait for
|
|
160
|
-
// its exit event which will in turn terminate the parent (see below).
|
|
161
|
-
const forwardSignal = (signal) => {
|
|
162
|
-
if (child.killed) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
child.kill(signal);
|
|
167
|
-
} catch {
|
|
168
|
-
/* ignore */
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
|
|
173
|
-
process.on(sig, () => forwardSignal(sig));
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// When the child exits, mirror its termination reason in the parent so that
|
|
177
|
-
// shell scripts and other tooling observe the correct exit status.
|
|
178
|
-
// Wrap the lifetime of the child process in a Promise so that we can await
|
|
179
|
-
// its termination in a structured way. The Promise resolves with an object
|
|
180
|
-
// describing how the child exited: either via exit code or due to a signal.
|
|
181
|
-
const childResult = await new Promise((resolve) => {
|
|
182
|
-
child.on("exit", (code, signal) => {
|
|
183
|
-
if (signal) {
|
|
184
|
-
resolve({ type: "signal", signal });
|
|
185
|
-
} else {
|
|
186
|
-
resolve({ type: "code", exitCode: code ?? 1 });
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
if (childResult.type === "signal") {
|
|
192
|
-
// Re-emit the same signal so that the parent terminates with the expected
|
|
193
|
-
// semantics (this also sets the correct exit code of 128 + n).
|
|
194
|
-
process.kill(process.pid, childResult.signal);
|
|
195
|
-
} else {
|
|
196
|
-
process.exit(childResult.exitCode);
|
|
197
|
-
}
|