bvm-core 1.1.36 → 1.1.38

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * BVM Post-install Script (Smart Edition)
5
- * Optimized for Speed: Priority given to npmmirror (China) and npmjs (Official).
5
+ * Bunker-First: Manages BVM private runtime in runtime/ bunker.
6
6
  */
7
7
 
8
8
  const fs = require('fs');
@@ -22,7 +22,18 @@ function log(msg) { console.log(`[bvm] ${msg}`); }
22
22
  function error(msg) { console.error(`\x1b[31m[bvm] ERROR: ${msg}\x1b[0m`); }
23
23
 
24
24
  function run(cmd, args, opts = {}) {
25
- return spawnSync(cmd, args, Object.assign({ encoding: 'utf-8', shell: IS_WINDOWS }, opts));
25
+ const options = Object.assign({ encoding: 'utf-8' }, opts);
26
+ if (IS_WINDOWS) {
27
+ // Avoid shell: true to prevent DEP0190, but handle .cmd manually
28
+ if (cmd.endsWith('.cmd') || cmd.endsWith('.bat') || cmd === 'npm') {
29
+ // npm might be npm.cmd or npm.ps1, safest is cmd /c
30
+ return spawnSync('cmd', ['/d', '/s', '/c', cmd, ...args], { ...options, windowsVerbatimArguments: true });
31
+ }
32
+ // For executables like curl.exe, tar.exe, execute directly
33
+ return spawnSync(cmd, args, options);
34
+ }
35
+ // Unix: avoid shell: true for raw commands to prevent DEP0190
36
+ return spawnSync(cmd, args, options);
26
37
  }
27
38
 
