bvm-core 1.1.13 → 1.1.15

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,79 +2,83 @@
2
2
 
3
3
  /**
4
4
  * BVM Post-install Script
5
- * This script handles persistence and environment setup after npm install -g.
5
+ * Standardized Installation Workflow:
6
+ * 1. Init & Deploy Assets
7
+ * 2. Detect System Bun
8
+ * 3. Smoke Test System Bun
9
+ * 4. Configure Runtime (Reuse or Download)
10
+ * 5. Setup Shell Environment
6
11
  */
7
12
 
8
13
  const fs = require('fs');
9
14
  const path = require('path');
10
15
  const { spawnSync } = require('child_process');
16
+ const os = require('os');
11
17
 
18
+ // --- Constants & Config ---
19
+ const ASCII_LOGO = `
20
+ \x1b[36m
21
+ __________
22
+ \______ \__ _______
23
+ | | _| \/ / \
24
+ | | \\ / Y Y \
25
+ |______ / \_/|__|_| /
26
+ \/ \/
27
+ \x1b[0m`;
28
+
29
+ const HOME = process.env.HOME || os.homedir();
30
+ const BVM_DIR = process.env.BVM_DIR || path.join(HOME, '.bvm');
31
+ const BVM_SRC_DIR = path.join(BVM_DIR, 'src');
32
+ const BVM_BIN_DIR = path.join(BVM_DIR, 'bin');
33
+ const PKG_ROOT = path.resolve(__dirname, '..');
34
+ const DIST_DIR = path.join(PKG_ROOT, 'dist');
35
+ const LOG_FILE = path.join(os.tmpdir(), 'bvm-install.log');
36
+
37
+ // --- Helpers ---
12
38
  function log(msg) {
13
- console.log(`[bvm] ${msg}`);
39
+ const line = `[bvm] ${msg}`;
40
+ process.stdout.write(line + '\n');
41
+ try { fs.appendFileSync(LOG_FILE, line + '\n'); } catch(e) {}
14
42
  }
15
43
 
16
44
  function error(msg) {
17
- console.error(`[bvm] ERROR: ${msg}`);
45
+ const line = `[bvm] ERROR: ${msg}`;
46
+ process.stderr.write(`\x1b[31m${line}\x1b[0m\n`);
47
+ try { fs.appendFileSync(LOG_FILE, line + '\n'); } catch(e) {}
18
48
  }
19
49
 
