@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/postinstall.js CHANGED
@@ -1,100 +1,100 @@
1
1
  #!/usr/bin/env node
2
- // Non-functional change to trigger release workflow
3
-
4
- import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync, unlinkSync, statSync, openSync, closeSync, copyFileSync, fsyncSync, renameSync, realpathSync } from 'fs';
5
- import { join, dirname, resolve } from 'path';
6
- import { fileURLToPath } from 'url';
7
- import { get } from 'https';
8
- import { platform, arch, tmpdir } from 'os';
9
- import { execSync } from 'child_process';
10
- import { createRequire } from 'module';
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
- 'darwin': 'apple-darwin',
18
- 'linux': 'unknown-linux-musl', // Default to musl for better compatibility
19
- 'win32': 'pc-windows-msvc'
32
+ darwin: "apple-darwin",
33
+ linux: "unknown-linux-musl",
34
+ win32: "pc-windows-msvc",
20
35
  };
21
-
36
+
22
37
  const archMap = {
23
- 'x64': 'x86_64',
24
- 'arm64': 'aarch64'
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 === 'win32') {
40
- base = process.env.LOCALAPPDATA || join(home, 'AppData', 'Local');
41
- } else if (plt === 'darwin') {
42
- base = join(home, 'Library', 'Caches');
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, '.cache');
57
+ base = process.env.XDG_CACHE_HOME || join(home, ".cache");
45
58
  }
46
- const dir = join(base, 'hanzo', 'dev', version);
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 ? '.exe' : '';
65
+ const ext = isWindows ? ".exe" : "";
53
66
  const cacheDir = getCacheDir(version);
54
- return join(cacheDir, `code-${targetTriple}${ext}`);
67
+ return join(cacheDir, `dev-${targetTriple}${ext}`);
55
68
  }
56
69
 
57
- const CODE_SHIM_SIGNATURES = [
58
- '@hanzo/dev',
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 contents = readFileSync(path, 'utf8');
71
- return shimContentsLookOurs(contents);
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('/proc/mounts', 'utf8').split(/\n/).filter(Boolean);
88
- let best = { mount: '/', type: 'unknown', len: 1 };
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) best = { mount: mnt, type: typ, len: mnt.length };
91
+ if (p.startsWith(mnt) && mnt.length > best.len)
92
+ best = { mount: mnt, type: typ, len: mnt.length };
95
93
  }
96
- return best.type === 'drvfs' || best.type === 'cifs';
97
- } catch { return false; }
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)) { try { mkdirSync(dir, { recursive: true }); } catch {} }
109
- const tmp = cachePath + '.tmp-' + Math.random().toString(36).slice(2, 8);
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 { const fd = openSync(tmp, 'r'); try { fsyncSync(fd); } finally { closeSync(fd); } } catch {}
112
- // Retry with exponential backoff up to ~1.6s total
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)) { try { unlinkSync(cachePath); } catch {} }
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)) { try { unlinkSync(cachePath); } catch {} }
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 === 'win32' ? prefixPath : join(prefixPath, 'bin');
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: ['ignore', 'pipe', 'ignore'],
161
+ stdio: ["ignore", "pipe", "ignore"],
144
162
  shell: true,
145
- }).toString().trim();
163
+ })
164
+ .toString()
165
+ .trim();
146
166
  } catch {
147
- return '';
167
+ return "";
148
168
  }
149
169
  };
150
170
 
151
- const prefixFromNpm = fromPrefix(tryExec('npm prefix -g'));
171
+ const prefixFromNpm = fromPrefix(tryExec("npm prefix -g"));
152
172
  if (prefixFromNpm) return prefixFromNpm;
153
173
 
154
- const binFromNpm = tryExec('npm bin -g');
174
+ const binFromNpm = tryExec("npm bin -g");
155
175
  if (binFromNpm) return binFromNpm;
156
176
 
