vibesdev 1.0.0 → 1.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 CHANGED
@@ -22,9 +22,9 @@ bunx vibesdev --help
22
22
 
23
23
  ## What This Package Does
24
24
 
25
- This package depends on `@vibesdotdev/bin`, the canonical Vibes CLI binary
26
- wrapper. It exposes `vibesdev` as a short npm package name and forwards command
27
- execution to the `vibes` binary installed by `@vibesdotdev/bin`.
25
+ This package installs the Vibes CLI binary from the public Vibes binary mirror.
26
+ It exposes `vibesdev` as a short npm package name and also installs the `vibes`
27
+ command.
28
28
 
29
29
  The installed command remains:
30
30
 
package/bin/vibesdev.js CHANGED
@@ -1,37 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { spawnSync } = require('child_process');
5
4
  const fs = require('fs');
6
5
  const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
7
 
8
8
  function fail(message) {
9
9
  process.stderr.write(`${message}\n`);
10
10
  process.exit(1);
11
11
  }
12
12
 
13
- function resolveCanonicalPackageDir() {
14
- try {
15
- return path.dirname(require.resolve('@vibesdotdev/bin/package.json', {
16
- paths: [__dirname]
17
- }));
18
- } catch {
19
- fail([
20
- 'Error: @vibesdotdev/bin is not installed.',
21
- '',
22
- 'Reinstall the alias package:',
23
- ' npm install -g vibesdev',
24
- ' or',
25
- ' bun add -g vibesdev'
26
- ].join('\n'));
27
- }
28
- }
29
-
30
- const packageDir = resolveCanonicalPackageDir();
13
+ const packageDir = path.resolve(__dirname, '..');
14
+ const installerPath = path.join(packageDir, 'scripts', 'postinstall.js');
31
15
  const binPath = path.join(packageDir, 'bin', 'vibes');
32
16
  const exePath = path.join(packageDir, 'bin', 'vibes.exe');
33
17
  const args = process.argv.slice(2);
34
18
 
19
+ function runInstaller() {
20
+ const result = spawnSync(process.execPath, [installerPath], {
21
+ stdio: 'inherit',
22
+ windowsHide: false
23
+ });
24
+ if (result.error) {
25
+ fail(`Failed to run vibes installer: ${result.error.message}`);
26
+ }
27
+ if (result.status !== 0) {
28
+ process.exit(result.status === null ? 1 : result.status);
29
+ }
30
+ }
31
+
35
32
  let command = binPath;
36
33
  let commandArgs = args;
37
34
 
@@ -44,14 +41,15 @@ if (process.platform === 'win32') {
44
41
  }
45
42
  }
46
43
 
44
+ if (!fs.existsSync(binPath) && !fs.existsSync(exePath)) {
45
+ runInstaller();
46
+ }
47
+
47
48
  if (!fs.existsSync(binPath) && !fs.existsSync(exePath)) {
48
49
  fail([
49
50
  'Error: vibes binary is not installed.',
50
51
  '',
51
- 'The @vibesdotdev/bin postinstall script did not complete. To fix, re-run install:',
52
- ' npm rebuild -g @vibesdotdev/bin',
53
- ' or',
54
- ' bun pm trust @vibesdotdev/bin && bun install -g @vibesdotdev/bin'
52
+ 'The vibesdev installer could not place the platform binary.'
55
53
  ].join('\n'));
56
54
  }
57
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibesdev",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Short npm alias for the Vibes CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,16 +15,15 @@
15
15
  },
16
16
  "files": [
17
17
  "bin/vibesdev.js",
18
+ "scripts/postinstall.js",
18
19
  "README.md"
19
20
  ],
20
21
  "scripts": {
22
+ "postinstall": "node scripts/postinstall.js",
21
23
  "pack:dry-run": "npm pack --dry-run --json --ignore-scripts",
22
24
  "publish:npm": "npm publish --access public --registry=https://registry.npmjs.org",
23
25
  "check": "node -c bin/vibesdev.js"
24
26
  },
