@hanzo/dev 3.0.1 → 3.0.2
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 +358 -0
- package/bin/dev.js +90 -97
- package/package.json +15 -27
- package/postinstall.js +229 -305
- package/scripts/preinstall.js +69 -0
- package/scripts/windows-cleanup.ps1 +31 -0
package/postinstall.js
CHANGED
|
@@ -1,37 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// Postinstall script for @hanzo/dev
|
|
3
3
|
|
|
4
|
-
import { existsSync, mkdirSync, createWriteStream, chmodSync, readFileSync, readSync, writeFileSync, unlinkSync, statSync, openSync, closeSync, copyFileSync } from 'fs';
|
|
5
|
-
import { join, dirname } from 'path';
|
|
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
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { get } from 'https';
|
|
8
|
-
import { platform, arch } from 'os';
|
|
8
|
+
import { platform, arch, tmpdir } from 'os';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
10
|
import { createRequire } from 'module';
|
|
11
11
|
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
|
|
14
|
-
// Map Node.js platform/arch to Rust target triples
|
|
15
14
|
function getTargetTriple() {
|
|
16
15
|
const platformMap = {
|
|
17
16
|
'darwin': 'apple-darwin',
|
|
18
|
-
'linux': 'unknown-linux-musl',
|
|
17
|
+
'linux': 'unknown-linux-musl',
|
|
19
18
|
'win32': 'pc-windows-msvc'
|
|
20
19
|
};
|
|
21
|
-
|
|
20
|
+
|
|
22
21
|
const archMap = {
|
|
23
22
|
'x64': 'x86_64',
|
|
24
23
|
'arm64': 'aarch64'
|
|
25
24
|
};
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
const rustArch = archMap[arch()] || arch();
|
|
28
27
|
const rustPlatform = platformMap[platform()] || platform();
|
|
29
|
-
|
|
28
|
+
|
|
30
29
|
return `${rustArch}-${rustPlatform}`;
|
|
31
30
|
}
|
|
32
31
|
|
|
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
32
|
function getCacheDir(version) {
|
|
36
33
|
const plt = platform();
|
|
37
34
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
@@ -43,7 +40,7 @@ function getCacheDir(version) {
|
|
|
43
40
|
} else {
|
|
44
41
|
base = process.env.XDG_CACHE_HOME || join(home, '.cache');
|
|
45
42
|
}
|
|
46
|
-
const dir = join(base, '
|
|
43
|
+
const dir = join(base, 'hanzo', 'dev', version);
|
|
47
44
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
48
45
|
return dir;
|
|
49
46
|
}
|
|
@@ -51,7 +48,99 @@ function getCacheDir(version) {
|
|
|
51
48
|
function getCachedBinaryPath(version, targetTriple, isWindows) {
|
|
52
49
|
const ext = isWindows ? '.exe' : '';
|
|
53
50
|
const cacheDir = getCacheDir(version);
|
|
54
|
-
return join(cacheDir, `
|
|
51
|
+
return join(cacheDir, `dev-${targetTriple}${ext}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isWSL() {
|
|
55
|
+
if (platform() !== 'linux') return false;
|
|
56
|
+
try {
|
|
57
|
+
const ver = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
58
|
+
return ver.includes('microsoft') || !!process.env.WSL_DISTRO_NAME;
|
|
59
|
+
} catch { return false; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isPathOnWindowsFs(p) {
|
|
63
|
+
try {
|
|
64
|
+
const mounts = readFileSync('/proc/mounts', 'utf8').split(/\n/).filter(Boolean);
|
|
65
|
+
let best = { mount: '/', type: 'unknown', len: 1 };
|
|
66
|
+
for (const line of mounts) {
|
|
67
|
+
const parts = line.split(' ');
|
|
68
|
+
if (parts.length < 3) continue;
|
|
69
|
+
const mnt = parts[1];
|
|
70
|
+
const typ = parts[2];
|
|
71
|
+
if (p.startsWith(mnt) && mnt.length > best.len) best = { mount: mnt, type: typ, len: mnt.length };
|
|
72
|
+
}
|
|
73
|
+
return best.type === 'drvfs' || best.type === 'cifs';
|
|
74
|
+
} catch { return false; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function writeCacheAtomic(srcPath, cachePath) {
|
|
78
|
+
try {
|
|
79
|
+
if (existsSync(cachePath)) {
|
|
80
|
+
const ok = validateDownloadedBinary(cachePath).ok;
|
|
81
|
+
if (ok) return;
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
84
|
+
const dir = dirname(cachePath);
|
|
85
|
+
if (!existsSync(dir)) { try { mkdirSync(dir, { recursive: true }); } catch {} }
|
|
86
|
+
const tmp = cachePath + '.tmp-' + Math.random().toString(36).slice(2, 8);
|
|
87
|
+
copyFileSync(srcPath, tmp);
|
|
88
|
+
try { const fd = openSync(tmp, 'r'); try { fsyncSync(fd); } finally { closeSync(fd); } } catch {}
|
|
89
|
+
const delays = [100, 200, 400, 800, 1200, 1600];
|
|
90
|
+
for (let i = 0; i < delays.length; i++) {
|
|
91
|
+
try {
|
|
92
|
+
if (existsSync(cachePath)) { try { unlinkSync(cachePath); } catch {} }
|
|
93
|
+
renameSync(tmp, cachePath);
|
|
94
|
+
return;
|
|
95
|
+
} catch {
|
|
96
|
+
await new Promise(r => setTimeout(r, delays[i]));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (existsSync(cachePath)) { try { unlinkSync(cachePath); } catch {} }
|
|
100
|
+
renameSync(tmp, cachePath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveGlobalBinDir() {
|
|
104
|
+
const plt = platform();
|
|
105
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
106
|
+
|
|
107
|
+
const fromPrefix = (prefixPath) => {
|
|
108
|
+
if (!prefixPath) return '';
|
|
109
|
+
return plt === 'win32' ? prefixPath : join(prefixPath, 'bin');
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const prefixEnv = process.env.npm_config_prefix || process.env.PREFIX || '';
|
|
113
|
+
const direct = fromPrefix(prefixEnv);
|
|
114
|
+
if (direct) return direct;
|
|
115
|
+
|
|
116
|
+
const tryExec = (command) => {
|
|
117
|
+
try {
|
|
118
|
+
return execSync(command, {
|
|
119
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
120
|
+
shell: true,
|
|
121
|
+
}).toString().trim();
|
|
122
|
+
} catch {
|
|
123
|
+
return '';
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const prefixFromNpm = fromPrefix(tryExec('npm prefix -g'));
|
|
128
|
+
if (prefixFromNpm) return prefixFromNpm;
|
|
129
|
+
|
|
130
|
+
const binFromNpm = tryExec('npm bin -g');
|
|
131
|
+
if (binFromNpm) return binFromNpm;
|
|
132
|
+
|
|
133
|
+
if (userAgent.includes('pnpm')) {
|
|
134
|
+
const pnpmBin = tryExec('pnpm bin --global');
|
|
135
|
+
if (pnpmBin) return pnpmBin;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (userAgent.includes('yarn')) {
|
|
139
|
+
const yarnBin = tryExec('yarn global bin');
|
|
140
|
+
if (yarnBin) return yarnBin;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return '';
|
|
55
144
|
}
|
|
56
145
|
|
|
57
146
|
async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
|
@@ -76,7 +165,7 @@ async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
|
|
76
165
|
const expected = parseInt(response.headers['content-length'] || '0', 10) || 0;
|
|
77
166
|
let bytes = 0;
|
|
78
167
|
let timer;
|
|
79
|
-
const timeoutMs = 30000;
|
|
168
|
+
const timeoutMs = 30000;
|
|
80
169
|
|
|
81
170
|
const resetTimer = () => {
|
|
82
171
|
if (timer) clearTimeout(timer);
|
|
@@ -121,7 +210,6 @@ async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
|
|
121
210
|
reject(err);
|
|
122
211
|
});
|
|
123
212
|
|
|
124
|
-
// Absolute request timeout to avoid hanging forever
|
|
125
213
|
req.setTimeout(120000, () => {
|
|
126
214
|
req.destroy(new Error('download timed out'));
|
|
127
215
|
});
|
|
@@ -173,102 +261,74 @@ function validateDownloadedBinary(p) {
|
|
|
173
261
|
}
|
|
174
262
|
}
|
|
175
263
|
|
|
176
|
-
async function
|
|
177
|
-
|
|
178
|
-
|
|
264
|
+
export async function runPostinstall(options = {}) {
|
|
265
|
+
const { skipGlobalAlias = false, invokedByRuntime = false } = options;
|
|
266
|
+
if (process.env.DEV_POSTINSTALL_DRY_RUN === '1') {
|
|
267
|
+
return { skipped: true };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (invokedByRuntime) {
|
|
271
|
+
process.env.DEV_RUNTIME_POSTINSTALL = process.env.DEV_RUNTIME_POSTINSTALL || '1';
|
|
272
|
+
}
|
|
273
|
+
|
|
179
274
|
const ua = process.env.npm_config_user_agent || '';
|
|
180
275
|
const isNpx = ua.includes('npx');
|
|
181
276
|
const isGlobal = process.env.npm_config_global === 'true';
|
|
182
|
-
if (isGlobal && !isNpx) {
|
|
183
|
-
try {
|
|
184
|
-
const whichCmd = process.platform === 'win32' ? 'where code' : 'command -v code || which code || true';
|
|
185
|
-
const resolved = execSync(whichCmd, { stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform !== 'win32' }).toString().split(/\r?\n/).filter(Boolean)[0];
|
|
186
|
-
if (resolved) {
|
|
187
|
-
let contents = '';
|
|
188
|
-
try {
|
|
189
|
-
contents = readFileSync(resolved, 'utf8');
|
|
190
|
-
} catch {
|
|
191
|
-
contents = '';
|
|
192
|
-
}
|
|
193
|
-
const looksLikeOurs = contents.includes('@just-every/code') || contents.includes('bin/coder.js');
|
|
194
|
-
if (!looksLikeOurs) {
|
|
195
|
-
console.warn('[notice] Found an existing `code` on PATH at:');
|
|
196
|
-
console.warn(` ${resolved}`);
|
|
197
|
-
console.warn('[notice] We will still install our CLI, also available as `coder`.');
|
|
198
|
-
console.warn(' If `code` runs another tool, prefer using: coder');
|
|
199
|
-
console.warn(' Or run our CLI explicitly via: npx -y @just-every/code');
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
// Ignore detection failures; proceed with install.
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
277
|
|
|
207
278
|
const targetTriple = getTargetTriple();
|
|
208
279
|
const isWindows = platform() === 'win32';
|
|
209
280
|
const binaryExt = isWindows ? '.exe' : '';
|
|
210
|
-
|
|
281
|
+
|
|
211
282
|
const binDir = join(__dirname, 'bin');
|
|
212
283
|
if (!existsSync(binDir)) {
|
|
213
284
|
mkdirSync(binDir, { recursive: true });
|
|
214
285
|
}
|
|
215
|
-
|
|
216
|
-
// Get package version - use readFileSync for compatibility
|
|
286
|
+
|
|
217
287
|
const packageJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
|
|
218
288
|
const version = packageJson.version;
|
|
219
|
-
|
|
220
|
-
//
|
|
289
|
+
|
|
290
|
+
// The release produces 'code-*' binaries; we'll download and rename to 'dev-*'
|
|
221
291
|
const binaries = ['code'];
|
|
222
|
-
|
|
223
|
-
console.log(`Installing @
|
|
224
|
-
|
|
292
|
+
|
|
293
|
+
console.log(`Installing @hanzo/dev v${version} for ${targetTriple}...`);
|
|
294
|
+
|
|
225
295
|
for (const binary of binaries) {
|
|
226
296
|
const binaryName = `${binary}-${targetTriple}${binaryExt}`;
|
|
227
|
-
const
|
|
297
|
+
const devBinaryName = `dev-${targetTriple}${binaryExt}`;
|
|
298
|
+
const localPath = join(binDir, devBinaryName);
|
|
228
299
|
const cachePath = getCachedBinaryPath(version, targetTriple, isWindows);
|
|
229
|
-
|
|
230
|
-
// Skip if already exists and has correct permissions
|
|
231
|
-
if (existsSync(localPath)) {
|
|
232
|
-
// Always try to fix permissions on Unix-like systems
|
|
233
|
-
if (!isWindows) {
|
|
234
|
-
try {
|
|
235
|
-
chmodSync(localPath, 0o755);
|
|
236
|
-
console.log(`✓ ${binaryName} already exists (permissions fixed)`);
|
|
237
|
-
} catch (e) {
|
|
238
|
-
console.log(`✓ ${binaryName} already exists`);
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
console.log(`✓ ${binaryName} already exists`);
|
|
242
|
-
}
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
300
|
|
|
246
|
-
// Fast path: if a valid cached binary exists
|
|
301
|
+
// Fast path: if a valid cached binary exists, reuse it
|
|
247
302
|
try {
|
|
248
303
|
if (existsSync(cachePath)) {
|
|
249
304
|
const valid = validateDownloadedBinary(cachePath);
|
|
250
305
|
if (valid.ok) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
306
|
+
const wsl = isWSL();
|
|
307
|
+
const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
|
|
308
|
+
const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
|
|
309
|
+
if (mirrorToLocal) {
|
|
310
|
+
copyFileSync(cachePath, localPath);
|
|
311
|
+
try { chmodSync(localPath, 0o755); } catch {}
|
|
312
|
+
}
|
|
313
|
+
console.log(`✓ ${devBinaryName} ready from user cache`);
|
|
314
|
+
continue;
|
|
255
315
|
}
|
|
256
316
|
}
|
|
257
317
|
} catch {
|
|
258
|
-
// Ignore cache errors
|
|
318
|
+
// Ignore cache errors
|
|
259
319
|
}
|
|
260
|
-
|
|
261
|
-
//
|
|
320
|
+
|
|
321
|
+
// Try platform package via npm optionalDependencies
|
|
262
322
|
const require = createRequire(import.meta.url);
|
|
263
323
|
const platformPkg = (() => {
|
|
264
324
|
const name = (() => {
|
|
265
|
-
if (isWindows) return '@
|
|
325
|
+
if (isWindows) return '@hanzo/dev-win32-x64';
|
|
266
326
|
const plt = platform();
|
|
267
327
|
const cpu = arch();
|
|
268
|
-
if (plt === 'darwin' && cpu === 'arm64') return '@
|
|
269
|
-
if (plt === 'darwin' && cpu === 'x64') return '@
|
|
270
|
-
if (plt === 'linux' && cpu === 'x64') return '@
|
|
271
|
-
if (plt === 'linux' && cpu === 'arm64') return '@
|
|
328
|
+
if (plt === 'darwin' && cpu === 'arm64') return '@hanzo/dev-darwin-arm64';
|
|
329
|
+
if (plt === 'darwin' && cpu === 'x64') return '@hanzo/dev-darwin-x64';
|
|
330
|
+
if (plt === 'linux' && cpu === 'x64') return '@hanzo/dev-linux-x64-musl';
|
|
331
|
+
if (plt === 'linux' && cpu === 'arm64') return '@hanzo/dev-linux-arm64-musl';
|
|
272
332
|
return null;
|
|
273
333
|
})();
|
|
274
334
|
if (!name) return null;
|
|
@@ -283,30 +343,30 @@ async function main() {
|
|
|
283
343
|
|
|
284
344
|
if (platformPkg) {
|
|
285
345
|
try {
|
|
286
|
-
|
|
287
|
-
const src = join(platformPkg.dir, 'bin', binaryName);
|
|
346
|
+
const src = join(platformPkg.dir, 'bin', devBinaryName);
|
|
288
347
|
if (!existsSync(src)) {
|
|
289
348
|
throw new Error(`platform package missing binary: ${platformPkg.name}`);
|
|
290
349
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
continue;
|
|
350
|
+
await writeCacheAtomic(src, cachePath);
|
|
351
|
+
const wsl = isWSL();
|
|
352
|
+
const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
|
|
353
|
+
const mirrorToLocal = !(isWindows || (wsl && isPathOnWindowsFs(binDirReal)));
|
|
354
|
+
if (mirrorToLocal) {
|
|
355
|
+
copyFileSync(cachePath, localPath);
|
|
356
|
+
try { chmodSync(localPath, 0o755); } catch {}
|
|
357
|
+
}
|
|
358
|
+
console.log(`✓ Installed ${devBinaryName} from ${platformPkg.name} (cached)`);
|
|
359
|
+
continue;
|
|
301
360
|
} catch (e) {
|
|
302
361
|
console.warn(`⚠ Failed platform package install (${e.message}), falling back to GitHub download`);
|
|
303
362
|
}
|
|
304
363
|
}
|
|
305
364
|
|
|
306
|
-
//
|
|
307
|
-
// - Windows: .zip
|
|
308
|
-
// - macOS/Linux: prefer .zst if `zstd` CLI is available; otherwise use .tar.gz
|
|
365
|
+
// Download from GitHub release
|
|
309
366
|
const isWin = isWindows;
|
|
367
|
+
const detectedWSL = isWSL();
|
|
368
|
+
const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
|
|
369
|
+
const mirrorToLocal = !(isWin || (detectedWSL && isPathOnWindowsFs(binDirReal)));
|
|
310
370
|
let useZst = false;
|
|
311
371
|
if (!isWin) {
|
|
312
372
|
try {
|
|
@@ -317,35 +377,61 @@ async function main() {
|
|
|
317
377
|
}
|
|
318
378
|
}
|
|
319
379
|
const archiveName = isWin ? `${binaryName}.zip` : (useZst ? `${binaryName}.zst` : `${binaryName}.tar.gz`);
|
|
320
|
-
const downloadUrl = `https://github.com/
|
|
380
|
+
const downloadUrl = `https://github.com/hanzoai/dev/releases/download/v${version}/${archiveName}`;
|
|
321
381
|
|
|
322
382
|
console.log(`Downloading ${archiveName}...`);
|
|
323
383
|
try {
|
|
324
|
-
const
|
|
384
|
+
const needsIsolation = isWin || (!isWin && !mirrorToLocal);
|
|
385
|
+
let safeTempDir = needsIsolation ? join(tmpdir(), 'hanzo', 'dev', version) : binDir;
|
|
386
|
+
if (needsIsolation) {
|
|
387
|
+
try {
|
|
388
|
+
if (!existsSync(safeTempDir)) mkdirSync(safeTempDir, { recursive: true });
|
|
389
|
+
} catch {
|
|
390
|
+
try {
|
|
391
|
+
safeTempDir = getCacheDir(version);
|
|
392
|
+
if (!existsSync(safeTempDir)) mkdirSync(safeTempDir, { recursive: true });
|
|
393
|
+
} catch {}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const tmpPath = join(needsIsolation ? safeTempDir : binDir, `.${archiveName}.part`);
|
|
325
397
|
await downloadBinary(downloadUrl, tmpPath);
|
|
326
398
|
|
|
327
399
|
if (isWin) {
|
|
328
|
-
|
|
400
|
+
const unzipDest = safeTempDir;
|
|
329
401
|
try {
|
|
330
|
-
const
|
|
331
|
-
|
|
402
|
+
const sysRoot = process.env.SystemRoot || process.env.windir || 'C:\\Windows';
|
|
403
|
+
const psFull = join(sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
404
|
+
const psCmd = `Expand-Archive -Path '${tmpPath}' -DestinationPath '${unzipDest}' -Force`;
|
|
405
|
+
let ok = false;
|
|
406
|
+
try { execSync(`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {}
|
|
407
|
+
if (!ok) { try { execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
|
|
408
|
+
if (!ok) { try { execSync(`pwsh -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
|
|
409
|
+
if (!ok) { execSync(`tar -xf "${tmpPath}" -C "${unzipDest}"`, { stdio: 'ignore', shell: true }); }
|
|
332
410
|
} catch (e) {
|
|
333
411
|
throw new Error(`failed to unzip archive: ${e.message}`);
|
|
334
412
|
} finally {
|
|
335
413
|
try { unlinkSync(tmpPath); } catch {}
|
|
336
414
|
}
|
|
415
|
+
try {
|
|
416
|
+
const extractedPath = join(unzipDest, binaryName);
|
|
417
|
+
// Rename code-* to dev-* in cache
|
|
418
|
+
const devCachePath = cachePath;
|
|
419
|
+
await writeCacheAtomic(extractedPath, devCachePath);
|
|
420
|
+
try { unlinkSync(extractedPath); } catch {}
|
|
421
|
+
} catch (e) {
|
|
422
|
+
throw new Error(`failed to move binary to cache: ${e.message}`);
|
|
423
|
+
}
|
|
337
424
|
} else {
|
|
425
|
+
const downloadedPath = join(binDir, binaryName);
|
|
338
426
|
if (useZst) {
|
|
339
|
-
// Decompress .zst via system zstd
|
|
340
427
|
try {
|
|
341
|
-
execSync(`zstd -d '${tmpPath}' -o '${
|
|
428
|
+
execSync(`zstd -d '${tmpPath}' -o '${downloadedPath}'`, { stdio: 'ignore', shell: true });
|
|
342
429
|
} catch (e) {
|
|
343
430
|
try { unlinkSync(tmpPath); } catch {}
|
|
344
431
|
throw new Error(`failed to decompress .zst (need zstd CLI): ${e.message}`);
|
|
345
432
|
}
|
|
346
433
|
try { unlinkSync(tmpPath); } catch {}
|
|
347
434
|
} else {
|
|
348
|
-
// Extract .tar.gz using system tar
|
|
349
435
|
try {
|
|
350
436
|
execSync(`tar -xzf '${tmpPath}' -C '${binDir}'`, { stdio: 'ignore', shell: true });
|
|
351
437
|
} catch (e) {
|
|
@@ -354,43 +440,47 @@ async function main() {
|
|
|
354
440
|
}
|
|
355
441
|
try { unlinkSync(tmpPath); } catch {}
|
|
356
442
|
}
|
|
443
|
+
// Rename code-* to dev-*
|
|
444
|
+
if (existsSync(downloadedPath)) {
|
|
445
|
+
if (mirrorToLocal) {
|
|
446
|
+
renameSync(downloadedPath, localPath);
|
|
447
|
+
} else {
|
|
448
|
+
const extractedPath = downloadedPath;
|
|
449
|
+
await writeCacheAtomic(extractedPath, cachePath);
|
|
450
|
+
try { unlinkSync(extractedPath); } catch {}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
357
453
|
}
|
|
358
454
|
|
|
359
|
-
|
|
360
|
-
const valid = validateDownloadedBinary(localPath);
|
|
455
|
+
const valid = validateDownloadedBinary(isWin ? cachePath : (mirrorToLocal ? localPath : cachePath));
|
|
361
456
|
if (!valid.ok) {
|
|
362
|
-
try { unlinkSync(localPath); } catch {}
|
|
457
|
+
try { (isWin || !mirrorToLocal) ? unlinkSync(cachePath) : unlinkSync(localPath); } catch {}
|
|
363
458
|
throw new Error(`invalid binary (${valid.reason})`);
|
|
364
459
|
}
|
|
365
460
|
|
|
366
|
-
|
|
367
|
-
if (!isWindows) {
|
|
461
|
+
if (!isWin && mirrorToLocal) {
|
|
368
462
|
chmodSync(localPath, 0o755);
|
|
369
463
|
}
|
|
370
|
-
|
|
371
|
-
console.log(`✓ Installed ${
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
} catch {}
|
|
464
|
+
|
|
465
|
+
console.log(`✓ Installed ${devBinaryName}${(isWin || !mirrorToLocal) ? ' (cached)' : ''}`);
|
|
466
|
+
if (!isWin && mirrorToLocal) {
|
|
467
|
+
try { await writeCacheAtomic(localPath, cachePath); } catch {}
|
|
468
|
+
}
|
|
376
469
|
} catch (error) {
|
|
377
|
-
console.error(`✗ Failed to install ${
|
|
470
|
+
console.error(`✗ Failed to install ${devBinaryName}: ${error.message}`);
|
|
378
471
|
console.error(` Downloaded from: ${downloadUrl}`);
|
|
379
|
-
// Continue with other binaries even if one fails
|
|
380
472
|
}
|
|
381
473
|
}
|
|
382
474
|
|
|
383
|
-
|
|
384
|
-
const mainBinary = `code-${targetTriple}${binaryExt}`;
|
|
475
|
+
const mainBinary = `dev-${targetTriple}${binaryExt}`;
|
|
385
476
|
const mainBinaryPath = join(binDir, mainBinary);
|
|
386
|
-
|
|
387
|
-
if (existsSync(mainBinaryPath)) {
|
|
477
|
+
|
|
478
|
+
if (existsSync(mainBinaryPath) || existsSync(getCachedBinaryPath(version, targetTriple, platform() === 'win32'))) {
|
|
388
479
|
try {
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const valid = validateDownloadedBinary(mainBinaryPath);
|
|
480
|
+
const probePath = existsSync(mainBinaryPath) ? mainBinaryPath : getCachedBinaryPath(version, targetTriple, platform() === 'win32');
|
|
481
|
+
const stats = statSync(probePath);
|
|
482
|
+
if (!stats.size) throw new Error('binary is empty (download likely failed)');
|
|
483
|
+
const valid = validateDownloadedBinary(probePath);
|
|
394
484
|
if (!valid.ok) {
|
|
395
485
|
console.warn(`⚠ Main dev binary appears invalid: ${valid.reason}`);
|
|
396
486
|
console.warn(' Try reinstalling or check your network/proxy settings.');
|
|
@@ -399,191 +489,25 @@ async function main() {
|
|
|
399
489
|
console.warn(`⚠ Main dev binary appears invalid: ${e.message}`);
|
|
400
490
|
console.warn(' Try reinstalling or check your network/proxy settings.');
|
|
401
491
|
}
|
|
402
|
-
console.log('Setting up main dev binary...');
|
|
403
|
-
|
|
404
|
-
// On Windows, we can't use symlinks easily, so update the JS wrapper
|
|
405
|
-
// On Unix, the JS wrapper will find the correct binary
|
|
406
492
|
console.log('✓ Installation complete!');
|
|
407
493
|
} else {
|
|
408
|
-
console.warn('⚠ Main
|
|
494
|
+
console.warn('⚠ Main dev binary not found. You may need to build from source.');
|
|
409
495
|
}
|
|
496
|
+
}
|
|
410
497
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
// our old shim if a conflict is detected.
|
|
417
|
-
if (isGlobal && !isNpx) try {
|
|
418
|
-
const isTTY = process.stdout && process.stdout.isTTY;
|
|
419
|
-
const isWindows = platform() === 'win32';
|
|
420
|
-
const ua = process.env.npm_config_user_agent || '';
|
|
421
|
-
const isBun = ua.includes('bun') || !!process.env.BUN_INSTALL;
|
|
422
|
-
|
|
423
|
-
const installedCmds = new Set(['coder']); // global install always exposes coder via package manager
|
|
424
|
-
const skippedCmds = [];
|
|
425
|
-
|
|
426
|
-
// Helper to resolve all 'code' on PATH
|
|
427
|
-
const resolveAllOnPath = () => {
|
|
428
|
-
try {
|
|
429
|
-
if (isWindows) {
|
|
430
|
-
const out = execSync('where code', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
431
|
-
return out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
432
|
-
}
|
|
433
|
-
let out = '';
|
|
434
|
-
try {
|
|
435
|
-
out = execSync('bash -lc "which -a code 2>/dev/null"', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
436
|
-
} catch {
|
|
437
|
-
try {
|
|
438
|
-
out = execSync('command -v code || true', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
439
|
-
} catch { out = ''; }
|
|
440
|
-
}
|
|
441
|
-
return out.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
442
|
-
} catch {
|
|
443
|
-
return [];
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
if (isBun) {
|
|
448
|
-
// Bun creates shims for every bin; if another 'code' exists elsewhere on PATH, remove Bun's shim
|
|
449
|
-
let bunBin = '';
|
|
450
|
-
try {
|
|
451
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
452
|
-
const bunBase = process.env.BUN_INSTALL || join(home, '.bun');
|
|
453
|
-
bunBin = join(bunBase, 'bin');
|
|
454
|
-
} catch {}
|
|
455
|
-
|
|
456
|
-
const bunShim = join(bunBin || '', isWindows ? 'code.cmd' : 'code');
|
|
457
|
-
const candidates = resolveAllOnPath();
|
|
458
|
-
const other = candidates.find(p => p && (!bunBin || !p.startsWith(bunBin)));
|
|
459
|
-
if (other && existsSync(bunShim)) {
|
|
460
|
-
try {
|
|
461
|
-
unlinkSync(bunShim);
|
|
462
|
-
console.log(`✓ Skipped global 'code' shim under Bun (existing: ${other})`);
|
|
463
|
-
skippedCmds.push({ name: 'code', reason: `existing: ${other}` });
|
|
464
|
-
} catch (e) {
|
|
465
|
-
console.log(`⚠ Could not remove Bun shim '${bunShim}': ${e.message}`);
|
|
466
|
-
}
|
|
467
|
-
} else if (!other) {
|
|
468
|
-
// No conflict: create a wrapper that forwards to `coder`
|
|
469
|
-
try {
|
|
470
|
-
const wrapperPath = bunShim;
|
|
471
|
-
if (isWindows) {
|
|
472
|
-
const content = `@echo off\r\n"%~dp0coder" %*\r\n`;
|
|
473
|
-
writeFileSync(wrapperPath, content);
|
|
474
|
-
} else {
|
|
475
|
-
const content = `#!/bin/sh\nexec "$(dirname \"$0\")/coder" "$@"\n`;
|
|
476
|
-
writeFileSync(wrapperPath, content);
|
|
477
|
-
chmodSync(wrapperPath, 0o755);
|
|
478
|
-
}
|
|
479
|
-
console.log("✓ Created 'code' wrapper -> coder (bun)");
|
|
480
|
-
installedCmds.add('code');
|
|
481
|
-
} catch (e) {
|
|
482
|
-
console.log(`⚠ Failed to create 'code' wrapper (bun): ${e.message}`);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Print summary for Bun
|
|
487
|
-
const list = Array.from(installedCmds).sort().join(', ');
|
|
488
|
-
console.log(`Commands installed (bun): ${list}`);
|
|
489
|
-
if (skippedCmds.length) {
|
|
490
|
-
for (const s of skippedCmds) console.error(`Commands skipped: ${s.name} (${s.reason})`);
|
|
491
|
-
console.error('→ Use `coder` to run this tool.');
|
|
492
|
-
}
|
|
493
|
-
// Final friendly usage hint
|
|
494
|
-
if (installedCmds.has('code')) {
|
|
495
|
-
console.log("Use 'dev' to launch Hanzo Dev.");
|
|
496
|
-
} else {
|
|
497
|
-
console.log("Use 'dev' to launch Hanzo Dev.");
|
|
498
|
-
}
|
|
499
|
-
} else {
|
|
500
|
-
// npm/pnpm/yarn path
|
|
501
|
-
let globalBin = '';
|
|
502
|
-
try {
|
|
503
|
-
globalBin = execSync('npm bin -g', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
504
|
-
} catch {}
|
|
505
|
-
|
|
506
|
-
const ourShim = join(globalBin || '', isWindows ? 'code.cmd' : 'code');
|
|
507
|
-
const candidates = resolveAllOnPath();
|
|
508
|
-
const others = candidates.filter(p => p && (!ourShim || p !== ourShim));
|
|
509
|
-
const collision = others.length > 0;
|
|
510
|
-
|
|
511
|
-
const ensureWrapper = (name, args) => {
|
|
512
|
-
if (!globalBin) return;
|
|
513
|
-
try {
|
|
514
|
-
const wrapperPath = join(globalBin, isWindows ? `${name}.cmd` : name);
|
|
515
|
-
if (isWindows) {
|
|
516
|
-
const content = `@echo off\r\n"%~dp0${collision ? 'coder' : 'code'}" ${args} %*\r\n`;
|
|
517
|
-
writeFileSync(wrapperPath, content);
|
|
518
|
-
} else {
|
|
519
|
-
const content = `#!/bin/sh\nexec "$(dirname \"$0\")/${collision ? 'coder' : 'code'}" ${args} "$@"\n`;
|
|
520
|
-
writeFileSync(wrapperPath, content);
|
|
521
|
-
chmodSync(wrapperPath, 0o755);
|
|
522
|
-
}
|
|
523
|
-
console.log(`✓ Created wrapper '${name}' -> ${collision ? 'coder' : 'code'} ${args}`);
|
|
524
|
-
installedCmds.add(name);
|
|
525
|
-
} catch (e) {
|
|
526
|
-
console.log(`⚠ Failed to create '${name}' wrapper: ${e.message}`);
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
// Always create legacy wrappers so existing scripts keep working
|
|
531
|
-
ensureWrapper('code-tui', '');
|
|
532
|
-
ensureWrapper('code-exec', 'exec');
|
|
533
|
-
|
|
534
|
-
if (collision) {
|
|
535
|
-
console.error('⚠ Detected existing `code` on PATH:');
|
|
536
|
-
for (const p of others) console.error(` - ${p}`);
|
|
537
|
-
if (globalBin) {
|
|
538
|
-
try {
|
|
539
|
-
if (existsSync(ourShim)) {
|
|
540
|
-
unlinkSync(ourShim);
|
|
541
|
-
console.error(`✓ Skipped global 'code' shim (removed ${ourShim})`);
|
|
542
|
-
skippedCmds.push({ name: 'code', reason: `existing: ${others[0]}` });
|
|
543
|
-
}
|
|
544
|
-
} catch (e) {
|
|
545
|
-
console.error(`⚠ Could not remove npm shim '${ourShim}': ${e.message}`);
|
|
546
|
-
}
|
|
547
|
-
console.error('→ Use `coder` to run this tool.');
|
|
548
|
-
} else {
|
|
549
|
-
console.log('Note: could not determine npm global bin; skipping alias creation.');
|
|
550
|
-
}
|
|
551
|
-
} else {
|
|
552
|
-
// No collision; ensure a 'code' wrapper exists forwarding to 'coder'
|
|
553
|
-
if (globalBin) {
|
|
554
|
-
try {
|
|
555
|
-
const content = isWindows
|
|
556
|
-
? `@echo off\r\n"%~dp0coder" %*\r\n`
|
|
557
|
-
: `#!/bin/sh\nexec "$(dirname \"$0\")/coder" "$@"\n`;
|
|
558
|
-
writeFileSync(ourShim, content);
|
|
559
|
-
if (!isWindows) chmodSync(ourShim, 0o755);
|
|
560
|
-
console.log("✓ Created 'code' wrapper -> coder");
|
|
561
|
-
installedCmds.add('code');
|
|
562
|
-
} catch (e) {
|
|
563
|
-
console.log(`⚠ Failed to create 'code' wrapper: ${e.message}`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Print summary for npm/pnpm/yarn
|
|
569
|
-
const list = Array.from(installedCmds).sort().join(', ');
|
|
570
|
-
console.log(`Commands installed: ${list}`);
|
|
571
|
-
if (skippedCmds.length) {
|
|
572
|
-
for (const s of skippedCmds) console.log(`Commands skipped: ${s.name} (${s.reason})`);
|
|
573
|
-
}
|
|
574
|
-
// Final friendly usage hint
|
|
575
|
-
if (installedCmds.has('code')) {
|
|
576
|
-
console.log("Use 'dev' to launch Hanzo Dev.");
|
|
577
|
-
} else {
|
|
578
|
-
console.log("Use 'dev' to launch Hanzo Dev.");
|
|
579
|
-
}
|
|
580
|
-
}
|
|
498
|
+
function isExecutedDirectly() {
|
|
499
|
+
const entry = process.argv[1];
|
|
500
|
+
if (!entry) return false;
|
|
501
|
+
try {
|
|
502
|
+
return resolve(entry) === fileURLToPath(import.meta.url);
|
|
581
503
|
} catch {
|
|
582
|
-
|
|
504
|
+
return false;
|
|
583
505
|
}
|
|
584
506
|
}
|
|
585
507
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
508
|
+
if (isExecutedDirectly()) {
|
|
509
|
+
runPostinstall().catch(error => {
|
|
510
|
+
console.error('Installation failed:', error);
|
|
511
|
+
process.exit(1);
|
|
512
|
+
});
|
|
513
|
+
}
|