157
- if (userAgent.includes('pnpm')) {
158
- const pnpmBin = tryExec('pnpm bin --global');
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('yarn')) {
165
- const yarnBin = tryExec('yarn global bin');
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 = () => new Promise((resolve, reject) => {
176
- const attempt = (currentUrl, redirectsLeft) => {
177
- const req = get(currentUrl, (response) => {
178
- const status = response.statusCode || 0;
179
- const location = response.headers.location;
180
-
181
- if ((status === 301 || status === 302 || status === 303 || status === 307 || status === 308) && location) {
182
- if (redirectsLeft <= 0) {
183
- reject(new Error(`Too many redirects while downloading ${currentUrl}`));
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
- if (status === 200) {
191
- const expected = parseInt(response.headers['content-length'] || '0', 10) || 0;
192
- let bytes = 0;
193
- let timer;
194
- const timeoutMs = 30000; // 30s inactivity timeout
195
-
196
- const resetTimer = () => {
197
- if (timer) clearTimeout(timer);
198
- timer = setTimeout(() => {
199
- req.destroy(new Error('download stalled'));
200
- }, timeoutMs);
201
- };
202
-
203
- resetTimer();
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
- const file = createWriteStream(dest);
210
- response.pipe(file);
211
- file.on('finish', () => {
212
- if (timer) clearTimeout(timer);
213
- file.close();
214
- if (expected && bytes !== expected) {
215
- try { unlinkSync(dest); } catch {}
216
- reject(new Error(`incomplete download: got ${bytes} of ${expected} bytes`));
217
- } else if (bytes === 0) {
218
- try { unlinkSync(dest); } catch {}
219
- reject(new Error('empty download'));
220
- } else {
221
- resolve();
222
- }
223
- });
224
- file.on('error', (err) => {
225
- if (timer) clearTimeout(timer);
226
- try { unlinkSync(dest); } catch {}
227
- reject(err);
228
- });
229
- } else {
230
- reject(new Error(`Failed to download: HTTP ${status}`));
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
- req.on('error', (err) => {
235
- try { unlinkSync(dest); } catch {}
236
- reject(err);
237
- });
273
+ req.on("error", (err) => {
274
+ try {
275
+ unlinkSync(dest);
276
+ } catch {}
277
+ reject(err);
278
+ });
238
279
 
239
- // Absolute request timeout to avoid hanging forever
240
- req.setTimeout(120000, () => {
241
- req.destroy(new Error('download timed out'));
242
- });
243
- };
280
+ req.setTimeout(120000, () => {
281
+ req.destroy(new Error("download timed out"));
282
+ });
283
+ };
244
284
 
245
- attempt(url, maxRedirects);
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: 'empty or not a regular file' };
305
+ return { ok: false, reason: "empty or not a regular file" };
266
306
  }
267
- const fd = openSync(p, 'r');
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: 'too short' };
311
+ if (n < 2) return { ok: false, reason: "too short" };
272
312
  const plt = platform();
273
- if (plt === 'win32') {
274
- if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: 'invalid PE header (missing MZ)' };
275
- } else if (plt === 'linux' || plt === 'android') {
276
- if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: 'invalid ELF header' };
277
- } else if (plt === 'darwin') {
278
- const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
279
- (buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
280
- if (!isMachO) return { ok: false, reason: 'invalid Mach-O header' };
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.CODE_POSTINSTALL_DRY_RUN === '1') {
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.CODE_RUNTIME_POSTINSTALL = process.env.CODE_RUNTIME_POSTINSTALL || '1';
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() === 'win32';
332
- const binaryExt = isWindows ? '.exe' : '';
333
-
334
- const binDir = join(__dirname, 'bin');
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
- // Get package version - use readFileSync for compatibility
340
- const packageJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
370
+
371
+ const packageJson = JSON.parse(
372
+ readFileSync(join(__dirname, "package.json"), "utf8"),
373
+ );
341
374
  const version = packageJson.version;
342
-
343
- // Download only the primary binary; we'll create wrappers for legacy names.
344
- const binaries = ['code'];
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 localPath = join(binDir, binaryName);
383
+ const devBinaryName = `dev-${targetTriple}${binaryExt}`;
384
+ const localPath = join(binDir, devBinaryName);
351
385
  const cachePath = getCachedBinaryPath(version, targetTriple, isWindows);
352
-
353
- // On Windows we avoid placing the executable inside node_modules to prevent
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 = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
367
- const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
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 { chmodSync(localPath, 0o755); } catch {}
406
+ try {
407
+ chmodSync(localPath, 0o755);
408
+ } catch {}
371
409
  }
372
- console.log(`✓ ${binaryName} ready from user cache`);
373
- continue; // next binary
410
+ console.log(`✓ ${devBinaryName} ready from user cache`);
411
+ continue;
374
412
  }
375
413
  }
376
414
  } catch {
377
- // Ignore cache errors and fall through to normal paths
415
+ // Ignore cache errors
378
416
  }
