ace-tool-rs 0.1.8 → 0.1.9

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.
Files changed (3) hide show
  1. package/README.md +49 -60
  2. package/package.json +14 -3
  3. package/run.js +32 -532
package/README.md CHANGED
@@ -1,88 +1,77 @@
1
- # ace-tool-rs npm shim
1
+ # ace-tool-rs
2
2
 
3
- This npm package provides a convenient way to install and run `ace-tool-rs` via npm/npx.
3
+ MCP server for codebase indexing, semantic search, and prompt enhancement.
4
4
 
5
- ## How It Works
6
-
7
- This is a **shim package** that automatically downloads the appropriate pre-built binary for your platform from GitHub Releases when first run. The binary is cached locally for subsequent invocations.
8
-
9
- ## Quick Start
5
+ ## Installation
10
6
 
11
7
  ```bash
12
- # Run directly with npx (no installation needed)
13
- npx ace-tool-rs --base-url <API_URL> --token <AUTH_TOKEN>
14
-
15
- # Or install globally
8
+ # Install globally
16
9
  npm install -g ace-tool-rs
17
- ace-tool-rs --base-url <API_URL> --token <AUTH_TOKEN>
18
- ```
19
10
 
20
- ## Supported Platforms
11
+ # Or run directly with npx
12
+ npx ace-tool-rs --help
13
+ ```
21
14
 
22
- | Platform | Architecture | Status |
23
- |----------|--------------|--------|
24
- | Windows | x64 | Supported |
25
- | macOS | x64, ARM64 | Supported (universal binary) |
26
- | Linux | x64 | Supported |
15
+ ## How It Works
27
16
 
28
- ## Cache Location
17
+ This package uses platform-specific optional dependencies to provide pre-built binaries. When you install `ace-tool-rs`, npm automatically downloads the correct binary for your platform.
29
18
 
30
- Downloaded binaries are cached in platform-specific directories:
19
+ ### Supported Platforms
31
20
 
32
- | Platform | Cache Path |
33
- |----------|------------|
34
- | Windows | `%LOCALAPPDATA%\ace-tool-rs\<version>\` |
35
- | macOS | `~/Library/Caches/ace-tool-rs/<version>/` |
36
- | Linux | `$XDG_CACHE_HOME/ace-tool-rs/<version>/` or `~/.cache/ace-tool-rs/<version>/` |
21
+ | Platform | Architecture | Package |
22
+ |----------|--------------|---------|
23
+ | macOS | x64, ARM64 | `@ace-tool-rs/darwin-universal` |
24
+ | Linux | x64 | `@ace-tool-rs/linux-x64` |
25
+ | Linux | ARM64 | `@ace-tool-rs/linux-arm64` |
26
+ | Windows | x64 | `@ace-tool-rs/win32-x64` |
27
+ | Windows | ARM64 | `@ace-tool-rs/win32-arm64` |
37
28
 
38
- The cache is versioned, so upgrading the npm package will download a new binary matching that version.
29
+ ## Usage
39
30
 
40
- ## Environment Variables
31
+ ```bash
32
+ ace-tool-rs --base-url <API_URL> --token <AUTH_TOKEN>
33
+ ```
41
34
 
42
- | Variable | Description |
43
- |----------|-------------|
44
- | `GITHUB_TOKEN` | GitHub personal access token to avoid rate limits when downloading |
35
+ ## Troubleshooting
45
36
 
46
- ## Requirements
37
+ ### Binary not found
47
38
 
48
- - Node.js 14.14.0 or later
49
- - `tar` command (Linux/macOS) or PowerShell 5.0+ (Windows) for extraction
39
+ If the platform-specific package failed to install, you can install it manually:
50
40
 
51
- ## Troubleshooting
41
+ ```bash
42
+ # For Linux x64
43
+ npm install @ace-tool-rs/linux-x64
52
44
 
53
- ### Download Fails
45
+ # For macOS
46
+ npm install @ace-tool-rs/darwin-universal
54
47
 
