@unitsvc/cc-helper 1.0.6 → 1.0.8

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/bin/cc-helper.js CHANGED
@@ -1,331 +1,411 @@
1
- #!/usr/bin/env node
2
-
3
- const https = require('https');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const os = require('os');
7
- const { spawn, execSync } = require('child_process');
8
-
9
- const GITHUB_OWNER = 'next-bin';
10
- const GITHUB_REPO = 'cc-helper';
11
- const CACHE_DIR = path.join(os.homedir(), '.cache', 'cc-helper');
12
- const VERSION_FILE = path.join(CACHE_DIR, 'version.txt');
13
-
14
- // Handle uninstall command directly
15
- function handleUninstall() {
16
- try {
17
- if (fs.existsSync(CACHE_DIR)) {
18
- console.log('Removing cached binaries...');
19
- fs.rmSync(CACHE_DIR, { recursive: true, force: true });
20
- console.log('Cleanup complete!');
21
- } else {
22
- console.log('No cached binaries found.');
23
- }
24
- console.log('\nTo complete uninstallation, run:');
25
- console.log(' npm uninstall -g @unitsvc/cc-helper');
26
- process.exit(0);
27
- } catch (err) {
28
- console.error('Error during uninstall:', err.message);
29
- process.exit(1);
30
- }
31
- }
32
-
33
- // Read version from package.json
34
- function getPackageVersion() {
35
- try {
36
- const packagePath = path.join(__dirname, '..', 'package.json');
37
- const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
38
- return pkg.version;
39
- } catch (err) {
40
- throw new Error('Cannot read package version: ' + err.message);
41
- }
42
- }
43
-
44
- function getPlatform() {
45
- const platform = os.platform();
46
- const arch = os.arch();
47
-
48
- const platformMap = {
49
- 'darwin': 'darwin',
50
- 'linux': 'linux',
51
- 'win32': 'windows'
52
- };
53
-
54
- const archMap = {
55
- 'x64': 'amd64',
56
- 'arm64': 'arm64'
57
- };
58
-
59
- const mappedPlatform = platformMap[platform];
60
- const mappedArch = archMap[arch];
61
-
62
- if (!mappedPlatform || !mappedArch) {
63
- throw new Error(`Unsupported platform: ${platform} ${arch}`);
64
- }
65
-
66
- // Windows arm64 not supported by GoReleaser
67
- if (platform === 'win32' && arch === 'arm64') {
68
- throw new Error('Windows arm64 is not supported. Please use Windows amd64 or macOS/Linux arm64.');
69
- }
70
-
71
- return { platform: mappedPlatform, arch: mappedArch };
72
- }
73
-
74
- function getArchiveName(version, platform, arch) {
75
- // GitHub release uses version without 'v' prefix in filename
76
- const ver = version.replace(/^v/, '');
77
- if (platform === 'windows') {
78
- return `cc-helper_${ver}_${platform}_${arch}.zip`;
79
- }
80
- return `cc-helper_${ver}_${platform}_${arch}.tar.gz`;
81
- }
82
-
83
- function getBinaryName(platform) {
84
- return platform === 'windows' ? 'cc-helper.exe' : 'cc-helper';
85
- }
86
-
87
- function getBinaryPath() {
88
- const { platform } = getPlatform();
89
- const binaryName = getBinaryName(platform);
90
- return path.join(CACHE_DIR, binaryName);
91
- }
92
-
93
- function downloadFile(url, dest) {
94
- return new Promise((resolve, reject) => {
95
- const file = fs.createWriteStream(dest);
96
-
97
- const requestUrl = url.startsWith('https://') ? url : 'https://github.com' + url;
98
-
99
- const request = https.get(requestUrl, { headers: { 'User-Agent': 'cc-helper-npm-installer' } }, (response) => {
100
- if (response.statusCode === 302 || response.statusCode === 301) {
101
- file.close();
102
- if (fs.existsSync(dest)) {
103
- fs.unlinkSync(dest);
104
- }
105
- downloadFile(response.headers.location, dest).then(resolve).catch(reject);
106
- return;
107
- }
108
-
109
- if (response.statusCode !== 200) {
110
- file.close();
111
- if (fs.existsSync(dest)) {
112
- fs.unlinkSync(dest);
113
- }
114
- reject(new Error(`Download failed with status ${response.statusCode}: ${url}`));
115
- return;
116
- }
117
-
118
- response.pipe(file);
119
-
120
- file.on('finish', () => {
121
- file.close();
122
- resolve();
123
- });
124
- });
125
-
126
- request.on('error', (err) => {
127
- if (fs.existsSync(dest)) {
128
- fs.unlinkSync(dest);
129
- }
130
- reject(err);
131
- });
132
-
133
- file.on('error', (err) => {
134
- if (fs.existsSync(dest)) {
135
- fs.unlinkSync(dest);
136
- }
137
- reject(err);
138
- });
139
- });
140
- }
141
-
142
- function extractTarGz(tarGzPath, destDir) {
143
- try {
144
- // Use system tar command (available on macOS and Linux)
145
- execSync(`tar -xzf "${tarGzPath}" -C "${destDir}"`, { stdio: 'pipe' });
146
- } catch (err) {
147
- throw new Error(`Failed to extract tar.gz archive: ${err.message}`);
148
- }
149
- }
150
-
151
- function extractZip(zipPath, destDir) {
152
- try {
153
- if (process.platform === 'win32') {
154
- // Use PowerShell on Windows (built-in, no external dependencies)
155
- // -NoProfile: Skip loading user profile scripts (faster, more reliable)
156
- // -ExecutionPolicy Bypass: Allow script execution without policy restrictions
157
- // -Command: Execute inline command
158
- const psCommand = `
159
- try {
160
- Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force -ErrorAction Stop
161
- exit 0
162
- } catch {
163
- exit 1
164
- }
165
- `;
166
- execSync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${psCommand}"`, { stdio: 'pipe' });
167
- } else {
168
- // Unix/Linux/macOS - use unzip
169
- // Check if unzip is available first
170
- try {
171
- execSync('which unzip', { stdio: 'pipe' });
172
- } catch {
173
- throw new Error(
174
- 'unzip command not found. Please install unzip:\n' +
175
- ' Ubuntu/Debian: sudo apt-get install unzip\n' +
176
- ' macOS: brew install unzip\n' +
177
- ' CentOS/RHEL: sudo yum install unzip'
178
- );
179
- }
180
- execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'pipe' });
181
- }
182
- } catch (err) {
183
- if (err.message.includes('unzip command not found')) {
184
- throw err;
185
- }
186
- throw new Error(`Failed to extract zip archive: ${err.message}`);
187
- }
188
- }
189
-
190
- async function extractArchive(archivePath, destDir, platform) {
191
- if (platform === 'windows') {
192
- extractZip(archivePath, destDir);
193
- } else {
194
- extractTarGz(archivePath, destDir);
195
- }
196
- }
197
-
198
- function findBinary(dir, platform) {
199
- const binaryName = getBinaryName(platform);
200
- const files = fs.readdirSync(dir);
201
-
202
- // First check root
203
- const rootBinary = path.join(dir, binaryName);
204
- if (fs.existsSync(rootBinary)) {
205
- return rootBinary;
206
- }
207
-
208
- // Check subdirectories
209
- for (const file of files) {
210
- const fullPath = path.join(dir, file);
211
- const stat = fs.statSync(fullPath);
212
- if (stat.isDirectory()) {
213
- const binaryInSubdir = path.join(fullPath, binaryName);
214
- if (fs.existsSync(binaryInSubdir)) {
215
- return binaryInSubdir;
216
- }
217
- }
218
- }
219
-
220
- throw new Error(`Binary ${binaryName} not found in archive`);
221
- }
222
-
223
- async function ensureBinary() {
224
- const binaryPath = getBinaryPath();
225
- const packageVersion = getPackageVersion();
226
-
227
- // Check if binary exists and matches package version
228
- if (fs.existsSync(binaryPath) && fs.existsSync(VERSION_FILE)) {
229
- const currentVersion = fs.readFileSync(VERSION_FILE, 'utf8').trim();
230
- if (currentVersion === packageVersion) {
231
- return binaryPath;
232
- }
233
- console.log(`Updating cc-helper from ${currentVersion} to ${packageVersion}...`);
234
- }
235
-
236
- // Create cache directory
237
- if (!fs.existsSync(CACHE_DIR)) {
238
- fs.mkdirSync(CACHE_DIR, { recursive: true });
239
- }
240
-
241
- // Download archive with fixed version from package.json
242
- const { platform, arch } = getPlatform();
243
- const archiveName = getArchiveName(packageVersion, platform, arch);
244
- const tagVersion = packageVersion.startsWith('v') ? packageVersion : `v${packageVersion}`;
245
- const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
246
- const archivePath = path.join(CACHE_DIR, archiveName);
247
-
248
- console.log(`Downloading cc-helper ${packageVersion} for ${platform}-${arch}...`);
249
-
250
- try {
251
- await downloadFile(downloadUrl, archivePath);
252
-
253
- // Extract archive
254
- console.log('Extracting archive...');
255
- const tempDir = path.join(CACHE_DIR, 'temp_extract_' + Date.now());
256
- fs.mkdirSync(tempDir, { recursive: true });
257
-
258
- await extractArchive(archivePath, tempDir, platform);
259
-
260
- // Find and move binary
261
- const extractedBinaryPath = findBinary(tempDir, platform);
262
-
263
- // Remove old binary if exists
264
- if (fs.existsSync(binaryPath)) {
265
- fs.unlinkSync(binaryPath);
266
- }
267
-
268
- // Move binary to cache dir
269
- fs.copyFileSync(extractedBinaryPath, binaryPath);
270
- fs.chmodSync(binaryPath, 0o755);
271
-
272
- // Clean up
273
- fs.rmSync(tempDir, { recursive: true, force: true });
274
- fs.unlinkSync(archivePath);
275
-
276
- // Save version
277
- fs.writeFileSync(VERSION_FILE, packageVersion);
278
-
279
- console.log('Download complete!');
280
- } catch (err) {
281
- // Clean up on failure
282
- if (fs.existsSync(archivePath)) {
283
- fs.unlinkSync(archivePath);
284
- }
285
- throw err;
286
- }
287
-
288
- return binaryPath;
289
- }
290
-
291
- async function main() {
292
- // Handle uninstall command
293
- const args = process.argv.slice(2);
294
- if (args.length > 0 && args[0] === 'uninstall') {
295
- handleUninstall();
296
- return;
297
- }
298
-
299
- try {
300
- const binaryPath = await ensureBinary();
301
-
302
- // Run the binary with all arguments
303
- // Windows requires shell: true for .exe files
304
- const isWindows = process.platform === 'win32';
305
- const child = spawn(binaryPath, args, {
306
- stdio: 'inherit',
307
- detached: false,
308
- shell: isWindows
309
- });
310
-
311
- child.on('close', (code) => {
312
- process.exit(code);
313
- });
314
-
315
- child.on('error', (err) => {
316
- console.error('Failed to start cc-helper:', err.message);
317
- process.exit(1);
318
- });
319
- } catch (err) {
320
- console.error('Error:', err.message);
321
- process.exit(1);
322
- }
323
- }
324
-
325
- // Export for use as module
326
- module.exports = { main, ensureBinary, getBinaryPath, getPlatform, getPackageVersion };
327
-
328
- // Run if called directly
329
- if (require.main === module) {
330
- main();
331
- }
1
+ #!/usr/bin/env node
2
+
3
+ const https = require('https');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { spawn, execSync } = require('child_process');
8
+
9
+ const GITHUB_OWNER = 'next-bin';
10
+ const GITHUB_REPO = 'cc-helper';
11
+ const CACHE_DIR = path.join(os.homedir(), '.cache', 'cc-helper');
12
+ const VERSION_FILE = path.join(CACHE_DIR, 'version.txt');
13
+
14
+ // Handle uninstall command directly
15
+ function handleUninstall() {
16
+ try {
17
+ if (fs.existsSync(CACHE_DIR)) {
18
+ console.log('Removing cached binaries...');
19
+ fs.rmSync(CACHE_DIR, { recursive: true, force: true });
20
+ console.log('Cleanup complete!');
21
+ } else {
22
+ console.log('No cached binaries found.');
23
+ }
24
+ console.log('\nTo complete uninstallation, run:');
25
+ console.log(' npm uninstall -g @unitsvc/cc-helper');
26
+ process.exit(0);
27
+ } catch (err) {
28
+ console.error('Error during uninstall:', err.message);
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ // Get latest release version from GitHub API
34
+ function getLatestVersion() {
35
+ return new Promise((resolve, reject) => {
36
+ const options = {
37
+ hostname: 'api.github.com',
38
+ path: `/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest`,
39
+ headers: {
40
+ 'User-Agent': 'cc-helper-npm-installer',
41
+ 'Accept': 'application/vnd.github.v3+json'
42
+ }
43
+ };
44
+
45
+ const req = https.get(options, (res) => {
46
+ let data = '';
47
+ res.on('data', chunk => data += chunk);
48
+ res.on('end', () => {
49
+ try {
50
+ if (res.statusCode !== 200) {
51
+ reject(new Error(`GitHub API returned status ${res.statusCode}`));
52
+ return;
53
+ }
54
+ const release = JSON.parse(data);
55
+ const version = release.tag_name.replace(/^v/, '');
56
+ resolve(version);
57
+ } catch (err) {
58
+ reject(new Error(`Failed to parse GitHub response: ${err.message}`));
59
+ }
60
+ });
61
+ });
62
+
63
+ req.on('error', reject);
64
+ req.setTimeout(10000, () => {
65
+ req.destroy();
66
+ reject(new Error('GitHub API request timeout'));
67
+ });
68
+ });
69
+ }
70
+
71
+ // Get all releases from GitHub API
72
+ function getAllReleases() {
73
+ return new Promise((resolve, reject) => {
74
+ const options = {
75
+ hostname: 'api.github.com',
76
+ path: `/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases?per_page=10`,
77
+ headers: {
78
+ 'User-Agent': 'cc-helper-npm-installer',
79
+ 'Accept': 'application/vnd.github.v3+json'
80
+ }
81
+ };
82
+
83
+ const req = https.get(options, (res) => {
84
+ let data = '';
85
+ res.on('data', chunk => data += chunk);
86
+ res.on('end', () => {
87
+ try {
88
+ if (res.statusCode !== 200) {
89
+ reject(new Error(`GitHub API returned status ${res.statusCode}`));
90
+ return;
91
+ }
92
+ const releases = JSON.parse(data);
93
+ resolve(releases);
94
+ } catch (err) {
95
+ reject(new Error(`Failed to parse GitHub response: ${err.message}`));
96
+ }
97
+ });
98
+ });
99
+
100
+ req.on('error', reject);
101
+ req.setTimeout(10000, () => {
102
+ req.destroy();
103
+ reject(new Error('GitHub API request timeout'));
104
+ });
105
+ });
106
+ }
107
+
108
+ // Find a release with binary assets for the given platform
109
+ async function findReleaseWithBinary(platform, arch) {
110
+ const releases = await getAllReleases();
111
+
112
+ for (const release of releases) {
113
+ const version = release.tag_name.replace(/^v/, '');
114
+ const expectedAsset = platform === 'windows'
115
+ ? `cc-helper_${version}_${platform}_${arch}.zip`
116
+ : `cc-helper_${version}_${platform}_${arch}.tar.gz`;
117
+
118
+ const hasBinary = release.assets && release.assets.some(asset => asset.name === expectedAsset);
119
+
120
+ if (hasBinary) {
121
+ return version;
122
+ }
123
+ }
124
+
125
+ throw new Error('No release with binary assets found');
126
+ }
127
+
128
+ function getPlatform() {
129
+ const platform = os.platform();
130
+ const arch = os.arch();
131
+
132
+ const platformMap = {
133
+ 'darwin': 'darwin',
134
+ 'linux': 'linux',
135
+ 'win32': 'windows'
136
+ };
137
+
138
+ const archMap = {
139
+ 'x64': 'amd64',
140
+ 'arm64': 'arm64'
141
+ };
142
+
143
+ const mappedPlatform = platformMap[platform];
144
+ const mappedArch = archMap[arch];
145
+
146
+ if (!mappedPlatform || !mappedArch) {
147
+ throw new Error(`Unsupported platform: ${platform} ${arch}`);
148
+ }
149
+
150
+ // Windows arm64 not supported by GoReleaser
151
+ if (platform === 'win32' && arch === 'arm64') {
152
+ throw new Error('Windows arm64 is not supported. Please use Windows amd64 or macOS/Linux arm64.');
153
+ }
154
+
155
+ return { platform: mappedPlatform, arch: mappedArch };
156
+ }
157
+
158
+ function getArchiveName(version, platform, arch) {
159
+ // GitHub release uses version without 'v' prefix in filename
160
+ const ver = version.replace(/^v/, '');
161
+ if (platform === 'windows') {
162
+ return `cc-helper_${ver}_${platform}_${arch}.zip`;
163
+ }
164
+ return `cc-helper_${ver}_${platform}_${arch}.tar.gz`;
165
+ }
166
+
167
+ function getBinaryName(platform) {
168
+ return platform === 'windows' ? 'cc-helper.exe' : 'cc-helper';
169
+ }
170
+
171
+ function getBinaryPath() {
172
+ const { platform } = getPlatform();
173
+ const binaryName = getBinaryName(platform);
174
+ return path.join(CACHE_DIR, binaryName);
175
+ }
176
+
177
+ function downloadFile(url, dest) {
178
+ return new Promise((resolve, reject) => {
179
+ const file = fs.createWriteStream(dest);
180
+
181
+ const requestUrl = url.startsWith('https://') ? url : 'https://github.com' + url;
182
+
183
+ const request = https.get(requestUrl, { headers: { 'User-Agent': 'cc-helper-npm-installer' } }, (response) => {
184
+ if (response.statusCode === 302 || response.statusCode === 301) {
185
+ file.close();
186
+ if (fs.existsSync(dest)) {
187
+ fs.unlinkSync(dest);
188
+ }
189
+ downloadFile(response.headers.location, dest).then(resolve).catch(reject);
190
+ return;
191
+ }
192
+
193
+ if (response.statusCode !== 200) {
194
+ file.close();
195
+ if (fs.existsSync(dest)) {
196
+ fs.unlinkSync(dest);
197
+ }
198
+ reject(new Error(`Download failed with status ${response.statusCode}: ${url}`));
199
+ return;
200
+ }
201
+
202
+ response.pipe(file);
203
+
204
+ file.on('finish', () => {
205
+ file.close();
206
+ resolve();
207
+ });
208
+ });
209
+
210
+ request.on('error', (err) => {
211
+ if (fs.existsSync(dest)) {
212
+ fs.unlinkSync(dest);
213
+ }
214
+ reject(err);
215
+ });
216
+
217
+ file.on('error', (err) => {
218
+ if (fs.existsSync(dest)) {
219
+ fs.unlinkSync(dest);
220
+ }
221
+ reject(err);
222
+ });
223
+ });
224
+ }
225
+
226
+ function extractTarGz(tarGzPath, destDir) {
227
+ try {
228
+ // Use system tar command (available on macOS and Linux)
229
+ execSync(`tar -xzf "${tarGzPath}" -C "${destDir}"`, { stdio: 'pipe' });
230
+ } catch (err) {
231
+ throw new Error(`Failed to extract tar.gz archive: ${err.message}`);
232
+ }
233
+ }
234
+
235
+ function extractZip(zipPath, destDir) {
236
+ try {
237
+ if (process.platform === 'win32') {
238
+ // Use PowerShell on Windows (built-in, no external dependencies)
239
+ // Use single-line command to avoid PowerShell multi-line parsing issues
240
+ const escapedZipPath = zipPath.replace(/'/g, "''");
241
+ const escapedDestDir = destDir.replace(/'/g, "''");
242
+ const psCommand = `Expand-Archive -Path '${escapedZipPath}' -DestinationPath '${escapedDestDir}' -Force -ErrorAction Stop`;
243
+ execSync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${psCommand}"`, { stdio: 'pipe' });
244
+ } else {
245
+ // Unix/Linux/macOS - use unzip
246
+ // Check if unzip is available first
247
+ try {
248
+ execSync('which unzip', { stdio: 'pipe' });
249
+ } catch {
250
+ throw new Error(
251
+ 'unzip command not found. Please install unzip:\n' +
252
+ ' Ubuntu/Debian: sudo apt-get install unzip\n' +
253
+ ' macOS: brew install unzip\n' +
254
+ ' CentOS/RHEL: sudo yum install unzip'
255
+ );
256
+ }
257
+ execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'pipe' });
258
+ }
259
+ } catch (err) {
260
+ if (err.message.includes('unzip command not found')) {
261
+ throw err;
262
+ }
263
+ throw new Error(`Failed to extract zip archive: ${err.message}`);
264
+ }
265
+ }
266
+
267
+ async function extractArchive(archivePath, destDir, platform) {
268
+ if (platform === 'windows') {
269
+ extractZip(archivePath, destDir);
270
+ } else {
271
+ extractTarGz(archivePath, destDir);
272
+ }
273
+ }
274
+
275
+ function findBinary(dir, platform) {
276
+ const binaryName = getBinaryName(platform);
277
+ const files = fs.readdirSync(dir);
278
+
279
+ // First check root
280
+ const rootBinary = path.join(dir, binaryName);
281
+ if (fs.existsSync(rootBinary)) {
282
+ return rootBinary;
283
+ }
284
+
285
+ // Check subdirectories
286
+ for (const file of files) {
287
+ const fullPath = path.join(dir, file);
288
+ const stat = fs.statSync(fullPath);
289
+ if (stat.isDirectory()) {
290
+ const binaryInSubdir = path.join(fullPath, binaryName);
291
+ if (fs.existsSync(binaryInSubdir)) {
292
+ return binaryInSubdir;
293
+ }
294
+ }
295
+ }
296
+
297
+ throw new Error(`Binary ${binaryName} not found in archive`);
298
+ }
299
+
300
+ async function downloadAndInstall(version, platform, arch, binaryPath) {
301
+ const archiveName = getArchiveName(version, platform, arch);
302
+ const tagVersion = version.startsWith('v') ? version : `v${version}`;
303
+ const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
304
+ const archivePath = path.join(CACHE_DIR, archiveName);
305
+
306
+ console.log(`Downloading cc-helper ${version} for ${platform}-${arch}...`);
307
+
308
+ try {
309
+ await downloadFile(downloadUrl, archivePath);
310
+
311
+ // Extract archive
312
+ console.log('Extracting archive...');
313
+ const tempDir = path.join(CACHE_DIR, 'temp_extract_' + Date.now());
314
+ fs.mkdirSync(tempDir, { recursive: true });
315
+
316
+ await extractArchive(archivePath, tempDir, platform);
317
+
318
+ // Find and move binary
319
+ const extractedBinaryPath = findBinary(tempDir, platform);
320
+
321
+ // Remove old binary if exists
322
+ if (fs.existsSync(binaryPath)) {
323
+ fs.unlinkSync(binaryPath);
324
+ }
325
+
326
+ // Move binary to cache dir
327
+ fs.copyFileSync(extractedBinaryPath, binaryPath);
328
+ fs.chmodSync(binaryPath, 0o755);
329
+
330
+ // Clean up
331
+ fs.rmSync(tempDir, { recursive: true, force: true });
332
+ fs.unlinkSync(archivePath);
333
+
334
+ // Save version
335
+ fs.writeFileSync(VERSION_FILE, version);
336
+
337
+ console.log('Download complete!');
338
+ } catch (err) {
339
+ // Clean up on failure
340
+ if (fs.existsSync(archivePath)) {
341
+ fs.unlinkSync(archivePath);
342
+ }
343
+ throw err;
344
+ }
345
+ }
346
+
347
+ async function ensureBinary() {
348
+ const binaryPath = getBinaryPath();
349
+
350
+ // If binary exists, use it directly (postinstall already handled version check)
351
+ if (fs.existsSync(binaryPath)) {
352
+ return binaryPath;
353
+ }
354
+
355
+ // Binary doesn't exist, find a compatible release and download
356
+ console.log('Finding compatible release...');
357
+ const { platform, arch } = getPlatform();
358
+ const compatibleVersion = await findReleaseWithBinary(platform, arch);
359
+
360
+ // Create cache directory
361
+ if (!fs.existsSync(CACHE_DIR)) {
362
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
363
+ }
364
+
365
+ // Download and install
366
+ await downloadAndInstall(compatibleVersion, platform, arch, binaryPath);
367
+
368
+ return binaryPath;
369
+ }
370
+
371
+ async function main() {
372
+ // Handle uninstall command
373
+ const args = process.argv.slice(2);
374
+ if (args.length > 0 && args[0] === 'uninstall') {
375
+ handleUninstall();
376
+ return;
377
+ }
378
+
379
+ try {
380
+ const binaryPath = await ensureBinary();
381
+
382
+ // Run the binary with all arguments
383
+ // Windows requires shell: true for .exe files
384
+ const isWindows = process.platform === 'win32';
385
+ const child = spawn(binaryPath, args, {
386
+ stdio: 'inherit',
387
+ detached: false,
388
+ shell: isWindows
389
+ });
390
+
391
+ child.on('close', (code) => {
392
+ process.exit(code);
393
+ });
394
+
395
+ child.on('error', (err) => {
396
+ console.error('Failed to start cc-helper:', err.message);
397
+ process.exit(1);
398
+ });
399
+ } catch (err) {
400
+ console.error('Error:', err.message);
401
+ process.exit(1);
402
+ }
403
+ }
404
+
405
+ // Export for use as module
406
+ module.exports = { main, ensureBinary, getBinaryPath, getPlatform, getLatestVersion };
407
+
408
+ // Run if called directly
409
+ if (require.main === module) {
410
+ main();
411
+ }