@liquidmetal-ai/raindrop-code 0.0.1-alpha20 → 0.0.1

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.
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * This is a shim that loads the platform-specific binary.
5
+ * During postinstall, this file may be replaced with the actual binary
6
+ * for performance optimization (except on Windows and when using Yarn).
7
+ */
8
+
9
+ const { spawnSync } = require('child_process');
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+
13
+ // Determine platform and arch
14
+ const platformKey = `${process.platform}-${process.arch}`;
15
+
16
+ // Map to package names
17
+ const PACKAGES = {
18
+ 'darwin-x64': '@liquidmetal-ai/raindrop-code-darwin-universal',
19
+ 'darwin-arm64': '@liquidmetal-ai/raindrop-code-darwin-universal',
20
+ 'linux-x64': '@liquidmetal-ai/raindrop-code-linux-x64',
21
+ 'linux-arm64': '@liquidmetal-ai/raindrop-code-linux-arm64',
22
+ 'win32-x64': '@liquidmetal-ai/raindrop-code-win32-x64',
23
+ };
24
+
25
+ const packageName = PACKAGES[platformKey];
26
+
27
+ if (!packageName) {
28
+ console.error(`Unsupported platform: ${platformKey}`);
29
+ console.error(`Supported platforms: ${Object.keys(PACKAGES).join(', ')}`);
30
+ process.exit(1);
31
+ }
32
+
33
+ // Determine binary name
34
+ const binaryName = process.platform === 'win32' ? 'raindrop-code.exe' : 'raindrop-code';
35
+
36
+ // Try to resolve the binary from the optional dependency
37
+ let binaryPath;
38
+ try {
39
+ // First try to resolve from the optional dependency
40
+ const packagePath = require.resolve(`${packageName}/package.json`);
41
+ binaryPath = path.join(path.dirname(packagePath), 'bin', binaryName);
42
+
43
+ if (!fs.existsSync(binaryPath)) {
44
+ throw new Error(`Binary not found at ${binaryPath}`);
45
+ }
46
+ } catch (error) {
47
+ console.error(`Error: Could not find ${packageName}`);
48
+ console.error('');
49
+ console.error('This usually means the package was installed with --no-optional or --ignore-scripts.');
50
+ console.error('Please reinstall raindrop-code without these flags:');
51
+ console.error('');
52
+ console.error(' npm install -g @liquidmetal-ai/raindrop-code');
53
+ console.error('');
54
+ process.exit(1);
55
+ }
56
+
57
+ // Execute the binary with all arguments
58
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
59
+ stdio: 'inherit',
60
+ windowsHide: false,
61
+ });
62
+
63
+ if (result.error) {
64
+ console.error(`Failed to execute binary: ${result.error.message}`);
65
+ process.exit(1);
66
+ }
67
+
68
+ process.exit(result.status || 0);
package/install.js CHANGED
@@ -3,10 +3,10 @@
3
3
  /**
4
4
  * Postinstall script for @liquidmetal-ai/raindrop-code
5
5
  *
6
- * This script ensures the correct platform-specific binary is available.
7
- * It follows a two-tier approach:
8
- * 1. First, check if a platform-specific optional dependency was installed
9
- * 2. If not, download the binary from npm as a fallback
6
+ * Approach inspired by esbuild's npm distribution:
7
+ * 1. A JavaScript shim exists at bin/raindrop-code that loads the binary from optional dependencies
8
+ * 2. On non-Windows, non-Yarn platforms, optimize by replacing the shim with the actual binary
9
+ * 3. If optional dependencies weren't installed, attempt to download as fallback
10
10
  */
11
11
 
12
12
  const fs = require('fs');