55
- If automatic download fails, you can:
48
+ # For Windows x64
49
+ npm install @ace-tool-rs/win32-x64
50
+ ```
56
51
 
57
- 1. **Manual download**: Download the appropriate binary from [GitHub Releases](https://github.com/missdeer/ace-tool-rs/releases) and place it in the cache directory.
52
+ ### Alternative installation
58
53
 
59
- 2. **Install via Cargo**: If you have Rust installed:
60
- ```bash
61
- cargo install ace-tool-rs
62
- ```
54
+ If you have Rust installed, you can build from source:
63
55
 
64
- 3. **Set GITHUB_TOKEN**: If you're hitting GitHub API rate limits:
65
- ```bash
66
- export GITHUB_TOKEN=your_github_token
67
- npx ace-tool-rs --base-url <API_URL> --token <AUTH_TOKEN>
68
- ```
56
+ ```bash
57
+ cargo install ace-tool-rs
58
+ ```
69
59
 
70
- ### Binary Not Found After Extraction
60
+ ## License
71
61
 
72
- Ensure your system has the required extraction tools:
73
- - **Windows**: PowerShell 5.0+ with `Expand-Archive` cmdlet
74
- - **Linux/macOS**: `tar` command
62
+ GPL-3.0-only
75
63
 
76
- ## How the Shim Works
64
+ For commercial use, please contact missdeer@gmail.com for licensing options.
77
65
 
78
- 1. On first run, checks if the binary exists in the cache directory
79
- 2. If not found, queries GitHub API for the release matching the npm package version
80
- 3. Downloads the platform-appropriate archive (`.zip` for Windows, `.tar.gz` for others)
81
- 4. Extracts the binary to the cache directory
82
- 5. Executes the binary with all provided arguments
66
+ ## Verifying Downloads
83
67
 
84
- ## License
68
+ Each GitHub release includes a `SHA256SUMS` file for integrity verification:
85
69
 
86
- This package is part of [ace-tool-rs](https://github.com/missdeer/ace-tool-rs) and is licensed under GPL-3.0.
70
+ ```bash
71
+ # Download the binary and checksum file
72
+ curl -LO https://github.com/missdeer/ace-tool-rs/releases/latest/download/ace-tool-rs_Linux_x86_64.tar.gz
73
+ curl -LO https://github.com/missdeer/ace-tool-rs/releases/latest/download/SHA256SUMS
87
74
 
