@hanzo/dev 0.6.73 → 0.6.74
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 +5 -5
- package/scripts/windows-cleanup.ps1 +6 -7
- package/bin/codex.js +0 -197
package/postinstall.js
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
// Postinstall script for @hanzo/dev
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
createWriteStream,
|
|
8
|
+
chmodSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
readSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
unlinkSync,
|
|
13
|
+
statSync,
|
|
14
|
+
openSync,
|
|
15
|
+
closeSync,
|
|
16
|
+
copyFileSync,
|
|
17
|
+
fsyncSync,
|
|
18
|
+
renameSync,
|
|
19
|
+
realpathSync,
|
|
20
|
+
} from "fs";
|
|
21
|
+
import { join, dirname, resolve } from "path";
|
|
22
|
+
import { fileURLToPath } from "url";
|
|
23
|
+
import { get } from "https";
|
|
24
|
+
import { platform, arch, tmpdir } from "os";
|
|
25
|
+
import { execSync } from "child_process";
|
|
26
|
+
import { createRequire } from "module";
|
|
11
27
|
|
|
12
28
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
29
|
|
|
14
|
-
// Map Node.js platform/arch to Rust target triples
|
|
15
30
|
function getTargetTriple() {
|
|
16
31
|
const platformMap = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
darwin: "apple-darwin",
|
|
33
|
+
linux: "unknown-linux-musl",
|
|
34
|
+
win32: "pc-windows-msvc",
|
|
20
35
|
};
|
|
21
|
-
|
|
36
|
+
|
|
22
37
|
const archMap = {
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
x64: "x86_64",
|
|
39
|
+
arm64: "aarch64",
|
|
25
40
|
};
|
|
26
|
-
|
|
41
|
+
|
|
27
42
|
const rustArch = archMap[arch()] || arch();
|
|
28
43
|
const rustPlatform = platformMap[platform()] || platform();
|
|
29
|
-
|
|
44
|
+
|
|
30
45
|
return `${rustArch}-${rustPlatform}`;
|
|
31
46
|
}
|
|
32
47
|
|
|
33
|
-
// Resolve a persistent user cache directory for binaries so that repeated
|
|
34
|
-
// npx installs can reuse a previously downloaded artifact and skip work.
|
|
35
48
|
function getCacheDir(version) {
|
|
36
49
|
const plt = platform();
|
|
37
|
-
const home = process.env.HOME || process.env.USERPROFILE ||
|
|
38
|
-
let base =
|
|
39
|
-
if (plt ===
|
|
40
|
-
base = process.env.LOCALAPPDATA || join(home,
|
|
41
|
-
} else if (plt ===
|
|
42
|
-
base = join(home,
|
|
50
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
51
|
+
let base = "";
|
|
52
|
+
if (plt === "win32") {
|
|
53
|
+
base = process.env.LOCALAPPDATA || join(home, "AppData", "Local");
|
|
54
|
+
} else if (plt === "darwin") {
|
|
55
|
+
base = join(home, "Library", "Caches");
|
|
43
56
|
} else {
|
|
44
|
-
base = process.env.XDG_CACHE_HOME || join(home,
|
|
57
|
+
base = process.env.XDG_CACHE_HOME || join(home, ".cache");
|
|
45
58
|
}
|
|
46
|
-
const dir = join(base,
|
|
59
|
+
const dir = join(base, "hanzo", "dev", version);
|
|
47
60
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
48
61
|
return dir;
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
function getCachedBinaryPath(version, targetTriple, isWindows) {
|
|
52
|
-
const ext = isWindows ?
|
|
65
|
+
const ext = isWindows ? ".exe" : "";
|
|
53
66
|
const cacheDir = getCacheDir(version);
|
|
54
|
-
return join(cacheDir, `
|
|
67
|
+
return join(cacheDir, `dev-${targetTriple}${ext}`);
|
|
55
68
|
}
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'bin/coder.js',
|
|
60
|
-
'$(dirname "$0")/coder',
|
|
61
|
-
'%~dp0coder'
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
function shimContentsLookOurs(contents) {
|
|
65
|
-
return CODE_SHIM_SIGNATURES.some(sig => contents.includes(sig));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function looksLikeOurCodeShim(path) {
|
|
70
|
+
function isWSL() {
|
|
71
|
+
if (platform() !== "linux") return false;
|
|
69
72
|
try {
|
|
70
|
-
const
|
|
71
|
-
return
|
|
73
|
+
const ver = readFileSync("/proc/version", "utf8").toLowerCase();
|
|
74
|
+
return ver.includes("microsoft") || !!process.env.WSL_DISTRO_NAME;
|
|
72
75
|
} catch {
|
|
73
76
|
return false;
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
function isWSL() {
|
|
78
|
-
if (platform() !== 'linux') return false;
|
|
79
|
-
try {
|
|
80
|
-
const ver = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
81
|
-
return ver.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
|
|
82
|
-
} catch { return false; }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
80
|
function isPathOnWindowsFs(p) {
|
|
86
81
|
try {
|
|
87
|
-
const mounts = readFileSync(
|
|
88
|
-
|
|
82
|
+
const mounts = readFileSync("/proc/mounts", "utf8")
|
|
83
|
+
.split(/\n/)
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
let best = { mount: "/", type: "unknown", len: 1 };
|
|
89
86
|
for (const line of mounts) {
|
|
90
|
-
const parts = line.split(
|
|
87
|
+
const parts = line.split(" ");
|
|
91
88
|
if (parts.length < 3) continue;
|
|
92
89
|
const mnt = parts[1];
|
|
93
90
|
const typ = parts[2];
|
|
94
|
-
if (p.startsWith(mnt) && mnt.length > best.len)
|
|
91
|
+
if (p.startsWith(mnt) && mnt.length > best.len)
|
|
92
|
+
best = { mount: mnt, type: typ, len: mnt.length };
|
|
95
93
|
}
|
|
96
|
-
return best.type ===
|
|
97
|
-
} catch {
|
|
94
|
+
return best.type === "drvfs" || best.type === "cifs";
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async function writeCacheAtomic(srcPath, cachePath) {
|
|
@@ -105,145 +105,185 @@ async function writeCacheAtomic(srcPath, cachePath) {
|
|
|
105
105
|
}
|
|
106
106
|
} catch {}
|
|
107
107
|
const dir = dirname(cachePath);
|
|
108
|
-
if (!existsSync(dir)) {
|
|
109
|
-
|
|
108
|
+
if (!existsSync(dir)) {
|
|
109
|
+
try {
|
|
110
|
+
mkdirSync(dir, { recursive: true });
|
|
111
|
+
} catch {}
|
|
112
|
+
}
|
|
113
|
+
const tmp = cachePath + ".tmp-" + Math.random().toString(36).slice(2, 8);
|
|
110
114
|
copyFileSync(srcPath, tmp);
|
|
111
|
-
try {
|
|
112
|
-
|
|
115
|
+
try {
|
|
116
|
+
const fd = openSync(tmp, "r");
|
|
117
|
+
try {
|
|
118
|
+
fsyncSync(fd);
|
|
119
|
+
} finally {
|
|
120
|
+
closeSync(fd);
|
|
121
|
+
}
|
|
122
|
+
} catch {}
|
|
113
123
|
const delays = [100, 200, 400, 800, 1200, 1600];
|
|
114
124
|
for (let i = 0; i < delays.length; i++) {
|
|
115
125
|
try {
|
|
116
|
-
if (existsSync(cachePath)) {
|
|
126
|
+
if (existsSync(cachePath)) {
|
|
127
|
+
try {
|
|
128
|
+
unlinkSync(cachePath);
|
|
129
|
+
} catch {}
|
|
130
|
+
}
|
|
117
131
|
renameSync(tmp, cachePath);
|
|
118
132
|
return;
|
|
119
133
|
} catch {
|
|
120
|
-
await new Promise(r => setTimeout(r, delays[i]));
|
|
134
|
+
await new Promise((r) => setTimeout(r, delays[i]));
|
|
121
135
|
}
|
|
122
136
|
}
|
|
123
|
-
if (existsSync(cachePath)) {
|
|
137
|
+
if (existsSync(cachePath)) {
|
|
138
|
+
try {
|
|
139
|
+
unlinkSync(cachePath);
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
124
142
|
renameSync(tmp, cachePath);
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
function resolveGlobalBinDir() {
|
|
128
146
|
const plt = platform();
|
|
129
|
-
const userAgent = process.env.npm_config_user_agent ||
|
|
147
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
130
148
|
|
|
131
149
|
const fromPrefix = (prefixPath) => {
|
|
132
|
-
if (!prefixPath) return
|
|
133
|
-
return plt ===
|
|
150
|
+
if (!prefixPath) return "";
|
|
151
|
+
return plt === "win32" ? prefixPath : join(prefixPath, "bin");
|
|
134
152
|
};
|
|
135
153
|
|
|
136
|
-
const prefixEnv = process.env.npm_config_prefix || process.env.PREFIX ||
|
|
154
|
+
const prefixEnv = process.env.npm_config_prefix || process.env.PREFIX || "";
|
|
137
155
|
const direct = fromPrefix(prefixEnv);
|
|
138
156
|
if (direct) return direct;
|
|
139
157
|
|
|
140
158
|
const tryExec = (command) => {
|
|
141
159
|
try {
|
|
142
160
|
return execSync(command, {
|
|
143
|
-
stdio: [
|
|
161
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
144
162
|
shell: true,
|
|
145
|
-
})
|
|
163
|
+
})
|
|
164
|
+
.toString()
|
|
165
|
+
.trim();
|
|
146
166
|
} catch {
|
|
147
|
-
return
|
|
167
|
+
return "";
|
|
148
168
|
}
|
|
149
169
|
};
|
|
150
170
|
|
|
151
|
-
const prefixFromNpm = fromPrefix(tryExec(
|
|
171
|
+
const prefixFromNpm = fromPrefix(tryExec("npm prefix -g"));
|
|
152
172
|
if (prefixFromNpm) return prefixFromNpm;
|
|
153
173
|
|
|
154
|
-
const binFromNpm = tryExec(
|
|
174
|
+
const binFromNpm = tryExec("npm bin -g");
|
|
155
175
|
if (binFromNpm) return binFromNpm;
|
|
156
176
|
|
|
157
|
-
if (userAgent.includes(
|
|
158
|
-
const pnpmBin = tryExec(
|
|
177
|
+
if (userAgent.includes("pnpm")) {
|
|
178
|
+
const pnpmBin = tryExec("pnpm bin --global");
|
|
159
179
|
if (pnpmBin) return pnpmBin;
|
|
160
|
-
const pnpmPrefix = fromPrefix(tryExec('pnpm env get prefix'));
|
|
161
|
-
if (pnpmPrefix) return pnpmPrefix;
|
|
162
180
|
}
|
|
163
181
|
|
|
164
|
-
if (userAgent.includes(
|
|
165
|
-
const yarnBin = tryExec(
|
|
182
|
+
if (userAgent.includes("yarn")) {
|
|
183
|
+
const yarnBin = tryExec("yarn global bin");
|
|
166
184
|
if (yarnBin) return yarnBin;
|
|
167
185
|
}
|
|
168
186
|
|
|
169
|
-
return
|
|
187
|
+
return "";
|
|
170
188
|
}
|
|
171
189
|
|
|
172
190
|
async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
|
173
|
-
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
174
|
-
|
|
175
|
-
const doAttempt = () =>
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
191
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
192
|
+
|
|
193
|
+
const doAttempt = () =>
|
|
194
|
+
new Promise((resolve, reject) => {
|
|
195
|
+
const attempt = (currentUrl, redirectsLeft) => {
|
|
196
|
+
const req = get(currentUrl, (response) => {
|
|
197
|
+
const status = response.statusCode || 0;
|
|
198
|
+
const location = response.headers.location;
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
(status === 301 ||
|
|
202
|
+
status === 302 ||
|
|
203
|
+
status === 303 ||
|
|
204
|
+
status === 307 ||
|
|
205
|
+
status === 308) &&
|
|
206
|
+
location
|
|
207
|
+
) {
|
|
208
|
+
if (redirectsLeft <= 0) {
|
|
209
|
+
reject(
|
|
210
|
+
new Error(`Too many redirects while downloading ${currentUrl}`),
|
|
211
|
+
);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
attempt(location, redirectsLeft - 1);
|
|
184
215
|
return;
|
|
185
216
|
}
|
|
186
|
-
attempt(location, redirectsLeft - 1);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
217
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
response.on('data', (chunk) => {
|
|
205
|
-
bytes += chunk.length;
|
|
218
|
+
if (status === 200) {
|
|
219
|
+
const expected =
|
|
220
|
+
parseInt(response.headers["content-length"] || "0", 10) || 0;
|
|
221
|
+
let bytes = 0;
|
|
222
|
+
let timer;
|
|
223
|
+
const timeoutMs = 30000;
|
|
224
|
+
|
|
225
|
+
const resetTimer = () => {
|
|
226
|
+
if (timer) clearTimeout(timer);
|
|
227
|
+
timer = setTimeout(() => {
|
|
228
|
+
req.destroy(new Error("download stalled"));
|
|
229
|
+
}, timeoutMs);
|
|
230
|
+
};
|
|
231
|
+
|
|
206
232
|
resetTimer();
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
response.on("data", (chunk) => {
|
|
234
|
+
bytes += chunk.length;
|
|
235
|
+
resetTimer();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const file = createWriteStream(dest);
|
|
239
|
+
response.pipe(file);
|
|
240
|
+
file.on("finish", () => {
|
|
241
|
+
if (timer) clearTimeout(timer);
|
|
242
|
+
file.close();
|
|
243
|
+
if (expected && bytes !== expected) {
|
|
244
|
+
try {
|
|
245
|
+
unlinkSync(dest);
|
|
246
|
+
} catch {}
|
|
247
|
+
reject(
|
|
248
|
+
new Error(
|
|
249
|
+
`incomplete download: got ${bytes} of ${expected} bytes`,
|
|
250
|
+
),
|
|
251
|
+
);
|
|
252
|
+
} else if (bytes === 0) {
|
|
253
|
+
try {
|
|
254
|
+
unlinkSync(dest);
|
|
255
|
+
} catch {}
|
|
256
|
+
reject(new Error("empty download"));
|
|
257
|
+
} else {
|
|
258
|
+
resolve();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
file.on("error", (err) => {
|
|
262
|
+
if (timer) clearTimeout(timer);
|
|
263
|
+
try {
|
|
264
|
+
unlinkSync(dest);
|
|
265
|
+
} catch {}
|
|
266
|
+
reject(err);
|
|
267
|
+
});
|
|
268
|
+
} else {
|
|
269
|
+
reject(new Error(`Failed to download: HTTP ${status}`));
|
|
270
|
+
}
|
|
271
|
+
});
|
|
233
272
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
273
|
+
req.on("error", (err) => {
|
|
274
|
+
try {
|
|
275
|
+
unlinkSync(dest);
|
|
276
|
+
} catch {}
|
|
277
|
+
reject(err);
|
|
278
|
+
});
|
|
238
279
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
};
|
|
280
|
+
req.setTimeout(120000, () => {
|
|
281
|
+
req.destroy(new Error("download timed out"));
|
|
282
|
+
});
|
|
283
|
+
};
|
|
244
284
|
|
|
245
|
-
|
|
246
|
-
|
|
285
|
+
attempt(url, maxRedirects);
|
|
286
|
+
});
|
|
247
287
|
|
|
248
288
|
let attemptNum = 0;
|
|
249
289
|
while (true) {
|
|
@@ -262,22 +302,38 @@ function validateDownloadedBinary(p) {
|
|
|
262
302
|
try {
|
|
263
303
|
const st = statSync(p);
|
|
264
304
|
if (!st.isFile() || st.size === 0) {
|
|
265
|
-
return { ok: false, reason:
|
|
305
|
+
return { ok: false, reason: "empty or not a regular file" };
|
|
266
306
|
}
|
|
267
|
-
const fd = openSync(p,
|
|
307
|
+
const fd = openSync(p, "r");
|
|
268
308
|
try {
|
|
269
309
|
const buf = Buffer.alloc(4);
|
|
270
310
|
const n = readSync(fd, buf, 0, 4, 0);
|
|
271
|
-
if (n < 2) return { ok: false, reason:
|
|
311
|
+
if (n < 2) return { ok: false, reason: "too short" };
|
|
272
312
|
const plt = platform();
|
|
273
|
-
if (plt ===
|
|
274
|
-
if (!(buf[0] === 0x4d && buf[1] === 0x5a))
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
if (plt === "win32") {
|
|
314
|
+
if (!(buf[0] === 0x4d && buf[1] === 0x5a))
|
|
315
|
+
return { ok: false, reason: "invalid PE header (missing MZ)" };
|
|
316
|
+
} else if (plt === "linux" || plt === "android") {
|
|
317
|
+
if (
|
|
318
|
+
!(
|
|
319
|
+
buf[0] === 0x7f &&
|
|
320
|
+
buf[1] === 0x45 &&
|
|
321
|
+
buf[2] === 0x4c &&
|
|
322
|
+
buf[3] === 0x46
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
return { ok: false, reason: "invalid ELF header" };
|
|
326
|
+
} else if (plt === "darwin") {
|
|
327
|
+
const isMachO =
|
|
328
|
+
(buf[0] === 0xcf &&
|
|
329
|
+
buf[1] === 0xfa &&
|
|
330
|
+
buf[2] === 0xed &&
|
|
331
|
+
buf[3] === 0xfe) ||
|
|
332
|
+
(buf[0] === 0xca &&
|
|
333
|
+
buf[1] === 0xfe &&
|
|
334
|
+
buf[2] === 0xba &&
|
|
335
|
+
buf[3] === 0xbe);
|
|
336
|
+
if (!isMachO) return { ok: false, reason: "invalid Mach-O header" };
|
|
281
337
|
}
|
|
282
338
|
return { ok: true };
|
|
283
339
|
} finally {
|
|
@@ -290,104 +346,89 @@ function validateDownloadedBinary(p) {
|
|
|
290
346
|
|
|
291
347
|
export async function runPostinstall(options = {}) {
|
|
292
348
|
const { skipGlobalAlias = false, invokedByRuntime = false } = options;
|
|
293
|
-
if (process.env.
|
|
349
|
+
if (process.env.DEV_POSTINSTALL_DRY_RUN === "1") {
|
|
294
350
|
return { skipped: true };
|
|
295
351
|
}
|
|
296
352
|
|
|
297
353
|
if (invokedByRuntime) {
|
|
298
|
-
process.env.
|
|
299
|
-
|
|
300
|
-
// Detect potential PATH conflict with an existing `code` command (e.g., VS Code)
|
|
301
|
-
// Only relevant for global installs; skip for npx/local installs to keep postinstall fast.
|
|
302
|
-
const ua = process.env.npm_config_user_agent || '';
|
|
303
|
-
const isNpx = ua.includes('npx');
|
|
304
|
-
const isGlobal = process.env.npm_config_global === 'true';
|
|
305
|
-
if (!skipGlobalAlias && isGlobal && !isNpx) {
|
|
306
|
-
try {
|
|
307
|
-
const whichCmd = process.platform === 'win32' ? 'where code' : 'command -v code || which code || true';
|
|
308
|
-
const resolved = execSync(whichCmd, { stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform !== 'win32' }).toString().split(/\r?\n/).filter(Boolean)[0];
|
|
309
|
-
if (resolved) {
|
|
310
|
-
let contents = '';
|
|
311
|
-
try {
|
|
312
|
-
contents = readFileSync(resolved, 'utf8');
|
|
313
|
-
} catch {
|
|
314
|
-
contents = '';
|
|
315
|
-
}
|
|
316
|
-
const looksLikeOurs = shimContentsLookOurs(contents);
|
|
317
|
-
if (!looksLikeOurs) {
|
|
318
|
-
console.warn('[notice] Found an existing `code` on PATH at:');
|
|
319
|
-
console.warn(` ${resolved}`);
|
|
320
|
-
console.warn('[notice] We will still install our CLI, also available as `coder`.');
|
|
321
|
-
console.warn(' If `code` runs another tool, prefer using: coder');
|
|
322
|
-
console.warn(' Or run our CLI explicitly via: npx -y @hanzo/dev');
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
} catch {
|
|
326
|
-
// Ignore detection failures; proceed with install.
|
|
327
|
-
}
|
|
354
|
+
process.env.DEV_RUNTIME_POSTINSTALL =
|
|
355
|
+
process.env.DEV_RUNTIME_POSTINSTALL || "1";
|
|
328
356
|
}
|
|
329
357
|
|
|
358
|
+
const ua = process.env.npm_config_user_agent || "";
|
|
359
|
+
const isNpx = ua.includes("npx");
|
|
360
|
+
const isGlobal = process.env.npm_config_global === "true";
|
|
361
|
+
|
|
330
362
|
const targetTriple = getTargetTriple();
|
|
331
|
-
const isWindows = platform() ===
|
|
332
|
-
const binaryExt = isWindows ?
|
|
333
|
-
|
|
334
|
-
const binDir = join(__dirname,
|
|
363
|
+
const isWindows = platform() === "win32";
|
|
364
|
+
const binaryExt = isWindows ? ".exe" : "";
|
|
365
|
+
|
|
366
|
+
const binDir = join(__dirname, "bin");
|
|
335
367
|
if (!existsSync(binDir)) {
|
|
336
368
|
mkdirSync(binDir, { recursive: true });
|
|
337
369
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
370
|
+
|
|
371
|
+
const packageJson = JSON.parse(
|
|
372
|
+
readFileSync(join(__dirname, "package.json"), "utf8"),
|
|
373
|
+
);
|
|
341
374
|
const version = packageJson.version;
|
|
342
|
-
|
|
343
|
-
//
|
|
344
|
-
const binaries = [
|
|
345
|
-
|
|
375
|
+
|
|
376
|
+
// The release produces 'code-*' binaries; we'll download and rename to 'dev-*'
|
|
377
|
+
const binaries = ["code"];
|
|
378
|
+
|
|
346
379
|
console.log(`Installing @hanzo/dev v${version} for ${targetTriple}...`);
|
|
347
|
-
|
|
380
|
+
|
|
348
381
|
for (const binary of binaries) {
|
|
349
382
|
const binaryName = `${binary}-${targetTriple}${binaryExt}`;
|
|
350
|
-
const
|
|
383
|
+
const devBinaryName = `dev-${targetTriple}${binaryExt}`;
|
|
384
|
+
const localPath = join(binDir, devBinaryName);
|
|
351
385
|
const cachePath = getCachedBinaryPath(version, targetTriple, isWindows);
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
// EBUSY/EPERM during global upgrades when the binary is in use.
|
|
355
|
-
// We treat the user cache path as the canonical home of the native binary.
|
|
356
|
-
// For macOS/Linux we keep previous behavior and also place a copy in binDir
|
|
357
|
-
// for convenience.
|
|
358
|
-
|
|
359
|
-
// Fast path: if a valid cached binary exists for this version+triple, reuse it.
|
|
386
|
+
|
|
387
|
+
// Fast path: if a valid cached binary exists, reuse it
|
|
360
388
|
try {
|
|
361
389
|
if (existsSync(cachePath)) {
|
|
362
390
|
const valid = validateDownloadedBinary(cachePath);
|
|
363
391
|
if (valid.ok) {
|
|
364
|
-
// Avoid mirroring into node_modules on Windows or WSL-on-NTFS.
|
|
365
392
|
const wsl = isWSL();
|
|
366
|
-
const binDirReal = (() => {
|
|
367
|
-
|
|
393
|
+
const binDirReal = (() => {
|
|
394
|
+
try {
|
|
395
|
+
return realpathSync(binDir);
|
|
396
|
+
} catch {
|
|
397
|
+
return binDir;
|
|
398
|
+
}
|
|
399
|
+
})();
|
|
400
|
+
const mirrorToLocal = !(
|
|
401
|
+
isWindows ||
|
|
402
|
+
(wsl && isPathOnWindowsFs(binDirReal))
|
|
403
|
+
);
|
|
368
404
|
if (mirrorToLocal) {
|
|
369
405
|
copyFileSync(cachePath, localPath);
|
|
370
|
-
try {
|
|
406
|
+
try {
|
|
407
|
+
chmodSync(localPath, 0o755);
|
|
408
|
+
} catch {}
|
|
371
409
|
}
|
|
372
|
-
console.log(`✓ ${
|
|
373
|
-
continue;
|
|
410
|
+
console.log(`✓ ${devBinaryName} ready from user cache`);
|
|
411
|
+
continue;
|
|
374
412
|
}
|
|
375
413
|
}
|
|
376
414
|
} catch {
|
|
377
|
-
// Ignore cache errors
|
|
415
|
+
// Ignore cache errors
|
|
378
416
|
}
|
|
379
|
-
|
|
380
|
-
//
|
|
417
|
+
|
|
418
|
+
// Try platform package via npm optionalDependencies
|
|
381
419
|
const require = createRequire(import.meta.url);
|
|
382
420
|
const platformPkg = (() => {
|
|
383
421
|
const name = (() => {
|
|
384
|
-
if (isWindows) return
|
|
422
|
+
if (isWindows) return "@hanzo/dev-win32-x64";
|
|
385
423
|
const plt = platform();
|
|
386
424
|
const cpu = arch();
|
|
387
|
-
if (plt ===
|
|
388
|
-
|
|
389
|
-
if (plt ===
|
|
390
|
-
if (plt ===
|
|
425
|
+
if (plt === "darwin" && cpu === "arm64")
|
|
426
|
+
return "@hanzo/dev-darwin-arm64";
|
|
427
|
+
if (plt === "darwin" && cpu === "x64") return "@hanzo/dev-darwin-x64";
|
|
428
|
+
if (plt === "linux" && cpu === "x64")
|
|
429
|
+
return "@hanzo/dev-linux-x64-musl";
|
|
430
|
+
if (plt === "linux" && cpu === "arm64")
|
|
431
|
+
return "@hanzo/dev-linux-arm64-musl";
|
|
391
432
|
return null;
|
|
392
433
|
})();
|
|
393
434
|
if (!name) return null;
|
|
@@ -402,369 +443,269 @@ export async function runPostinstall(options = {}) {
|
|
|
402
443
|
|
|
403
444
|
if (platformPkg) {
|
|
404
445
|
try {
|
|
405
|
-
|
|
406
|
-
const src = join(platformPkg.dir, 'bin', binaryName);
|
|
446
|
+
const src = join(platformPkg.dir, "bin", devBinaryName);
|
|
407
447
|
if (!existsSync(src)) {
|
|
408
|
-
throw new Error(
|
|
448
|
+
throw new Error(
|
|
449
|
+
`platform package missing binary: ${platformPkg.name}`,
|
|
450
|
+
);
|
|
409
451
|
}
|
|
410
|
-
// Populate cache first (canonical location) atomically
|
|
411
452
|
await writeCacheAtomic(src, cachePath);
|
|
412
|
-
// Mirror into local bin only on Unix-like filesystems (not Windows/WSL-on-NTFS)
|
|
413
453
|
const wsl = isWSL();
|
|
414
|
-
const binDirReal = (() => {
|
|
415
|
-
|
|
454
|
+
const binDirReal = (() => {
|
|
455
|
+
try {
|
|
456
|
+
return realpathSync(binDir);
|
|
457
|
+
} catch {
|
|
458
|
+
return binDir;
|
|
459
|
+
}
|
|
460
|
+
})();
|
|
461
|
+
const mirrorToLocal = !(
|
|
462
|
+
isWindows ||
|
|
463
|
+
(wsl && isPathOnWindowsFs(binDirReal))
|
|
464
|
+
);
|
|
416
465
|
if (mirrorToLocal) {
|
|
417
466
|
copyFileSync(cachePath, localPath);
|
|
418
|
-
try {
|
|
467
|
+
try {
|
|
468
|
+
chmodSync(localPath, 0o755);
|
|
469
|
+
} catch {}
|
|
419
470
|
}
|
|
420
|
-
console.log(
|
|
421
|
-
|
|
471
|
+
console.log(
|
|
472
|
+
`✓ Installed ${devBinaryName} from ${platformPkg.name} (cached)`,
|
|
473
|
+
);
|
|
474
|
+
continue;
|
|
422
475
|
} catch (e) {
|
|
423
|
-
console.warn(
|
|
476
|
+
console.warn(
|
|
477
|
+
`⚠ Failed platform package install (${e.message}), falling back to GitHub download`,
|
|
478
|
+
);
|
|
424
479
|
}
|
|
425
480
|
}
|
|
426
481
|
|
|
427
|
-
//
|
|
428
|
-
// - Windows: .zip
|
|
429
|
-
// - macOS/Linux: prefer .zst if `zstd` CLI is available; otherwise use .tar.gz
|
|
482
|
+
// Download from GitHub release
|
|
430
483
|
const isWin = isWindows;
|
|
431
|
-
const detectedWSL = (
|
|
432
|
-
|
|
484
|
+
const detectedWSL = isWSL();
|
|
485
|
+
const binDirReal = (() => {
|
|
433
486
|
try {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
487
|
+
return realpathSync(binDir);
|
|
488
|
+
} catch {
|
|
489
|
+
return binDir;
|
|
490
|
+
}
|
|
437
491
|
})();
|
|
438
|
-
const
|
|
439
|
-
|
|
492
|
+
const mirrorToLocal = !(
|
|
493
|
+
isWin ||
|
|
494
|
+
(detectedWSL && isPathOnWindowsFs(binDirReal))
|
|
495
|
+
);
|
|
440
496
|
let useZst = false;
|
|
441
497
|
if (!isWin) {
|
|
442
498
|
try {
|
|
443
|
-
execSync(
|
|
499
|
+
execSync("zstd --version", { stdio: "ignore", shell: true });
|
|
444
500
|
useZst = true;
|
|
445
501
|
} catch {
|
|
446
502
|
useZst = false;
|
|
447
503
|
}
|
|
448
504
|
}
|
|
449
|
-
const archiveName = isWin
|
|
505
|
+
const archiveName = isWin
|
|
506
|
+
? `${binaryName}.zip`
|
|
507
|
+
: useZst
|
|
508
|
+
? `${binaryName}.zst`
|
|
509
|
+
: `${binaryName}.tar.gz`;
|
|
450
510
|
const downloadUrl = `https://github.com/hanzoai/dev/releases/download/v${version}/${archiveName}`;
|
|
451
511
|
|
|
452
512
|
console.log(`Downloading ${archiveName}...`);
|
|
453
513
|
try {
|
|
454
|
-
const needsIsolation = isWin || (!isWin && !mirrorToLocal);
|
|
455
|
-
let safeTempDir = needsIsolation
|
|
456
|
-
|
|
514
|
+
const needsIsolation = isWin || (!isWin && !mirrorToLocal);
|
|
515
|
+
let safeTempDir = needsIsolation
|
|
516
|
+
? join(tmpdir(), "hanzo", "dev", version)
|
|
517
|
+
: binDir;
|
|
457
518
|
if (needsIsolation) {
|
|
458
519
|
try {
|
|
459
|
-
if (!existsSync(safeTempDir))
|
|
520
|
+
if (!existsSync(safeTempDir))
|
|
521
|
+
mkdirSync(safeTempDir, { recursive: true });
|
|
460
522
|
} catch {
|
|
461
523
|
try {
|
|
462
524
|
safeTempDir = getCacheDir(version);
|
|
463
|
-
if (!existsSync(safeTempDir))
|
|
525
|
+
if (!existsSync(safeTempDir))
|
|
526
|
+
mkdirSync(safeTempDir, { recursive: true });
|
|
464
527
|
} catch {}
|
|
465
528
|
}
|
|
466
529
|
}
|
|
467
|
-
const tmpPath = join(
|
|
530
|
+
const tmpPath = join(
|
|
531
|
+
needsIsolation ? safeTempDir : binDir,
|
|
532
|
+
`.${archiveName}.part`,
|
|
533
|
+
);
|
|
468
534
|
await downloadBinary(downloadUrl, tmpPath);
|
|
469
535
|
|
|
470
536
|
if (isWin) {
|
|
471
|
-
// Unzip to a temp directory, then move into the per-user cache.
|
|
472
537
|
const unzipDest = safeTempDir;
|
|
473
538
|
try {
|
|
474
|
-
const sysRoot =
|
|
475
|
-
|
|
539
|
+
const sysRoot =
|
|
540
|
+
process.env.SystemRoot || process.env.windir || "C:\\Windows";
|
|
541
|
+
const psFull = join(
|
|
542
|
+
sysRoot,
|
|
543
|
+
"System32",
|
|
544
|
+
"WindowsPowerShell",
|
|
545
|
+
"v1.0",
|
|
546
|
+
"powershell.exe",
|
|
547
|
+
);
|
|
476
548
|
const psCmd = `Expand-Archive -Path '${tmpPath}' -DestinationPath '${unzipDest}' -Force`;
|
|
477
549
|
let ok = false;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (!ok) {
|
|
550
|
+
try {
|
|
551
|
+
execSync(
|
|
552
|
+
`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`,
|
|
553
|
+
{ stdio: "ignore" },
|
|
554
|
+
);
|
|
555
|
+
ok = true;
|
|
556
|
+
} catch {}
|
|
557
|
+
if (!ok) {
|
|
558
|
+
try {
|
|
559
|
+
execSync(
|
|
560
|
+
`powershell -NoProfile -NonInteractive -Command "${psCmd}"`,
|
|
561
|
+
{ stdio: "ignore" },
|
|
562
|
+
);
|
|
563
|
+
ok = true;
|
|
564
|
+
} catch {}
|
|
565
|
+
}
|
|
566
|
+
if (!ok) {
|
|
567
|
+
try {
|
|
568
|
+
execSync(`pwsh -NoProfile -NonInteractive -Command "${psCmd}"`, {
|
|
569
|
+
stdio: "ignore",
|
|
570
|
+
});
|
|
571
|
+
ok = true;
|
|
572
|
+
} catch {}
|
|
573
|
+
}
|
|
574
|
+
if (!ok) {
|
|
575
|
+
execSync(`tar -xf "${tmpPath}" -C "${unzipDest}"`, {
|
|
576
|
+
stdio: "ignore",
|
|
577
|
+
shell: true,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
486
580
|
} catch (e) {
|
|
487
581
|
throw new Error(`failed to unzip archive: ${e.message}`);
|
|
488
582
|
} finally {
|
|
489
|
-
try {
|
|
583
|
+
try {
|
|
584
|
+
unlinkSync(tmpPath);
|
|
585
|
+
} catch {}
|
|
490
586
|
}
|
|
491
|
-
// Move the extracted file from temp to cache; do not leave a copy in node_modules
|
|
492
587
|
try {
|
|
493
588
|
const extractedPath = join(unzipDest, binaryName);
|
|
494
|
-
|
|
495
|
-
|
|
589
|
+
// Rename code-* to dev-* in cache
|
|
590
|
+
const devCachePath = cachePath;
|
|
591
|
+
await writeCacheAtomic(extractedPath, devCachePath);
|
|
592
|
+
try {
|
|
593
|
+
unlinkSync(extractedPath);
|
|
594
|
+
} catch {}
|
|
496
595
|
} catch (e) {
|
|
497
596
|
throw new Error(`failed to move binary to cache: ${e.message}`);
|
|
498
597
|
}
|
|
499
598
|
} else {
|
|
599
|
+
const downloadedPath = join(binDir, binaryName);
|
|
500
600
|
if (useZst) {
|
|
501
|
-
// Decompress .zst via system zstd
|
|
502
601
|
try {
|
|
503
|
-
|
|
504
|
-
|
|
602
|
+
execSync(`zstd -d '${tmpPath}' -o '${downloadedPath}'`, {
|
|
603
|
+
stdio: "ignore",
|
|
604
|
+
shell: true,
|
|
605
|
+
});
|
|
505
606
|
} catch (e) {
|
|
506
|
-
try {
|
|
507
|
-
|
|
607
|
+
try {
|
|
608
|
+
unlinkSync(tmpPath);
|
|
609
|
+
} catch {}
|
|
610
|
+
throw new Error(
|
|
611
|
+
`failed to decompress .zst (need zstd CLI): ${e.message}`,
|
|
612
|
+
);
|
|
508
613
|
}
|
|
509
|
-
try {
|
|
614
|
+
try {
|
|
615
|
+
unlinkSync(tmpPath);
|
|
616
|
+
} catch {}
|
|
510
617
|
} else {
|
|
511
|
-
// Extract .tar.gz using system tar
|
|
512
618
|
try {
|
|
513
|
-
|
|
514
|
-
|
|
619
|
+
execSync(`tar -xzf '${tmpPath}' -C '${binDir}'`, {
|
|
620
|
+
stdio: "ignore",
|
|
621
|
+
shell: true,
|
|
622
|
+
});
|
|
515
623
|
} catch (e) {
|
|
516
|
-
try {
|
|
624
|
+
try {
|
|
625
|
+
unlinkSync(tmpPath);
|
|
626
|
+
} catch {}
|
|
517
627
|
throw new Error(`failed to extract .tar.gz: ${e.message}`);
|
|
518
628
|
}
|
|
519
|
-
try { unlinkSync(tmpPath); } catch {}
|
|
520
|
-
}
|
|
521
|
-
if (!mirrorToLocal) {
|
|
522
629
|
try {
|
|
523
|
-
|
|
630
|
+
unlinkSync(tmpPath);
|
|
631
|
+
} catch {}
|
|
632
|
+
}
|
|
633
|
+
// Rename code-* to dev-*
|
|
634
|
+
if (existsSync(downloadedPath)) {
|
|
635
|
+
if (mirrorToLocal) {
|
|
636
|
+
renameSync(downloadedPath, localPath);
|
|
637
|
+
} else {
|
|
638
|
+
const extractedPath = downloadedPath;
|
|
524
639
|
await writeCacheAtomic(extractedPath, cachePath);
|
|
525
|
-
try {
|
|
526
|
-
|
|
527
|
-
|
|
640
|
+
try {
|
|
641
|
+
unlinkSync(extractedPath);
|
|
642
|
+
} catch {}
|
|
528
643
|
}
|
|
529
644
|
}
|
|
530
645
|
}
|
|
531
646
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
647
|
+
const valid = validateDownloadedBinary(
|
|
648
|
+
isWin ? cachePath : mirrorToLocal ? localPath : cachePath,
|
|
649
|
+
);
|
|
535
650
|
if (!valid.ok) {
|
|
536
|
-
try {
|
|
651
|
+
try {
|
|
652
|
+
isWin || !mirrorToLocal
|
|
653
|
+
? unlinkSync(cachePath)
|
|
654
|
+
: unlinkSync(localPath);
|
|
655
|
+
} catch {}
|
|
537
656
|
throw new Error(`invalid binary (${valid.reason})`);
|
|
538
657
|
}
|
|
539
658
|
|
|
540
|
-
// Make executable on Unix-like systems
|
|
541
659
|
if (!isWin && mirrorToLocal) {
|
|
542
660
|
chmodSync(localPath, 0o755);
|
|
543
661
|
}
|
|
544
|
-
|
|
545
|
-
console.log(
|
|
546
|
-
|
|
662
|
+
|
|
663
|
+
console.log(
|
|
664
|
+
`✓ Installed ${devBinaryName}${isWin || !mirrorToLocal ? " (cached)" : ""}`,
|
|
665
|
+
);
|
|
547
666
|
if (!isWin && mirrorToLocal) {
|
|
548
|
-
try {
|
|
667
|
+
try {
|
|
668
|
+
await writeCacheAtomic(localPath, cachePath);
|
|
669
|
+
} catch {}
|
|
549
670
|
}
|
|
550
671
|
} catch (error) {
|
|
551
|
-
console.error(`✗ Failed to install ${
|
|
672
|
+
console.error(`✗ Failed to install ${devBinaryName}: ${error.message}`);
|
|
552
673
|
console.error(` Downloaded from: ${downloadUrl}`);
|
|
553
|
-
// Continue with other binaries even if one fails
|
|
554
674
|
}
|
|
555
675
|
}
|
|
556
676
|
|
|
557
|
-
|
|
558
|
-
const mainBinary = `code-${targetTriple}${binaryExt}`;
|
|
677
|
+
const mainBinary = `dev-${targetTriple}${binaryExt}`;
|
|
559
678
|
const mainBinaryPath = join(binDir, mainBinary);
|
|
560
|
-
|
|
561
|
-
if (
|
|
679
|
+
|
|
680
|
+
if (
|
|
681
|
+
existsSync(mainBinaryPath) ||
|
|
682
|
+
existsSync(
|
|
683
|
+
getCachedBinaryPath(version, targetTriple, platform() === "win32"),
|
|
684
|
+
)
|
|
685
|
+
) {
|
|
562
686
|
try {
|
|
563
|
-
const probePath = existsSync(mainBinaryPath)
|
|
687
|
+
const probePath = existsSync(mainBinaryPath)
|
|
688
|
+
? mainBinaryPath
|
|
689
|
+
: getCachedBinaryPath(version, targetTriple, platform() === "win32");
|
|
564
690
|
const stats = statSync(probePath);
|
|
565
|
-
if (!stats.size)
|
|
691
|
+
if (!stats.size)
|
|
692
|
+
throw new Error("binary is empty (download likely failed)");
|
|
566
693
|
const valid = validateDownloadedBinary(probePath);
|
|
567
694
|
if (!valid.ok) {
|
|
568
|
-
console.warn(`⚠ Main
|
|
569
|
-
console.warn(
|
|
695
|
+
console.warn(`⚠ Main dev binary appears invalid: ${valid.reason}`);
|
|
696
|
+
console.warn(
|
|
697
|
+
" Try reinstalling or check your network/proxy settings.",
|
|
698
|
+
);
|
|
570
699
|
}
|
|
571
700
|
} catch (e) {
|
|
572
|
-
console.warn(`⚠ Main
|
|
573
|
-
console.warn(
|
|
701
|
+
console.warn(`⚠ Main dev binary appears invalid: ${e.message}`);
|
|
702
|
+
console.warn(" Try reinstalling or check your network/proxy settings.");
|
|
574
703
|
}
|
|
575
|
-
console.log(
|
|
576
|
-
|
|
577
|
-
// On Windows, we can't use symlinks easily, so update the JS wrapper
|
|
578
|
-
// On Unix, the JS wrapper will find the correct binary
|
|
579
|
-
console.log('✓ Installation complete!');
|
|
704
|
+
console.log("✓ Installation complete!");
|
|
580
705
|
} else {
|
|
581
|
-
console.warn(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
// Handle collisions (e.g., VS Code) and add wrappers. We no longer publish a
|
|
585
|
-
// `code` bin in package.json. Instead, for global installs we create a `code`
|
|
586
|
-
// wrapper only when there is no conflicting `code` earlier on PATH. This avoids
|
|
587
|
-
// hijacking the VS Code CLI while still giving users a friendly name when safe.
|
|
588
|
-
// For upgrades from older versions that published a `code` bin, we also remove
|
|
589
|
-
// our old shim if a conflict is detected.
|
|
590
|
-
if (isGlobal && !isNpx) try {
|
|
591
|
-
const isTTY = process.stdout && process.stdout.isTTY;
|
|
592
|
-
const isWindows = platform() === 'win32';
|
|
593
|
-
const ua = process.env.npm_config_user_agent || '';
|
|
594
|
-
const isBun = ua.includes('bun') || !!process.env.BUN_INSTALL;
|
|
595
|
-
|
|
596
|
-
const installedCmds = new Set(['coder']); // global install always exposes coder via package manager
|
|
597
|
-
const skippedCmds = [];
|
|
598
|
-
|
|
599
|
-
// Helper to resolve all 'code' on PATH
|
|
600
|
-
const resolveAllOnPath = () => {
|
|
601
|
-
try {
|
|
602
|
-
if (isWindows) {
|
|
603
|
-
const out = execSync('where code', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
604
|
-
return out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
605
|
-
}
|
|
606
|
-
let out = '';
|
|
607
|
-
try {
|
|
608
|
-
out = execSync('bash -lc "which -a code 2>/dev/null"', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
609
|
-
} catch {
|
|
610
|
-
try {
|
|
611
|
-
out = execSync('command -v code || true', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
612
|
-
} catch { out = ''; }
|
|
613
|
-
}
|
|
614
|
-
return out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
615
|
-
} catch {
|
|
616
|
-
return [];
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
if (isBun) {
|
|
621
|
-
// Bun creates shims for every bin; if another 'code' exists elsewhere on PATH, remove Bun's shim
|
|
622
|
-
let bunBin = '';
|
|
623
|
-
try {
|
|
624
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
625
|
-
const bunBase = process.env.BUN_INSTALL || join(home, '.bun');
|
|
626
|
-
bunBin = join(bunBase, 'bin');
|
|
627
|
-
} catch {}
|
|
628
|
-
|
|
629
|
-
const bunShim = join(bunBin || '', isWindows ? 'code.cmd' : 'code');
|
|
630
|
-
const candidates = resolveAllOnPath();
|
|
631
|
-
const other = candidates.find(p => p && (!bunBin || !p.startsWith(bunBin)));
|
|
632
|
-
if (other && existsSync(bunShim)) {
|
|
633
|
-
try {
|
|
634
|
-
unlinkSync(bunShim);
|
|
635
|
-
console.log(`✓ Skipped global 'code' shim under Bun (existing: ${other})`);
|
|
636
|
-
skippedCmds.push({ name: 'code', reason: `existing: ${other}` });
|
|
637
|
-
} catch (e) {
|
|
638
|
-
console.log(`⚠ Could not remove Bun shim '${bunShim}': ${e.message}`);
|
|
639
|
-
}
|
|
640
|
-
} else if (!other) {
|
|
641
|
-
// No conflict: create a wrapper that forwards to `coder`
|
|
642
|
-
try {
|
|
643
|
-
const wrapperPath = bunShim;
|
|
644
|
-
if (isWindows) {
|
|
645
|
-
const content = `@echo off\r\n"%~dp0coder" %*\r\n`;
|
|
646
|
-
writeFileSync(wrapperPath, content);
|
|
647
|
-
} else {
|
|
648
|
-
const content = `#!/bin/sh\nexec "$(dirname \"$0\")/coder" "$@"\n`;
|
|
649
|
-
writeFileSync(wrapperPath, content);
|
|
650
|
-
chmodSync(wrapperPath, 0o755);
|
|
651
|
-
}
|
|
652
|
-
console.log("✓ Created 'code' wrapper -> coder (bun)");
|
|
653
|
-
installedCmds.add('code');
|
|
654
|
-
} catch (e) {
|
|
655
|
-
console.log(`⚠ Failed to create 'code' wrapper (bun): ${e.message}`);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Print summary for Bun
|
|
660
|
-
const list = Array.from(installedCmds).sort().join(', ');
|
|
661
|
-
console.log(`Commands installed (bun): ${list}`);
|
|
662
|
-
if (skippedCmds.length) {
|
|
663
|
-
for (const s of skippedCmds) console.error(`Commands skipped: ${s.name} (${s.reason})`);
|
|
664
|
-
console.error('→ Use `coder` to run this tool.');
|
|
665
|
-
}
|
|
666
|
-
// Final friendly usage hint
|
|
667
|
-
if (installedCmds.has('code')) {
|
|
668
|
-
console.log("Use 'code' to launch Code.");
|
|
669
|
-
} else {
|
|
670
|
-
console.log("Use 'coder' to launch Code.");
|
|
671
|
-
}
|
|
672
|
-
} else {
|
|
673
|
-
// npm/pnpm/yarn path
|
|
674
|
-
const globalBin = resolveGlobalBinDir();
|
|
675
|
-
const ourShim = globalBin ? join(globalBin, isWindows ? 'code.cmd' : 'code') : '';
|
|
676
|
-
const candidates = resolveAllOnPath();
|
|
677
|
-
const others = candidates.filter(p => p && (!ourShim || p !== ourShim));
|
|
678
|
-
const ourShimExists = ourShim && existsSync(ourShim);
|
|
679
|
-
const shimLooksOurs = ourShimExists && looksLikeOurCodeShim(ourShim);
|
|
680
|
-
const conflictPaths = [
|
|
681
|
-
...others,
|
|
682
|
-
...(ourShimExists && !shimLooksOurs ? [ourShim] : []),
|
|
683
|
-
];
|
|
684
|
-
const collision = conflictPaths.length > 0;
|
|
685
|
-
|
|
686
|
-
const ensureWrapper = (name, args) => {
|
|
687
|
-
if (!globalBin) return;
|
|
688
|
-
try {
|
|
689
|
-
const wrapperPath = join(globalBin, isWindows ? `${name}.cmd` : name);
|
|
690
|
-
if (isWindows) {
|
|
691
|
-
const content = `@echo off\r\n"%~dp0${collision ? 'coder' : 'code'}" ${args} %*\r\n`;
|
|
692
|
-
writeFileSync(wrapperPath, content);
|
|
693
|
-
} else {
|
|
694
|
-
const content = `#!/bin/sh\nexec "$(dirname \"$0\")/${collision ? 'coder' : 'code'}" ${args} "$@"\n`;
|
|
695
|
-
writeFileSync(wrapperPath, content);
|
|
696
|
-
chmodSync(wrapperPath, 0o755);
|
|
697
|
-
}
|
|
698
|
-
console.log(`✓ Created wrapper '${name}' -> ${collision ? 'coder' : 'code'} ${args}`);
|
|
699
|
-
installedCmds.add(name);
|
|
700
|
-
} catch (e) {
|
|
701
|
-
console.log(`⚠ Failed to create '${name}' wrapper: ${e.message}`);
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
// Always create legacy wrappers so existing scripts keep working
|
|
706
|
-
ensureWrapper('code-tui', '');
|
|
707
|
-
ensureWrapper('code-exec', 'exec');
|
|
708
|
-
|
|
709
|
-
if (collision) {
|
|
710
|
-
console.error('⚠ Detected existing `code` on PATH:');
|
|
711
|
-
for (const p of conflictPaths) console.error(` - ${p}`);
|
|
712
|
-
if (globalBin) {
|
|
713
|
-
try {
|
|
714
|
-
if (ourShimExists) {
|
|
715
|
-
if (shimLooksOurs && others.length > 0) {
|
|
716
|
-
unlinkSync(ourShim);
|
|
717
|
-
console.error(`✓ Removed global 'code' shim (ours) at ${ourShim}`);
|
|
718
|
-
const reason = others[0] || ourShim;
|
|
719
|
-
skippedCmds.push({ name: 'code', reason: `existing: ${reason}` });
|
|
720
|
-
} else if (!shimLooksOurs) {
|
|
721
|
-
console.error(`✓ Skipped global 'code' shim (different CLI at ${ourShim})`);
|
|
722
|
-
const reason = conflictPaths[0] || ourShim;
|
|
723
|
-
skippedCmds.push({ name: 'code', reason: `existing: ${reason}` });
|
|
724
|
-
}
|
|
725
|
-
} else {
|
|
726
|
-
const reason = conflictPaths[0] || 'another command on PATH';
|
|
727
|
-
skippedCmds.push({ name: 'code', reason: `existing: ${reason}` });
|
|
728
|
-
}
|
|
729
|
-
} catch (e) {
|
|
730
|
-
console.error(`⚠ Could not remove npm shim '${ourShim}': ${e.message}`);
|
|
731
|
-
}
|
|
732
|
-
console.error('→ Use `coder` to run this tool.');
|
|
733
|
-
} else {
|
|
734
|
-
console.log('Note: could not determine npm global bin; skipping alias creation.');
|
|
735
|
-
}
|
|
736
|
-
} else {
|
|
737
|
-
// No collision; ensure a 'code' wrapper exists forwarding to 'coder'
|
|
738
|
-
if (globalBin) {
|
|
739
|
-
try {
|
|
740
|
-
const content = isWindows
|
|
741
|
-
? `@echo off\r\n"%~dp0coder" %*\r\n`
|
|
742
|
-
: `#!/bin/sh\nexec "$(dirname \"$0\")/coder" "$@"\n`;
|
|
743
|
-
writeFileSync(ourShim, content);
|
|
744
|
-
if (!isWindows) chmodSync(ourShim, 0o755);
|
|
745
|
-
console.log("✓ Created 'code' wrapper -> coder");
|
|
746
|
-
installedCmds.add('code');
|
|
747
|
-
} catch (e) {
|
|
748
|
-
console.log(`⚠ Failed to create 'code' wrapper: ${e.message}`);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// Print summary for npm/pnpm/yarn
|
|
754
|
-
const list = Array.from(installedCmds).sort().join(', ');
|
|
755
|
-
console.log(`Commands installed: ${list}`);
|
|
756
|
-
if (skippedCmds.length) {
|
|
757
|
-
for (const s of skippedCmds) console.log(`Commands skipped: ${s.name} (${s.reason})`);
|
|
758
|
-
}
|
|
759
|
-
// Final friendly usage hint
|
|
760
|
-
if (installedCmds.has('code')) {
|
|
761
|
-
console.log("Use 'code' to launch Code.");
|
|
762
|
-
} else {
|
|
763
|
-
console.log("Use 'coder' to launch Code.");
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
} catch {
|
|
767
|
-
// non-fatal
|
|
706
|
+
console.warn(
|
|
707
|
+
"⚠ Main dev binary not found. You may need to build from source.",
|
|
708
|
+
);
|
|
768
709
|
}
|
|
769
710
|
}
|
|
770
711
|
|
|
@@ -779,8 +720,8 @@ function isExecutedDirectly() {
|
|
|
779
720
|
}
|
|
780
721
|
|
|
781
722
|
if (isExecutedDirectly()) {
|
|
782
|
-
runPostinstall().catch(error => {
|
|
783
|
-
console.error(
|
|
723
|
+
runPostinstall().catch((error) => {
|
|
724
|
+
console.error("Installation failed:", error);
|
|
784
725
|
process.exit(1);
|
|
785
726
|
});
|
|
786
727
|
}
|