@qoder-ai/qodercli 0.1.8 → 0.1.10
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/package.json +12 -12
- package/scripts/install.js +218 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qoder-ai/qodercli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "qodercli - npm installer",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -44,37 +44,37 @@
|
|
|
44
44
|
],
|
|
45
45
|
"preferGlobal": true,
|
|
46
46
|
"binaries": {
|
|
47
|
-
"version": "0.1.
|
|
47
|
+
"version": "0.1.10",
|
|
48
48
|
"files": [
|
|
49
49
|
{
|
|
50
50
|
"os": "linux",
|
|
51
51
|
"arch": "amd64",
|
|
52
|
-
"url": "https://download.qoder.com/qodercli/releases/0.1.
|
|
53
|
-
"sha256": "
|
|
52
|
+
"url": "https://download.qoder.com/qodercli/releases/0.1.10/qodercli_0.1.10_linux_amd64.tar.gz",
|
|
53
|
+
"sha256": "0d633fdb1e4955098d64ca53d34e8f889e54ce07f282bfbbae57e95a6869956a"
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
"os": "linux",
|
|
57
57
|
"arch": "arm64",
|
|
58
|
-
"url": "https://download.qoder.com/qodercli/releases/0.1.
|
|
59
|
-
"sha256": "
|
|
58
|
+
"url": "https://download.qoder.com/qodercli/releases/0.1.10/qodercli_0.1.10_linux_arm64.tar.gz",
|
|
59
|
+
"sha256": "47f70de0dee6508e5a5260ca9b19e5c05ce2d3ba22b13a7130482a77eb2d9e10"
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"os": "darwin",
|
|
63
63
|
"arch": "amd64",
|
|
64
|
-
"url": "https://download.qoder.com/qodercli/releases/0.1.
|
|
65
|
-
"sha256": "
|
|
64
|
+
"url": "https://download.qoder.com/qodercli/releases/0.1.10/qodercli_0.1.10_darwin_amd64.zip",
|
|
65
|
+
"sha256": "238595330a5c7207ec8fcfcbcdfa31b1140f2e17492839b710235d3e9c1dbf5b"
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
"os": "darwin",
|
|
69
69
|
"arch": "arm64",
|
|
70
|
-
"url": "https://download.qoder.com/qodercli/releases/0.1.
|
|
71
|
-
"sha256": "
|
|
70
|
+
"url": "https://download.qoder.com/qodercli/releases/0.1.10/qodercli_0.1.10_darwin_arm64.zip",
|
|
71
|
+
"sha256": "5ef0da68ef6b7037105b590fee1f93101baf4c1f2f23da40adc3bf49d7ae2bc8"
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"os": "windows",
|
|
75
75
|
"arch": "amd64",
|
|
76
|
-
"url": "https://download.qoder.com/qodercli/releases/0.1.
|
|
77
|
-
"sha256": "
|
|
76
|
+
"url": "https://download.qoder.com/qodercli/releases/0.1.10/qodercli_0.1.10_windows_amd64.zip",
|
|
77
|
+
"sha256": "4fc86ba44efacecdb99d5ccd3c8edd049a36add8b2d55bf803e6bd5637eb04af"
|
|
78
78
|
}
|
|
79
79
|
]
|
|
80
80
|
}
|
package/scripts/install.js
CHANGED
|
@@ -6,15 +6,30 @@ const https = require('https');
|
|
|
6
6
|
const http = require('http');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
8
|
const crypto = require('crypto');
|
|
9
|
+
const os = require('os');
|
|
9
10
|
|
|
10
11
|
// Configuration
|
|
11
12
|
const BINARY_NAME = 'qodercli';
|
|
12
13
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
13
14
|
const BIN_DIR = path.join(PACKAGE_ROOT, 'bin');
|
|
14
15
|
const PACKAGE_JSON_PATH = path.join(PACKAGE_ROOT, 'package.json');
|
|
16
|
+
const INSTALL_METHOD = 'npm';
|
|
15
17
|
|
|
16
18
|
class QoderInstaller {
|
|
17
19
|
constructor() {
|
|
20
|
+
// Mark this as an installation process
|
|
21
|
+
process.env.QODER_CLI_INSTALL = '1';
|
|
22
|
+
|
|
23
|
+
// Initialize logging variables
|
|
24
|
+
this.logFile = null;
|
|
25
|
+
this.logStream = null;
|
|
26
|
+
this.originalConsoleLog = console.log;
|
|
27
|
+
this.originalConsoleError = console.error;
|
|
28
|
+
|
|
29
|
+
// Setup logging first to capture all output including platform detection
|
|
30
|
+
this.setupLogging();
|
|
31
|
+
|
|
32
|
+
// Detect platform and architecture (will be logged if they fail)
|
|
18
33
|
this.platform = this.detectPlatform();
|
|
19
34
|
this.arch = this.detectArch();
|
|
20
35
|
this.binPath = path.join(BIN_DIR, BINARY_NAME + (process.platform === 'win32' ? '.exe' : ''));
|
|
@@ -41,15 +56,106 @@ class QoderInstaller {
|
|
|
41
56
|
}
|
|
42
57
|
}
|
|
43
58
|
|
|
59
|
+
setupLogging() {
|
|
60
|
+
try {
|
|
61
|
+
// Create log directory: ~/.qoder/logs on all platforms
|
|
62
|
+
const logDir = path.join(os.homedir(), '.qoder', 'logs');
|
|
63
|
+
if (!fs.existsSync(logDir)) {
|
|
64
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create log file with timestamp
|
|
68
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').split('Z')[0];
|
|
69
|
+
this.logFile = path.join(logDir, `qodercli_install_${INSTALL_METHOD}_${timestamp}.log`);
|
|
70
|
+
|
|
71
|
+
// Create write stream
|
|
72
|
+
this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
|
|
73
|
+
|
|
74
|
+
// Write log header
|
|
75
|
+
this.logStream.write(`Installation started at ${new Date().toISOString()}\n`);
|
|
76
|
+
this.logStream.write(`Installation method: ${INSTALL_METHOD}\n`);
|
|
77
|
+
this.logStream.write(`Platform: ${process.platform}/${process.arch}\n`);
|
|
78
|
+
this.logStream.write(`Node.js version: ${process.version}\n`);
|
|
79
|
+
this.logStream.write('================================\n\n');
|
|
80
|
+
|
|
81
|
+
// Create latest log marker
|
|
82
|
+
// Unix: symlink immediately, Windows: will copy after completion
|
|
83
|
+
const latestLogLink = path.join(logDir, 'qodercli_install.log');
|
|
84
|
+
try {
|
|
85
|
+
if (process.platform !== 'win32') {
|
|
86
|
+
// Unix: use symlink (immediate)
|
|
87
|
+
if (fs.existsSync(latestLogLink)) {
|
|
88
|
+
fs.unlinkSync(latestLogLink);
|
|
89
|
+
}
|
|
90
|
+
fs.symlinkSync(this.logFile, latestLogLink);
|
|
91
|
+
}
|
|
92
|
+
// Windows: will copy complete log in closeLogging()
|
|
93
|
+
} catch (e) {
|
|
94
|
+
// Ignore errors - not critical
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Redirect console.log and console.error to both terminal and log file
|
|
98
|
+
const self = this;
|
|
99
|
+
console.log = function(...args) {
|
|
100
|
+
const message = args.map(arg =>
|
|
101
|
+
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
|
|
102
|
+
).join(' ');
|
|
103
|
+
self.originalConsoleLog.apply(console, args);
|
|
104
|
+
if (self.logStream) {
|
|
105
|
+
self.logStream.write(message + '\n');
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
console.error = function(...args) {
|
|
110
|
+
const message = args.map(arg =>
|
|
111
|
+
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
|
|
112
|
+
).join(' ');
|
|
113
|
+
self.originalConsoleError.apply(console, args);
|
|
114
|
+
if (self.logStream) {
|
|
115
|
+
self.logStream.write('[ERROR] ' + message + '\n');
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Log file location saved but not printed (will show on error)
|
|
120
|
+
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// If logging setup fails, continue without logging
|
|
123
|
+
this.originalConsoleError.call(console, 'Warning: Failed to setup logging:', error.message);
|
|
124
|
+
this.originalConsoleError.call(console, 'Installation will continue without logging');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
closeLogging() {
|
|
129
|
+
if (this.logStream) {
|
|
130
|
+
this.logStream.end();
|
|
131
|
+
this.logStream = null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Update latest log marker after installation completes
|
|
135
|
+
if (this.logFile && process.platform === 'win32') {
|
|
136
|
+
try {
|
|
137
|
+
const logDir = path.dirname(this.logFile);
|
|
138
|
+
const latestLogLink = path.join(logDir, 'qodercli_install.log');
|
|
139
|
+
fs.copyFileSync(this.logFile, latestLogLink);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Ignore errors - not critical
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Restore original console methods
|
|
146
|
+
console.log = this.originalConsoleLog;
|
|
147
|
+
console.error = this.originalConsoleError;
|
|
148
|
+
}
|
|
149
|
+
|
|
44
150
|
loadPackageInfo() {
|
|
45
151
|
try {
|
|
46
152
|
const packageJson = fs.readFileSync(PACKAGE_JSON_PATH, 'utf8');
|
|
47
153
|
const packageInfo = JSON.parse(packageJson);
|
|
48
|
-
|
|
154
|
+
|
|
49
155
|
if (!packageInfo.binaries || !packageInfo.binaries.files) {
|
|
50
156
|
throw new Error('Binary information missing in package configuration');
|
|
51
157
|
}
|
|
52
|
-
|
|
158
|
+
|
|
53
159
|
return packageInfo;
|
|
54
160
|
} catch (error) {
|
|
55
161
|
throw new Error(`Unable to read package configuration: ${error.message}`);
|
|
@@ -58,7 +164,7 @@ class QoderInstaller {
|
|
|
58
164
|
|
|
59
165
|
findBinaryInfo() {
|
|
60
166
|
const files = this.packageInfo.binaries.files;
|
|
61
|
-
const targetFile = files.find(file =>
|
|
167
|
+
const targetFile = files.find(file =>
|
|
62
168
|
file.os === this.platform && file.arch === this.arch
|
|
63
169
|
);
|
|
64
170
|
|
|
@@ -71,11 +177,11 @@ class QoderInstaller {
|
|
|
71
177
|
|
|
72
178
|
async downloadBinary(url, expectedSha256) {
|
|
73
179
|
console.log(`Downloading binary: ${url}`);
|
|
74
|
-
|
|
180
|
+
|
|
75
181
|
// Create temporary directory for download operations
|
|
76
182
|
const os = require('os');
|
|
77
183
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qodercli-install-'));
|
|
78
|
-
|
|
184
|
+
|
|
79
185
|
// Ensure target directory exists
|
|
80
186
|
if (!fs.existsSync(BIN_DIR)) {
|
|
81
187
|
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
@@ -84,29 +190,29 @@ class QoderInstaller {
|
|
|
84
190
|
// Download file to temporary directory
|
|
85
191
|
const filename = path.basename(url);
|
|
86
192
|
const archivePath = path.join(tempDir, filename);
|
|
87
|
-
|
|
193
|
+
|
|
88
194
|
try {
|
|
89
195
|
await this.downloadFile(url, archivePath);
|
|
90
|
-
|
|
196
|
+
|
|
91
197
|
// Verify checksum
|
|
92
198
|
console.log('Verifying file integrity...');
|
|
93
199
|
const actualSha256 = this.calculateSha256(archivePath);
|
|
94
200
|
if (actualSha256 !== expectedSha256) {
|
|
95
201
|
throw new Error(`Checksum mismatch. Expected: ${expectedSha256}, Got: ${actualSha256}`);
|
|
96
202
|
}
|
|
97
|
-
|
|
203
|
+
|
|
98
204
|
// Extract file to temporary directory first
|
|
99
205
|
console.log('Extracting binary...');
|
|
100
206
|
const extractDir = path.join(tempDir, 'extract');
|
|
101
207
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
102
208
|
await this.extractArchive(archivePath, filename, extractDir);
|
|
103
|
-
|
|
209
|
+
|
|
104
210
|
// Move extracted binary to final destination
|
|
105
211
|
const extractedBinary = this.findExtractedBinary(extractDir);
|
|
106
212
|
if (extractedBinary.length === 0) {
|
|
107
213
|
throw new Error(`Binary file not found after extraction in ${extractDir}`);
|
|
108
214
|
}
|
|
109
|
-
|
|
215
|
+
|
|
110
216
|
// Try rename first (efficient), fallback to copy+delete if cross-device
|
|
111
217
|
try {
|
|
112
218
|
fs.renameSync(extractedBinary[0], this.binPath);
|
|
@@ -120,19 +226,16 @@ class QoderInstaller {
|
|
|
120
226
|
throw error;
|
|
121
227
|
}
|
|
122
228
|
}
|
|
123
|
-
|
|
229
|
+
|
|
124
230
|
// Set executable permission
|
|
125
231
|
if (process.platform !== 'win32') {
|
|
126
232
|
fs.chmodSync(this.binPath, 0o755);
|
|
127
233
|
}
|
|
128
|
-
|
|
234
|
+
|
|
129
235
|
// Create installation source marker
|
|
130
236
|
const sourceFile = path.join(BIN_DIR, '.qodercli-install-resource');
|
|
131
237
|
fs.writeFileSync(sourceFile, 'npm', 'utf8');
|
|
132
|
-
|
|
133
|
-
// Verify installation
|
|
134
|
-
this.verifyInstallation();
|
|
135
|
-
|
|
238
|
+
|
|
136
239
|
} catch (error) {
|
|
137
240
|
throw error;
|
|
138
241
|
} finally {
|
|
@@ -149,7 +252,7 @@ class QoderInstaller {
|
|
|
149
252
|
if (filename.endsWith('.zip')) {
|
|
150
253
|
// Extract ZIP file using Node.js packages first
|
|
151
254
|
let extracted = false;
|
|
152
|
-
|
|
255
|
+
|
|
153
256
|
// Method 1: Use adm-zip package (preferred)
|
|
154
257
|
try {
|
|
155
258
|
const AdmZip = require('adm-zip');
|
|
@@ -160,7 +263,7 @@ class QoderInstaller {
|
|
|
160
263
|
} catch (error) {
|
|
161
264
|
console.log('adm-zip extraction failed, trying system commands...', error.message);
|
|
162
265
|
}
|
|
163
|
-
|
|
266
|
+
|
|
164
267
|
// Method 2: System command fallbacks
|
|
165
268
|
if (!extracted) {
|
|
166
269
|
if (process.platform === 'win32') {
|
|
@@ -192,7 +295,7 @@ class QoderInstaller {
|
|
|
192
295
|
}
|
|
193
296
|
}
|
|
194
297
|
}
|
|
195
|
-
|
|
298
|
+
|
|
196
299
|
if (!extracted) {
|
|
197
300
|
const platform = process.platform === 'win32' ? 'Windows' : 'Unix';
|
|
198
301
|
throw new Error(`ZIP extraction failed on ${platform}. Please ensure extraction tools are available.`);
|
|
@@ -200,7 +303,7 @@ class QoderInstaller {
|
|
|
200
303
|
} else {
|
|
201
304
|
// Extract tar.gz file using Node.js tar package first
|
|
202
305
|
let extracted = false;
|
|
203
|
-
|
|
306
|
+
|
|
204
307
|
// Method 1: Use tar package (preferred)
|
|
205
308
|
try {
|
|
206
309
|
const tar = require('tar');
|
|
@@ -214,7 +317,7 @@ class QoderInstaller {
|
|
|
214
317
|
} catch (error) {
|
|
215
318
|
console.log('Node.js tar extraction failed, trying system tar command...', error.message);
|
|
216
319
|
}
|
|
217
|
-
|
|
320
|
+
|
|
218
321
|
// Method 2: System tar command fallback
|
|
219
322
|
if (!extracted) {
|
|
220
323
|
try {
|
|
@@ -240,13 +343,13 @@ class QoderInstaller {
|
|
|
240
343
|
findExtractedBinary(searchDir) {
|
|
241
344
|
const results = [];
|
|
242
345
|
const expectedFilename = BINARY_NAME + (process.platform === 'win32' ? '.exe' : '');
|
|
243
|
-
|
|
346
|
+
|
|
244
347
|
try {
|
|
245
348
|
const items = fs.readdirSync(searchDir, { withFileTypes: true });
|
|
246
|
-
|
|
349
|
+
|
|
247
350
|
for (const item of items) {
|
|
248
351
|
const fullPath = path.join(searchDir, item.name);
|
|
249
|
-
|
|
352
|
+
|
|
250
353
|
if (item.isDirectory()) {
|
|
251
354
|
// Recursively search in subdirectories
|
|
252
355
|
results.push(...this.findExtractedBinary(fullPath));
|
|
@@ -257,7 +360,7 @@ class QoderInstaller {
|
|
|
257
360
|
} catch (error) {
|
|
258
361
|
console.warn(`Unable to search directory ${searchDir}:`, error.message);
|
|
259
362
|
}
|
|
260
|
-
|
|
363
|
+
|
|
261
364
|
return results;
|
|
262
365
|
}
|
|
263
366
|
|
|
@@ -265,17 +368,25 @@ class QoderInstaller {
|
|
|
265
368
|
if (!fs.existsSync(this.binPath)) {
|
|
266
369
|
throw new Error('Binary installation failed');
|
|
267
370
|
}
|
|
371
|
+
if (this.logStream?.fd !== undefined) {
|
|
372
|
+
try {
|
|
373
|
+
fs.fsyncSync(this.logStream.fd);
|
|
374
|
+
} catch (e) {
|
|
375
|
+
}
|
|
376
|
+
}
|
|
268
377
|
|
|
269
378
|
try {
|
|
270
|
-
// Try to run version command for verification
|
|
271
379
|
const output = execSync(`"${this.binPath}" --version`, {
|
|
272
380
|
encoding: 'utf8',
|
|
273
|
-
stdio: 'pipe'
|
|
381
|
+
stdio: 'pipe',
|
|
382
|
+
env: { ...process.env, QODER_CLI_INSTALL: '1' }
|
|
274
383
|
});
|
|
384
|
+
const versionInfo = output.trim();
|
|
275
385
|
console.log('Installation verified successfully');
|
|
276
|
-
|
|
386
|
+
return versionInfo;
|
|
277
387
|
} catch (error) {
|
|
278
388
|
console.warn('Warning: Unable to verify installation, but binary file exists');
|
|
389
|
+
return null;
|
|
279
390
|
}
|
|
280
391
|
}
|
|
281
392
|
|
|
@@ -284,17 +395,17 @@ class QoderInstaller {
|
|
|
284
395
|
const file = fs.createWriteStream(filePath);
|
|
285
396
|
const client = url.startsWith('https:') ? https : http;
|
|
286
397
|
let cleanupDone = false;
|
|
287
|
-
|
|
398
|
+
|
|
288
399
|
const cleanup = () => {
|
|
289
400
|
if (cleanupDone) return;
|
|
290
401
|
cleanupDone = true;
|
|
291
|
-
|
|
402
|
+
|
|
292
403
|
try {
|
|
293
404
|
file.close();
|
|
294
405
|
} catch (e) {
|
|
295
406
|
// Ignore errors during cleanup
|
|
296
407
|
}
|
|
297
|
-
|
|
408
|
+
|
|
298
409
|
try {
|
|
299
410
|
if (fs.existsSync(filePath)) {
|
|
300
411
|
fs.unlinkSync(filePath);
|
|
@@ -303,15 +414,24 @@ class QoderInstaller {
|
|
|
303
414
|
// Ignore errors during cleanup
|
|
304
415
|
}
|
|
305
416
|
};
|
|
306
|
-
|
|
307
|
-
const
|
|
417
|
+
const parsedUrl = new URL(url);
|
|
418
|
+
const options = {
|
|
419
|
+
hostname: parsedUrl.hostname,
|
|
420
|
+
port: parsedUrl.port,
|
|
421
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
422
|
+
headers: {
|
|
423
|
+
'User-Agent': 'qodercli-installer/npm (https://qoder.com)'
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const request = client.get(options, (response) => {
|
|
308
428
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
309
429
|
// Handle redirect
|
|
310
430
|
cleanup();
|
|
311
431
|
return this.downloadFile(response.headers.location, filePath, timeout)
|
|
312
432
|
.then(resolve).catch(reject);
|
|
313
433
|
}
|
|
314
|
-
|
|
434
|
+
|
|
315
435
|
if (response.statusCode !== 200) {
|
|
316
436
|
cleanup();
|
|
317
437
|
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
|
@@ -319,14 +439,14 @@ class QoderInstaller {
|
|
|
319
439
|
}
|
|
320
440
|
|
|
321
441
|
response.pipe(file);
|
|
322
|
-
|
|
442
|
+
|
|
323
443
|
file.on('finish', () => {
|
|
324
444
|
if (!cleanupDone) {
|
|
325
445
|
file.close();
|
|
326
446
|
resolve();
|
|
327
447
|
}
|
|
328
448
|
});
|
|
329
|
-
|
|
449
|
+
|
|
330
450
|
file.on('error', (error) => {
|
|
331
451
|
cleanup();
|
|
332
452
|
reject(error);
|
|
@@ -342,27 +462,27 @@ class QoderInstaller {
|
|
|
342
462
|
cleanup();
|
|
343
463
|
reject(new Error(`Download timeout (${timeout}ms): ${url}`));
|
|
344
464
|
});
|
|
345
|
-
|
|
465
|
+
|
|
346
466
|
// Handle process interruption signals
|
|
347
467
|
const handleSignal = () => {
|
|
348
468
|
request.destroy();
|
|
349
469
|
cleanup();
|
|
350
470
|
reject(new Error('Download interrupted by signal'));
|
|
351
471
|
};
|
|
352
|
-
|
|
472
|
+
|
|
353
473
|
process.once('SIGINT', handleSignal);
|
|
354
474
|
process.once('SIGTERM', handleSignal);
|
|
355
|
-
|
|
475
|
+
|
|
356
476
|
// Clean up signal handlers when promise resolves/rejects
|
|
357
477
|
const originalResolve = resolve;
|
|
358
478
|
const originalReject = reject;
|
|
359
|
-
|
|
479
|
+
|
|
360
480
|
resolve = (...args) => {
|
|
361
481
|
process.removeListener('SIGINT', handleSignal);
|
|
362
482
|
process.removeListener('SIGTERM', handleSignal);
|
|
363
483
|
originalResolve(...args);
|
|
364
484
|
};
|
|
365
|
-
|
|
485
|
+
|
|
366
486
|
reject = (...args) => {
|
|
367
487
|
process.removeListener('SIGINT', handleSignal);
|
|
368
488
|
process.removeListener('SIGTERM', handleSignal);
|
|
@@ -372,11 +492,14 @@ class QoderInstaller {
|
|
|
372
492
|
}
|
|
373
493
|
|
|
374
494
|
async install() {
|
|
495
|
+
let installSuccess = false;
|
|
496
|
+
let versionInfo = null;
|
|
497
|
+
|
|
375
498
|
try {
|
|
376
499
|
console.log('Installing Qoder CLI...');
|
|
377
500
|
console.log(`Target platform: ${this.platform}/${this.arch}`);
|
|
378
501
|
console.log(`Version: ${this.packageInfo.binaries.version}`);
|
|
379
|
-
|
|
502
|
+
|
|
380
503
|
// If already installed, reinstall
|
|
381
504
|
if (fs.existsSync(this.binPath)) {
|
|
382
505
|
console.log('Existing version detected, will reinstall');
|
|
@@ -384,27 +507,75 @@ class QoderInstaller {
|
|
|
384
507
|
|
|
385
508
|
const binaryInfo = this.findBinaryInfo();
|
|
386
509
|
await this.downloadBinary(binaryInfo.url, binaryInfo.sha256);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
510
|
+
|
|
511
|
+
// Verify and get version info
|
|
512
|
+
versionInfo = this.verifyInstallation();
|
|
513
|
+
installSuccess = true;
|
|
514
|
+
|
|
391
515
|
} catch (error) {
|
|
516
|
+
console.error('');
|
|
392
517
|
console.error('❌ Installation failed:', error.message);
|
|
518
|
+
console.error('');
|
|
519
|
+
if (this.logFile) {
|
|
520
|
+
console.error(`Installation log: ${this.logFile}`);
|
|
521
|
+
console.error('');
|
|
522
|
+
}
|
|
523
|
+
console.error('For help, visit: https://forum.qoder.com/c/bug-reports');
|
|
524
|
+
console.error('');
|
|
525
|
+
|
|
526
|
+
this.closeLogging();
|
|
393
527
|
process.exit(1);
|
|
528
|
+
} finally {
|
|
529
|
+
// Show final summary
|
|
530
|
+
if (installSuccess) {
|
|
531
|
+
this.showSuccessSummary(versionInfo);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Close logging
|
|
535
|
+
this.closeLogging();
|
|
394
536
|
}
|
|
395
537
|
}
|
|
538
|
+
|
|
539
|
+
showSuccessSummary(versionInfo) {
|
|
540
|
+
console.log('');
|
|
541
|
+
|
|
542
|
+
if (versionInfo) {
|
|
543
|
+
console.log(`🎉 ${versionInfo} installed successfully!`);
|
|
544
|
+
} else {
|
|
545
|
+
console.log('🎉 Qoder CLI installed successfully!');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
console.log('');
|
|
549
|
+
console.log('Get started: qodercli --help');
|
|
550
|
+
console.log('Need help? Visit: https://forum.qoder.com/c/bug-reports');
|
|
551
|
+
console.log('');
|
|
552
|
+
}
|
|
396
553
|
}
|
|
397
554
|
|
|
398
555
|
// Main program
|
|
399
556
|
if (require.main === module) {
|
|
557
|
+
let installer = null;
|
|
400
558
|
try {
|
|
401
|
-
|
|
559
|
+
installer = new QoderInstaller();
|
|
402
560
|
installer.install();
|
|
403
561
|
} catch (error) {
|
|
562
|
+
console.error('');
|
|
404
563
|
console.error('❌ Failed to initialize installer:', error.message);
|
|
564
|
+
console.error('');
|
|
405
565
|
console.error('This might be due to Node.js version compatibility issues.');
|
|
406
566
|
console.error(`Current Node.js version: ${process.version}`);
|
|
407
567
|
console.error('Required Node.js version: >=14');
|
|
568
|
+
console.error('');
|
|
569
|
+
|
|
570
|
+
// Show log file location if logging was initialized
|
|
571
|
+
// (now possible since setupLogging() runs first)
|
|
572
|
+
if (installer && installer.logFile) {
|
|
573
|
+
console.error(`Installation log: ${installer.logFile}`);
|
|
574
|
+
console.error('');
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
console.error('For help, visit: https://forum.qoder.com/c/bug-reports');
|
|
578
|
+
console.error('');
|
|
408
579
|
process.exit(1);
|
|
409
580
|
}
|
|
410
581
|
}
|