@@ -31,8 +31,7 @@ const BINARY_DISTRIBUTION_PACKAGES = {
31
31
  const binaryName = process.platform === 'win32' ? 'raindrop-code.exe' : 'raindrop-code';
32
32
 
33
33
  // Paths
34
- const binDir = path.join(__dirname, 'bin');
35
- const fallbackBinaryPath = path.join(binDir, binaryName);
34
+ const shimPath = path.join(__dirname, 'bin', binaryName);
36
35
 
37
36
  // Determine the platform-specific package name
38
37
  const platformKey = `${process.platform}-${process.arch}`;
@@ -45,46 +44,115 @@ if (!platformSpecificPackageName) {
45
44
  }
46
45
 
47
46
  /**
48
- * Check if the optional dependency was installed successfully
47
+ * Check if Yarn is being used
49
48
  */
50
- function checkOptionalDependency() {
49
+ function isYarn() {
50
+ const { npm_config_user_agent } = process.env;
51
+ if (npm_config_user_agent) {
52
+ return /\byarn\//.test(npm_config_user_agent);
53
+ }
54
+ return false;
55
+ }
56
+
57
+ /**
58
+ * Try to find the binary from the optional dependency
59
+ */
60
+ function findBinaryFromOptionalDependency() {
51
61
  try {
52
- const optionalDepPath = path.join(__dirname, 'node_modules', platformSpecificPackageName);
53
- const binaryPath = path.join(optionalDepPath, 'bin', binaryName);
62
+ // Try to resolve the package
63
+ const packageJsonPath = require.resolve(`${platformSpecificPackageName}/package.json`);
64
+ const packageDir = path.dirname(packageJsonPath);
65
+ const binaryPath = path.join(packageDir, 'bin', binaryName);
54
66
 
55
67
  if (fs.existsSync(binaryPath)) {
56
- console.log(`✓ Platform-specific package installed: ${platformSpecificPackageName}`);
57
-
58
- // Create bin directory if it doesn't exist
59
- if (!fs.existsSync(binDir)) {
60
- fs.mkdirSync(binDir, { recursive: true });
61
- }
62
-
63
- // Create a symlink or copy to the expected location
64
- if (process.platform === 'win32') {
65
- // Windows doesn't handle symlinks well, so copy
66
- fs.copyFileSync(binaryPath, fallbackBinaryPath);
67
- } else {
68
- // Unix: create symlink
69
- const relativePath = path.relative(binDir, binaryPath);
70
- if (fs.existsSync(fallbackBinaryPath)) {
71
- fs.unlinkSync(fallbackBinaryPath);
72
- }
73
- fs.symlinkSync(relativePath, fallbackBinaryPath);
74
- }
75
-
76
- // Ensure executable
77
- if (process.platform !== 'win32') {
78
- fs.chmodSync(fallbackBinaryPath, 0o755);
79
- }
80
-
81
- return true;
68
+ return binaryPath;
82
69
  }
83
70
  } catch (error) {
84
- // Ignore errors, fall through to download
71
+ // Package not found
85
72
  }
86
73
 
87
- return false;
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Verify the binary works
79
+ */
80
+ function verifyBinary(binaryPath) {
81
+ try {
82
+ const output = execSync(`"${binaryPath}" --version`, {
83
+ encoding: 'utf-8',
84
+ stdio: ['ignore', 'pipe', 'ignore']
85
+ });
86
+ const version = output.trim();
87
+ console.log(`✓ Binary verified: ${version}`);
88
+
89
+ // Check version matches
90
+ if (!version.includes(VERSION.split('-')[0])) {
91
+ console.warn(`Warning: Binary version (${version}) may not match package version (${VERSION})`);
92
+ }
93
+
94
+ return true;
95
+ } catch (error) {
96
+ console.error(`✗ Binary verification failed: ${error.message}`);
97
+ return false;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Optimize by replacing the shim with the actual binary.
103
+ * This is an optional optimization that improves startup time by avoiding an extra Node.js process.
104
+ *
105
+ * Only done on Unix-like systems (not Windows) and not when using Yarn.
106
+ * - Windows requires .exe extension for binaries, so we keep the shim
107
+ * - Yarn may run install scripts multiple times from different platforms (e.g., in Docker)
108
+ */
109
+ function maybeOptimizePackage(binaryPath) {
110
+ // Skip optimization on Windows
111
+ if (process.platform === 'win32') {
112
+ console.log('ℹ Using JavaScript shim (Windows)');
113
+ return false;
114
+ }
115
+
116
+ // Skip optimization when using Yarn to avoid idempotency issues
117
+ if (isYarn()) {
118
+ console.log('ℹ Using JavaScript shim (Yarn detected)');
119
+ return false;
120
+ }
121
+
122
+ const tempPath = path.join(__dirname, 'bin', `${binaryName}-temp`);
123
+
124
+ try {
125
+ // Create a hard link to the binary with a temporary name
126
+ // This avoids taking up additional space on the file system
127
+ fs.linkSync(binaryPath, tempPath);
128
+
129
+ // Atomically replace the shim with the binary using rename
130
+ // If this fails, we just leave the temporary file (harmless)
131
+ fs.renameSync(tempPath, shimPath);
132
+
133
+ console.log('✓ Optimized: Replaced shim with binary for faster startup');
134
+
135
+ // Clean up the temp file if it still exists
136
+ // (renameSync may return without doing anything if inodes are the same)
137
+ try {
138
+ fs.unlinkSync(tempPath);
139
+ } catch {
140
+ // Ignore - file may not exist
141
+ }
142
+
143
+ return true;
144
+ } catch (error) {
145
+ console.log('ℹ Using JavaScript shim (optimization not possible)');
146
+
147
+ // Clean up temp file if it exists
148
+ try {
149
+ fs.unlinkSync(tempPath);
150
+ } catch {
151
+ // Ignore
152
+ }
153
+
154
+ return false;
155
+ }
88
156
  }
89
157
 
90
158
  /**
@@ -132,11 +200,10 @@ function extractFileFromTarball(tarballBuffer, filepath) {
132
200
  }
133
201
 
134
202
  /**
135
- * Download the platform-specific binary from npm
203
+ * Download and install the binary from npm registry as a fallback
136
204
  */
137
- async function downloadBinary() {
138
- console.log(`Downloading binary for ${platformKey}...`);
139
- console.log(`Package: ${platformSpecificPackageName}@${VERSION}`);
205
+ async function downloadBinaryFromNPM() {
206
+ console.log(`Downloading ${platformSpecificPackageName}@${VERSION}...`);
140
207
 
141
208
  try {
142
209
  // Construct npm registry URL
@@ -156,19 +223,40 @@ async function downloadBinary() {
156
223
  const binaryPathInTar = `package/bin/${binaryName}`;
157
224
  const binaryBuffer = extractFileFromTarball(tarBuffer, binaryPathInTar);
158
225
 
159
- // Write binary to disk
160
- if (!fs.existsSync(binDir)) {
161
- fs.mkdirSync(binDir, { recursive: true });
226
+ // Create a temporary directory to extract to
227
+ const tempDir = path.join(__dirname, '.tmp-install');
228
+ const tempBinaryPath = path.join(tempDir, binaryName);
229
+
230
+ if (!fs.existsSync(tempDir)) {
231
+ fs.mkdirSync(tempDir, { recursive: true });
162
232
  }
163
233
 
164
- fs.writeFileSync(fallbackBinaryPath, binaryBuffer);
234
+ // Write binary to temp location
235
+ fs.writeFileSync(tempBinaryPath, binaryBuffer);
165
236
 
166
237
  // Make executable on Unix
167
238
  if (process.platform !== 'win32') {
168
- fs.chmodSync(fallbackBinaryPath, 0o755);
239
+ fs.chmodSync(tempBinaryPath, 0o755);
240
+ }
241
+
242
+ console.log(`✓ Binary extracted to temporary location`);
243
+
244
+ // Verify it works before installing
245
+ if (!verifyBinary(tempBinaryPath)) {
246
+ throw new Error('Downloaded binary failed verification');
247
+ }
248
+
249
+ // Try to optimize (replace shim with binary)
250
+ maybeOptimizePackage(tempBinaryPath);
251
+
252
+ // Clean up temp directory
253
+ try {
254
+ fs.unlinkSync(tempBinaryPath);
255
+ fs.rmdirSync(tempDir);
256
+ } catch {
257
+ // Ignore cleanup errors
169
258
  }
170
259
 
171
- console.log(`✓ Binary installed to ${fallbackBinaryPath}`);
172
260
  return true;
173
261
  } catch (error) {
174
262
  console.error(`✗ Failed to download binary: ${error.message}`);
@@ -177,54 +265,160 @@ async function downloadBinary() {
177
265
  }
178
266
 
179
267
  /**
180
- * Verify the binary works
268
+ * Install using npm as a fallback
181
269
  */
182
- function verifyBinary() {
270
+ function installUsingNPM() {
271
+ console.log('Attempting to install using npm...');
272
+
183
273
  try {
184
- const output = execSync(`"${fallbackBinaryPath}" --version`, {
185
- encoding: 'utf-8',
186
- stdio: ['ignore', 'pipe', 'ignore']
187
- });
188
- console.log(`✓ Binary verified: ${output.trim()}`);
189
- return true;
274
+ // Erase npm_config_global to prevent deadlock in global installs
275
+ const env = { ...process.env, npm_config_global: undefined };
276
+
277
+ // Create temporary directory for installation
278
+ const tempDir = path.join(__dirname, '.tmp-npm-install');
279
+ fs.mkdirSync(tempDir, { recursive: true });
280
+
281
+ try {
282
+ // Create empty package.json
283
+ fs.writeFileSync(path.join(tempDir, 'package.json'), '{}');
284
+
285
+ // Install the platform-specific package
286
+ console.log(`Running: npm install ${platformSpecificPackageName}@${VERSION}`);
287
+ execSync(
288
+ `npm install --loglevel=error --prefer-offline --no-audit --progress=false ${platformSpecificPackageName}@${VERSION}`,
289
+ { cwd: tempDir, stdio: 'pipe', env }
290
+ );
291
+
292
+ // Find the installed binary
293
+ const installedBinaryPath = path.join(
294
+ tempDir,
295
+ 'node_modules',
296
+ platformSpecificPackageName,
297
+ 'bin',
298
+ binaryName
299
+ );
300
+
301
+ if (!fs.existsSync(installedBinaryPath)) {
302
+ throw new Error('Binary not found after npm install');
303
+ }
304
+
305
+ console.log('✓ Package installed via npm');
306
+
307
+ // Verify the binary
308
+ if (!verifyBinary(installedBinaryPath)) {
309
+ throw new Error('Installed binary failed verification');
310
+ }
311
+
312
+ // Try to optimize
313
+ maybeOptimizePackage(installedBinaryPath);
314
+
315
+ return true;
316
+ } finally {
317
+ // Clean up temp directory
318
+ try {
319
+ removeRecursive(tempDir);
320
+ } catch {
321
+ // Ignore cleanup errors (especially on Windows where files may be locked)
322
+ }
323
+ }
190
324
  } catch (error) {
191
- console.error(`✗ Binary verification failed`);
325
+ console.error(`✗ Failed to install using npm: ${error.message}`);
192
326
  return false;
193
327
  }
194
328
  }
195
329
 
330
+ /**
331
+ * Recursively remove directory
332
+ */
333
+ function removeRecursive(dir) {
334
+ for (const entry of fs.readdirSync(dir)) {
335
+ const entryPath = path.join(dir, entry);
336
+ let stats;
337
+ try {
338
+ stats = fs.lstatSync(entryPath);
339
+ } catch {
340
+ continue;
341
+ }
342
+ if (stats.isDirectory()) {
343
+ removeRecursive(entryPath);
344
+ } else {
345
+ fs.unlinkSync(entryPath);
346
+ }
347
+ }
348
+ fs.rmdirSync(dir);
349
+ }
350
+
196
351
  /**
197
352
  * Main installation logic
198
353
  */
199
354
  async function main() {
200
355
  console.log('raindrop-code postinstall');
201
356
  console.log('========================');
357
+ console.log(`Platform: ${platformKey}`);
358
+ console.log(`Package: ${platformSpecificPackageName}`);
359
+ console.log('');
202
360
 
203
361
  // Strategy 1: Check if optional dependency was installed
204
- if (checkOptionalDependency()) {
205
- console.log('✓ Installation complete (via optional dependency)');
362
+ const binaryPath = findBinaryFromOptionalDependency();
363
+
364
+ if (binaryPath) {
365
+ console.log(`✓ Found binary from optional dependency: ${platformSpecificPackageName}`);
366
+
367
+ // Verify the binary works
368
+ if (!verifyBinary(binaryPath)) {
369
+ console.error('✗ Binary verification failed');
370
+ process.exit(1);
371
+ }
372
+
373
+ // Try to optimize by replacing shim with binary
374
+ maybeOptimizePackage(binaryPath);
375
+
376
+ console.log('');
377
+ console.log('✓ Installation complete');
206
378
  return;
207
379
  }
208
380
 
209
- console.log('Optional dependency not found, downloading binary...');
381
+ // Strategy 2: Try to install using npm
382
+ console.log('Optional dependency not found.');
383
+ console.log('This can happen if you used --no-optional or --ignore-scripts.');
384
+ console.log('');
210
385
 
211
- // Strategy 2: Download binary from npm
212
- const downloadSuccess = await downloadBinary();
213
-
214
- if (!downloadSuccess) {
215
- console.error('\n⚠ Installation incomplete');
216
- console.error('Please report this issue: https://github.com/ianschenck/raindrop-code/issues');
217
- process.exit(1);
386
+ const npmSuccess = installUsingNPM();
387
+ if (npmSuccess) {
388
+ console.log('');
389
+ console.log('✓ Installation complete (via npm)');
390
+ return;
218
391
  }
219
392
 
220
- // Verify the binary
221
- verifyBinary();
393
+ // Strategy 3: Download directly from npm registry
394
+ console.log('');
395
+ console.log('npm install failed, attempting direct download...');
396
+ console.log('');
397
+
398
+ const downloadSuccess = await downloadBinaryFromNPM();
399
+ if (downloadSuccess) {
400
+ console.log('');
401
+ console.log('✓ Installation complete (via direct download)');
402
+ return;
403
+ }
222
404
 
223
- console.log('✓ Installation complete (via download)');
405
+ // All strategies failed
406
+ console.error('');
407
+ console.error('✗ Installation failed');
408
+ console.error('');
409
+ console.error('All installation strategies failed. Please try:');
410
+ console.error(' 1. Reinstall without --no-optional or --ignore-scripts');
411
+ console.error(' 2. Check your internet connection');
412
+ console.error(' 3. Check npm registry access');
413
+ console.error('');
414
+ console.error('If the problem persists, please report an issue:');
415
+ console.error(' https://github.com/ianschenck/raindrop-code/issues');
416
+ console.error('');
417
+ process.exit(1);
224
418
  }
225
419
 
226
420
  // Run installation
227
421
  main().catch((error) => {
228
- console.error('Installation failed:', error);
422
+ console.error('Installation error:', error);
229
423
  process.exit(1);
230
424
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquidmetal-ai/raindrop-code",
3
- "version": "0.0.1-alpha20",
3
+ "version": "0.0.1",
4
4
  "description": "AI-powered terminal coding assistant with deep code understanding, file operations, and extensible tools",
5
5
  "keywords": [
6
6
  "ai",
@@ -41,9 +41,9 @@
41
41
  "bin/"
42
42
  ],
43
43
  "optionalDependencies": {
44
- "@liquidmetal-ai/raindrop-code-darwin-universal": "0.0.1-alpha20",
45
- "@liquidmetal-ai/raindrop-code-linux-x64": "0.0.1-alpha20",
46
- "@liquidmetal-ai/raindrop-code-linux-arm64": "0.0.1-alpha20",
47
- "@liquidmetal-ai/raindrop-code-win32-x64": "0.0.1-alpha20"
44
+ "@liquidmetal-ai/raindrop-code-darwin-universal": "0.0.1",
45
+ "@liquidmetal-ai/raindrop-code-linux-x64": "0.0.1",
46
+ "@liquidmetal-ai/raindrop-code-linux-arm64": "0.0.1",
47
+ "@liquidmetal-ai/raindrop-code-win32-x64": "0.0.1"
48
48
  }
49
49
  }