@curl-runner/cli 1.16.0 → 1.16.2

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.
Files changed (40) hide show
  1. package/package.json +2 -2
  2. package/src/ci-exit.test.ts +0 -216
  3. package/src/cli.ts +0 -1351
  4. package/src/commands/upgrade.ts +0 -262
  5. package/src/diff/baseline-manager.test.ts +0 -181
  6. package/src/diff/baseline-manager.ts +0 -266
  7. package/src/diff/diff-formatter.ts +0 -316
  8. package/src/diff/index.ts +0 -3
  9. package/src/diff/response-differ.test.ts +0 -330
  10. package/src/diff/response-differ.ts +0 -489
  11. package/src/executor/max-concurrency.test.ts +0 -139
  12. package/src/executor/profile-executor.test.ts +0 -132
  13. package/src/executor/profile-executor.ts +0 -167
  14. package/src/executor/request-executor.ts +0 -663
  15. package/src/parser/yaml.test.ts +0 -480
  16. package/src/parser/yaml.ts +0 -271
  17. package/src/snapshot/index.ts +0 -3
  18. package/src/snapshot/snapshot-differ.test.ts +0 -358
  19. package/src/snapshot/snapshot-differ.ts +0 -296
  20. package/src/snapshot/snapshot-formatter.ts +0 -170
  21. package/src/snapshot/snapshot-manager.test.ts +0 -204
  22. package/src/snapshot/snapshot-manager.ts +0 -342
  23. package/src/types/bun-yaml.d.ts +0 -11
  24. package/src/types/config.ts +0 -638
  25. package/src/utils/colors.ts +0 -30
  26. package/src/utils/condition-evaluator.test.ts +0 -415
  27. package/src/utils/condition-evaluator.ts +0 -327
  28. package/src/utils/curl-builder.test.ts +0 -165
  29. package/src/utils/curl-builder.ts +0 -209
  30. package/src/utils/installation-detector.test.ts +0 -52
  31. package/src/utils/installation-detector.ts +0 -123
  32. package/src/utils/logger.ts +0 -856
  33. package/src/utils/response-store.test.ts +0 -213
  34. package/src/utils/response-store.ts +0 -108
  35. package/src/utils/stats.test.ts +0 -161
  36. package/src/utils/stats.ts +0 -151
  37. package/src/utils/version-checker.ts +0 -158
  38. package/src/version.ts +0 -43
  39. package/src/watcher/file-watcher.test.ts +0 -186
  40. package/src/watcher/file-watcher.ts +0 -140