379
-
380
- // First try platform package via npm optionalDependencies (fast path on npm CDN).
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 '@hanzo/dev-win32-x64';
422
+ if (isWindows) return "@hanzo/dev-win32-x64";
385
423
  const plt = platform();
386
424
  const cpu = arch();
387
- if (plt === 'darwin' && cpu === 'arm64') return '@hanzo/dev-darwin-arm64';
388
- if (plt === 'darwin' && cpu === 'x64') return '@hanzo/dev-darwin-x64';
389
- if (plt === 'linux' && cpu === 'x64') return '@hanzo/dev-linux-x64-musl';
390
- if (plt === 'linux' && cpu === 'arm64') return '@hanzo/dev-linux-arm64-musl';
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
- // Expect binary inside platform package bin directory
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(`platform package missing binary: ${platformPkg.name}`);
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 = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
415
- const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
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 { chmodSync(localPath, 0o755); } catch {}
467
+ try {
468
+ chmodSync(localPath, 0o755);
469
+ } catch {}
419
470
  }
420
- console.log(`✓ Installed ${binaryName} from ${platformPkg.name} (cached)`);
421
- continue; // next binary
471
+ console.log(
472
+ `✓ Installed ${devBinaryName} from ${platformPkg.name} (cached)`,
473
+ );
474
+ continue;
422
475
  } catch (e) {
423
- console.warn(`⚠ Failed platform package install (${e.message}), falling back to GitHub download`);
476
+ console.warn(
477
+ `⚠ Failed platform package install (${e.message}), falling back to GitHub download`,
478
+ );
424
479
  }
425
480
  }
426
481
 
427
- // Decide archive format per OS with fallback on macOS/Linux:
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
- if (platform() !== 'linux') return false;
484
+ const detectedWSL = isWSL();
485
+ const binDirReal = (() => {
433
486
  try {
434
- const ver = readFileSync('/proc/version', 'utf8').toLowerCase();
435
- return ver.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
436
- } catch { return false; }
487
+ return realpathSync(binDir);
488
+ } catch {
489
+ return binDir;
490
+ }
437
491
  })();
438
- const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
439
- const mirrorToLocal = !(isWin || (detectedWSL && isPathOnWindowsFs(binDirReal)));
492
+ const mirrorToLocal = !(
493
+ isWin ||
494
+ (detectedWSL && isPathOnWindowsFs(binDirReal))
495
+ );
440
496
  let useZst = false;
441
497
  if (!isWin) {
442
498
  try {
443
- execSync('zstd --version', { stdio: 'ignore', shell: true });
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 ? `${binaryName}.zip` : (useZst ? `${binaryName}.zst` : `${binaryName}.tar.gz`);
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); // Windows or WSL-on-NTFS
455
- let safeTempDir = needsIsolation ? join(tmpdir(), 'hanzo', 'dev', version) : binDir;
456
- // Ensure staging dir exists; if tmp fails (permissions/space), fall back to user cache.
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)) mkdirSync(safeTempDir, { recursive: true });
520
+ if (!existsSync(safeTempDir))
521
+ mkdirSync(safeTempDir, { recursive: true });
460
522
  } catch {
461
523
  try {
462
524
  safeTempDir = getCacheDir(version);
463
- if (!existsSync(safeTempDir)) mkdirSync(safeTempDir, { recursive: true });
525
+ if (!existsSync(safeTempDir))
526
+ mkdirSync(safeTempDir, { recursive: true });
464
527
  } catch {}
465
528
  }
466
529
  }