28
39
  function findBinary(dir, name) {
@@ -40,45 +51,53 @@ function findBinary(dir, name) {
40
51
  return null;
41
52
  }
42
53
 
43
- /**
44
- * Prioritized registry sniffing for speed and reliability
45
- */
46
- function getPackageInfo(pkgName) {
47
- const registries = [
48
- 'https://registry.npmmirror.com', // High speed for China
49
- 'https://registry.npmjs.org' // Official reliability
50
- ];
51
-
52
- for (const registry of registries) {
53
- log(`Checking ${pkgName} info from ${registry}...`);
54
- const res = run('npm', ['info', pkgName, '--json', '--registry', registry]);
55
- if (res.status === 0 && res.stdout) {
56
- try {
57
- const info = JSON.parse(res.stdout);
58
- if (info.dist && info.dist.tarball) {
59
- return { url: info.dist.tarball, version: info.version };
60
- }
61
- } catch (e) {}
62
- }
54
+ function setupBunker(verDir, ver) {
55
+ const runtimeRoot = path.join(BVM_DIR, 'runtime');
56
+ if (!fs.existsSync(runtimeRoot)) fs.mkdirSync(runtimeRoot, { recursive: true });
57
+
58
+ const bunkerDir = path.join(runtimeRoot, ver);
59
+ if (verDir !== bunkerDir && fs.existsSync(verDir)) {
60
+ if (!fs.existsSync(bunkerDir)) fs.renameSync(verDir, bunkerDir);
61
+ } else if (!fs.existsSync(bunkerDir)) {
62
+ fs.mkdirSync(bunkerDir, { recursive: true });
63
63
  }
64
- return null;
65
- }
66
64
 
67
- function setupRuntimeLink(verDir, ver) {
68
- const runtimeDir = path.join(BVM_DIR, 'runtime');
69
- if (!fs.existsSync(runtimeDir)) fs.mkdirSync(runtimeDir, { recursive: true });
70
- const currentLink = path.join(runtimeDir, 'current');
65
+ const currentLink = path.join(runtimeRoot, 'current');
71
66
  const userCurrentLink = path.join(BVM_DIR, 'current');
67
+ const versionsDir = path.join(BVM_DIR, 'versions');
68
+ if (!fs.existsSync(versionsDir)) fs.mkdirSync(versionsDir, { recursive: true });
69
+ const versionLink = path.join(versionsDir, ver);
70
+
72
71
  const linkType = IS_WINDOWS ? 'junction' : 'dir';
73
72
 
73
+ // 1. Link versions/vX.Y.Z -> runtime/vX.Y.Z (Crucial for bvm ls/use)
74
+ try { if (fs.existsSync(versionLink)) fs.unlinkSync(versionLink); } catch(e) {
75
+ if (IS_WINDOWS) run('cmd', ['/c', 'rmdir', versionLink]);
76
+ }
77
+ try { fs.symlinkSync(bunkerDir, versionLink, linkType); } catch(e) {}
78
+
79
+ // 2. Link current -> versions/vX.Y.Z
74
80
  [currentLink, userCurrentLink].forEach(link => {
75
- try { if (fs.existsSync(link)) fs.unlinkSync(link); } catch(e) {}
76
- try { fs.symlinkSync(verDir, link, linkType); } catch(e) {}
81
+ try { if (fs.existsSync(link)) fs.unlinkSync(link); } catch(e) {
82
+ if (IS_WINDOWS) run('cmd', ['/c', 'rmdir', link]);
83
+ }
84
+ // Current always points to the registry entry
85
+ const target = link === currentLink ? bunkerDir : versionLink;
86
+ try { fs.symlinkSync(target, link, linkType); } catch(e) {}
77
87
  });
78
88
 
79
89
  const aliasDir = path.join(BVM_DIR, 'aliases');
80
90
  if (!fs.existsSync(aliasDir)) fs.mkdirSync(aliasDir, { recursive: true });
81
91
  fs.writeFileSync(path.join(aliasDir, 'default'), ver);
92
+
93
+ // 3. Generate bunfig.toml in bunker
94
+ const binDir = path.join(bunkerDir, 'bin');
95
+ const bunkerAbs = path.resolve(bunkerDir);
96
+ const binAbs = path.resolve(binDir);
97
+ const winBunker = bunkerAbs.replace(/\\/g, '\\\\');
98
+ const winBin = binAbs.replace(/\\/g, '\\\\');
99
+ const bunfigContent = `[install]\nglobalDir = "${winBunker}"\nglobalBinDir = "${winBin}"\n`;
100
+ fs.writeFileSync(path.join(bunkerDir, 'bunfig.toml'), bunfigContent);
82
101
  }
83
102
 
84
103
  function getNativeArch() {
@@ -86,9 +105,7 @@ function getNativeArch() {
86
105
  if (process.platform === 'darwin' && arch === 'x64') {
87
106
  try {
88
107
  const check = spawnSync('sysctl', ['-n', 'sysctl.proc_translated'], { encoding: 'utf-8' });
89
- if (check.stdout.trim() === '1') {
90
- return 'arm64';
91
- }
108
+ if (check.stdout.trim() === '1') return 'arm64';
92
109
  } catch (e) {}
93
110
  }
94
111
  return arch;
@@ -97,56 +114,79 @@ function getNativeArch() {
97
114
  function hasAvx2() {
98
115
  if (process.platform === 'win32') return true;
99
116
  try {
100
- if (process.platform === 'darwin') {
101
- return spawnSync('sysctl', ['-a'], { encoding: 'utf-8' }).stdout.includes('AVX2');
102
- } else if (process.platform === 'linux') {
103
- return fs.readFileSync('/proc/cpuinfo', 'utf-8').includes('avx2');
104
- }
117
+ if (process.platform === 'darwin') return spawnSync('sysctl', ['-a'], { encoding: 'utf-8' }).stdout.includes('AVX2');
118
+ else if (process.platform === 'linux') return fs.readFileSync('/proc/cpuinfo', 'utf-8').includes('avx2');
105
119
  } catch (e) {}
106
120
  return true;
107
121
  }
108
122
 
123
+ function getFastestRegistry() {
124
+ const registries = [
125
+ { name: 'npmmirror', url: 'https://registry.npmmirror.com' },
126
+ { name: 'npmjs', url: 'https://registry.npmjs.org' }
127
+ ];
128
+ log('Racing registries for speed...');
129
+ const results = registries.map(reg => {
130
+ const start = Date.now();
131
+ const res = run('curl', ['-I', '-s', '--connect-timeout', '2', reg.url]);
132
+ return { ...reg, time: res.status === 0 ? (Date.now() - start) : 9999 };
133
+ });
134
+ results.sort((a, b) => a.time - b.time);
135
+ log(`Winner: ${results[0].name} (${results[0].time}ms)`);
136
+ return results;
137
+ }
138
+
109
139
  function downloadAndInstall() {
110
140
  const platform = process.platform === 'win32' ? 'windows' : process.platform;
111
- const nativeArch = getNativeArch();
112
- const arch = nativeArch === 'arm64' ? 'aarch64' : 'x64';
113
- const suffix = (arch === 'x64' && !hasAvx2()) ? '-baseline' : '';
114
- const pkgName = `@oven/bun-${platform}-${arch}`;
141
+ const arch = getNativeArch() === 'arm64' ? 'aarch64' : 'x64';
142
+ const pkgName = `@oven/bun-${platform}-${arch}${ (arch === 'x64' && !hasAvx2()) ? '-baseline' : ''}`;
115
143
 
116
- const info = getPackageInfo(pkgName);
117
- if (!info) {
118
- error(`Failed to locate ${pkgName} on both npmmirror and npmjs.`);
119
- return false;
120
- }
121
-
122
- const { url, version } = info;
123
- const downloadUrl = url.replace('.tgz', `${suffix}.tgz`);
144
+ const sortedRegs = getFastestRegistry();
145
+ const versionsToTry = ['latest', '1.3.6'];
124
146
  const tempTgz = path.join(os.tmpdir(), `bvm-bun-${Date.now()}.tgz`);
125
- log(`Downloading Bun v${version} from: ${downloadUrl}`);
126
-
127
- const dl = run('curl', ['-L', '-s', '-o', tempTgz, url]);
128
- if (dl.status !== 0) {
129
- error('Download failed. Please check your internet connection.');
130
- return false;
131
- }
132
-
133
- const extractDir = path.join(os.tmpdir(), `bvm-ext-${Date.now()}`);
134
- fs.mkdirSync(extractDir, { recursive: true });
135
- const ex = run('tar', ['-xzf', tempTgz, '-C', extractDir]);
136
- if (ex.status !== 0) { error('Extraction failed.'); return false; }
137
-
138
- const exeName = IS_WINDOWS ? 'bun.exe' : 'bun';
139
- const foundBin = findBinary(extractDir, exeName);
140
-
141
- if (foundBin) {
142
- const verName = 'v' + version.replace(/^v/, '');
143
- const verDir = path.join(BVM_DIR, 'versions', verName);
144
- const binDir = path.join(verDir, 'bin');
145
- if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true });
146
- fs.copyFileSync(foundBin, path.join(binDir, IS_WINDOWS ? 'bun.exe' : 'bun'));
147
- setupRuntimeLink(verDir, verName);
148
- log(`Successfully installed Bun ${verName} as BVM runtime.`);
149
- return true;
147
+
148
+ for (const verReq of versionsToTry) {
149
+ log(`\n--- Attempting Bun ${verReq} ---`);
150
+ for (const reg of sortedRegs) {
151
+ if (reg.time >= 9999) continue;
152
+ const target = verReq === 'latest' ? pkgName : `${pkgName}@${verReq}`;
153
+ log(`Checking ${target} from ${reg.name}...`);
154
+ const infoRes = run('npm', ['info', target, '--json', '--registry', reg.url]);
155
+ if (infoRes.status !== 0 || !infoRes.stdout) continue;
156
+ try {
157
+ const info = JSON.parse(infoRes.stdout);
158
+ const data = Array.isArray(info) ? info[0] : info;
159
+ if (!data.dist || !data.dist.tarball) continue;
160
+ const url = data.dist.tarball.trim();
161
+ const version = data.version;
162
+ log(`Downloading Bun v${version} from ${reg.name}...`);
163
+ // Use -C - to support resume, and increase timeout to 10 minutes (600s)
164
+ const dl = run('curl', ['-L', '--fail', '-C', '-', '--connect-timeout', '20', '--max-time', '600', '--retry', '3', '-o', tempTgz, url], { stdio: 'inherit' });
165
+ if (dl.status === 0) {
166
+ log('Extracting runtime... ');
167
+ const extractDir = path.join(os.tmpdir(), `bvm-ext-${Date.now()}`);
168
+ fs.mkdirSync(extractDir, { recursive: true });
169
+ const ex = run('tar', ['-xzf', tempTgz, '-C', extractDir]);
170
+ if (ex.status === 0) {
171
+ const foundBin = findBinary(extractDir, IS_WINDOWS ? 'bun.exe' : 'bun');
172
+ if (foundBin) {
173
+ const verName = 'v' + version.replace(/^v/, '');
174
+ const bunkerDir = path.join(BVM_DIR, 'runtime', verName);
175
+ const binDir = path.join(bunkerDir, 'bin');
176
+ if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true });
177
+ fs.copyFileSync(foundBin, path.join(binDir, IS_WINDOWS ? 'bun.exe' : 'bun'));
178
+ setupBunker(bunkerDir, verName);
179
+ log(`🎉 Successfully installed Bun ${verName} via ${reg.name}.`);
180
+ try { fs.unlinkSync(tempTgz); } catch(e) {}
181
+ return true;
182
+ }
183
+ }
184
+ } else {
185
+ // Cleanup partial file on complete failure of this URL attempt
186
+ try { if (fs.existsSync(tempTgz)) fs.unlinkSync(tempTgz); } catch(e) {}
187
+ }
188
+ } catch (e) {}
189
+ }
150
190
  }
151
191
  return false;
152
192
  }
@@ -159,7 +199,6 @@ function createWrappers() {
159
199
  const bunkerBun = path.join(BVM_DIR, 'runtime', 'current', 'bin', IS_WINDOWS ? 'bun.exe' : 'bun');
160
200
  const bunkerBunWin = bunkerBun.replace(/\//g, '\\');
161
201
  const bvmSrcWin = bvmSrc.replace(/\//g, '\\');
162
-
163
202
  if (IS_WINDOWS) {
164
203
  const content = `@echo off\r\nset "BVM_DIR=${bvmDirWin}"\r\nif exist "${bunkerBunWin}" (\r\n "${bunkerBunWin}" "${bvmSrcWin}" %*\r\n) else (\r\n node "${entryPath}" %*\r\n)`;
165
204
  fs.writeFileSync(bvmBin, content);
@@ -173,92 +212,89 @@ function createWrappers() {
173
212
 
174
213
  function main() {
175
214
  log('Starting BVM post-install setup...');
215
+ if (!fs.existsSync(path.join(DIST_DIR, 'index.js'))) return;
216
+ [BVM_SRC_DIR, BVM_BIN_DIR].forEach(d => { if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); });
176
217
 
177
- // Check if dist/index.js exists (it might not in dev/CI environment before build)
178
- if (!fs.existsSync(path.join(DIST_DIR, 'index.js'))) {
179
- log('Development environment detected: dist/index.js missing.');
180
- log('Skipping BVM runtime setup. Please run "bun run build" to generate artifacts.');
181
- return;
218
+ // Windows-specific diagnostics
219
+ if (IS_WINDOWS) {
220
+ log('Windows detected. Checking PATH configuration...');
221
+ const userPath = process.env.PATH || '';
222
+ const bvmInPath = userPath.includes('.bvm\\shims') || userPath.includes('.bvm\\bin');
223
+ if (!bvmInPath) {
224
+ console.log('\x1b[33m[bvm] WARNING: BVM directories not found in PATH.\x1b[0m');
225
+ console.log('\x1b[33m[bvm] After installation, please run: bvm setup\x1b[0m');
226
+ }
182
227
  }
183
-
184
- [BVM_SRC_DIR, BVM_BIN_DIR].forEach(d => { if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); });
185
- const assets = [
186
- { src: 'index.js', dest: path.join(BVM_SRC_DIR, 'index.js') },
187
- { src: 'bvm-shim.sh', dest: path.join(BVM_BIN_DIR, 'bvm-shim.sh') },
188
- { src: 'bvm-shim.js', dest: path.join(BVM_BIN_DIR, 'bvm-shim.js') }
189
- ];
190
- assets.forEach(a => {
191
- const srcPath = path.join(DIST_DIR, a.src);
192
- if (fs.existsSync(srcPath)) {
193
- fs.copyFileSync(srcPath, a.dest);
194
- if (!a.dest.endsWith('.js')) fs.chmodSync(a.dest, 0o755);
228
+ const assets = [ { src: 'index.js', dest: path.join(BVM_SRC_DIR, 'index.js') }, { src: 'bvm-shim.sh', dest: path.join(BVM_BIN_DIR, 'bvm-shim.sh') }, { src: 'bvm-shim.js', dest: path.join(BVM_BIN_DIR, 'bvm-shim.js') } ];
229
+ assets.forEach(a => { const srcPath = path.join(DIST_DIR, a.src); if (fs.existsSync(srcPath)) fs.copyFileSync(srcPath, a.dest); });
230
+
231
+ function normalizeForCompare(p) {
232
+ try { return path.resolve(p).replace(/\\/g, '/').toLowerCase(); } catch { return String(p).replace(/\\/g, '/').toLowerCase(); }
233
+ }
234
+
235
+ function isLikelyScript(p) {
236
+ const lower = p.toLowerCase();
237
+ if (IS_WINDOWS) {
238
+ return lower.endsWith('.cmd') || lower.endsWith('.bat') || lower.endsWith('.ps1');
195
239
  }
196
- });
240
+ try {
241
+ const buf = fs.readFileSync(p, { encoding: 'utf-8' });
242
+ return buf.startsWith('#!');
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
197
247
 
198
248
  let hasValidBun = false;
199
- const whichCmd = IS_WINDOWS ? 'where' : 'which';
200
- const checkBun = run(whichCmd, ['bun']);
201
-
249
+ const checkBun = run(IS_WINDOWS ? 'where' : 'which', ['bun']);
202
250
  if (checkBun.status === 0 && checkBun.stdout) {
203
- const binPath = checkBun.stdout.trim().split('\n')[0].trim();
204
- log(`System Bun detected at: ${binPath}. Running Smoke Test...`);
205
-
206
- const verRes = run(binPath, ['--version']);
207
- const ver = 'v' + (verRes.stdout || '1.3.6').trim().replace(/^v/, '');
208
- const verDir = path.join(BVM_DIR, 'versions', ver);
209
-
210
- // Architecture match check (Native vs. Emulated)
211
- const sysArchRes = run(binPath, ['-e', 'console.log(process.arch)']);
212
- const sysArch = (sysArchRes.stdout || '').trim();
213
- const nativeArch = getNativeArch();
251
+ const candidates = checkBun.stdout.trim().split(/\r?\n/).map(p => p.trim()).filter(Boolean);
252
+ const bvmDirNorm = normalizeForCompare(BVM_DIR) + '/';
214
253
 
215
- if (sysArch !== nativeArch) {
216
- log(`System Bun architecture (${sysArch}) doesn't match native hardware (${nativeArch}). Skipping reuse.`);
217
- } else {
218
- // Register anyway to preserve user's version
219
- const binDir = path.join(verDir, 'bin');
220
- if (!fs.existsSync(binDir)) {
221
- fs.mkdirSync(binDir, { recursive: true });
222
- const destBin = path.join(binDir, IS_WINDOWS ? 'bun.exe' : 'bun');
223
- try {
224
- if (path.resolve(binPath) !== path.resolve(destBin)) {
225
- fs.copyFileSync(binPath, destBin);
254
+ // Prefer a bun binary that is NOT under *this* BVM_DIR (fresh install),
255
+ // but allow other bun installs (including other .bvm locations) to bootstrap.
256
+ const binPath = candidates.find((p) => !normalizeForCompare(p).startsWith(bvmDirNorm)) || candidates[0];
257
+ if (binPath) {
258
+ log(`System Bun detected at: ${binPath}. Running Smoke Test...`);
259
+ const verRes = run(binPath, ['--version']);
260
+ const verRaw = (verRes.stdout || '').trim();
261
+ if (verRes.status === 0 && verRaw && !verRaw.includes('BVM Error') && /^\d+\.\d+\.\d+/.test(verRaw.replace(/^v/, ''))) {
262
+ const ver = 'v' + verRaw.replace(/^v/, '');
263
+ const bunkerDir = path.join(BVM_DIR, 'runtime', ver);
264
+ if (!fs.existsSync(path.join(bunkerDir, 'bin'))) {
265
+ fs.mkdirSync(path.join(bunkerDir, 'bin'), { recursive: true });
266
+ // Only copy real executables; avoid copying shims/scripts.
267
+ if (!isLikelyScript(binPath)) {
268
+ try { fs.copyFileSync(binPath, path.join(bunkerDir, 'bin', IS_WINDOWS ? 'bun.exe' : 'bun')); } catch (e) {}
226
269
  }
227
- } catch (e) {
228
- error(`Failed to copy system Bun: ${e.message}`);
229
270
  }
230
- }
231
-
232
- const test = run(binPath, [path.join(BVM_SRC_DIR, 'index.js'), '--version'], { env: { BVM_DIR } });
233
- if (test.status === 0) {
234
- log('Smoke test passed. Reusing system Bun.');
235
- setupRuntimeLink(verDir, ver);
236
- hasValidBun = true;
237
- } else {
238
- log('Smoke test failed. System Bun is incompatible with BVM core.');
271
+ const test = run(binPath, [path.join(BVM_SRC_DIR, 'index.js'), '--version'], { env: { BVM_DIR } });
272
+ if (test.status === 0) {
273
+ setupBunker(bunkerDir, ver);
274
+ hasValidBun = true;
275
+ }
239
276
  }
240
277
  }
241
278
  }
242
-
243
- if (!hasValidBun) {
244
- log('No compatible system Bun found. Performing smart download...');
245
- hasValidBun = downloadAndInstall();
246
- }
247
-
279
+ if (!hasValidBun) hasValidBun = downloadAndInstall();
248
280
  createWrappers();
249
- const bvmBin = path.join(BVM_BIN_DIR, IS_WINDOWS ? 'bvm.cmd' : 'bvm');
250
- const setupResult = run(bvmBin, ['setup', '--silent'], { env: { BVM_DIR } });
251
- if (setupResult.status !== 0) {
252
- error(`bvm setup failed with exit code ${setupResult.status}`);
253
- if (setupResult.stderr) console.error(setupResult.stderr);
254
- process.exit(1);
255
- }
281
+ const bvmEntry = path.join(BVM_BIN_DIR, IS_WINDOWS ? 'bvm.cmd' : 'bvm');
282
+ const baseEnv = Object.assign({}, process.env, { BVM_DIR, BVM_INSTALL_RUNNING: '1' });
283
+ run(bvmEntry, ['setup', '--silent'], { env: baseEnv });
284
+ // Ensure user shims are updated to the latest template logic (critical for Windows isolation).
285
+ run(bvmEntry, ['rehash', '--silent'], { env: baseEnv });
256
286
  log('🎉 BVM initialized successfully.');
287
+
288
+ // Final Windows instructions
289
+ if (IS_WINDOWS) {
290
+ console.log('\n\x1b[36m[bvm] Next steps for Windows:\x1b[0m');
291
+ console.log(' 1. Close and reopen your terminal/PowerShell');
292
+ console.log(' 2. Run: bvm --version');
293
+ console.log(' 3. If command not found, run: bvm setup');
294
+ console.log(' 4. Add BVM to PATH manually if needed:');
295
+ console.log(` %USERPROFILE%\\.bvm\\shims`);
296
+ console.log(` %USERPROFILE%\\.bvm\\bin`);
297
+ }
257
298
  }
258
299
 
259
- try {
260
- main();
261
- } catch (e) {
262
- error(e.message);
263
- process.exit(1);
264
- }
300
+ try { main(); } catch (e) { error(e.message); process.exit(1); }