20
- async function main() {
21
- log('Running post-install setup...');
50
+ function ensureDir(dir) {
51
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
52
+ }
22
53
 
23
- const isTTY = process.stdin.isTTY || process.stdout.isTTY;
24
- const isCI = process.env.CI === 'true';
25
-
26
- // Improved Bun detection
27
- const isBun = (process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes('bun/')) ||
28
- (process.versions && process.versions.bun) ||
29
- process.isBun;
30
-
31
- log(`Environment: TTY=${!!isTTY}, CI=${isCI}, isBun=${!!isBun}`);
32
- if (isBun) {
33
- log('Detected Bun installation. Optimizing setup...');
34
- }
54
+ function ensureVersionPrefix(version) {
55
+ if (!version) return 'v0.0.0';
56
+ return version.startsWith('v') ? version : `v${version}`;
57
+ }
35
58
 
36
- // --- Configuration ---
37
- const HOME = process.env.HOME || require('os').homedir();
38
- const BVM_DIR = process.env.BVM_DIR || path.join(HOME, '.bvm');
39
- const BVM_SRC_DIR = path.join(BVM_DIR, 'src');
40
- const BVM_BIN_DIR = path.join(BVM_DIR, 'bin');
41
-
42
- const PKG_ROOT = path.resolve(__dirname, '..');
43
- const DIST_DIR = path.join(PKG_ROOT, 'dist');
44
- const BVM_EXEC = path.join(BVM_BIN_DIR, 'bvm');
59
+ // --- Core Logic ---
45
60
 
46
- // --- 1. Conflict Detection ---
47
- let forceInstall = process.env.BVM_FORCE_INSTALL === 'true';
61
+ function main() {
62
+ // Force a leading newline to break from npm's output
63
+ process.stdout.write('\n' + ASCII_LOGO + '\n');
64
+ log('Starting BVM post-install setup...');
48
65
 
49
- if (fs.existsSync(BVM_EXEC)) {
50
- // Check if existing installation is from npm
51
- let isNpmInstall = false;
66
+ // 1. Conflict Detection
67
+ const BVM_EXEC = path.join(BVM_BIN_DIR, 'bvm');
68
+ if (fs.existsSync(BVM_EXEC) && !process.env.BVM_FORCE_INSTALL) {
52
69
  try {
53
70
  const content = fs.readFileSync(BVM_EXEC, 'utf-8');
54
- if (content.includes('export BVM_INSTALL_SOURCE="npm"')) {
55
- isNpmInstall = true;
71
+ if (!content.includes('BVM_INSTALL_SOURCE="npm"')) {
72
+ log('Native BVM installation detected. Proceeding to update assets...');
56
73
  }
57
74
  } catch (e) {}
58
-
59
- if (isNpmInstall) {
60
- log('Detected existing npm installation. Proceeding with update...');
61
- forceInstall = true;
62
- } else if (!forceInstall) {
63
- error(`Conflict detected: ${BVM_EXEC} already exists (Native installation).`);
64
- error('Please run "bvm upgrade" to update your native BVM installation.');
65
- error('To force overwrite, run with BVM_FORCE_INSTALL=true');
66
- process.exit(1);
67
- } else {
68
- log('BVM_FORCE_INSTALL set. Overwriting existing installation.');
69
- }
70
75
  }
71
76
 
72
- // --- 2. Prepare Directories ---
77
+ // 2. Deploy Assets
73
78
  log(`Deploying to ${BVM_DIR}...`);
74
- fs.mkdirSync(BVM_SRC_DIR, { recursive: true });
75
- fs.mkdirSync(BVM_BIN_DIR, { recursive: true });
79
+ ensureDir(BVM_SRC_DIR);
80
+ ensureDir(BVM_BIN_DIR);
76
81
 
77
- // --- 3. Copy Files ---
78
82
  const filesToCopy = [
79
83
  { src: 'index.js', destDir: BVM_SRC_DIR, name: 'index.js' },
80
84
  { src: 'bvm-shim.sh', destDir: BVM_BIN_DIR, name: 'bvm-shim.sh', mode: 0o755 },
@@ -86,119 +90,210 @@ async function main() {
86
90
  const destPath = path.join(file.destDir, file.name);
87
91
 
88
92
  if (fs.existsSync(srcPath)) {
89
- log(`Copying ${file.src} -> ${destPath}`);
90
93
  fs.copyFileSync(srcPath, destPath);
91
- if (file.mode) {
92
- fs.chmodSync(destPath, file.mode);
93
- }
94
+ if (file.mode) fs.chmodSync(destPath, file.mode);
94
95
  } else {
95
96
  error(`Source file not found: ${srcPath}`);
96
97
  }
97
98
  }
98
-
99
- // --- 4. Runtime Bootstrapping (Scenario 2) ---
100
- const currentRuntime = path.join(BVM_DIR, 'current');
101
-
102
- const wrapperContent = `#!/bin/bash
103
- export BVM_DIR="${BVM_DIR}"
104
- export BVM_INSTALL_SOURCE="npm"
105
- # 1. Try internal runtime
106
- if [ -x "\${BVM_DIR}/current/bin/bun" ]; then
107
- exec "\${BVM_DIR}/current/bin/bun" "\${BVM_DIR}/src/index.js" "$@"
108
- # 2. Try global/system bun
109
- elif command -v bun >/dev/null 2>&1; then
110
- exec bun "\${BVM_DIR}/src/index.js" "$@"
111
- else
112
- echo "Error: BVM requires Bun. Please install Bun or ensure it is in your PATH."
113
- exit 1
114
- fi
115
- `;
116
99
 
117
- if (!fs.existsSync(currentRuntime)) {
118
- log('Current runtime missing. Attempting to bootstrap...');
119
- try {
120
- // A. Find a Bun binary to use for bootstrapping
121
- let sysBun = null;
122
- let hostVer = null;
123
-
124
- // Check if current process is Bun
125
- if (process.versions && process.versions.bun) {
126
- sysBun = process.execPath;
127
- hostVer = process.versions.bun;
128
- } else {
129
- // Try PATH
130
- const whichBun = spawnSync('which', ['bun'], { encoding: 'utf-8' });
131
- if (whichBun.status === 0 && whichBun.stdout) {
132
- sysBun = whichBun.stdout.trim();
133
- const verOut = spawnSync(sysBun, ['--version'], { encoding: 'utf-8' });
134
- if (verOut.status === 0) {
135
- hostVer = verOut.stdout.trim().replace(/^v/, '');
136
- }
137
- }
138
- }
100
+ // 3. Detect & Configure Runtime
101
+ let activeRuntimePath = null;
102
+ let activeVersion = null;
103
+
104
+ const systemBun = detectSystemBun();
105
+ let systemBunCompatible = false;
139
106
 
140
- if (sysBun && fs.existsSync(sysBun)) {
141
- log(`Bootstrapping using system Bun v${hostVer}...`);
142
- const hostVerDir = path.join(BVM_DIR, 'versions', hostVer);
143
- const hostVerBinDir = path.join(hostVerDir, 'bin');
144
-
145
- if (!fs.existsSync(hostVerBinDir)) {
146
- fs.mkdirSync(hostVerBinDir, { recursive: true });
147
- fs.copyFileSync(sysBun, path.join(hostVerBinDir, 'bun'));
148
- fs.chmodSync(path.join(hostVerBinDir, 'bun'), 0o755);
149
- }
150
-
151
- // Always set as fallback first
152
- try { fs.unlinkSync(currentRuntime); } catch(e) {}
153
- fs.symlinkSync(hostVerDir, currentRuntime, 'dir');
154
-
155
- // B. Try to Setup Latest (Upgrading to newest)
156
- log('Attempting to install latest Bun version for BVM...');
157
- const installCmd = spawnSync(sysBun, [path.join(BVM_SRC_DIR, 'index.js'), 'install', 'latest'], {
158
- env: Object.assign({}, process.env, { BVM_DIR }),
159
- stdio: 'inherit'
160
- });
161
-
162
- if (installCmd.status === 0) {
163
- const useCmd = spawnSync(sysBun, [path.join(BVM_SRC_DIR, 'index.js'), 'use', 'latest', '--silent'], {
164
- env: Object.assign({}, process.env, { BVM_DIR })
165
- });
166
-
167
- if (useCmd.status === 0) {
168
- const target = fs.readlinkSync(currentRuntime);
169
- const usedVersion = path.basename(target);
170
- log(`Setup complete. Using Bun v${usedVersion} as default.`);
171
- }
172
- } else {
173
- log(`Warning: Failed to install latest. Stayed with Bun v${hostVer}.`);
174
- }
107
+ if (systemBun) {
108
+ log(`Found system Bun at ${systemBun.path} (v${systemBun.version})`);
109
+ if (runSmokeTest(systemBun.path)) {
110
+ log('Smoke Test passed: System Bun is compatible.');
111
+ systemBunCompatible = true;
112
+ activeVersion = ensureVersionPrefix(systemBun.version);
113
+ const versionDir = path.join(BVM_DIR, 'versions', activeVersion);
114
+ registerBunVersion(systemBun.path, versionDir);
115
+ activeRuntimePath = versionDir;
116
+ } else {
117
+ log('Smoke Test failed: System Bun cannot run BVM core.');
118
+ const versionDir = path.join(BVM_DIR, 'versions', ensureVersionPrefix(systemBun.version));
119
+ registerBunVersion(systemBun.path, versionDir);
120
+ }
121
+ } else {
122
+ log('No system Bun detected.');
123
+ }
124
+
125
+ if (!systemBunCompatible) {
126
+ log('Downloading compatible Bun runtime...');
127
+ try {
128
+ const installedVersion = downloadAndInstallRuntime(BVM_DIR);
129
+ if (installedVersion) {
130
+ activeVersion = installedVersion;
131
+ activeRuntimePath = path.join(BVM_DIR, 'versions', activeVersion);
175
132
  } else {
176
- log('Warning: Skipping bootstrap - No system Bun found.');
133
+ throw new Error('Download failed');
177
134
  }
178
135
  } catch (e) {
179
- log(`Warning: Bootstrap failed: \${e.message}`);
136
+ error(`Failed to download runtime: ${e.message}`);
180
137
  }
181
138
  }
182
139
 
183
- // --- 5. Create Wrapper ---
184
- fs.writeFileSync(BVM_EXEC, wrapperContent);
185
- try { fs.chmodSync(BVM_EXEC, 0o755); } catch (e) {}
140
+ // 4. Link Runtime & Default Alias
141
+ if (activeRuntimePath && activeVersion) {
142
+ const legacyCurrentLink = path.join(BVM_DIR, 'current');
143
+ const privateRuntimeLink = path.join(BVM_DIR, 'runtime', 'current');
144
+ ensureDir(path.join(BVM_DIR, 'runtime'));
145
+
146
+ try { if (fs.existsSync(privateRuntimeLink)) fs.unlinkSync(privateRuntimeLink); } catch(e) {}
147
+ try { fs.symlinkSync(activeRuntimePath, privateRuntimeLink, 'dir'); } catch(e) {}
186
148
 
187
- // --- 6. Trigger Setup ---
188
- log('Configuring environment...');
189
- const setupResult = spawnSync(BVM_EXEC, ['setup', '--silent'], {
149
+ try { if (fs.existsSync(legacyCurrentLink)) fs.unlinkSync(legacyCurrentLink); } catch(e) {}
150
+ try { fs.symlinkSync(activeRuntimePath, legacyCurrentLink, 'dir'); } catch(e) {}
151
+
152
+ const aliasDir = path.join(BVM_DIR, 'aliases');
153
+ ensureDir(aliasDir);
154
+ fs.writeFileSync(path.join(aliasDir, 'default'), activeVersion);
155
+ log(`Active runtime set to ${activeVersion}`);
156
+ }
157
+
158
+ // 5. Create BVM Wrapper
159
+ createBvmWrapper();
160
+
161
+ // 6. Configure Shell (bvm setup)
162
+ log('Configuring shell environment...');
163
+ spawnSync(path.join(BVM_BIN_DIR, 'bvm'), ['setup', '--silent'], {
190
164
  stdio: 'inherit',
191
165
  env: Object.assign({}, process.env, { BVM_DIR })
192
166
  });
193
167
 
194
- if (setupResult.status !== 0) {
195
- log('Warning: "bvm setup" failed.');
168
+ // 7. Final Success Message
169
+ printSuccessMessage(!!systemBun);
170
+ }
171
+
172
+ // --- Implementation Details ---
173
+
174
+ function detectSystemBun() {
175
+ const which = spawnSync('which', ['bun'], { encoding: 'utf-8' });
176
+ if (which.status === 0 && which.stdout) {
177
+ const binPath = which.stdout.trim();
178
+ const ver = getBunVersion(binPath);
179
+ if (ver) return { path: binPath, version: ver };
180
+ }
181
+ return null;
182
+ }
183
+
184
+ function getBunVersion(binPath) {
185
+ try {
186
+ const proc = spawnSync(binPath, ['--version'], { encoding: 'utf-8' });
187
+ if (proc.status === 0) return proc.stdout.trim().replace(/^v/, '');
188
+ } catch (e) {}
189
+ return null;
190
+ }
191
+
192
+ function runSmokeTest(binPath) {
193
+ try {
194
+ const indexJs = path.join(BVM_SRC_DIR, 'index.js');
195
+ const proc = spawnSync(binPath, [indexJs, '--version'], {
196
+ encoding: 'utf-8',
197
+ env: Object.assign({}, process.env, { BVM_DIR })
198
+ });
199
+ return proc.status === 0;
200
+ } catch (e) {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ function registerBunVersion(sourceBin, targetDir) {
206
+ const targetBinDir = path.join(targetDir, 'bin');
207
+ ensureDir(targetBinDir);
208
+ const targetBin = path.join(targetBinDir, 'bun');
209
+ if (!fs.existsSync(targetBin)) {
210
+ fs.copyFileSync(sourceBin, targetBin);
211
+ fs.chmodSync(targetBin, 0o755);
212
+ }
213
+ }
214
+
215
+ function downloadAndInstallRuntime(bvmDir) {
216
+ const platform = process.platform;
217
+ const arch = process.arch;
218
+
219
+ let pkgPlatform = '';
220
+ if (platform === 'darwin') pkgPlatform = 'darwin';
221
+ else if (platform === 'linux') pkgPlatform = 'linux';
222
+ else if (platform === 'win32') pkgPlatform = 'windows';
223
+ else return null;
224
+
225
+ const pkgArch = arch === 'arm64' ? 'aarch64' : 'x64';
226
+ const bunVer = '1.1.13';
227
+ const vBunVer = ensureVersionPrefix(bunVer);
228
+ const registry = 'https://registry.npmmirror.com';
229
+ const pkgName = `@oven/bun-${pkgPlatform}-${pkgArch}`;
230
+ const url = `${registry}/${pkgName}/-/${pkgName.split('/').pop()}-${bunVer}.tgz`;
231
+
232
+ const tempTgz = path.join(os.tmpdir(), `bun-${Date.now()}.tgz`);
233
+ const extractDir = path.join(os.tmpdir(), `bun-ext-${Date.now()}`);
234
+
235
+ try {
236
+ log(`Downloading Bun ${bunVer}...`);
237
+ spawnSync('curl', ['-L', '-s', '-o', tempTgz, url]);
238
+ ensureDir(extractDir);
239
+ spawnSync('tar', ['-xzf', tempTgz, '-C', extractDir]);
240
+ const exeName = platform === 'win32' ? 'bun.exe' : 'bun';
241
+ const foundBinProc = spawnSync('find', [extractDir, '-name', exeName], { encoding: 'utf-8' });
242
+ const binPath = foundBinProc.stdout ? foundBinProc.stdout.trim().split('\n')[0] : null;
243
+ if (binPath && fs.existsSync(binPath)) {
244
+ const versionDir = path.join(bvmDir, 'versions', vBunVer);
245
+ registerBunVersion(binPath, versionDir);
246
+ return vBunVer;
247
+ }
248
+ } catch (e) {
249
+ error(`Download failed: ${e.message}`);
250
+ } finally {
251
+ try {
252
+ if (fs.existsSync(tempTgz)) fs.unlinkSync(tempTgz);
253
+ if (fs.existsSync(extractDir)) fs.rmSync(extractDir, { recursive: true });
254
+ } catch(e) {}
255
+ }
256
+ return null;
257
+ }
258
+
259
+ function createBvmWrapper() {
260
+ const wrapperContent = `#!/bin/bash
261
+ export BVM_DIR="${BVM_DIR}"
262
+ export BVM_INSTALL_SOURCE="npm"
263
+ if [ -x "\${BVM_DIR}/runtime/current/bin/bun" ]; then
264
+ exec "\${BVM_DIR}/runtime/current/bin/bun" "\${BVM_DIR}/src/index.js" "$@"
265
+ elif command -v bun >/dev/null 2>&1; then
266
+ exec bun "\${BVM_DIR}/src/index.js" "$@"
267
+ else
268
+ echo "Error: BVM requires Bun. Please ensure setup completed correctly."
269
+ exit 1
270
+ fi
271
+ `;
272
+ const bvmExec = path.join(BVM_BIN_DIR, 'bvm');
273
+ fs.writeFileSync(bvmExec, wrapperContent);
274
+ fs.chmodSync(bvmExec, 0o755);
275
+ }
276
+
277
+ function printSuccessMessage(hasSystemBun) {
278
+ const shell = process.env.SHELL || '';
279
+ let configFile = '~/.zshrc';
280
+ if (shell.includes('bash')) configFile = '~/.bashrc';
281
+ else if (shell.includes('fish')) configFile = '~/.config/fish/config.fish';
282
+
283
+ process.stdout.write('\n\x1b[32m\x1b[1m🎉 BVM (Bun Version Manager) installed successfully!\x1b[0m\n');
284
+ if (hasSystemBun) {
285
+ process.stdout.write('\x1b[33mBVM has been added to the END of your PATH configuration to ensure priority.\x1b[0m\n');
286
+ process.stdout.write('\x1b[33mYour existing Bun installations were NOT deleted and will coexist.\x1b[0m\n');
196
287
  } else {
197
- log('BVM installed successfully.');
288
+ process.stdout.write('\x1b[33mBVM has installed a compatible Bun runtime for you.\x1b[0m\n');
198
289
  }
290
+ process.stdout.write('\n\x1b[1mTo finalize the setup, please restart your terminal or run:\x1b[0m\n');
291
+ process.stdout.write(` source ${configFile}\n\n`);
199
292
  }
200
293
 
201
- main().catch(err => {
202
- error(err.message);
203
- process.exit(1);
204
- });
294
+ // Execute
295
+ try {
296
+ main();
297
+ } catch (e) {
298
+ error(e.message);
299
+ }