@@ -1,158 +0,0 @@
1
- import { getVersion } from '../version';
2
- import { color } from './colors';
3
-
4
- interface VersionCheckCache {
5
- lastCheck: number;
6
- latestVersion: string;
7
- }
8
-
9
- const CACHE_FILE = `${process.env.HOME}/.curl-runner-version-cache.json`;
10
- const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
11
- const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@curl-runner/cli/latest';
12
-
13
- export class VersionChecker {
14
- async checkForUpdates(skipCache = false): Promise<void> {
15
- try {
16
- // Don't check in CI environments
17
- if (process.env.CI) {
18
- return;
19
- }
20
-
21
- const currentVersion = getVersion();
22
-
23
- // Don't check for development versions
24
- if (currentVersion === '0.0.0') {
25
- return;
26
- }
27
-
28
- // Check cache first
29
- if (!skipCache) {
30
- const cached = await this.getCachedVersion();
31
- if (cached && Date.now() - cached.lastCheck < CACHE_DURATION) {
32
- this.compareVersions(currentVersion, cached.latestVersion);
33
- return;
34
- }
35
- }
36
-
37
- // Fetch latest version from npm registry
38
- const latestVersion = await this.fetchLatestVersion();
39
- if (latestVersion) {
40
- // Update cache
41
- await this.setCachedVersion(latestVersion);
42
-
43
- // Compare versions
44
- this.compareVersions(currentVersion, latestVersion);
45
- }
46
- } catch {
47
- // Silently fail - we don't want to interrupt the CLI usage
48
- // due to version check failures
49
- }
50
- }
51
-
52
- private async fetchLatestVersion(): Promise<string | null> {
53
- try {
54
- const response = await fetch(NPM_REGISTRY_URL, {
55
- signal: AbortSignal.timeout(3000), // 3 second timeout
56
- });
57
-
58
- if (!response.ok) {
59
- return null;
60
- }
61
-
62
- const data = (await response.json()) as { version: string };
63
- return data.version;
64
- } catch {
65
- return null;
66
- }
67
- }
68
-
69
- private compareVersions(current: string, latest: string): void {
70
- if (this.isNewerVersion(current, latest)) {
71
- console.log();
72
- console.log(color('╭───────────────────────────────────────────────╮', 'yellow'));
73
- console.log(
74
- color('│', 'yellow') +
75
- ' ' +
76
- color('│', 'yellow'),
77
- );
78
- console.log(
79
- color('│', 'yellow') +
80
- ' ' +
81
- color('Update available!', 'bright') +
82
- ` ${color(current, 'red')} → ${color(latest, 'green')}` +
83
- ' ' +
84
- color('│', 'yellow'),
85
- );
86
- console.log(
87
- color('│', 'yellow') +
88
- ' ' +
89
- color('│', 'yellow'),
90
- );
91
- console.log(
92
- color('│', 'yellow') +
93
- ' Run ' +
94
- color('curl-runner upgrade', 'cyan') +
95
- ' to update ' +
96
- color('│', 'yellow'),
97
- );
98
- console.log(
99
- color('│', 'yellow') +
100
- ' ' +
101
- color('│', 'yellow'),
102
- );
103
- console.log(color('╰───────────────────────────────────────────────╯', 'yellow'));
104
- console.log();
105
- }
106
- }
107
-
108
- private isNewerVersion(current: string, latest: string): boolean {
109
- try {
110
- // Remove 'v' prefix if present
111
- const currentVersion = current.replace(/^v/, '');
112
- const latestVersion = latest.replace(/^v/, '');
113
-
114
- const currentParts = currentVersion.split('.').map(Number);
115
- const latestParts = latestVersion.split('.').map(Number);
116
-
117
- for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
118
- const currentPart = currentParts[i] || 0;
119
- const latestPart = latestParts[i] || 0;
120
-
121
- if (latestPart > currentPart) {
122
- return true;
123
- }
124
- if (latestPart < currentPart) {
125
- return false;
126
- }
127
- }
128
-
129
- return false;
130
- } catch {
131
- return false;
132
- }
133
- }
134
-
135
- private async getCachedVersion(): Promise<VersionCheckCache | null> {
136
- try {
137
- const file = Bun.file(CACHE_FILE);
138
- if (await file.exists()) {
139
- return JSON.parse(await file.text());
140
- }
141
- } catch {
142
- // Ignore cache read errors
143
- }
144
- return null;
145
- }
146
-
147
- private async setCachedVersion(latestVersion: string): Promise<void> {
148
- try {
149
- const cache: VersionCheckCache = {
150
- lastCheck: Date.now(),
151
- latestVersion,
152
- };
153
- await Bun.write(CACHE_FILE, JSON.stringify(cache));
154
- } catch {
155
- // Ignore cache write errors
156
- }
157
- }
158
- }
package/src/version.ts DELETED
@@ -1,43 +0,0 @@
1
- // Version management for curl-runner CLI
2
- // This file handles version detection for both development and compiled binaries
3
-
4
- declare const BUILD_VERSION: string | undefined;
5
-
6
- export function getVersion(): string {
7
- // Check compile-time constant first (set by --define flag during build)
8
- if (typeof BUILD_VERSION !== 'undefined') {
9
- return BUILD_VERSION;
10
- }
11
-
12
- // Check environment variable (for local builds with our script)
13
- if (process.env.CURL_RUNNER_VERSION) {
14
- return process.env.CURL_RUNNER_VERSION;
15
- }
16
-
17
- // In development or npm installation, try to read from package.json
18
- try {
19
- // Try multiple paths to find package.json
20
- const possiblePaths = [
21
- '../package.json', // Development or npm installation
22
- './package.json', // In case we're at root
23
- '../../package.json', // In case of different directory structure
24
- ];
25
-
26
- for (const path of possiblePaths) {
27
- try {
28
- const packageJson = require(path);
29
- if (packageJson.name === '@curl-runner/cli' && packageJson.version) {
30
- return packageJson.version;
31
- }
32
- } catch {
33
- // Try next path
34
- }
35
- }
36
-
37
- // If no package.json found, return default
38
- return '0.0.0';
39
- } catch {
40
- // If all else fails, return a default version
41
- return '0.0.0';
42
- }
43
- }
@@ -1,186 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
2
- import { FileWatcher, type WatcherOptions } from './file-watcher';
3
-
4
- // Mock logger
5
- const createMockLogger = () => ({
6
- logWatch: mock(() => {}),
7
- logWatchReady: mock(() => {}),
8
- logFileChanged: mock(() => {}),
9
- logError: mock(() => {}),
10
- logWarning: mock(() => {}),
11
- });
12
-
13
- // Helper to access private method for testing
14
- const triggerFileChange = (watcher: FileWatcher, filename: string) => {
15
- // biome-ignore lint/complexity/useLiteralKeys: accessing private method for testing
16
- watcher['handleFileChange'](filename);
17
- };
18
-
19
- describe('FileWatcher', () => {
20
- let watcher: FileWatcher;
21
- let mockLogger: ReturnType<typeof createMockLogger>;
22
- let onRunMock: ReturnType<typeof mock>;
23
-
24
- beforeEach(() => {
25
- mockLogger = createMockLogger();
26
- onRunMock = mock(() => Promise.resolve());
27
- });
28
-
29
- afterEach(() => {
30
- if (watcher) {
31
- watcher.stop();
32
- }
33
- });
34
-
35
- test('should debounce rapid file changes', async () => {
36
- const options: WatcherOptions = {
37
- files: [],
38
- config: { debounce: 50 },
39
- onRun: onRunMock,
40
- logger: mockLogger as unknown as WatcherOptions['logger'],
41
- };
42
-
43
- watcher = new FileWatcher(options);
44
-
45
- // Simulate rapid file changes
46
- triggerFileChange(watcher, 'test.yaml');
47
- triggerFileChange(watcher, 'test.yaml');
48
- triggerFileChange(watcher, 'test.yaml');
49
-
50
- // Wait for debounce to settle
51
- await Bun.sleep(100);
52
-
53
- // Should only have been called once due to debouncing
54
- // Initial run doesn't happen because we didn't call start()
55
- expect(onRunMock).toHaveBeenCalledTimes(1);
56
- });
57
-
58
- test('should queue runs during execution', async () => {
59
- let runCount = 0;
60
- const slowOnRun = mock(async () => {
61
- runCount++;
62
- await Bun.sleep(100);
63
- });
64
-
65
- const options: WatcherOptions = {
66
- files: [],
67
- config: { debounce: 10, clear: false },
68
- onRun: slowOnRun,
69
- logger: mockLogger as unknown as WatcherOptions['logger'],
70
- };
71
-
72
- watcher = new FileWatcher(options);
73
-
74
- // Start first run
75
- triggerFileChange(watcher, 'test.yaml');
76
- await Bun.sleep(20); // Let debounce fire
77
-
78
- // Change during run (should be queued)
79
- triggerFileChange(watcher, 'test.yaml');
80
-
81
- // Wait for both runs to complete
82
- await Bun.sleep(300);
83
-
84
- expect(runCount).toBe(2);
85
- });
86
-
87
- test('should clear debounce timer on stop', () => {
88
- const options: WatcherOptions = {
89
- files: [],
90
- config: { debounce: 1000 },
91
- onRun: onRunMock,
92
- logger: mockLogger as unknown as WatcherOptions['logger'],
93
- };
94
-
95
- watcher = new FileWatcher(options);
96
-
97
- // Trigger a change to start debounce timer
98
- triggerFileChange(watcher, 'test.yaml');
99
-
100
- // Stop should clear the timer
101
- watcher.stop();
102
-
103
- // biome-ignore lint/complexity/useLiteralKeys: accessing private field for testing
104
- expect(watcher['debounceTimer']).toBeNull();
105
- // biome-ignore lint/complexity/useLiteralKeys: accessing private field for testing
106
- expect(watcher['watchers']).toHaveLength(0);
107
- });
108
-
109
- test('should use default debounce of 300ms', async () => {
110
- const options: WatcherOptions = {
111
- files: [],
112
- config: {}, // No debounce specified
113
- onRun: onRunMock,
114
- logger: mockLogger as unknown as WatcherOptions['logger'],
115
- };
116
-
117
- watcher = new FileWatcher(options);
118
-
119
- const startTime = performance.now();
120
- triggerFileChange(watcher, 'test.yaml');
121
-
122
- // Wait less than default debounce
123
- await Bun.sleep(100);
124
- expect(onRunMock).not.toHaveBeenCalled();
125
-
126
- // Wait for full debounce
127
- await Bun.sleep(250);
128
- expect(onRunMock).toHaveBeenCalledTimes(1);
129
-
130
- const elapsed = performance.now() - startTime;
131
- expect(elapsed).toBeGreaterThanOrEqual(300);
132
- });
133
-
134
- test('should handle onRun errors gracefully', async () => {
135
- const errorOnRun = mock(async () => {
136
- throw new Error('Test error');
137
- });
138
-
139
- const options: WatcherOptions = {
140
- files: [],
141
- config: { debounce: 10 },
142
- onRun: errorOnRun,
143
- logger: mockLogger as unknown as WatcherOptions['logger'],
144
- };
145
-
146
- watcher = new FileWatcher(options);
147
-
148
- // Should not throw
149
- triggerFileChange(watcher, 'test.yaml');
150
- await Bun.sleep(50);
151
-
152
- expect(mockLogger.logError).toHaveBeenCalledWith('Test error');
153
- });
154
-
155
- test('should log file changed with filename', async () => {
156
- const options: WatcherOptions = {
157
- files: [],
158
- config: { debounce: 10, clear: false },
159
- onRun: onRunMock,
160
- logger: mockLogger as unknown as WatcherOptions['logger'],
161
- };
162
-
163
- watcher = new FileWatcher(options);
164
-
165
- triggerFileChange(watcher, 'my-api.yaml');
166
- await Bun.sleep(50);
167
-
168
- expect(mockLogger.logFileChanged).toHaveBeenCalledWith('my-api.yaml');
169
- });
170
-
171
- test('should call logWatchReady after run completes', async () => {
172
- const options: WatcherOptions = {
173
- files: [],
174
- config: { debounce: 10, clear: false },
175
- onRun: onRunMock,
176
- logger: mockLogger as unknown as WatcherOptions['logger'],
177
- };
178
-
179
- watcher = new FileWatcher(options);
180
-
181
- triggerFileChange(watcher, 'test.yaml');
182
- await Bun.sleep(50);
183
-
184
- expect(mockLogger.logWatchReady).toHaveBeenCalled();
185
- });
186
- });
@@ -1,140 +0,0 @@
1
- import { type FSWatcher, watch } from 'node:fs';
2
- import type { WatchConfig } from '../types/config';
3
- import type { Logger } from '../utils/logger';
4
-
5
- export interface WatcherOptions {
6
- files: string[];
7
- config: WatchConfig;
8
- onRun: () => Promise<void>;
9
- logger: Logger;
10
- }
11
-
12
- export class FileWatcher {
13
- private watchers: FSWatcher[] = [];
14
- private debounceTimer: Timer | null = null;
15
- private isRunning = false;
16
- private pendingRun = false;
17
- private options: WatcherOptions;
18
-
19
- constructor(options: WatcherOptions) {
20
- this.options = options;
21
- }
22
-
23
- async start(): Promise<void> {
24
- const { files, logger } = this.options;
25
-
26
- // Initial run
27
- await this.runRequests();
28
-
29
- // Setup watchers for each file
30
- for (const file of files) {
31
- try {
32
- const watcher = watch(file, (event) => {
33
- if (event === 'change') {
34
- this.handleFileChange(file);
35
- }
36
- });
37
-
38
- watcher.on('error', (error) => {
39
- logger.logWarning(`Watch error on ${file}: ${error.message}`);
40
- });
41
-
42
- this.watchers.push(watcher);
43
- } catch (error) {
44
- logger.logWarning(
45
- `Failed to watch ${file}: ${error instanceof Error ? error.message : String(error)}`,
46
- );
47
- }
48
- }
49
-
50
- // Log watching status
51
- logger.logWatch(files);
52
-
53
- // Handle graceful shutdown
54
- this.setupSignalHandlers();
55
-
56
- // Keep process alive
57
- await this.keepAlive();
58
- }
59
-
60
- private handleFileChange(filename: string): void {
61
- const { config, logger } = this.options;
62
- const debounce = config.debounce ?? 300;
63
-
64
- // Clear existing debounce timer
65
- if (this.debounceTimer) {
66
- clearTimeout(this.debounceTimer);
67
- }
68
-
69
- // Debounce the run
70
- this.debounceTimer = setTimeout(async () => {
71
- // If already running, queue for after completion
72
- if (this.isRunning) {
73
- this.pendingRun = true;
74
- return;
75
- }
76
-
77
- if (config.clear !== false) {
78
- console.clear();
79
- }
80
-
81
- logger.logFileChanged(filename);
82
- await this.runRequests();
83
- }, debounce);
84
- }
85
-
86
- private async runRequests(): Promise<void> {
87
- const { onRun, logger } = this.options;
88
-
89
- this.isRunning = true;
90
-
91
- try {
92
- await onRun();
93
- } catch (error) {
94
- logger.logError(error instanceof Error ? error.message : String(error));
95
- }
96
-
97
- this.isRunning = false;
98
-
99
- // Check for pending runs
100
- if (this.pendingRun) {
101
- this.pendingRun = false;
102
- const { config } = this.options;
103
- if (config.clear !== false) {
104
- console.clear();
105
- }
106
- logger.logFileChanged('(queued change)');
107
- await this.runRequests();
108
- } else {
109
- logger.logWatchReady();
110
- }
111
- }
112
-
113
- private setupSignalHandlers(): void {
114
- const cleanup = () => {
115
- this.stop();
116
- process.exit(0);
117
- };
118
-
119
- process.on('SIGINT', cleanup);
120
- process.on('SIGTERM', cleanup);
121
- }
122
-
123
- private async keepAlive(): Promise<never> {
124
- while (true) {
125
- await Bun.sleep(1000);
126
- }
127
- }
128
-
129
- stop(): void {
130
- for (const watcher of this.watchers) {
131
- watcher.close();
132
- }
133
- this.watchers = [];
134
-
135
- if (this.debounceTimer) {
136
- clearTimeout(this.debounceTimer);
137
- this.debounceTimer = null;
138
- }
139
- }
140
- }