88
- For commercial use, please contact missdeer@gmail.com for licensing options.
75
+ # Verify the checksum
76
+ sha256sum -c SHA256SUMS --ignore-missing
77
+ ```
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "ace-tool-rs",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "MCP server for codebase indexing, semantic search, and prompt enhancement",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
5
8
  "repository": {
6
9
  "type": "git",
7
10
  "url": "git+https://github.com/missdeer/ace-tool-rs.git"
@@ -25,7 +28,8 @@
25
28
  "ace-tool-rs": "run.js"
26
29
  },
27
30
  "files": [
28
- "run.js"
31
+ "run.js",
32
+ "README.md"
29
33
  ],
30
34
  "engines": {
31
35
  "node": ">=14.14.0"
@@ -38,5 +42,12 @@
38
42
  "cpu": [
39
43
  "x64",
40
44
  "arm64"
41
- ]
45
+ ],
46
+ "optionalDependencies": {
47
+ "@ace-tool-rs/darwin-universal": "0.1.9",
48
+ "@ace-tool-rs/linux-x64": "0.1.9",
49
+ "@ace-tool-rs/linux-arm64": "0.1.9",
50
+ "@ace-tool-rs/win32-x64": "0.1.9",
51
+ "@ace-tool-rs/win32-arm64": "0.1.9"
52
+ }
42
53
  }
package/run.js CHANGED
@@ -2,545 +2,48 @@
2
2
 
3
3
  const { spawn } = require("child_process");
4
4
  const path = require("path");
5
- const fs = require("fs");
6
- const https = require("https");
7
5
  const os = require("os");
8
- const crypto = require("crypto");
9
6
 
10
- // Note: We do not use 'kexec' to replace the process because the available packages
11
- // are largely unmaintained or have compatibility issues with modern Node.js versions.
12
- // Instead, we spawn the child and forward signals/stdio.
13
- // Crucially, we ensure the wrapper ONLY writes to stderr (logs, extraction output)
14
- // so that the Rust binary's stdout (MCP JSON-RPC) is the only thing on stdout.
15
-
16
- const PACKAGE_NAME = "ace-tool-rs";
17
- const REPO_OWNER = "missdeer";
18
- const REPO_NAME = "ace-tool-rs";
19
- const MAX_REDIRECTS = 10;
20
- const REQUEST_TIMEOUT = 60000; // 60 seconds
21
- const MAX_RETRIES = 3;
22
- const RETRY_DELAY = 1000; // 1 second
23
-
24
- // Helper for retry with exponential backoff
25
- async function withRetry(fn, retries = MAX_RETRIES) {
26
- let lastError;
27
- for (let attempt = 0; attempt < retries; attempt++) {
28
- try {
29
- return await fn();
30
- } catch (err) {
31
- lastError = err;
32
- // Don't retry on non-retryable errors
33
- if (
34
- err.message.includes("Unsupported") ||
35
- err.message.includes("rate limit") ||
36
- err.message.includes("404")
37
- ) {
38
- throw err;
39
- }
40
- if (attempt < retries - 1) {
41
- const delay = RETRY_DELAY * Math.pow(2, attempt);
42
- await new Promise((resolve) => setTimeout(resolve, delay));
43
- }
44
- }
45
- }
46
- throw lastError;
47
- }
48
-
49
- // Cache package version
50
- let cachedVersion = null;
51
-
52
- // Get package version from package.json
53
- function getPackageVersion() {
54
- if (cachedVersion === null) {
55
- cachedVersion = require("./package.json").version;
56
- }
57
- return cachedVersion;
58
- }
59
-
60
- // Get cache directory based on OS
61
- function getCacheDir() {
62
- const homeDir = os.homedir();
63
- const version = getPackageVersion();
64
- let baseDir;
65
-
66
- switch (process.platform) {
67
- case "win32":
68
- baseDir = path.join(
69
- process.env.LOCALAPPDATA || path.join(homeDir, "AppData", "Local"),
70
- PACKAGE_NAME
71
- );
72
- break;
73
- case "darwin":
74
- baseDir = path.join(homeDir, "Library", "Caches", PACKAGE_NAME);
75
- break;
76
- default:
77
- baseDir = path.join(
78
- process.env.XDG_CACHE_HOME || path.join(homeDir, ".cache"),
79
- PACKAGE_NAME
80
- );
81
- }
82
-
83
- // Include version in cache path to handle upgrades
84
- return path.join(baseDir, version);
85
- }
86
-
87
- // Get asset name based on platform (matching release.yml)
88
- function getAssetName() {
89
- const platform = process.platform;
90
- const arch = process.arch;
91
-
92
- switch (platform) {
93
- case "darwin":
94
- // macOS uses universal binary (supports both x64 and arm64)
95
- return "ace-tool-rs_Darwin_universal.tar.gz";
96
- case "linux":
97
- if (arch === "x64") {
98
- return "ace-tool-rs_Linux_x86_64.tar.gz";
99
- } else if (arch === "arm64") {
100
- return "ace-tool-rs_Linux_aarch64.tar.gz";
101
- }
102
- throw new Error(
103
- `Unsupported architecture: ${arch} on Linux. Only x64 and arm64 are supported.`
104
- );
105
- case "win32":
106
- if (arch === "x64") {
107
- return "ace-tool-rs_Windows_x86_64.zip";
108
- } else if (arch === "arm64") {
109
- return "ace-tool-rs_Windows_aarch64.zip";
110
- }
111
- throw new Error(
112
- `Unsupported architecture: ${arch} on Windows. Only x64 and arm64 are supported.`
113
- );
114
- default:
115
- throw new Error(`Unsupported platform: ${platform}`);
116
- }
117
- }
118
-
119
- function getBinaryName() {
120
- return process.platform === "win32" ? `${PACKAGE_NAME}.exe` : PACKAGE_NAME;
121
- }
122
-
123
- function httpsGet(url, options = {}, redirectCount = 0) {
124
- return new Promise((resolve, reject) => {
125
- if (redirectCount > MAX_REDIRECTS) {
126
- reject(new Error("Too many redirects"));
127
- return;
128
- }
129
-
130
- const req = https.get(url, options, (res) => {
131
- // Handle redirects
132
- if (
133
- res.statusCode >= 300 &&
134
- res.statusCode < 400 &&
135
- res.headers.location
136
- ) {
137
- // Consume response to free up connection
138
- res.resume();
139
- const redirectUrl = res.headers.location;
140
- // Only follow HTTPS redirects
141
- if (!redirectUrl.startsWith("https://")) {
142
- reject(new Error(`Insecure redirect to: ${redirectUrl}`));
143
- return;
144
- }
145
- httpsGet(redirectUrl, options, redirectCount + 1)
146
- .then(resolve)
147
- .catch(reject);
148
- return;
149
- }
150
-
151
- if (res.statusCode === 403) {
152
- res.resume();
153
- reject(
154
- new Error(
155
- "GitHub API rate limit exceeded. Please try again later or set GITHUB_TOKEN environment variable."
156
- )
157
- );
158
- return;
159
- }
160
-
161
- if (res.statusCode !== 200) {
162
- res.resume();
163
- reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
164
- return;
165
- }
166
-
167
- const chunks = [];
168
- res.on("data", (chunk) => chunks.push(chunk));
169
- res.on("end", () => resolve(Buffer.concat(chunks)));
170
- res.on("error", reject);
171
- });
172
-
173
- req.on("error", reject);
174
- req.setTimeout(REQUEST_TIMEOUT, () => {
175
- req.destroy();
176
- reject(new Error("Request timeout"));
177
- });
178
- });
179
- }
180
-
181
- // Parse JSON with helpful error message
182
- function parseJSON(data, context) {
183
- try {
184
- return JSON.parse(data.toString());
185
- } catch (err) {
186
- const preview = data.toString().slice(0, 200);
187
- throw new Error(
188
- `Failed to parse ${context} response. GitHub may be experiencing issues. Response preview: ${preview}`
189
- );
190
- }
191
- }
192
-
193
- async function getReleaseByTag(version) {
194
- const tag = `v${version}`;
195
- const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${tag}`;
196
- const options = {
197
- headers: {
198
- "User-Agent": PACKAGE_NAME,
199
- Accept: "application/vnd.github.v3+json",
200
- ...(process.env.GITHUB_TOKEN && {
201
- Authorization: `token ${process.env.GITHUB_TOKEN}`,
202
- }),
203
- },
204
- };
205
-
206
- try {
207
- const data = await httpsGet(url, options);
208
- return parseJSON(data, "release");
209
- } catch (error) {
210
- // If the specific version tag doesn't exist, fall back to latest
211
- if (error.message.includes("404")) {
212
- console.error(
213
- `Release v${version} not found, falling back to latest release...`
214
- );
215
- return getLatestRelease();
216
- }
217
- throw error;
218
- }
219
- }
220
-
221
- async function getLatestRelease() {
222
- const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
223
- const options = {
224
- headers: {
225
- "User-Agent": PACKAGE_NAME,
226
- Accept: "application/vnd.github.v3+json",
227
- ...(process.env.GITHUB_TOKEN && {
228
- Authorization: `token ${process.env.GITHUB_TOKEN}`,
229
- }),
230
- },
231
- };
232
-
233
- const data = await httpsGet(url, options);
234
- return parseJSON(data, "latest release");
235
- }
236
-
237
- function downloadToFile(url, destPath, options = {}, redirectCount = 0) {
238
- return new Promise((resolve, reject) => {
239
- if (redirectCount > MAX_REDIRECTS) {
240
- reject(new Error("Too many redirects"));
241
- return;
242
- }
243
-
244
- const file = fs.createWriteStream(destPath);
245
- const req = https.get(url, options, (res) => {
246
- // Handle redirects
247
- if (
248
- res.statusCode >= 300 &&
249
- res.statusCode < 400 &&
250
- res.headers.location
251
- ) {
252
- res.resume(); // Consume response
253
- file.close(() => {
254
- try {
255
- fs.unlinkSync(destPath);
256
- } catch {}
257
- const redirectUrl = res.headers.location;
258
- if (!redirectUrl.startsWith("https://")) {
259
- reject(new Error(`Insecure redirect to: ${redirectUrl}`));
260
- return;
261
- }
262
- downloadToFile(redirectUrl, destPath, options, redirectCount + 1)
263
- .then(resolve)
264
- .catch(reject);
265
- });
266
- return;
267
- }
268
-
269
- if (res.statusCode !== 200) {
270
- res.resume(); // Consume response
271
- file.close(() => {
272
- try {
273
- fs.unlinkSync(destPath);
274
- } catch {}
275
- reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
276
- });
277
- return;
278
- }
279
-
280
- res.pipe(file);
281
- file.on("finish", () => {
282
- file.close(() => resolve());
283
- });
284
- file.on("error", (err) => {
285
- file.close(() => {
286
- try {
287
- fs.unlinkSync(destPath);
288
- } catch {}
289
- reject(err);
290
- });
291
- });
292
- });
293
-
294
- req.on("error", (err) => {
295
- file.close(() => {
296
- try {
297
- fs.unlinkSync(destPath);
298
- } catch {}
299
- reject(err);
300
- });
301
- });
302
-
303
- req.setTimeout(REQUEST_TIMEOUT, () => {
304
- req.destroy();
305
- file.close(() => {
306
- try {
307
- fs.unlinkSync(destPath);
308
- } catch {}
309
- reject(new Error("Download timeout"));
310
- });
311
- });
312
- });
313
- }
314
-
315
- async function extractTarGz(archivePath, destDir) {
316
- return new Promise((resolve, reject) => {
317
- // Redirect stdout to stderr to prevent MCP corruption
318
- const tar = spawn("tar", ["-xzf", archivePath, "-C", destDir], {
319
- stdio: ["ignore", process.stderr, process.stderr],
320
- });
321
- tar.on("close", (code) => {
322
- if (code === 0) resolve();
323
- else reject(new Error(`tar exited with code ${code}`));
324
- });
325
- tar.on("error", (err) => {
326
- if (err.code === "ENOENT") {
327
- reject(new Error("tar command not found. Please install tar."));
328
- } else {
329
- reject(err);
330
- }
331
- });
332
- });
333
- }
334
-
335
- async function extractZip(archivePath, destDir) {
336
- return new Promise((resolve, reject) => {
337
- // Escape paths for PowerShell: escape backticks and single quotes
338
- const escapePath = (p) => p.replace(/`/g, "``").replace(/'/g, "''");
339
- // Redirect stdout to stderr to prevent MCP corruption
340
- const unzipProcess = spawn(
341
- "powershell",
342
- [
343
- "-NoProfile",
344
- "-ExecutionPolicy",
345
- "Bypass",
346
- "-Command",
347
- `Expand-Archive -LiteralPath '${escapePath(archivePath)}' -DestinationPath '${escapePath(destDir)}' -Force`,
348
- ],
349
- { stdio: ["ignore", process.stderr, process.stderr] }
350
- );
351
- unzipProcess.on("close", (code) => {
352
- if (code === 0) resolve();
353
- else reject(new Error(`PowerShell Expand-Archive exited with code ${code}`));
354
- });
355
- unzipProcess.on("error", (err) => {
356
- if (err.code === "ENOENT") {
357
- reject(new Error("PowerShell not found. Please install PowerShell 5.0+."));
358
- } else {
359
- reject(err);
360
- }
361
- });
362
- });
363
- }
364
-
365
- // Move file with fallback for cross-device moves
366
- function moveFile(src, dest) {
367
- try {
368
- fs.renameSync(src, dest);
369
- } catch (err) {
370
- if (err.code === "EXDEV") {
371
- // Cross-device move: copy + delete
372
- fs.copyFileSync(src, dest);
373
- fs.unlinkSync(src);
374
- } else {
375
- throw err;
376
- }
377
- }
378
- }
379
-
380
- // Create a lock file to prevent concurrent downloads
381
- function acquireLock(lockPath) {
382
- try {
383
- fs.writeFileSync(lockPath, process.pid.toString(), { flag: "wx" });
384
- return true;
385
- } catch (err) {
386
- if (err.code === "EEXIST") {
387
- // Check if the process that created the lock is still running
388
- try {
389
- const pid = parseInt(fs.readFileSync(lockPath, "utf8"), 10);
390
- try {
391
- process.kill(pid, 0); // Check if process exists
392
- return false; // Process is still running
393
- } catch {
394
- // Process is not running, remove stale lock
395
- fs.unlinkSync(lockPath);
396
- return acquireLock(lockPath);
397
- }
398
- } catch {
399
- return false;
400
- }
401
- }
402
- throw err;
403
- }
404
- }
405
-
406
- function releaseLock(lockPath) {
407
- try {
408
- fs.unlinkSync(lockPath);
409
- } catch {
410
- // Ignore errors
411
- }
412
- }
413
-
414
- async function downloadAndExtract(cacheDir) {
415
- const assetName = getAssetName();
416
- const binaryName = getBinaryName();
417
- const binaryPath = path.join(cacheDir, binaryName);
418
- const lockPath = path.join(cacheDir, ".lock");
419
- const tempId = crypto.randomBytes(8).toString("hex");
420
-
421
- // Ensure cache directory exists
422
- fs.mkdirSync(cacheDir, { recursive: true });
423
-
424
- // Try to acquire lock
425
- if (!acquireLock(lockPath)) {
426
- // Wait for other process to complete
427
- console.error("Another process is downloading, waiting...");
428
- let attempts = 0;
429
- while (!fs.existsSync(binaryPath) && attempts < 60) {
430
- await new Promise((resolve) => setTimeout(resolve, 1000));
431
- attempts++;
432
- }
433
- if (fs.existsSync(binaryPath)) {
434
- return binaryPath;
435
- }
436
- throw new Error("Timeout waiting for download to complete");
7
+ const PLATFORMS = {
8
+ "darwin-x64": "@ace-tool-rs/darwin-universal",
9
+ "darwin-arm64": "@ace-tool-rs/darwin-universal",
10
+ "linux-x64": "@ace-tool-rs/linux-x64",
11
+ "linux-arm64": "@ace-tool-rs/linux-arm64",
12
+ "win32-x64": "@ace-tool-rs/win32-x64",
13
+ "win32-arm64": "@ace-tool-rs/win32-arm64",
14
+ };
15
+
16
+ function getBinaryPath() {
17
+ const platformKey = `${process.platform}-${process.arch}`;
18
+ const pkgName = PLATFORMS[platformKey];
19
+
20
+ if (!pkgName) {
21
+ console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
22
+ console.error("Supported platforms: " + Object.keys(PLATFORMS).join(", "));
23
+ process.exit(1);
437
24
  }
438
25
 
439
26
  try {
440
- // Double check after acquiring lock
441
- if (fs.existsSync(binaryPath)) {
442
- return binaryPath;
443
- }
444
-
445
- // Get release for the specific version (with retry)
446
- const version = getPackageVersion();
447
- console.error(`Downloading ${PACKAGE_NAME} v${version}...`);
448
-
449
- const release = await withRetry(() => getReleaseByTag(version));
450
- const asset = release.assets.find((a) => a.name === assetName);
451
-
452
- if (!asset) {
453
- const availableAssets = release.assets.map((a) => a.name).join(", ");
454
- throw new Error(
455
- `No matching asset found: ${assetName}. Available: ${availableAssets}`
456
- );
457
- }
458
-
459
- // Download to temporary file first
460
- const tempArchive = path.join(cacheDir, `${tempId}-${assetName}`);
461
- const tempExtractDir = path.join(cacheDir, `${tempId}-extract`);
462
-
463
- const downloadOptions = {
464
- headers: {
465
- "User-Agent": PACKAGE_NAME,
466
- Accept: "application/octet-stream",
467
- ...(process.env.GITHUB_TOKEN && {
468
- Authorization: `token ${process.env.GITHUB_TOKEN}`,
469
- }),
470
- },
471
- };
472
-
473
- await withRetry(() =>
474
- downloadToFile(asset.browser_download_url, tempArchive, downloadOptions)
475
- );
476
-
477
- // Extract to temporary directory
478
- console.error("Extracting...");
479
- fs.mkdirSync(tempExtractDir, { recursive: true });
480
-
481
- if (assetName.endsWith(".zip")) {
482
- await extractZip(tempArchive, tempExtractDir);
483
- } else {
484
- await extractTarGz(tempArchive, tempExtractDir);
485
- }
486
-
487
- // Find the binary in the extracted directory
488
- const extractedBinary = path.join(tempExtractDir, binaryName);
489
- if (!fs.existsSync(extractedBinary)) {
490
- throw new Error(
491
- `Binary not found in archive. Expected: ${binaryName} in extracted contents.`
492
- );
493
- }
494
-
495
- // Atomic move to final location (with cross-device fallback)
496
- moveFile(extractedBinary, binaryPath);
497
-
498
- // Make binary executable on Unix
499
- if (process.platform !== "win32") {
500
- fs.chmodSync(binaryPath, 0o755);
501
- }
502
-
503
- // Clean up
504
- fs.unlinkSync(tempArchive);
505
- fs.rmSync(tempExtractDir, { recursive: true, force: true });
506
-
507
- console.error(`Installed ${PACKAGE_NAME} to ${binaryPath}`);
508
- return binaryPath;
509
- } catch (error) {
510
- console.error(`Failed to download ${PACKAGE_NAME}: ${error.message}`);
27
+ const pkgPath = require.resolve(`${pkgName}/package.json`);
28
+ const binName = process.platform === "win32" ? "ace-tool-rs.exe" : "ace-tool-rs";
29
+ return path.join(path.dirname(pkgPath), "bin", binName);
30
+ } catch (e) {
31
+ console.error(`Failed to find platform package: ${pkgName}`);
32
+ console.error("This may happen if npm failed to install the optional dependency.");
511
33
  console.error("");
512
- console.error("You can install manually:");
513
- console.error(
514
- " 1. Download from https://github.com/missdeer/ace-tool-rs/releases"
515
- );
516
- console.error(` 2. Place binary at: ${binaryPath}`);
34
+ console.error("Try reinstalling:");
35
+ console.error(" npm install ace-tool-rs");
517
36
  console.error("");
518
- console.error("Or install via cargo:");
519
- console.error(" cargo install ace-tool-rs");
37
+ console.error("Or install the platform package directly:");
38
+ console.error(` npm install ${pkgName}`);
520
39
  process.exit(1);
521
- } finally {
522
- releaseLock(lockPath);
523
40
  }
524
41
  }
525
42
 
526
- async function run() {
527
- const cacheDir = getCacheDir();
528
- const binaryName = getBinaryName();
529
- const binaryPath = path.join(cacheDir, binaryName);
530
-
531
- // Check if binary exists in cache
532
- if (!fs.existsSync(binaryPath)) {
533
- await downloadAndExtract(cacheDir);
534
- }
535
-
536
- // Final check that binary exists
537
- if (!fs.existsSync(binaryPath)) {
538
- console.error(`Binary not found at ${binaryPath}`);
539
- process.exit(1);
540
- }
541
-
542
- // Run the binary with all arguments
43
+ function run() {
44
+ const binaryPath = getBinaryPath();
543
45
  const args = process.argv.slice(2);
46
+
544
47
  const child = spawn(binaryPath, args, {
545
48
  stdio: "inherit",
546
49
  env: process.env,
@@ -557,7 +60,7 @@ async function run() {
557
60
  });
558
61
 
559
62
  child.on("error", (error) => {
560
- console.error(`Failed to start ${PACKAGE_NAME}: ${error.message}`);
63
+ console.error(`Failed to start ace-tool-rs: ${error.message}`);
561
64
  process.exit(1);
562
65
  });
563
66
 
@@ -569,7 +72,4 @@ async function run() {
569
72
  });
570
73
  }
571
74
 
572
- run().catch((error) => {
573
- console.error(error);
574
- process.exit(1);
575
- });
75
+ run();