@curl-runner/cli 1.15.0 → 1.16.0
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 +2 -1
- package/src/ci-exit.test.ts +1 -0
- package/src/cli.ts +39 -14
- package/src/commands/upgrade.ts +262 -0
- package/src/parser/yaml.ts +2 -3
- package/src/types/bun-yaml.d.ts +11 -0
- package/src/utils/curl-builder.ts +9 -1
- package/src/utils/installation-detector.test.ts +52 -0
- package/src/utils/installation-detector.ts +123 -0
- package/src/utils/logger.ts +1 -1
- package/src/utils/version-checker.ts +10 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curl-runner/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "A powerful CLI tool for HTTP request management using YAML configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"format": "biome format --write .",
|
|
15
15
|
"lint": "biome lint .",
|
|
16
16
|
"check": "biome check --write .",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
17
18
|
"test": "bun test"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
package/src/ci-exit.test.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { Glob } from 'bun';
|
|
4
|
+
import { showUpgradeHelp, UpgradeCommand } from './commands/upgrade';
|
|
4
5
|
import { BaselineManager, DiffFormatter, DiffOrchestrator } from './diff';
|
|
5
6
|
import { ProfileExecutor } from './executor/profile-executor';
|
|
6
7
|
import { RequestExecutor } from './executor/request-executor';
|
|
@@ -38,9 +39,9 @@ class CurlRunnerCLI {
|
|
|
38
39
|
if (await file.exists()) {
|
|
39
40
|
const yamlContent = await YamlParser.parseFile(filename);
|
|
40
41
|
// Extract global config from the YAML file
|
|
41
|
-
const config = yamlContent.global ||
|
|
42
|
+
const config = yamlContent.global || {};
|
|
42
43
|
this.logger.logInfo(`Loaded configuration from ${filename}`);
|
|
43
|
-
return config
|
|
44
|
+
return config as Partial<GlobalConfig>;
|
|
44
45
|
}
|
|
45
46
|
} catch (error) {
|
|
46
47
|
this.logger.logWarning(`Failed to load configuration from ${filename}: ${error}`);
|
|
@@ -318,6 +319,17 @@ class CurlRunnerCLI {
|
|
|
318
319
|
return;
|
|
319
320
|
}
|
|
320
321
|
|
|
322
|
+
// Handle upgrade subcommand: curl-runner upgrade [options]
|
|
323
|
+
if (args[0] === 'upgrade') {
|
|
324
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
325
|
+
showUpgradeHelp();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const upgradeCmd = new UpgradeCommand();
|
|
329
|
+
await upgradeCmd.run(args.slice(1));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
321
333
|
// Handle diff subcommand: curl-runner diff <label1> <label2> [file]
|
|
322
334
|
if (args[0] === 'diff' && args.length >= 3) {
|
|
323
335
|
await this.executeDiffSubcommand(args.slice(1), options);
|
|
@@ -373,16 +385,16 @@ class CurlRunnerCLI {
|
|
|
373
385
|
globalConfig.maxConcurrency = options.maxConcurrent as number;
|
|
374
386
|
}
|
|
375
387
|
if (options.continueOnError !== undefined) {
|
|
376
|
-
globalConfig.continueOnError = options.continueOnError;
|
|
388
|
+
globalConfig.continueOnError = options.continueOnError as boolean;
|
|
377
389
|
}
|
|
378
390
|
if (options.verbose !== undefined) {
|
|
379
|
-
globalConfig.output = { ...globalConfig.output, verbose: options.verbose };
|
|
391
|
+
globalConfig.output = { ...globalConfig.output, verbose: options.verbose as boolean };
|
|
380
392
|
}
|
|
381
393
|
if (options.quiet !== undefined) {
|
|
382
394
|
globalConfig.output = { ...globalConfig.output, verbose: false };
|
|
383
395
|
}
|
|
384
396
|
if (options.output) {
|
|
385
|
-
globalConfig.output = { ...globalConfig.output, saveToFile: options.output };
|
|
397
|
+
globalConfig.output = { ...globalConfig.output, saveToFile: options.output as string };
|
|
386
398
|
}
|
|
387
399
|
if (options.outputFormat) {
|
|
388
400
|
globalConfig.output = {
|
|
@@ -397,21 +409,27 @@ class CurlRunnerCLI {
|
|
|
397
409
|
};
|
|
398
410
|
}
|
|
399
411
|
if (options.showHeaders !== undefined) {
|
|
400
|
-
globalConfig.output = {
|
|
412
|
+
globalConfig.output = {
|
|
413
|
+
...globalConfig.output,
|
|
414
|
+
showHeaders: options.showHeaders as boolean,
|
|
415
|
+
};
|
|
401
416
|
}
|
|
402
417
|
if (options.showBody !== undefined) {
|
|
403
|
-
globalConfig.output = { ...globalConfig.output, showBody: options.showBody };
|
|
418
|
+
globalConfig.output = { ...globalConfig.output, showBody: options.showBody as boolean };
|
|
404
419
|
}
|
|
405
420
|
if (options.showMetrics !== undefined) {
|
|
406
|
-
globalConfig.output = {
|
|
421
|
+
globalConfig.output = {
|
|
422
|
+
...globalConfig.output,
|
|
423
|
+
showMetrics: options.showMetrics as boolean,
|
|
424
|
+
};
|
|
407
425
|
}
|
|
408
426
|
|
|
409
427
|
// Apply timeout and retry settings to defaults
|
|
410
428
|
if (options.timeout) {
|
|
411
|
-
globalConfig.defaults = { ...globalConfig.defaults, timeout: options.timeout };
|
|
429
|
+
globalConfig.defaults = { ...globalConfig.defaults, timeout: options.timeout as number };
|
|
412
430
|
}
|
|
413
431
|
if (options.retries || options.noRetry) {
|
|
414
|
-
const retryCount = options.noRetry ? 0 : options.retries || 0;
|
|
432
|
+
const retryCount = options.noRetry ? 0 : (options.retries as number) || 0;
|
|
415
433
|
globalConfig.defaults = {
|
|
416
434
|
...globalConfig.defaults,
|
|
417
435
|
retry: {
|
|
@@ -425,7 +443,7 @@ class CurlRunnerCLI {
|
|
|
425
443
|
...globalConfig.defaults,
|
|
426
444
|
retry: {
|
|
427
445
|
...globalConfig.defaults?.retry,
|
|
428
|
-
delay: options.retryDelay,
|
|
446
|
+
delay: options.retryDelay as number,
|
|
429
447
|
},
|
|
430
448
|
};
|
|
431
449
|
}
|
|
@@ -670,13 +688,15 @@ class CurlRunnerCLI {
|
|
|
670
688
|
}
|
|
671
689
|
|
|
672
690
|
// Create combined summary
|
|
673
|
-
const successful = allResults.filter((r) => r.success).length;
|
|
674
|
-
const failed = allResults.filter((r) => !r.success).length;
|
|
691
|
+
const successful = allResults.filter((r) => r.success && !r.skipped).length;
|
|
692
|
+
const failed = allResults.filter((r) => !r.success && !r.skipped).length;
|
|
693
|
+
const skipped = allResults.filter((r) => r.skipped).length;
|
|
675
694
|
|
|
676
695
|
summary = {
|
|
677
696
|
total: allResults.length,
|
|
678
697
|
successful,
|
|
679
698
|
failed,
|
|
699
|
+
skipped,
|
|
680
700
|
duration: totalDuration,
|
|
681
701
|
results: allResults,
|
|
682
702
|
};
|
|
@@ -1081,7 +1101,7 @@ class CurlRunnerCLI {
|
|
|
1081
1101
|
variables: Record<string, string>,
|
|
1082
1102
|
defaults: Partial<RequestConfig>,
|
|
1083
1103
|
): RequestConfig {
|
|
1084
|
-
const interpolated = YamlParser.interpolateVariables(request, variables);
|
|
1104
|
+
const interpolated = YamlParser.interpolateVariables(request, variables) as RequestConfig;
|
|
1085
1105
|
return YamlParser.mergeConfigs(defaults, interpolated);
|
|
1086
1106
|
}
|
|
1087
1107
|
|
|
@@ -1214,6 +1234,11 @@ ${this.logger.color('DIFF SUBCOMMAND:', 'yellow')}
|
|
|
1214
1234
|
curl-runner diff <label1> <label2> [file.yaml]
|
|
1215
1235
|
Compare two stored baselines without making requests
|
|
1216
1236
|
|
|
1237
|
+
${this.logger.color('UPGRADE:', 'yellow')}
|
|
1238
|
+
curl-runner upgrade Upgrade to latest version (auto-detects install method)
|
|
1239
|
+
curl-runner upgrade --dry-run Preview upgrade command without executing
|
|
1240
|
+
curl-runner upgrade --force Force reinstall even if up to date
|
|
1241
|
+
|
|
1217
1242
|
${this.logger.color('EXAMPLES:', 'yellow')}
|
|
1218
1243
|
# Run all YAML files in current directory
|
|
1219
1244
|
curl-runner
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Upgrade command for curl-runner
|
|
2
|
+
// Automatically detects installation source and upgrades to latest version
|
|
3
|
+
|
|
4
|
+
import { color } from '../utils/colors';
|
|
5
|
+
import {
|
|
6
|
+
type DetectionResult,
|
|
7
|
+
detectInstallationSource,
|
|
8
|
+
getUpgradeCommand,
|
|
9
|
+
getUpgradeCommandWindows,
|
|
10
|
+
type InstallationSource,
|
|
11
|
+
isWindows,
|
|
12
|
+
} from '../utils/installation-detector';
|
|
13
|
+
import { getVersion } from '../version';
|
|
14
|
+
|
|
15
|
+
const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@curl-runner/cli/latest';
|
|
16
|
+
const GITHUB_API_URL = 'https://api.github.com/repos/alexvcasillas/curl-runner/releases/latest';
|
|
17
|
+
|
|
18
|
+
interface UpgradeOptions {
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
force?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class UpgradeCommand {
|
|
24
|
+
async run(args: string[]): Promise<void> {
|
|
25
|
+
const options = this.parseArgs(args);
|
|
26
|
+
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(color('curl-runner upgrade', 'bright'));
|
|
29
|
+
console.log();
|
|
30
|
+
|
|
31
|
+
// Detect installation source
|
|
32
|
+
const detection = detectInstallationSource();
|
|
33
|
+
console.log(`${color('Installation:', 'cyan')} ${this.formatSource(detection.source)}`);
|
|
34
|
+
console.log(`${color('Current version:', 'cyan')} ${getVersion()}`);
|
|
35
|
+
|
|
36
|
+
// Fetch latest version
|
|
37
|
+
const latestVersion = await this.fetchLatestVersion(detection.source);
|
|
38
|
+
if (!latestVersion) {
|
|
39
|
+
console.log(color('Failed to fetch latest version', 'red'));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
console.log(`${color('Latest version:', 'cyan')} ${latestVersion}`);
|
|
43
|
+
console.log();
|
|
44
|
+
|
|
45
|
+
// Compare versions
|
|
46
|
+
const currentVersion = getVersion();
|
|
47
|
+
if (!options.force && !this.isNewerVersion(currentVersion, latestVersion)) {
|
|
48
|
+
console.log(color('Already up to date!', 'green'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get upgrade command
|
|
53
|
+
const upgradeCmd = isWindows()
|
|
54
|
+
? getUpgradeCommandWindows(detection.source)
|
|
55
|
+
: getUpgradeCommand(detection.source);
|
|
56
|
+
|
|
57
|
+
if (options.dryRun) {
|
|
58
|
+
console.log(color('Dry run - would execute:', 'yellow'));
|
|
59
|
+
console.log(` ${color(upgradeCmd, 'cyan')}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Execute upgrade
|
|
64
|
+
console.log(`${color('Upgrading...', 'yellow')}`);
|
|
65
|
+
console.log();
|
|
66
|
+
|
|
67
|
+
await this.executeUpgrade(detection, upgradeCmd);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private parseArgs(args: string[]): UpgradeOptions {
|
|
71
|
+
const options: UpgradeOptions = {};
|
|
72
|
+
|
|
73
|
+
for (const arg of args) {
|
|
74
|
+
if (arg === '--dry-run' || arg === '-n') {
|
|
75
|
+
options.dryRun = true;
|
|
76
|
+
} else if (arg === '--force' || arg === '-f') {
|
|
77
|
+
options.force = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return options;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private formatSource(source: InstallationSource): string {
|
|
85
|
+
switch (source) {
|
|
86
|
+
case 'bun':
|
|
87
|
+
return 'bun (global)';
|
|
88
|
+
case 'npm':
|
|
89
|
+
return 'npm (global)';
|
|
90
|
+
case 'curl':
|
|
91
|
+
return 'curl installer';
|
|
92
|
+
case 'standalone':
|
|
93
|
+
return 'standalone binary';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private async fetchLatestVersion(source: InstallationSource): Promise<string | null> {
|
|
98
|
+
try {
|
|
99
|
+
// For npm/bun, use npm registry
|
|
100
|
+
if (source === 'bun' || source === 'npm') {
|
|
101
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
102
|
+
signal: AbortSignal.timeout(5000),
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const data = (await response.json()) as { version: string };
|
|
108
|
+
return data.version;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// For curl/standalone, use GitHub releases
|
|
112
|
+
const response = await fetch(GITHUB_API_URL, {
|
|
113
|
+
signal: AbortSignal.timeout(5000),
|
|
114
|
+
headers: {
|
|
115
|
+
Accept: 'application/vnd.github.v3+json',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const data = (await response.json()) as { tag_name: string };
|
|
122
|
+
return data.tag_name.replace(/^v/, '');
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private isNewerVersion(current: string, latest: string): boolean {
|
|
129
|
+
const currentVersion = current.replace(/^v/, '');
|
|
130
|
+
const latestVersion = latest.replace(/^v/, '');
|
|
131
|
+
|
|
132
|
+
const currentParts = currentVersion.split('.').map(Number);
|
|
133
|
+
const latestParts = latestVersion.split('.').map(Number);
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
136
|
+
const currentPart = currentParts[i] || 0;
|
|
137
|
+
const latestPart = latestParts[i] || 0;
|
|
138
|
+
|
|
139
|
+
if (latestPart > currentPart) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
if (latestPart < currentPart) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async executeUpgrade(detection: DetectionResult, upgradeCmd: string): Promise<void> {
|
|
151
|
+
const { source } = detection;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
if (source === 'bun') {
|
|
155
|
+
await this.runBunUpgrade();
|
|
156
|
+
} else if (source === 'npm') {
|
|
157
|
+
await this.runNpmUpgrade();
|
|
158
|
+
} else {
|
|
159
|
+
// curl or standalone - run shell command
|
|
160
|
+
await this.runShellUpgrade(upgradeCmd);
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
164
|
+
|
|
165
|
+
// Check for permission errors
|
|
166
|
+
if (errorMsg.includes('EACCES') || errorMsg.includes('permission')) {
|
|
167
|
+
console.log();
|
|
168
|
+
console.log(color('Permission denied. Try running with sudo:', 'yellow'));
|
|
169
|
+
console.log(` ${color(`sudo ${upgradeCmd}`, 'cyan')}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(color(`Upgrade failed: ${errorMsg}`, 'red'));
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(color('Manual upgrade:', 'yellow'));
|
|
176
|
+
console.log(` ${color(upgradeCmd, 'cyan')}`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private async runBunUpgrade(): Promise<void> {
|
|
182
|
+
const proc = Bun.spawn(['bun', 'install', '-g', '@curl-runner/cli@latest'], {
|
|
183
|
+
stdout: 'inherit',
|
|
184
|
+
stderr: 'inherit',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const exitCode = await proc.exited;
|
|
188
|
+
if (exitCode !== 0) {
|
|
189
|
+
throw new Error(`bun install failed with exit code ${exitCode}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.showSuccess();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async runNpmUpgrade(): Promise<void> {
|
|
196
|
+
const proc = Bun.spawn(['npm', 'install', '-g', '@curl-runner/cli@latest'], {
|
|
197
|
+
stdout: 'inherit',
|
|
198
|
+
stderr: 'inherit',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const exitCode = await proc.exited;
|
|
202
|
+
if (exitCode !== 0) {
|
|
203
|
+
throw new Error(`npm install failed with exit code ${exitCode}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.showSuccess();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async runShellUpgrade(cmd: string): Promise<void> {
|
|
210
|
+
let proc: ReturnType<typeof Bun.spawn>;
|
|
211
|
+
|
|
212
|
+
if (isWindows()) {
|
|
213
|
+
// PowerShell for Windows
|
|
214
|
+
proc = Bun.spawn(['powershell', '-Command', cmd], {
|
|
215
|
+
stdout: 'inherit',
|
|
216
|
+
stderr: 'inherit',
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
// Bash for Unix
|
|
220
|
+
proc = Bun.spawn(['bash', '-c', cmd], {
|
|
221
|
+
stdout: 'inherit',
|
|
222
|
+
stderr: 'inherit',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const exitCode = await proc.exited;
|
|
227
|
+
if (exitCode !== 0) {
|
|
228
|
+
throw new Error(`Upgrade script failed with exit code ${exitCode}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this.showSuccess();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private showSuccess(): void {
|
|
235
|
+
console.log();
|
|
236
|
+
console.log(color('Upgrade complete!', 'green'));
|
|
237
|
+
console.log();
|
|
238
|
+
console.log('Run `curl-runner --version` to verify.');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function showUpgradeHelp(): void {
|
|
243
|
+
console.log(`
|
|
244
|
+
${color('curl-runner upgrade', 'bright')}
|
|
245
|
+
|
|
246
|
+
Automatically upgrade curl-runner to the latest version.
|
|
247
|
+
Detects installation source (bun, npm, curl) and uses appropriate method.
|
|
248
|
+
|
|
249
|
+
${color('USAGE:', 'yellow')}
|
|
250
|
+
curl-runner upgrade [options]
|
|
251
|
+
|
|
252
|
+
${color('OPTIONS:', 'yellow')}
|
|
253
|
+
-n, --dry-run Show what would be executed without running
|
|
254
|
+
-f, --force Force upgrade even if already on latest version
|
|
255
|
+
-h, --help Show this help message
|
|
256
|
+
|
|
257
|
+
${color('EXAMPLES:', 'yellow')}
|
|
258
|
+
curl-runner upgrade # Upgrade to latest
|
|
259
|
+
curl-runner upgrade --dry-run # Preview upgrade command
|
|
260
|
+
curl-runner upgrade --force # Force reinstall latest
|
|
261
|
+
`);
|
|
262
|
+
}
|
package/src/parser/yaml.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { YAML } from 'bun';
|
|
2
1
|
import type { RequestConfig, ResponseStoreContext, YamlFile } from '../types/config';
|
|
3
2
|
|
|
4
3
|
// Using class for organization, but could be refactored to functions
|
|
@@ -6,11 +5,11 @@ export class YamlParser {
|
|
|
6
5
|
static async parseFile(filepath: string): Promise<YamlFile> {
|
|
7
6
|
const file = Bun.file(filepath);
|
|
8
7
|
const content = await file.text();
|
|
9
|
-
return YAML.parse(content) as YamlFile;
|
|
8
|
+
return Bun.YAML.parse(content) as YamlFile;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
static parse(content: string): YamlFile {
|
|
13
|
-
return YAML.parse(content) as YamlFile;
|
|
12
|
+
return Bun.YAML.parse(content) as YamlFile;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -133,7 +133,15 @@ export class CurlBuilder {
|
|
|
133
133
|
status?: number;
|
|
134
134
|
headers?: Record<string, string>;
|
|
135
135
|
body?: string;
|
|
136
|
-
metrics?:
|
|
136
|
+
metrics?: {
|
|
137
|
+
duration: number;
|
|
138
|
+
size?: number;
|
|
139
|
+
dnsLookup?: number;
|
|
140
|
+
tcpConnection?: number;
|
|
141
|
+
tlsHandshake?: number;
|
|
142
|
+
firstByte?: number;
|
|
143
|
+
download?: number;
|
|
144
|
+
};
|
|
137
145
|
error?: string;
|
|
138
146
|
}> {
|
|
139
147
|
try {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { getUpgradeCommand, getUpgradeCommandWindows, isWindows } from './installation-detector';
|
|
3
|
+
|
|
4
|
+
describe('getUpgradeCommand', () => {
|
|
5
|
+
test('returns bun command for bun source', () => {
|
|
6
|
+
expect(getUpgradeCommand('bun')).toBe('bun install -g @curl-runner/cli@latest');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('returns npm command for npm source', () => {
|
|
10
|
+
expect(getUpgradeCommand('npm')).toBe('npm install -g @curl-runner/cli@latest');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('returns curl command for curl source', () => {
|
|
14
|
+
expect(getUpgradeCommand('curl')).toBe(
|
|
15
|
+
'curl -fsSL https://www.curl-runner.com/install.sh | bash',
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('returns curl command for standalone source', () => {
|
|
20
|
+
expect(getUpgradeCommand('standalone')).toBe(
|
|
21
|
+
'curl -fsSL https://www.curl-runner.com/install.sh | bash',
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('getUpgradeCommandWindows', () => {
|
|
27
|
+
test('returns bun command for bun source', () => {
|
|
28
|
+
expect(getUpgradeCommandWindows('bun')).toBe('bun install -g @curl-runner/cli@latest');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('returns npm command for npm source', () => {
|
|
32
|
+
expect(getUpgradeCommandWindows('npm')).toBe('npm install -g @curl-runner/cli@latest');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('returns PowerShell command for curl source', () => {
|
|
36
|
+
expect(getUpgradeCommandWindows('curl')).toBe(
|
|
37
|
+
'irm https://www.curl-runner.com/install.ps1 | iex',
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('returns PowerShell command for standalone source', () => {
|
|
42
|
+
expect(getUpgradeCommandWindows('standalone')).toBe(
|
|
43
|
+
'irm https://www.curl-runner.com/install.ps1 | iex',
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('isWindows', () => {
|
|
49
|
+
test('returns boolean based on platform', () => {
|
|
50
|
+
expect(typeof isWindows()).toBe('boolean');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Installation source detection for curl-runner upgrade command
|
|
2
|
+
|
|
3
|
+
export type InstallationSource = 'bun' | 'npm' | 'curl' | 'standalone';
|
|
4
|
+
|
|
5
|
+
export interface DetectionResult {
|
|
6
|
+
source: InstallationSource;
|
|
7
|
+
path: string;
|
|
8
|
+
canAutoUpgrade: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const CURL_INSTALL_PATHS = [
|
|
12
|
+
`${process.env.HOME}/.local/bin/curl-runner`,
|
|
13
|
+
`${process.env.USERPROFILE}\\.local\\bin\\curl-runner.exe`,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export function detectInstallationSource(): DetectionResult {
|
|
17
|
+
const execPath = process.execPath;
|
|
18
|
+
const argv0 = process.argv[0];
|
|
19
|
+
|
|
20
|
+
// Check for bun installation
|
|
21
|
+
if (isBunInstall(execPath)) {
|
|
22
|
+
return {
|
|
23
|
+
source: 'bun',
|
|
24
|
+
path: execPath,
|
|
25
|
+
canAutoUpgrade: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check for npm installation
|
|
30
|
+
if (isNpmInstall(execPath, argv0)) {
|
|
31
|
+
return {
|
|
32
|
+
source: 'npm',
|
|
33
|
+
path: execPath,
|
|
34
|
+
canAutoUpgrade: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for curl installation (binary in ~/.local/bin)
|
|
39
|
+
if (isCurlInstall(execPath)) {
|
|
40
|
+
return {
|
|
41
|
+
source: 'curl',
|
|
42
|
+
path: execPath,
|
|
43
|
+
canAutoUpgrade: true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Standalone binary
|
|
48
|
+
return {
|
|
49
|
+
source: 'standalone',
|
|
50
|
+
path: execPath,
|
|
51
|
+
canAutoUpgrade: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isBunInstall(execPath: string): boolean {
|
|
56
|
+
// Check if running from bun's global install directory
|
|
57
|
+
if (execPath.includes('.bun')) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (process.env.BUN_INSTALL && execPath.includes(process.env.BUN_INSTALL)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check for bun-specific paths
|
|
65
|
+
const bunPaths = ['/bun/install/', '/.bun/bin/', '/bun/bin/'];
|
|
66
|
+
return bunPaths.some((p) => execPath.includes(p));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isNpmInstall(execPath: string, argv0: string): boolean {
|
|
70
|
+
// Check for npm global install indicators
|
|
71
|
+
if (execPath.includes('node_modules')) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (argv0.includes('node_modules')) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check for npm config env vars (set when running via npm)
|
|
79
|
+
if (process.env.npm_config_prefix) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (process.env.npm_execpath) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check common npm global paths
|
|
87
|
+
const npmPaths = ['/lib/node_modules/', '/node_modules/.bin/'];
|
|
88
|
+
return npmPaths.some((p) => execPath.includes(p) || argv0.includes(p));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isCurlInstall(execPath: string): boolean {
|
|
92
|
+
// Check if binary is in curl installer's default location
|
|
93
|
+
return CURL_INSTALL_PATHS.some((p) => execPath === p || execPath.startsWith(p));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getUpgradeCommand(source: InstallationSource): string {
|
|
97
|
+
switch (source) {
|
|
98
|
+
case 'bun':
|
|
99
|
+
return 'bun install -g @curl-runner/cli@latest';
|
|
100
|
+
case 'npm':
|
|
101
|
+
return 'npm install -g @curl-runner/cli@latest';
|
|
102
|
+
case 'curl':
|
|
103
|
+
return 'curl -fsSL https://www.curl-runner.com/install.sh | bash';
|
|
104
|
+
case 'standalone':
|
|
105
|
+
return 'curl -fsSL https://www.curl-runner.com/install.sh | bash';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getUpgradeCommandWindows(source: InstallationSource): string {
|
|
110
|
+
switch (source) {
|
|
111
|
+
case 'bun':
|
|
112
|
+
return 'bun install -g @curl-runner/cli@latest';
|
|
113
|
+
case 'npm':
|
|
114
|
+
return 'npm install -g @curl-runner/cli@latest';
|
|
115
|
+
case 'curl':
|
|
116
|
+
case 'standalone':
|
|
117
|
+
return 'irm https://www.curl-runner.com/install.ps1 | iex';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isWindows(): boolean {
|
|
122
|
+
return process.platform === 'win32';
|
|
123
|
+
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -106,7 +106,7 @@ export class Logger {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
color(text: string, color: keyof typeof this.colors): string {
|
|
110
110
|
return `${this.colors[color]}${text}${this.colors.reset}`;
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -69,45 +69,38 @@ export class VersionChecker {
|
|
|
69
69
|
private compareVersions(current: string, latest: string): void {
|
|
70
70
|
if (this.isNewerVersion(current, latest)) {
|
|
71
71
|
console.log();
|
|
72
|
-
console.log(color('
|
|
72
|
+
console.log(color('╭───────────────────────────────────────────────╮', 'yellow'));
|
|
73
73
|
console.log(
|
|
74
74
|
color('│', 'yellow') +
|
|
75
|
-
'
|
|
75
|
+
' ' +
|
|
76
76
|
color('│', 'yellow'),
|
|
77
77
|
);
|
|
78
78
|
console.log(
|
|
79
79
|
color('│', 'yellow') +
|
|
80
80
|
' ' +
|
|
81
|
-
color('
|
|
81
|
+
color('Update available!', 'bright') +
|
|
82
82
|
` ${color(current, 'red')} → ${color(latest, 'green')}` +
|
|
83
|
-
'
|
|
83
|
+
' ' +
|
|
84
84
|
color('│', 'yellow'),
|
|
85
85
|
);
|
|
86
86
|
console.log(
|
|
87
87
|
color('│', 'yellow') +
|
|
88
|
-
'
|
|
88
|
+
' ' +
|
|
89
89
|
color('│', 'yellow'),
|
|
90
90
|
);
|
|
91
91
|
console.log(
|
|
92
92
|
color('│', 'yellow') +
|
|
93
|
-
'
|
|
94
|
-
color('
|
|
95
|
-
'
|
|
93
|
+
' Run ' +
|
|
94
|
+
color('curl-runner upgrade', 'cyan') +
|
|
95
|
+
' to update ' +
|
|
96
96
|
color('│', 'yellow'),
|
|
97
97
|
);
|
|
98
98
|
console.log(
|
|
99
99
|
color('│', 'yellow') +
|
|
100
|
-
'
|
|
101
|
-
color('bun install -g @curl-runner/cli', 'cyan') +
|
|
102
|
-
' ' +
|
|
100
|
+
' ' +
|
|
103
101
|
color('│', 'yellow'),
|
|
104
102
|
);
|
|
105
|
-
console.log(
|
|
106
|
-
color('│', 'yellow') +
|
|
107
|
-
' ' +
|
|
108
|
-
color('│', 'yellow'),
|
|
109
|
-
);
|
|
110
|
-
console.log(color('╰────────────────────────────────────────────────────────╯', 'yellow'));
|
|
103
|
+
console.log(color('╰───────────────────────────────────────────────╯', 'yellow'));
|
|
111
104
|
console.log();
|
|
112
105
|
}
|
|
113
106
|
}
|