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