467
- const tmpPath = join(needsIsolation ? safeTempDir : binDir, `.${archiveName}.part`);
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 = process.env.SystemRoot || process.env.windir || 'C:\\Windows';
475
- const psFull = join(sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
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
- // Attempt full-path powershell.exe
479
- try { execSync(`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {}
480
- // Fallback to powershell in PATH
481
- if (!ok) { try { execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
482
- // Fallback to pwsh (PowerShell 7)
483
- if (!ok) { try { execSync(`pwsh -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
484
- // Final fallback: bsdtar can extract .zip
485
- if (!ok) { execSync(`tar -xf "${tmpPath}" -C "${unzipDest}"`, { stdio: 'ignore', shell: true }); }
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 { unlinkSync(tmpPath); } catch {}
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
- await writeCacheAtomic(extractedPath, cachePath);
495
- try { unlinkSync(extractedPath); } catch {}
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
- const outPath = mirrorToLocal ? localPath : join(safeTempDir, binaryName);
504
- execSync(`zstd -d '${tmpPath}' -o '${outPath}'`, { stdio: 'ignore', shell: true });
602
+ execSync(`zstd -d '${tmpPath}' -o '${downloadedPath}'`, {
603
+ stdio: "ignore",
604
+ shell: true,
605
+ });
505
606
  } catch (e) {
506
- try { unlinkSync(tmpPath); } catch {}
507
- throw new Error(`failed to decompress .zst (need zstd CLI): ${e.message}`);
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 { unlinkSync(tmpPath); } catch {}
614
+ try {
615
+ unlinkSync(tmpPath);
616
+ } catch {}
510
617
  } else {
511
- // Extract .tar.gz using system tar
512
618
  try {
513
- const dest = mirrorToLocal ? binDir : safeTempDir;
514
- execSync(`tar -xzf '${tmpPath}' -C '${dest}'`, { stdio: 'ignore', shell: true });
619
+ execSync(`tar -xzf '${tmpPath}' -C '${binDir}'`, {
620
+ stdio: "ignore",
621
+ shell: true,
622
+ });
515
623
  } catch (e) {
516
- try { unlinkSync(tmpPath); } catch {}
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
- const extractedPath = join(safeTempDir, binaryName);
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 { unlinkSync(extractedPath); } catch {}
526
- } catch (e) {
527
- throw new Error(`failed to move binary to cache: ${e.message}`);
640
+ try {
641
+ unlinkSync(extractedPath);
642
+ } catch {}
528
643
  }
529
644
  }
530
645
  }
531
646
 
532
- // Validate header to avoid corrupt binaries causing spawn EFTYPE/ENOEXEC
533
-
534
- const valid = validateDownloadedBinary(isWin ? cachePath : (mirrorToLocal ? localPath : cachePath));
647
+ const valid = validateDownloadedBinary(
648
+ isWin ? cachePath : mirrorToLocal ? localPath : cachePath,
649
+ );
535
650
  if (!valid.ok) {
536
- try { (isWin || !mirrorToLocal) ? unlinkSync(cachePath) : unlinkSync(localPath); } catch {}
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(`✓ Installed ${binaryName}${(isWin || !mirrorToLocal) ? ' (cached)' : ''}`);
546
- // Ensure persistent cache holds the binary (already true for Windows path)
662
+
663
+ console.log(
664
+ `✓ Installed ${devBinaryName}${isWin || !mirrorToLocal ? " (cached)" : ""}`,
665
+ );
547
666
  if (!isWin && mirrorToLocal) {
548
- try { await writeCacheAtomic(localPath, cachePath); } catch {}
667
+ try {
668
+ await writeCacheAtomic(localPath, cachePath);
669
+ } catch {}
549
670
  }
550
671
  } catch (error) {
551
- console.error(`✗ Failed to install ${binaryName}: ${error.message}`);
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
- // Create platform-specific symlink/copy for main binary
558
- const mainBinary = `code-${targetTriple}${binaryExt}`;
677
+ const mainBinary = `dev-${targetTriple}${binaryExt}`;
559
678
  const mainBinaryPath = join(binDir, mainBinary);
560
-
561
- if (existsSync(mainBinaryPath) || existsSync(getCachedBinaryPath(version, targetTriple, platform() === 'win32'))) {
679
+
680
+ if (
681
+ existsSync(mainBinaryPath) ||
682
+ existsSync(
683
+ getCachedBinaryPath(version, targetTriple, platform() === "win32"),
684
+ )
685
+ ) {
562
686
  try {
563
- const probePath = existsSync(mainBinaryPath) ? mainBinaryPath : getCachedBinaryPath(version, targetTriple, platform() === 'win32');
687
+ const probePath = existsSync(mainBinaryPath)
688
+ ? mainBinaryPath
689
+ : getCachedBinaryPath(version, targetTriple, platform() === "win32");
564
690
  const stats = statSync(probePath);
565
- if (!stats.size) throw new Error('binary is empty (download likely failed)');
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 code binary appears invalid: ${valid.reason}`);
569
- console.warn(' Try reinstalling or check your network/proxy settings.');
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 code binary appears invalid: ${e.message}`);
573
- console.warn(' Try reinstalling or check your network/proxy settings.');
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('Setting up main code binary...');
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('⚠ Main code binary not found. You may need to build from source.');
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('Installation failed:', error);
723
+ runPostinstall().catch((error) => {
724
+ console.error("Installation failed:", error);
784
725
  process.exit(1);
785
726
  });
786
727
  }