@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,342 +0,0 @@
1
- import * as path from 'node:path';
2
- import type {
3
- ExecutionResult,
4
- GlobalSnapshotConfig,
5
- JsonValue,
6
- Snapshot,
7
- SnapshotCompareResult,
8
- SnapshotConfig,
9
- SnapshotFile,
10
- } from '../types/config';
11
- import { SnapshotDiffer } from './snapshot-differ';
12
-
13
- const SNAPSHOT_VERSION = 1;
14
- const DEFAULT_SNAPSHOT_DIR = '__snapshots__';
15
-
16
- /**
17
- * Manages snapshot files: reading, writing, comparing, and updating.
18
- */
19
- export class SnapshotManager {
20
- private snapshotDir: string;
21
- private globalConfig: GlobalSnapshotConfig;
22
- private writeLocks: Map<string, Promise<void>> = new Map();
23
-
24
- constructor(globalConfig: GlobalSnapshotConfig = {}) {
25
- this.globalConfig = globalConfig;
26
- this.snapshotDir = globalConfig.dir || DEFAULT_SNAPSHOT_DIR;
27
- }
28
-
29
- /**
30
- * Gets the snapshot file path for a YAML file.
31
- */
32
- getSnapshotPath(yamlPath: string): string {
33
- const dir = path.dirname(yamlPath);
34
- const basename = path.basename(yamlPath, path.extname(yamlPath));
35
- return path.join(dir, this.snapshotDir, `${basename}.snap.json`);
36
- }
37
-
38
- /**
39
- * Loads snapshot file for a YAML file.
40
- */
41
- async load(yamlPath: string): Promise<SnapshotFile | null> {
42
- const snapshotPath = this.getSnapshotPath(yamlPath);
43
- try {
44
- const file = Bun.file(snapshotPath);
45
- if (!(await file.exists())) {
46
- return null;
47
- }
48
- const content = await file.text();
49
- return JSON.parse(content) as SnapshotFile;
50
- } catch {
51
- return null;
52
- }
53
- }
54
-
55
- /**
56
- * Saves snapshot file with write queue for parallel safety.
57
- */
58
- async save(yamlPath: string, data: SnapshotFile): Promise<void> {
59
- const snapshotPath = this.getSnapshotPath(yamlPath);
60
-
61
- // Queue writes to prevent race conditions
62
- const existingLock = this.writeLocks.get(snapshotPath);
63
- const writePromise = (async () => {
64
- if (existingLock) {
65
- await existingLock;
66
- }
67
-
68
- // Ensure directory exists
69
- const dir = path.dirname(snapshotPath);
70
- const fs = await import('node:fs/promises');
71
- await fs.mkdir(dir, { recursive: true });
72
-
73
- // Write with pretty formatting
74
- const content = JSON.stringify(data, null, 2);
75
- await Bun.write(snapshotPath, content);
76
- })();
77
-
78
- this.writeLocks.set(snapshotPath, writePromise);
79
- await writePromise;
80
- this.writeLocks.delete(snapshotPath);
81
- }
82
-
83
- /**
84
- * Gets a single snapshot by request name.
85
- */
86
- async get(yamlPath: string, requestName: string): Promise<Snapshot | null> {
87
- const file = await this.load(yamlPath);
88
- return file?.snapshots[requestName] || null;
89
- }
90
-
91
- /**
92
- * Creates a snapshot from execution result.
93
- */
94
- createSnapshot(result: ExecutionResult, config: SnapshotConfig): Snapshot {
95
- const include = config.include || ['body'];
96
- const snapshot: Snapshot = {
97
- hash: '',
98
- updatedAt: new Date().toISOString(),
99
- };
100
-
101
- if (include.includes('status') && result.status !== undefined) {
102
- snapshot.status = result.status;
103
- }
104
-
105
- if (include.includes('headers') && result.headers) {
106
- // Normalize headers: lowercase keys, sorted
107
- snapshot.headers = this.normalizeHeaders(result.headers);
108
- }
109
-
110
- if (include.includes('body') && result.body !== undefined) {
111
- snapshot.body = result.body;
112
- }
113
-
114
- // Generate hash from content
115
- snapshot.hash = this.hash(snapshot);
116
-
117
- return snapshot;
118
- }
119
-
120
- /**
121
- * Normalizes headers for consistent comparison.
122
- */
123
- private normalizeHeaders(headers: Record<string, string>): Record<string, string> {
124
- const normalized: Record<string, string> = {};
125
- const sortedKeys = Object.keys(headers).sort();
126
- for (const key of sortedKeys) {
127
- normalized[key.toLowerCase()] = headers[key];
128
- }
129
- return normalized;
130
- }
131
-
132
- /**
133
- * Generates a hash for snapshot content.
134
- */
135
- hash(content: unknown): string {
136
- const str = JSON.stringify(content);
137
- const hasher = new Bun.CryptoHasher('md5');
138
- hasher.update(str);
139
- return hasher.digest('hex').slice(0, 8);
140
- }
141
-
142
- /**
143
- * Compares execution result against stored snapshot and optionally updates.
144
- */
145
- async compareAndUpdate(
146
- yamlPath: string,
147
- requestName: string,
148
- result: ExecutionResult,
149
- config: SnapshotConfig,
150
- ): Promise<SnapshotCompareResult> {
151
- const snapshotName = config.name || requestName;
152
- const existingSnapshot = await this.get(yamlPath, snapshotName);
153
- const newSnapshot = this.createSnapshot(result, config);
154
-
155
- // No existing snapshot
156
- if (!existingSnapshot) {
157
- if (this.globalConfig.ci) {
158
- // CI mode: fail on missing snapshot
159
- return {
160
- match: false,
161
- isNew: true,
162
- updated: false,
163
- differences: [
164
- {
165
- path: '',
166
- expected: 'snapshot',
167
- received: 'none',
168
- type: 'removed',
169
- },
170
- ],
171
- };
172
- }
173
-
174
- // Create new snapshot
175
- await this.updateSnapshot(yamlPath, snapshotName, newSnapshot);
176
- return {
177
- match: true,
178
- isNew: true,
179
- updated: true,
180
- differences: [],
181
- };
182
- }
183
-
184
- // Compare snapshots
185
- const differ = new SnapshotDiffer(config);
186
- const diffResult = differ.compare(existingSnapshot, newSnapshot);
187
-
188
- if (diffResult.match) {
189
- return {
190
- match: true,
191
- isNew: false,
192
- updated: false,
193
- differences: [],
194
- };
195
- }
196
-
197
- // Handle update modes
198
- const updateMode = this.globalConfig.updateMode || 'none';
199
- if (updateMode === 'all' || updateMode === 'failing') {
200
- await this.updateSnapshot(yamlPath, snapshotName, newSnapshot);
201
- return {
202
- match: true,
203
- isNew: false,
204
- updated: true,
205
- differences: diffResult.differences,
206
- };
207
- }
208
-
209
- return {
210
- match: false,
211
- isNew: false,
212
- updated: false,
213
- differences: diffResult.differences,
214
- };
215
- }
216
-
217
- /**
218
- * Updates a single snapshot in the file.
219
- */
220
- private async updateSnapshot(
221
- yamlPath: string,
222
- snapshotName: string,
223
- snapshot: Snapshot,
224
- ): Promise<void> {
225
- let file = await this.load(yamlPath);
226
- if (!file) {
227
- file = {
228
- version: SNAPSHOT_VERSION,
229
- snapshots: {},
230
- };
231
- }
232
-
233
- file.snapshots[snapshotName] = snapshot;
234
- await this.save(yamlPath, file);
235
- }
236
-
237
- /**
238
- * Merges request-level config with global config.
239
- */
240
- static mergeConfig(
241
- globalConfig: GlobalSnapshotConfig | undefined,
242
- requestConfig: SnapshotConfig | boolean | undefined,
243
- ): SnapshotConfig | null {
244
- // Not enabled
245
- if (!requestConfig && !globalConfig?.enabled) {
246
- return null;
247
- }
248
-
249
- // Simple boolean enable
250
- if (requestConfig === true) {
251
- return {
252
- enabled: true,
253
- include: globalConfig?.include || ['body'],
254
- exclude: globalConfig?.exclude || [],
255
- match: globalConfig?.match || {},
256
- };
257
- }
258
-
259
- // Detailed config
260
- if (typeof requestConfig === 'object' && requestConfig.enabled !== false) {
261
- return {
262
- enabled: true,
263
- name: requestConfig.name,
264
- include: requestConfig.include || globalConfig?.include || ['body'],
265
- exclude: [...(globalConfig?.exclude || []), ...(requestConfig.exclude || [])],
266
- match: { ...(globalConfig?.match || {}), ...(requestConfig.match || {}) },
267
- };
268
- }
269
-
270
- // Global enabled but request not specified
271
- if (globalConfig?.enabled && requestConfig === undefined) {
272
- return {
273
- enabled: true,
274
- include: globalConfig.include || ['body'],
275
- exclude: globalConfig.exclude || [],
276
- match: globalConfig.match || {},
277
- };
278
- }
279
-
280
- return null;
281
- }
282
- }
283
-
284
- /**
285
- * Extracts body content for snapshot, applying exclusions.
286
- */
287
- export function filterSnapshotBody(body: JsonValue, exclude: string[]): JsonValue {
288
- if (body === null || typeof body !== 'object') {
289
- return body;
290
- }
291
-
292
- const bodyExcludes = exclude.filter((p) => p.startsWith('body.')).map((p) => p.slice(5)); // Remove 'body.' prefix
293
-
294
- if (bodyExcludes.length === 0) {
295
- return body;
296
- }
297
-
298
- return filterObject(body, bodyExcludes, '');
299
- }
300
-
301
- function filterObject(obj: JsonValue, excludes: string[], currentPath: string): JsonValue {
302
- if (obj === null || typeof obj !== 'object') {
303
- return obj;
304
- }
305
-
306
- if (Array.isArray(obj)) {
307
- return obj.map((item, index) => {
308
- const itemPath = currentPath ? `${currentPath}[${index}]` : `[${index}]`;
309
- return filterObject(item, excludes, itemPath);
310
- });
311
- }
312
-
313
- const result: Record<string, JsonValue> = {};
314
- for (const [key, value] of Object.entries(obj)) {
315
- const fullPath = currentPath ? `${currentPath}.${key}` : key;
316
-
317
- // Check if this path should be excluded
318
- const shouldExclude = excludes.some((pattern) => {
319
- // Exact match
320
- if (pattern === fullPath) {
321
- return true;
322
- }
323
- // Wildcard match (e.g., '*.timestamp' matches 'user.timestamp')
324
- if (pattern.startsWith('*.')) {
325
- const suffix = pattern.slice(2);
326
- return fullPath.endsWith(`.${suffix}`) || fullPath === suffix;
327
- }
328
- // Array wildcard (e.g., '[*].id')
329
- if (pattern.includes('[*]')) {
330
- const regex = new RegExp(`^${pattern.replace(/\[\*\]/g, '\\[\\d+\\]')}$`);
331
- return regex.test(fullPath);
332
- }
333
- return false;
334
- });
335
-
336
- if (!shouldExclude) {
337
- result[key] = filterObject(value, excludes, fullPath);
338
- }
339
- }
340
-
341
- return result;
342
- }
@@ -1,11 +0,0 @@
1
- // Type declarations for Bun.YAML which is not in bun-types
2
- declare global {
3
- namespace Bun {
4
- const YAML: {
5
- parse(content: string): unknown;
6
- stringify(value: unknown): string;
7
- };
8
- }
9
- }
10
-
11
- export {};