25
- "dependencies": {
26
- "@vibesdotdev/bin": "1.0.0"
27
- },
28
27
  "os": [
29
28
  "darwin",
30
29
  "linux",
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+
3
+ const https = require('https');
4
+ const http = require('http');
5
+ const crypto = require('crypto');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+ const BASE_URL = process.env.VIBES_BINARY_MIRROR || 'https://assets.vibes.dev/downloads/cli';
10
+
11
+ function getPlatform() {
12
+ const platform = os.platform();
13
+ if (platform === 'win32') return 'win32';
14
+ if (platform === 'darwin') return 'darwin';
15
+ return 'linux';
16
+ }
17
+
18
+ function getArch() {
19
+ const arch = os.arch();
20
+ if (arch === 'x64') return 'x64';
21
+ if (arch === 'arm64') return 'arm64';
22
+ // Node on macOS can report 'arm' for Apple Silicon
23
+ if (arch === 'arm' && getPlatform() === 'darwin') return 'arm64';
24
+ return 'x64';
25
+ }
26
+
27
+ function getBinaryName() {
28
+ const platform = getPlatform();
29
+ const arch = getArch();
30
+ const baseName = `vibes-${platform}-${arch}`;
31
+ return platform === 'win32' ? `${baseName}.exe` : baseName;
32
+ }
33
+
34
+ function getOutputPath() {
35
+ const platform = getPlatform();
36
+ const dir = path.join(__dirname, '..', 'bin');
37
+ const outputName = platform === 'win32' ? 'vibes.exe' : 'vibes';
38
+ return path.join(dir, outputName);
39
+ }
40
+
41
+ function showProgress(total, current) {
42
+ if (total > 0) {
43
+ const percent = Math.round((current / total) * 100);
44
+ const bar = '[' + '='.repeat(Math.floor(percent / 5)) + ' '.repeat(20 - Math.floor(percent / 5)) + ']';
45
+ process.stdout.write(`\r${bar} ${percent}%`);
46
+ } else {
47
+ process.stdout.write('.');
48
+ }
49
+ }
50
+
51
+ function download(url, dest, callback) {
52
+ const protocol = url.startsWith('https') ? https : http;
53
+
54
+ const request = protocol.get(url, { timeout: 30000 }, (response) => {
55
+ // Handle redirects
56
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
57
+ download(response.headers.location, dest, callback);
58
+ return;
59
+ }
60
+
61
+ if (response.statusCode !== 200) {
62
+ callback(new Error(`Failed to download: HTTP ${response.statusCode}`));
63
+ return;
64
+ }
65
+
66
+ const total = parseInt(response.headers['content-length'], 10);
67
+ let downloaded = 0;
68
+
69
+ const fp = fs.openSync(dest, 'w');
70
+ response.on('data', (chunk) => {
71
+ downloaded += chunk.length;
72
+ showProgress(total, downloaded);
73
+ fs.writeSync(fp, chunk);
74
+ });
75
+
76
+ response.on('end', () => {
77
+ fs.closeSync(fp);
78
+ console.log();
79
+ callback(null);
80
+ });
81
+
82
+ response.on('error', (err) => {
83
+ fs.closeSync(fp);
84
+ callback(err);
85
+ });
86
+ });
87
+
88
+ request.on('error', (err) => {
89
+ callback(err);
90
+ });
91
+
92
+ request.on('timeout', () => {
93
+ request.destroy();
94
+ callback(new Error('Download timed out'));
95
+ });
96
+ }
97
+
98
+ function fetchText(url, callback) {
99
+ const protocol = url.startsWith('https') ? https : http;
100
+
101
+ const request = protocol.get(url, { timeout: 30000 }, (response) => {
102
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
103
+ fetchText(response.headers.location, callback);
104
+ return;
105
+ }
106
+
107
+ if (response.statusCode !== 200) {
108
+ callback(new Error(`HTTP ${response.statusCode}`));
109
+ return;
110
+ }
111
+
112
+ let body = '';
113
+ response.setEncoding('utf8');
114
+ response.on('data', (chunk) => {
115
+ body += chunk;
116
+ });
117
+ response.on('end', () => callback(null, body));
118
+ response.on('error', callback);
119
+ });
120
+
121
+ request.on('error', callback);
122
+ request.on('timeout', () => {
123
+ request.destroy();
124
+ callback(new Error('Download timed out'));
125
+ });
126
+ }
127
+
128
+ function verifyChecksum(binaryName, filePath) {
129
+ return new Promise((resolve, reject) => {
130
+ if (process.env.VIBES_SKIP_CHECKSUM === '1') {
131
+ console.warn('Warning: VIBES_SKIP_CHECKSUM=1 set; skipping checksum verification.');
132
+ resolve();
133
+ return;
134
+ }
135
+
136
+ const checksumUrl = `${BASE_URL}/SHA256SUMS.txt`;
137
+ fetchText(checksumUrl, (err, text) => {
138
+ if (err) {
139
+ reject(new Error(`Could not fetch checksum file from ${checksumUrl}: ${err.message}`));
140
+ return;
141
+ }
142
+
143
+ const line = text
144
+ .split(/\r?\n/)
145
+ .find((entry) => entry.trim().endsWith(` ${binaryName}`));
146
+ if (!line) {
147
+ reject(new Error(`No checksum entry for ${binaryName} in ${checksumUrl}`));
148
+ return;
149
+ }
150
+
151
+ const expected = line.trim().split(/\s+/)[0];
152
+ const actual = crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
153
+ if (actual !== expected) {
154
+ reject(new Error(`Checksum mismatch for ${binaryName}`));
155
+ return;
156
+ }
157
+
158
+ console.log('Verified release checksum');
159
+ resolve();
160
+ });
161
+ });
162
+ }
163
+
164
+ function makeExecutable(filePath) {
165
+ const platform = getPlatform();
166
+ if (platform === 'win32') return;
167
+
168
+ try {
169
+ fs.chmodSync(filePath, 0o755);
170
+ } catch (err) {
171
+ console.error('Warning: Could not chmod +x:', err.message);
172
+ }
173
+ }
174
+
175
+ function isWorkspaceInstall() {
176
+ // Explicit opt-out (set in CI, the monorepo's bun install, or any env that
177
+ // already has the binary built locally and doesn't want network on install).
178
+ if (process.env.VIBES_SKIP_BINARY_DOWNLOAD === '1') return true;
179
+
180
+ // When this package is installed from the npm registry, the realpath of the
181
+ // script location includes `/node_modules/` (the unpacked tarball path). When
182
+ // bun installs us via a workspace link, realpath dereferences the symlink to
183
+ // the monorepo source — which lives outside any `node_modules` directory. We
184
+ // use this asymmetry to skip downloads cleanly inside the workspace while
185
+ // failing loudly for real end-user installs.
186
+ try {
187
+ const real = fs.realpathSync(__dirname);
188
+ const sep = path.sep;
189
+ return !real.includes(`${sep}node_modules${sep}`);
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+
195
+ async function main() {
196
+ if (isWorkspaceInstall()) {
197
+ console.log('Vibes CLI installer: workspace install detected, skipping binary download.');
198
+ return;
199
+ }
200
+
201
+ const binaryName = getBinaryName();
202
+ const outputPath = getOutputPath();
203
+ const url = `${BASE_URL}/${binaryName}`;
204
+
205
+ console.log(`Vibes CLI installer`);
206
+ console.log(`Platform: ${getPlatform()}, Arch: ${getArch()}`);
207
+ console.log(`Downloading ${binaryName}...`);
208
+
209
+ // Ensure bin directory exists
210
+ const binDir = path.dirname(outputPath);
211
+ if (!fs.existsSync(binDir)) {
212
+ fs.mkdirSync(binDir, { recursive: true });
213
+ }
214
+
215
+ // Check if already exists and up-to-date
216
+ if (fs.existsSync(outputPath)) {
217
+ try {
218
+ fs.unlinkSync(outputPath);
219
+ } catch (err) {
220
+ console.error('Could not remove existing binary:', err.message);
221
+ }
222
+ }
223
+
224
+ return new Promise((resolve, reject) => {
225
+ download(url, outputPath, (err) => {
226
+ if (err) {
227
+ console.error();
228
+ console.error('Error:', err.message);
229
+ console.error(`Could not download binary from ${url}`);
230
+ console.error();
231
+ console.error('To resolve:');
232
+ console.error(` - Confirm a release exists at ${BASE_URL}`);
233
+ console.error(` - Confirm ${BASE_URL}/SHA256SUMS.txt is reachable and contains ${binaryName}`);
234
+ console.error(' - Or point VIBES_BINARY_MIRROR at an alternate download base URL');
235
+ console.error(' - Or download the binary manually and place it at:');
236
+ console.error(` ${outputPath}`);
237
+ console.error(' - Or set VIBES_SKIP_BINARY_DOWNLOAD=1 to skip this step');
238
+ console.error(' - Or set VIBES_SKIP_CHECKSUM=1 only if you trust the mirror and need to bypass checksum verification');
239
+ reject(err);
240
+ return;
241
+ }
242
+
243
+ verifyChecksum(binaryName, outputPath)
244
+ .then(() => {
245
+ makeExecutable(outputPath);
246
+ console.log(`Installed to ${outputPath}`);
247
+ resolve();
248
+ })
249
+ .catch(reject);
250
+ });
251
+ });
252
+ }
253
+
254
+ main().catch((err) => {
255
+ console.error('Installation failed:', err.message);
256
+ // End-user installs reach here when isWorkspaceInstall() was false above
257
+ // (i.e., the script lives under node_modules) and the download failed. Fail
258
+ // loud so the user sees a broken install instead of a silently empty bin/.
259
+ process.exit(1);
260
+ });