@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/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
|
-
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
'
|
|
240
|
-
'
|
|
241
|
-
'
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
fs.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
fs.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
console.log(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
await
|
|
364
|
-
|
|
365
|
-
console.log(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
console.error('
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
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();
|