@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/LICENSE +661 -661
- package/README.md +126 -126
- package/bin/cc-helper.js +412 -411
- package/index.js +9 -9
- package/install.js +374 -373
- package/package.json +38 -38
- package/uninstall.js +23 -23
package/bin/cc-helper.js
CHANGED
|
@@ -1,411 +1,412 @@
|
|
|
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
|
-
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
'
|
|
253
|
-
'
|
|
254
|
-
'
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
fs.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
fs.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
+
// 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
|
+
// Disable progress bar to avoid Write-Progress IndexOutOfRangeException bug in PowerShell 5.1
|
|
241
|
+
const escapedZipPath = zipPath.replace(/'/g, "''");
|
|
242
|
+
const escapedDestDir = destDir.replace(/'/g, "''");
|
|
243
|
+
const psCommand = `$ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path '${escapedZipPath}' -DestinationPath '${escapedDestDir}' -Force -ErrorAction Stop`;
|
|
244
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${psCommand}"`, { stdio: 'pipe' });
|
|
245
|
+
} else {
|
|
246
|
+
// Unix/Linux/macOS - use unzip
|
|
247
|
+
// Check if unzip is available first
|
|
248
|
+
try {
|
|
249
|
+
execSync('which unzip', { stdio: 'pipe' });
|
|
250
|
+
} catch {
|
|
251
|
+
throw new Error(
|
|
252
|
+
'unzip command not found. Please install unzip:\n' +
|
|
253
|
+
' Ubuntu/Debian: sudo apt-get install unzip\n' +
|
|
254
|
+
' macOS: brew install unzip\n' +
|
|
255
|
+
' CentOS/RHEL: sudo yum install unzip'
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'pipe' });
|
|
259
|
+
}
|
|
260
|
+
} catch (err) {
|
|
261
|
+
if (err.message.includes('unzip command not found')) {
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
264
|
+
throw new Error(`Failed to extract zip archive: ${err.message}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function extractArchive(archivePath, destDir, platform) {
|
|
269
|
+
if (platform === 'windows') {
|
|
270
|
+
extractZip(archivePath, destDir);
|
|
271
|
+
} else {
|
|
272
|
+
extractTarGz(archivePath, destDir);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function findBinary(dir, platform) {
|
|
277
|
+
const binaryName = getBinaryName(platform);
|
|
278
|
+
const files = fs.readdirSync(dir);
|
|
279
|
+
|
|
280
|
+
// First check root
|
|
281
|
+
const rootBinary = path.join(dir, binaryName);
|
|
282
|
+
if (fs.existsSync(rootBinary)) {
|
|
283
|
+
return rootBinary;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check subdirectories
|
|
287
|
+
for (const file of files) {
|
|
288
|
+
const fullPath = path.join(dir, file);
|
|
289
|
+
const stat = fs.statSync(fullPath);
|
|
290
|
+
if (stat.isDirectory()) {
|
|
291
|
+
const binaryInSubdir = path.join(fullPath, binaryName);
|
|
292
|
+
if (fs.existsSync(binaryInSubdir)) {
|
|
293
|
+
return binaryInSubdir;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
throw new Error(`Binary ${binaryName} not found in archive`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function downloadAndInstall(version, platform, arch, binaryPath) {
|
|
302
|
+
const archiveName = getArchiveName(version, platform, arch);
|
|
303
|
+
const tagVersion = version.startsWith('v') ? version : `v${version}`;
|
|
304
|
+
const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
|
|
305
|
+
const archivePath = path.join(CACHE_DIR, archiveName);
|
|
306
|
+
|
|
307
|
+
console.log(`Downloading cc-helper ${version} for ${platform}-${arch}...`);
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
await downloadFile(downloadUrl, archivePath);
|
|
311
|
+
|
|
312
|
+
// Extract archive
|
|
313
|
+
console.log('Extracting archive...');
|
|
314
|
+
const tempDir = path.join(CACHE_DIR, 'temp_extract_' + Date.now());
|
|
315
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
316
|
+
|
|
317
|
+
await extractArchive(archivePath, tempDir, platform);
|
|
318
|
+
|
|
319
|
+
// Find and move binary
|
|
320
|
+
const extractedBinaryPath = findBinary(tempDir, platform);
|
|
321
|
+
|
|
322
|
+
// Remove old binary if exists
|
|
323
|
+
if (fs.existsSync(binaryPath)) {
|
|
324
|
+
fs.unlinkSync(binaryPath);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Move binary to cache dir
|
|
328
|
+
fs.copyFileSync(extractedBinaryPath, binaryPath);
|
|
329
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
330
|
+
|
|
331
|
+
// Clean up
|
|
332
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
333
|
+
fs.unlinkSync(archivePath);
|
|
334
|
+
|
|
335
|
+
// Save version
|
|
336
|
+
fs.writeFileSync(VERSION_FILE, version);
|
|
337
|
+
|
|
338
|
+
console.log('Download complete!');
|
|
339
|
+
} catch (err) {
|
|
340
|
+
// Clean up on failure
|
|
341
|
+
if (fs.existsSync(archivePath)) {
|
|
342
|
+
fs.unlinkSync(archivePath);
|
|
343
|
+
}
|
|
344
|
+
throw err;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function ensureBinary() {
|
|
349
|
+
const binaryPath = getBinaryPath();
|
|
350
|
+
|
|
351
|
+
// If binary exists, use it directly (postinstall already handled version check)
|
|
352
|
+
if (fs.existsSync(binaryPath)) {
|
|
353
|
+
return binaryPath;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Binary doesn't exist, find a compatible release and download
|
|
357
|
+
console.log('Finding compatible release...');
|
|
358
|
+
const { platform, arch } = getPlatform();
|
|
359
|
+
const compatibleVersion = await findReleaseWithBinary(platform, arch);
|
|
360
|
+
|
|
361
|
+
// Create cache directory
|
|
362
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
363
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Download and install
|
|
367
|
+
await downloadAndInstall(compatibleVersion, platform, arch, binaryPath);
|
|
368
|
+
|
|
369
|
+
return binaryPath;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function main() {
|
|
373
|
+
// Handle uninstall command
|
|
374
|
+
const args = process.argv.slice(2);
|
|
375
|
+
if (args.length > 0 && args[0] === 'uninstall') {
|
|
376
|
+
handleUninstall();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const binaryPath = await ensureBinary();
|
|
382
|
+
|
|
383
|
+
// Run the binary with all arguments
|
|
384
|
+
// Windows requires shell: true for .exe files
|
|
385
|
+
const isWindows = process.platform === 'win32';
|
|
386
|
+
const child = spawn(binaryPath, args, {
|
|
387
|
+
stdio: 'inherit',
|
|
388
|
+
detached: false,
|
|
389
|
+
shell: isWindows
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
child.on('close', (code) => {
|
|
393
|
+
process.exit(code);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
child.on('error', (err) => {
|
|
397
|
+
console.error('Failed to start cc-helper:', err.message);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
});
|
|
400
|
+
} catch (err) {
|
|
401
|
+
console.error('Error:', err.message);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Export for use as module
|
|
407
|
+
module.exports = { main, ensureBinary, getBinaryPath, getPlatform, getLatestVersion };
|
|
408
|
+
|
|
409
|
+
// Run if called directly
|
|
410
|
+
if (require.main === module) {
|
|
411
|
+
main();
|
|
412
|
+
}
|