@piyushdoorwar/yamlet 1.0.1

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 (3) hide show
  1. package/README.md +67 -0
  2. package/dist/yamlet.js +3607 -0
  3. package/package.json +40 -0
package/dist/yamlet.js ADDED
@@ -0,0 +1,3607 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cli/src/index.ts
4
+ import { existsSync as existsSync4, readFileSync, statSync as statSync2, writeFileSync } from "node:fs";
5
+ import { basename, resolve } from "node:path";
6
+
7
+ // core/src/requestExecutor.ts
8
+ import { existsSync, promises as fs } from "node:fs";
9
+ import { STATUS_CODES } from "node:http";
10
+ import path from "node:path";
11
+ import { performance } from "node:perf_hooks";
12
+ import { Agent, FormData, request as request2 } from "undici";
13
+
14
+ // core/src/cookieJar.ts
15
+ function parseSetCookie(header) {
16
+ const parts = header.split(";");
17
+ const first = parts.shift() ?? "";
18
+ const eq = first.indexOf("=");
19
+ if (eq <= 0) return void 0;
20
+ const cookie = { name: first.slice(0, eq).trim(), value: first.slice(eq + 1).trim() };
21
+ if (!cookie.name) return void 0;
22
+ if (cookie.value.length >= 2 && cookie.value.startsWith('"') && cookie.value.endsWith('"')) cookie.value = cookie.value.slice(1, -1);
23
+ let maxAge;
24
+ for (const raw of parts) {
25
+ const i = raw.indexOf("=");
26
+ const key = (i >= 0 ? raw.slice(0, i) : raw).trim().toLowerCase();
27
+ const val = i >= 0 ? raw.slice(i + 1).trim() : "";
28
+ switch (key) {
29
+ case "domain":
30
+ if (val) cookie.domain = val.replace(/^\./, "").toLowerCase();
31
+ break;
32
+ case "path":
33
+ if (val.startsWith("/")) cookie.path = val;
34
+ break;
35
+ case "expires": {
36
+ const t = Date.parse(val);
37
+ if (!Number.isNaN(t)) cookie.expires = new Date(t).toISOString();
38
+ break;
39
+ }
40
+ case "max-age": {
41
+ const n = parseInt(val, 10);
42
+ if (!Number.isNaN(n)) maxAge = n;
43
+ break;
44
+ }
45
+ case "secure":
46
+ cookie.secure = true;
47
+ break;
48
+ case "httponly":
49
+ cookie.httpOnly = true;
50
+ break;
51
+ case "samesite":
52
+ cookie.sameSite = val;
53
+ break;
54
+ }
55
+ }
56
+ if (maxAge !== void 0) cookie.expires = new Date(Date.now() + maxAge * 1e3).toISOString();
57
+ return cookie;
58
+ }
59
+ function defaultPath(pathname) {
60
+ if (!pathname.startsWith("/")) return "/";
61
+ const last = pathname.lastIndexOf("/");
62
+ return last <= 0 ? "/" : pathname.slice(0, last);
63
+ }
64
+ function domainMatches(host, domain) {
65
+ return host === domain || host.endsWith("." + domain) && !/^\d+\.\d+\.\d+\.\d+$/.test(host);
66
+ }
67
+ function pathMatches(reqPath, cookiePath) {
68
+ if (reqPath === cookiePath) return true;
69
+ if (!reqPath.startsWith(cookiePath)) return false;
70
+ return cookiePath.endsWith("/") || reqPath[cookiePath.length] === "/";
71
+ }
72
+ function parseUrl(url) {
73
+ try {
74
+ return new URL(url);
75
+ } catch {
76
+ return void 0;
77
+ }
78
+ }
79
+ var CookieJar = class _CookieJar {
80
+ cookies = [];
81
+ /** Stores cookies from a response's Set-Cookie headers. */
82
+ setFromResponse(url, setCookie) {
83
+ const u = parseUrl(url);
84
+ if (!u) return;
85
+ const host = u.hostname.toLowerCase();
86
+ for (const header of setCookie) {
87
+ const c = parseSetCookie(header);
88
+ if (!c) continue;
89
+ let domain = host;
90
+ let hostOnly = true;
91
+ if (c.domain) {
92
+ if (!domainMatches(host, c.domain)) continue;
93
+ domain = c.domain;
94
+ hostOnly = false;
95
+ }
96
+ const stored = {
97
+ name: c.name,
98
+ value: c.value,
99
+ domain,
100
+ path: c.path ?? defaultPath(u.pathname),
101
+ hostOnly,
102
+ secure: !!c.secure,
103
+ httpOnly: !!c.httpOnly,
104
+ sameSite: c.sameSite,
105
+ expires: c.expires,
106
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
107
+ };
108
+ const idx = this.cookies.findIndex((x) => x.name === stored.name && x.domain === stored.domain && x.path === stored.path);
109
+ if (idx >= 0) stored.createdAt = this.cookies[idx].createdAt;
110
+ const expired = stored.expires !== void 0 && Date.parse(stored.expires) <= Date.now();
111
+ if (idx >= 0) this.cookies.splice(idx, 1);
112
+ if (!expired) this.cookies.push(stored);
113
+ }
114
+ }
115
+ /** Cookies that apply to a request URL, longest path first. */
116
+ cookiesFor(url) {
117
+ const u = parseUrl(url);
118
+ if (!u) return [];
119
+ this.purgeExpired();
120
+ const host = u.hostname.toLowerCase();
121
+ const secure = u.protocol === "https:" || u.protocol === "wss:";
122
+ return this.cookies.filter((c) => c.hostOnly ? host === c.domain : domainMatches(host, c.domain)).filter((c) => pathMatches(u.pathname || "/", c.path)).filter((c) => !c.secure || secure || host === "localhost" || host === "127.0.0.1").sort((a, b) => b.path.length - a.path.length || a.createdAt.localeCompare(b.createdAt));
123
+ }
124
+ cookieHeaderFor(url) {
125
+ const list = this.cookiesFor(url);
126
+ return list.length ? list.map((c) => `${c.name}=${c.value}`).join("; ") : void 0;
127
+ }
128
+ list() {
129
+ this.purgeExpired();
130
+ return this.cookies.map((c) => ({ ...c }));
131
+ }
132
+ /** Adds or replaces a cookie directly (e.g. from a cookie manager UI). */
133
+ set(cookie) {
134
+ const domain = cookie.domain.replace(/^\./, "").toLowerCase();
135
+ this.remove(domain, cookie.name, cookie.path);
136
+ this.cookies.push({ ...cookie, domain, hostOnly: cookie.hostOnly ?? false, createdAt: cookie.createdAt ?? (/* @__PURE__ */ new Date()).toISOString() });
137
+ }
138
+ remove(domain, name, path4) {
139
+ const d = domain.replace(/^\./, "").toLowerCase();
140
+ this.cookies = this.cookies.filter((c) => !(c.domain === d && c.name === name && (path4 === void 0 || c.path === path4)));
141
+ }
142
+ clear(domain) {
143
+ if (domain === void 0) this.cookies = [];
144
+ else {
145
+ const d = domain.replace(/^\./, "").toLowerCase();
146
+ this.cookies = this.cookies.filter((c) => c.domain !== d);
147
+ }
148
+ }
149
+ toJSON() {
150
+ return { cookies: this.list() };
151
+ }
152
+ static fromJSON(json) {
153
+ const jar = new _CookieJar();
154
+ const list = Array.isArray(json) ? json : json?.cookies;
155
+ if (Array.isArray(list)) {
156
+ for (const c of list) {
157
+ if (c && typeof c === "object" && typeof c.name === "string" && typeof c.domain === "string") {
158
+ jar.cookies.push({
159
+ name: c.name,
160
+ value: String(c.value ?? ""),
161
+ domain: c.domain.replace(/^\./, "").toLowerCase(),
162
+ path: typeof c.path === "string" ? c.path : "/",
163
+ hostOnly: !!c.hostOnly,
164
+ secure: !!c.secure,
165
+ httpOnly: !!c.httpOnly,
166
+ sameSite: c.sameSite,
167
+ expires: c.expires,
168
+ createdAt: c.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
169
+ });
170
+ }
171
+ }
172
+ }
173
+ jar.purgeExpired();
174
+ return jar;
175
+ }
176
+ purgeExpired() {
177
+ const now = Date.now();
178
+ this.cookies = this.cookies.filter((c) => c.expires === void 0 || Date.parse(c.expires) > now);
179
+ }
180
+ };
181
+
182
+ // core/src/models.ts
183
+ function newId() {
184
+ return globalThis.crypto.randomUUID();
185
+ }
186
+ function defaultOAuth2() {
187
+ return {
188
+ grantType: "client_credentials",
189
+ accessToken: "",
190
+ refreshToken: "",
191
+ headerPrefix: "Bearer",
192
+ accessTokenUrl: "",
193
+ authUrl: "",
194
+ clientId: "",
195
+ clientSecret: "",
196
+ scope: "",
197
+ redirectUri: "",
198
+ username: "",
199
+ password: "",
200
+ addTokenTo: "header",
201
+ clientAuthentication: "basic",
202
+ challengeAlgorithm: "S256"
203
+ };
204
+ }
205
+ function defaultAuth(type = "inherit") {
206
+ return {
207
+ type,
208
+ token: "",
209
+ username: "",
210
+ password: "",
211
+ apiKeyName: "",
212
+ apiKeyValue: "",
213
+ apiKeyIn: "header",
214
+ cookie: "",
215
+ oauth2: defaultOAuth2()
216
+ };
217
+ }
218
+ function defaultBody() {
219
+ return { type: "none", raw: "", fields: [], graphqlQuery: "", graphqlVariables: "", binaryFile: "" };
220
+ }
221
+ function defaultSettings() {
222
+ return { timeoutMs: 0, followRedirects: true, skipSslVerification: false };
223
+ }
224
+ function newRequest(partial = {}) {
225
+ return {
226
+ id: newId(),
227
+ name: "New Request",
228
+ method: "GET",
229
+ url: "",
230
+ headers: [],
231
+ queryParams: [],
232
+ pathVariables: [],
233
+ body: defaultBody(),
234
+ auth: defaultAuth("inherit"),
235
+ variables: [],
236
+ description: "",
237
+ preRequestScript: "",
238
+ postResponseScript: "",
239
+ settings: defaultSettings(),
240
+ examples: [],
241
+ order: 0,
242
+ ...partial
243
+ };
244
+ }
245
+ function newFolder(partial = {}) {
246
+ return { id: newId(), name: "New Folder", folders: [], requests: [], order: 0, ...partial };
247
+ }
248
+ function newCollection(partial = {}) {
249
+ return {
250
+ id: newId(),
251
+ name: "New Collection",
252
+ folders: [],
253
+ requests: [],
254
+ variables: [],
255
+ auth: defaultAuth("none"),
256
+ preRequestScript: "",
257
+ postResponseScript: "",
258
+ order: 0,
259
+ ...partial
260
+ };
261
+ }
262
+ function newEnvironment(partial = {}) {
263
+ return { id: newId(), name: "New Environment", variables: [], ...partial };
264
+ }
265
+ var YAMLET_USER_AGENT = "Yamlet/1.0.0";
266
+
267
+ // core/src/oauth2.ts
268
+ import { request } from "undici";
269
+ function parseTokenBody(text, contentType) {
270
+ let raw;
271
+ if (contentType.includes("x-www-form-urlencoded") || !text.trim().startsWith("{") && text.includes("access_token=")) {
272
+ raw = Object.fromEntries(new URLSearchParams(text));
273
+ } else {
274
+ try {
275
+ raw = JSON.parse(text);
276
+ } catch {
277
+ throw new Error(`Token endpoint returned a non-JSON response: ${text.slice(0, 200)}`);
278
+ }
279
+ }
280
+ const o = raw ?? {};
281
+ const accessToken = typeof o.access_token === "string" ? o.access_token : "";
282
+ if (!accessToken) {
283
+ const err = o.error ? `${o.error}${o.error_description ? `: ${o.error_description}` : ""}` : "no access_token in response";
284
+ throw new Error(`Token request failed: ${err}`);
285
+ }
286
+ const expires = Number(o.expires_in);
287
+ const result = { accessToken, raw };
288
+ if (typeof o.refresh_token === "string" && o.refresh_token) result.refreshToken = o.refresh_token;
289
+ if (typeof o.token_type === "string" && o.token_type) result.tokenType = o.token_type;
290
+ if (Number.isFinite(expires) && expires > 0) result.expiresIn = expires;
291
+ return result;
292
+ }
293
+ async function requestToken(cfg, resolve2, form, opts = {}) {
294
+ const url = resolve2(cfg.accessTokenUrl).trim();
295
+ if (!url) throw new Error("OAuth 2.0 access token URL is not set");
296
+ const clientId = resolve2(cfg.clientId);
297
+ const clientSecret = resolve2(cfg.clientSecret);
298
+ const headers = {
299
+ "content-type": "application/x-www-form-urlencoded",
300
+ accept: "application/json"
301
+ };
302
+ const body = new URLSearchParams(form);
303
+ if (cfg.clientAuthentication === "body") {
304
+ if (clientId) body.set("client_id", clientId);
305
+ if (clientSecret) body.set("client_secret", clientSecret);
306
+ } else if (clientId || clientSecret) {
307
+ headers.authorization = `Basic ${Buffer.from(`${clientId}:${clientSecret}`, "utf8").toString("base64")}`;
308
+ }
309
+ const res = await request(url, {
310
+ method: "POST",
311
+ headers,
312
+ body: body.toString(),
313
+ dispatcher: opts.dispatcher,
314
+ signal: opts.signal
315
+ });
316
+ const text = await res.body.text();
317
+ if (res.statusCode < 200 || res.statusCode >= 300) {
318
+ throw new Error(`Token request failed (${res.statusCode}): ${text.slice(0, 500)}`);
319
+ }
320
+ const ct = res.headers["content-type"];
321
+ return parseTokenBody(text, String(Array.isArray(ct) ? ct[0] : ct ?? ""));
322
+ }
323
+ async function fetchToken(cfg, resolve2, opts) {
324
+ const form = {};
325
+ if (cfg.grantType === "password") {
326
+ form.grant_type = "password";
327
+ form.username = resolve2(cfg.username);
328
+ form.password = resolve2(cfg.password);
329
+ } else if (cfg.grantType === "client_credentials") {
330
+ form.grant_type = "client_credentials";
331
+ } else {
332
+ throw new Error("The authorization-code grant needs the browser flow; use buildAuthorizationUrl + exchangeAuthorizationCode");
333
+ }
334
+ const scope = resolve2(cfg.scope).trim();
335
+ if (scope) form.scope = scope;
336
+ return requestToken(cfg, resolve2, form, opts);
337
+ }
338
+ var cache = /* @__PURE__ */ new Map();
339
+ var EXPIRY_MARGIN_MS = 3e4;
340
+ var DEFAULT_LIFETIME_S = 3600;
341
+ function cacheKey(cfg, resolve2) {
342
+ return [cfg.grantType, resolve2(cfg.accessTokenUrl), resolve2(cfg.clientId), resolve2(cfg.scope), resolve2(cfg.username), cfg.clientAuthentication].join("|");
343
+ }
344
+ function canFetchAutomatically(cfg, resolve2) {
345
+ if (cfg.grantType !== "client_credentials" && cfg.grantType !== "password") return false;
346
+ if (!resolve2(cfg.accessTokenUrl).trim() || !resolve2(cfg.clientId).trim()) return false;
347
+ return cfg.grantType !== "password" || !!resolve2(cfg.username).trim();
348
+ }
349
+ async function getCachedToken(cfg, resolve2, opts) {
350
+ const key = cacheKey(cfg, resolve2);
351
+ const hit = cache.get(key);
352
+ if (hit && hit.expiresAt - EXPIRY_MARGIN_MS > Date.now()) return hit.token;
353
+ const token = await fetchToken(cfg, resolve2, opts);
354
+ cache.set(key, { token, expiresAt: Date.now() + (token.expiresIn ?? DEFAULT_LIFETIME_S) * 1e3 });
355
+ return token;
356
+ }
357
+
358
+ // core/src/dynamicVariables.ts
359
+ var rnd = (maxExclusive) => Math.floor(Math.random() * maxExclusive);
360
+ var between = (min, maxExclusive) => min + rnd(maxExclusive - min);
361
+ var pick = (pool) => pool[rnd(pool.length)];
362
+ var randomString = (alphabet, length) => Array.from({ length }, () => alphabet[rnd(alphabet.length)]).join("");
363
+ var capitalize = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
364
+ var times = (n, fn) => Array.from({ length: n }, fn);
365
+ function uuid() {
366
+ const c = globalThis.crypto;
367
+ if (c?.randomUUID) return c.randomUUID();
368
+ const hex = randomString("0123456789abcdef", 32).split("");
369
+ hex[12] = "4";
370
+ hex[16] = "89ab"[rnd(4)];
371
+ const h = hex.join("");
372
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
373
+ }
374
+ function jsDate(d) {
375
+ return d.toString().replace(/\s*\(.*\)\s*$/, "");
376
+ }
377
+ var ALPHA_NUMERIC = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
378
+ var ALPHA_NUMERIC_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
379
+ var UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
380
+ var COLORS = ["red", "green", "blue", "yellow", "purple", "fuchsia", "grey", "white", "black", "orange", "cyan", "lime"];
381
+ var ABBREVIATIONS = ["SQL", "PCI", "JSON", "HTTP", "TCP", "SSL", "API", "XML", "HDD", "SSD", "RAM", "USB"];
382
+ var LOCALES = ["ny", "sr", "si", "en", "fr", "de", "es", "it", "pt", "ja", "zh", "ru"];
383
+ var USER_AGENTS = [
384
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36",
385
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
386
+ "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0",
387
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
388
+ ];
389
+ var FIRST_NAMES = ["Ethan", "Chandler", "Megane", "Connie", "Sylvan", "Jonathon", "Pablo", "Ruthe", "Iva", "Talon", "Quinten", "Casey", "Alia", "Lottie", "Jarrell", "Gwen", "Jaden", "Donnell"];
390
+ var LAST_NAMES = ["Schaden", "Schneider", "Willms", "Runolfsdottir", "Fay", "Kunze", "Gutkowski", "Smitham", "Kovacek", "Kerluke", "Johns", "Grady", "Armando", "Trevor", "Gracie"];
391
+ var JOB_AREAS = ["Mobility", "Intranet", "Configuration", "Operations", "Marketing", "Optimization", "Accountability", "Functionality", "Identity"];
392
+ var JOB_DESCRIPTORS = ["Forward", "Corporate", "Senior", "International", "Lead", "Global", "Dynamic", "Regional", "Principal", "Chief"];
393
+ var JOB_TYPES = ["Supervisor", "Manager", "Coordinator", "Director", "Engineer", "Specialist", "Liaison", "Analyst", "Administrator", "Officer"];
394
+ var CITIES = ["Spinkahaven", "Korbinburgh", "Lefflerport", "New Audreanne", "Port Domenica", "Lake Gabe", "East Citlalli", "South Anibal"];
395
+ var COUNTRIES = ["Lao People's Democratic Republic", "Kazakhstan", "Austria", "Cape Verde", "Moldova", "Chad", "Norway", "Brazil", "Japan", "Kenya"];
396
+ var COUNTRY_CODES = ["CV", "MD", "TD", "US", "GB", "DE", "FR", "JP", "BR", "IN", "KE", "AU"];
397
+ var CURRENCY_CODES = ["CDF", "ZMK", "GNF", "USD", "EUR", "GBP", "JPY", "INR", "BRL", "AUD"];
398
+ var CURRENCY_NAMES = ["CFP Franc", "Cordoba Oro", "Pound Sterling", "US Dollar", "Euro", "Japanese Yen", "Indian Rupee", "Brazilian Real"];
399
+ var BS_ADJECTIVES = ["viral", "24/7", "24/365", "back-end", "next-generation", "value-added", "frictionless", "robust"];
400
+ var BS_BUZZ = ["repurpose", "harness", "transition", "leverage", "synergize", "incentivize", "morph", "engage"];
401
+ var BS_NOUNS = ["e-services", "markets", "interfaces", "schemas", "channels", "paradigms", "deliverables", "bandwidth"];
402
+ var CATCH_ADJECTIVES = ["Future-proofed", "Self-enabling", "Business-focused", "Down-sized", "Streamlined", "Synergistic", "Open-architected"];
403
+ var CATCH_DESCRIPTORS = ["heuristic", "bandwidth-monitored", "needs-based", "homogeneous", "client-server", "actuating", "executive"];
404
+ var CATCH_NOUNS = ["open architecture", "secured line", "superstructure", "installation", "infrastructure", "framework", "capability"];
405
+ var NOUNS = ["matrix", "bus", "bandwidth", "circuit", "transmitter", "protocol", "interface", "capacitor", "monitor", "firewall"];
406
+ var VERBS = ["parse", "quantify", "navigate", "synthesize", "calculate", "transmit", "program", "index", "override", "compress"];
407
+ var ING_VERBS = ["synthesizing", "navigating", "backing up", "parsing", "quantifying", "indexing", "transmitting", "compressing"];
408
+ var ADJECTIVES = ["auxiliary", "multi-byte", "back-end", "primary", "open-source", "virtual", "digital", "redundant", "solid state", "cross-platform"];
409
+ var WORD_POOL = ["withdrawal", "infrastructures", "circuit", "bandwidth", "matrix", "protocol", "synergy", "capacitor", "interface", "array"];
410
+ var LOREM_WORDS = [
411
+ "lorem",
412
+ "ipsum",
413
+ "dolor",
414
+ "sit",
415
+ "amet",
416
+ "consectetur",
417
+ "adipiscing",
418
+ "elit",
419
+ "sed",
420
+ "do",
421
+ "eiusmod",
422
+ "tempor",
423
+ "incididunt",
424
+ "labore",
425
+ "magna",
426
+ "aliqua",
427
+ "enim",
428
+ "minim",
429
+ "veniam",
430
+ "quis",
431
+ "nostrud",
432
+ "exercitation",
433
+ "ullamco",
434
+ "laboris",
435
+ "aliquip",
436
+ "commodo",
437
+ "consequat",
438
+ "duis",
439
+ "aute",
440
+ "irure",
441
+ "voluptate",
442
+ "velit"
443
+ ];
444
+ var loremWords = (n) => times(n, () => pick(LOREM_WORDS)).join(" ");
445
+ var loremSentence = () => capitalize(loremWords(between(4, 10))) + ".";
446
+ var loremParagraph = () => times(between(3, 7), loremSentence).join(" ");
447
+ var hex2 = (n) => n.toString(16).padStart(2, "0");
448
+ var img = (topic) => `https://loremflickr.com/640/480${topic ? "/" + topic : ""}`;
449
+ var DEFINITIONS = [
450
+ // Common
451
+ ["$guid", "A uuid-v4 style guid", uuid],
452
+ ["$timestamp", "The current UNIX timestamp in seconds", () => String(Math.floor(Date.now() / 1e3))],
453
+ ["$isoTimestamp", "The current ISO timestamp at zero UTC", () => (/* @__PURE__ */ new Date()).toISOString()],
454
+ ["$randomUUID", "A random 36-character UUID", uuid],
455
+ // Text, numbers, and colors
456
+ ["$randomAlphaNumeric", "A random alpha-numeric character", () => ALPHA_NUMERIC[rnd(ALPHA_NUMERIC.length)]],
457
+ ["$randomBoolean", "A random boolean value", () => String(rnd(2) === 0)],
458
+ ["$randomInt", "A random integer between 0 and 1000", () => String(rnd(1001))],
459
+ ["$randomColor", "A random color", () => pick(COLORS)],
460
+ ["$randomHexColor", "A random hex value", () => "#" + rnd(16777216).toString(16).padStart(6, "0")],
461
+ ["$randomAbbreviation", "A random abbreviation", () => pick(ABBREVIATIONS)],
462
+ // Internet and IP addresses
463
+ ["$randomIP", "A random IPv4 address", () => `${between(1, 256)}.${rnd(256)}.${rnd(256)}.${between(1, 256)}`],
464
+ ["$randomIPV6", "A random IPv6 address", () => times(8, () => rnd(65536).toString(16).padStart(4, "0")).join(":")],
465
+ ["$randomMACAddress", "A random MAC address", () => times(6, () => hex2(rnd(256))).join(":")],
466
+ ["$randomPassword", "A random 15-character alpha-numeric password", () => randomString(ALPHA_NUMERIC, 15)],
467
+ ["$randomLocale", "A random two-letter language code (ISO 639-1)", () => pick(LOCALES)],
468
+ ["$randomUserAgent", "A random user agent", () => pick(USER_AGENTS)],
469
+ ["$randomProtocol", "A random internet protocol", () => pick(["http", "https"])],
470
+ ["$randomSemver", "A random semantic version number", () => `${rnd(10)}.${rnd(20)}.${rnd(20)}`],
471
+ // Names
472
+ ["$randomFirstName", "A random first name", () => pick(FIRST_NAMES)],
473
+ ["$randomLastName", "A random last name", () => pick(LAST_NAMES)],
474
+ ["$randomFullName", "A random first and last name", () => `${pick(FIRST_NAMES)} ${pick(LAST_NAMES)}`],
475
+ ["$randomNamePrefix", "A random name prefix", () => pick(["Dr.", "Ms.", "Mr.", "Mrs.", "Miss", "Mx."])],
476
+ ["$randomNameSuffix", "A random name suffix", () => pick(["I", "II", "III", "IV", "Jr.", "Sr.", "MD", "DDS", "PhD", "DVM"])],
477
+ // Profession
478
+ ["$randomJobArea", "A random job area", () => pick(JOB_AREAS)],
479
+ ["$randomJobDescriptor", "A random job descriptor", () => pick(JOB_DESCRIPTORS)],
480
+ ["$randomJobTitle", "A random job title", () => `${pick(JOB_DESCRIPTORS)} ${pick(JOB_AREAS)} ${pick(JOB_TYPES)}`],
481
+ ["$randomJobType", "A random job type", () => pick(JOB_TYPES)],
482
+ // Phone, address, and location
483
+ ["$randomPhoneNumber", "A random ten-digit phone number", () => `${between(200, 999)}-${between(100, 999)}-${between(1e3, 9999)}`],
484
+ ["$randomPhoneNumberExt", "A random phone number with extension (12 digits)", () => `${between(10, 99)}-${between(100, 999)}-${between(100, 999)}-${between(1e3, 9999)}`],
485
+ ["$randomCity", "A random city name", () => pick(CITIES)],
486
+ ["$randomStreetName", "A random street name", () => `${pick(LAST_NAMES)} ${pick(["Street", "Island", "Springs", "Orchard", "Avenue", "Lane"])}`],
487
+ ["$randomStreetAddress", "A random street address", () => `${between(100, 99999)} ${pick(LAST_NAMES)} ${pick(["Streets", "Orchard", "Avenue", "Lane", "Way"])}`],
488
+ ["$randomCountry", "A random country", () => pick(COUNTRIES)],
489
+ ["$randomCountryCode", "A random two-letter country code (ISO 3166-1 alpha-2)", () => pick(COUNTRY_CODES)],
490
+ ["$randomLatitude", "A random latitude coordinate", () => (Math.random() * 180 - 90).toFixed(4)],
491
+ ["$randomLongitude", "A random longitude coordinate", () => (Math.random() * 360 - 180).toFixed(4)],
492
+ // Images
493
+ ["$randomAvatarImage", "A random avatar image", () => `https://avatars.githubusercontent.com/u/${rnd(1e5)}`],
494
+ ["$randomImageUrl", "A URL of a random image", () => img()],
495
+ ["$randomAbstractImage", "A URL of a random abstract image", () => img("abstract")],
496
+ ["$randomAnimalsImage", "A URL of a random animal image", () => img("animals")],
497
+ ["$randomBusinessImage", "A URL of a random stock business image", () => img("business")],
498
+ ["$randomCatsImage", "A URL of a random cat image", () => img("cats")],
499
+ ["$randomCityImage", "A URL of a random city image", () => img("city")],
500
+ ["$randomFoodImage", "A URL of a random food image", () => img("food")],
501
+ ["$randomNightlifeImage", "A URL of a random nightlife image", () => img("nightlife")],
502
+ ["$randomFashionImage", "A URL of a random fashion image", () => img("fashion")],
503
+ ["$randomPeopleImage", "A URL of a random image of a person", () => img("people")],
504
+ ["$randomNatureImage", "A URL of a random nature image", () => img("nature")],
505
+ ["$randomSportsImage", "A URL of a random sports image", () => img("sports")],
506
+ ["$randomTransportImage", "A URL of a random transportation image", () => img("transport")],
507
+ ["$randomImageDataUri", "A random image data URI", () => "data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%2F%3E"],
508
+ // Finance
509
+ ["$randomBankAccount", "A random 8-digit bank account number", () => String(between(1e7, 99999999))],
510
+ ["$randomBankAccountName", "A random bank account name", () => pick(["Home Loan Account", "Checking Account", "Savings Account", "Auto Loan Account", "Personal Loan Account", "Credit Card Account"])],
511
+ ["$randomCreditCardMask", "A random masked credit card number", () => String(between(1e3, 9999))],
512
+ ["$randomBankAccountBic", "A random BIC (Bank Identifier Code)", () => randomString(UPPER, 4) + randomString(UPPER, 2) + randomString(ALPHA_NUMERIC_UPPER, 2)],
513
+ ["$randomBankAccountIban", "A random 15-31 character IBAN", () => randomString(UPPER, 2) + between(10, 99) + randomString(ALPHA_NUMERIC_UPPER, between(11, 27))],
514
+ ["$randomTransactionType", "A random transaction type", () => pick(["invoice", "payment", "deposit", "withdrawal"])],
515
+ ["$randomCurrencyCode", "A random 3-letter currency code (ISO-4217)", () => pick(CURRENCY_CODES)],
516
+ ["$randomCurrencyName", "A random currency name", () => pick(CURRENCY_NAMES)],
517
+ ["$randomCurrencySymbol", "A random currency symbol", () => pick(["$", "\xA3", "\u20AC", "\xA5", "\u20B9", "\u20A9"])],
518
+ ["$randomBitcoin", "A random bitcoin address", () => pick(["1", "3", "bc1"]) + randomString(ALPHA_NUMERIC, between(25, 34))],
519
+ // Business
520
+ ["$randomCompanyName", "A random company name", () => `${pick(LAST_NAMES)} ${pick(["- " + pick(LAST_NAMES), "LLC", "Inc", "Group", "and Sons"])}`],
521
+ ["$randomCompanySuffix", "A random company suffix", () => pick(["Inc", "LLC", "Group", "and Sons", "Ltd"])],
522
+ ["$randomBs", "A random phrase of business-speak", () => `${pick(BS_ADJECTIVES)} ${pick(BS_BUZZ)} ${pick(BS_NOUNS)}`],
523
+ ["$randomBsAdjective", "A random business-speak adjective", () => pick(BS_ADJECTIVES)],
524
+ ["$randomBsBuzz", "A random business-speak buzzword", () => pick(BS_BUZZ)],
525
+ ["$randomBsNoun", "A random business-speak noun", () => pick(BS_NOUNS)],
526
+ // Catchphrases
527
+ ["$randomCatchPhrase", "A random catchphrase", () => `${pick(CATCH_ADJECTIVES)} ${pick(CATCH_DESCRIPTORS)} ${pick(CATCH_NOUNS)}`],
528
+ ["$randomCatchPhraseAdjective", "A random catchphrase adjective", () => pick(CATCH_ADJECTIVES)],
529
+ ["$randomCatchPhraseDescriptor", "A random catchphrase descriptor", () => pick(CATCH_DESCRIPTORS)],
530
+ ["$randomCatchPhraseNoun", "A random catchphrase noun", () => pick(CATCH_NOUNS)],
531
+ // Databases
532
+ ["$randomDatabaseColumn", "A random database column name", () => pick(["id", "title", "name", "email", "password", "group", "avatar", "status", "createdAt", "updatedAt", "token"])],
533
+ ["$randomDatabaseType", "A random database type", () => pick(["tinyint", "smallint", "mediumint", "int", "bigint", "varchar", "text", "blob", "timestamp", "datetime", "decimal"])],
534
+ ["$randomDatabaseCollation", "A random database collation", () => pick(["utf8_general_ci", "utf8_unicode_ci", "cp1250_bin", "cp1250_general_ci", "latin1_swedish_ci"])],
535
+ ["$randomDatabaseEngine", "A random database engine", () => pick(["InnoDB", "MyISAM", "Memory", "CSV", "Archive", "Federated"])],
536
+ // Dates
537
+ ["$randomDateFuture", "A random future datetime", () => jsDate(new Date(Date.now() + between(1, 365) * 864e5))],
538
+ ["$randomDatePast", "A random past datetime", () => jsDate(new Date(Date.now() - between(1, 365) * 864e5))],
539
+ ["$randomDateRecent", "A random recent datetime", () => jsDate(new Date(Date.now() - between(1, 10080) * 6e4))],
540
+ ["$randomWeekday", "A random weekday", () => pick(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])],
541
+ ["$randomMonth", "A random month", () => pick(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"])],
542
+ // Domains, emails, and usernames
543
+ ["$randomDomainName", "A random domain name", () => `${pick(FIRST_NAMES).toLowerCase()}.${pick(["biz", "info", "name", "com", "net"])}`],
544
+ ["$randomDomainSuffix", "A random domain suffix", () => pick(["com", "net", "org", "biz", "info", "name", "io"])],
545
+ ["$randomDomainWord", "A random unqualified domain name", () => pick(FIRST_NAMES).toLowerCase()],
546
+ ["$randomEmail", "A random email address", () => `${pick(FIRST_NAMES)}${rnd(100)}@${pick(["gmail.com", "hotmail.com", "yahoo.com"])}`],
547
+ ["$randomExampleEmail", "A random email address from an example domain", () => `${pick(FIRST_NAMES)}${rnd(100)}@${pick(["example.com", "example.net", "example.org"])}`],
548
+ ["$randomUserName", "A random username", () => `${pick(FIRST_NAMES)}${pick([".", "_", ""])}${pick(LAST_NAMES)}${rnd(100)}`],
549
+ ["$randomUrl", "A random URL", () => `${pick(["https://", "http://"])}${pick(FIRST_NAMES).toLowerCase()}.${pick(["net", "com", "name", "info"])}`],
550
+ // Files and directories
551
+ ["$randomFileName", "A random file name", () => `${pick(LOREM_WORDS)}_${pick(LOREM_WORDS)}.${pick(["gdoc", "gif", "mp4v", "key", "sql", "txt"])}`],
552
+ ["$randomFileType", "A random file type", () => pick(["application", "audio", "image", "message", "model", "multipart", "text", "video"])],
553
+ ["$randomFileExt", "A random file extension", () => pick(["war", "book", "fsc", "gif", "key", "html", "mp3", "war", "wav"])],
554
+ ["$randomCommonFileName", "A random common file name", () => `${pick(LOREM_WORDS)}.${pick(["mp4", "wav", "png", "jpg", "csv", "doc"])}`],
555
+ ["$randomCommonFileType", "A random common file type", () => pick(["application", "audio", "image", "text", "video"])],
556
+ ["$randomCommonFileExt", "A random common file extension", () => pick(["m2v", "wav", "png", "jpeg", "pdf", "doc", "csv"])],
557
+ ["$randomFilePath", "A random file path", () => `/home/${pick(LOREM_WORDS)}_${pick(LOREM_WORDS)}.${pick(["cpio", "txt", "png", "log"])}`],
558
+ ["$randomDirectoryPath", "A random directory path", () => pick(["/usr/bin", "/root", "/usr/local/bin", "/opt", "/var/log", "/home", "/etc"])],
559
+ ["$randomMimeType", "A random MIME type", () => pick(["application/json", "text/plain", "image/png", "audio/mpeg", "video/mp4", "application/xml"])],
560
+ // Stores
561
+ ["$randomPrice", "A random price between 0.00 and 1000.00", () => (Math.random() * 1e3).toFixed(2)],
562
+ ["$randomProduct", "A random product", () => pick(["Towels", "Pizza", "Pants", "Cheese", "Chair", "Computer", "Shoes", "Gloves"])],
563
+ ["$randomProductAdjective", "A random product adjective", () => pick(["Unbranded", "Incredible", "Tasty", "Handcrafted", "Refined", "Generic", "Rustic", "Ergonomic"])],
564
+ ["$randomProductMaterial", "A random product material", () => pick(["Steel", "Plastic", "Frozen", "Concrete", "Cotton", "Granite", "Rubber", "Wooden"])],
565
+ ["$randomProductName", "A random product name", () => `${pick(["Handmade", "Refined", "Ergonomic", "Rustic"])} ${pick(["Concrete", "Steel", "Rubber", "Cotton"])} ${pick(["Tuna", "Hat", "Chair", "Shoes"])}`],
566
+ ["$randomDepartment", "A random commerce category", () => pick(["Tools", "Movies", "Electronics", "Books", "Garden", "Toys", "Health", "Music"])],
567
+ // Grammar
568
+ ["$randomNoun", "A random noun", () => pick(NOUNS)],
569
+ ["$randomVerb", "A random verb", () => pick(VERBS)],
570
+ ["$randomIngverb", "A random verb ending in -ing", () => pick(ING_VERBS)],
571
+ ["$randomAdjective", "A random adjective", () => pick(ADJECTIVES)],
572
+ ["$randomWord", "A random word", () => pick(WORD_POOL)],
573
+ ["$randomWords", "Some random words", () => capitalize(times(between(2, 6), () => pick(WORD_POOL)).join(" "))],
574
+ ["$randomPhrase", "A random phrase", () => `${pick(["You can't", "Try to", "We need to", "I'll"])} ${pick(VERBS)} the ${pick(NOUNS)}!`],
575
+ // Lorem ipsum
576
+ ["$randomLoremWord", "A random word of lorem ipsum text", () => pick(LOREM_WORDS)],
577
+ ["$randomLoremWords", "Some random words of lorem ipsum text", () => loremWords(between(2, 5))],
578
+ ["$randomLoremSentence", "A random sentence of lorem ipsum text", loremSentence],
579
+ ["$randomLoremSentences", "A random 2 to 6 sentences of lorem ipsum text", () => times(between(2, 7), loremSentence).join(" ")],
580
+ ["$randomLoremParagraph", "A random paragraph of lorem ipsum text", loremParagraph],
581
+ ["$randomLoremParagraphs", "3 random paragraphs of lorem ipsum text", () => times(3, loremParagraph).join("\n")],
582
+ ["$randomLoremText", "A random amount of lorem ipsum text", () => times(between(3, 8), loremSentence).join(" ")],
583
+ ["$randomLoremSlug", "A random lorem ipsum URL slug", () => times(3, () => pick(LOREM_WORDS)).join("-")],
584
+ ["$randomLoremLines", "1 to 5 random lines of lorem ipsum", () => times(between(1, 6), loremSentence).join("\n")]
585
+ ];
586
+ var GENERATORS = new Map(DEFINITIONS.map(([name, , gen]) => [name.toLowerCase(), gen]));
587
+ function example(gen) {
588
+ try {
589
+ const s = gen();
590
+ return s.length > 48 ? s.slice(0, 48) + "..." : s;
591
+ } catch {
592
+ return "";
593
+ }
594
+ }
595
+ var DYNAMIC_VARIABLES = DEFINITIONS.map(([name, description, gen]) => ({
596
+ name,
597
+ description,
598
+ example: example(gen)
599
+ }));
600
+ var CATALOG = new Map(DYNAMIC_VARIABLES.map((d) => [d.name.toLowerCase(), d]));
601
+ function tryGenerate(name) {
602
+ if (!name) return void 0;
603
+ const gen = GENERATORS.get(name.trim().toLowerCase());
604
+ if (!gen) return void 0;
605
+ try {
606
+ return gen();
607
+ } catch {
608
+ return void 0;
609
+ }
610
+ }
611
+
612
+ // core/src/variableResolver.ts
613
+ var PLACEHOLDER = /\{\{\s*([^{}\s]+)\s*\}\}/g;
614
+ function layerFromVariables(scope, vars) {
615
+ if (!vars?.length) return void 0;
616
+ const values = /* @__PURE__ */ new Map();
617
+ for (const v of vars) {
618
+ if (v && v.enabled !== false && v.key) values.set(v.key.toLowerCase(), v.value ?? "");
619
+ }
620
+ return { scope, values };
621
+ }
622
+ function layers(ctx) {
623
+ const data = ctx.iterationData ? {
624
+ scope: "data",
625
+ values: new Map(Object.entries(ctx.iterationData).map(([k, v]) => [k.toLowerCase(), v == null ? "" : String(v)]))
626
+ } : void 0;
627
+ return [
628
+ layerFromVariables("request", ctx.request),
629
+ data,
630
+ layerFromVariables("collection", ctx.collection),
631
+ layerFromVariables("environment", ctx.environment),
632
+ layerFromVariables("globals", ctx.globals)
633
+ ].filter((l) => !!l);
634
+ }
635
+ function lookupIn(ls, name) {
636
+ const key = name.toLowerCase();
637
+ for (const l of ls) {
638
+ const v = l.values.get(key);
639
+ if (v !== void 0) return { value: v, scope: l.scope };
640
+ }
641
+ return void 0;
642
+ }
643
+ function lookupVariable(name, ctx) {
644
+ const found = lookupIn(layers(ctx), name.trim());
645
+ if (found) return found;
646
+ const generated = tryGenerate(name);
647
+ return generated === void 0 ? void 0 : { value: generated, scope: "dynamic" };
648
+ }
649
+ function mergedVariables(ctx) {
650
+ const out = {};
651
+ const seen = /* @__PURE__ */ new Set();
652
+ const add = (k, v) => {
653
+ const lk = k.toLowerCase();
654
+ if (seen.has(lk)) return;
655
+ seen.add(lk);
656
+ out[k] = v;
657
+ };
658
+ const addVars = (vars) => {
659
+ if (!vars) return;
660
+ for (let i = vars.length - 1; i >= 0; i--) {
661
+ const v = vars[i];
662
+ if (v && v.enabled !== false && v.key) add(v.key, v.value ?? "");
663
+ }
664
+ };
665
+ addVars(ctx.request);
666
+ if (ctx.iterationData) for (const [k, v] of Object.entries(ctx.iterationData)) add(k, v == null ? "" : String(v));
667
+ addVars(ctx.collection);
668
+ addVars(ctx.environment);
669
+ addVars(ctx.globals);
670
+ return out;
671
+ }
672
+ function resolveVariables(text, ctx) {
673
+ if (!text) return text ?? "";
674
+ if (!text.includes("{{")) return text;
675
+ const ls = layers(ctx);
676
+ return text.replace(PLACEHOLDER, (match, name) => {
677
+ const found = lookupIn(ls, name);
678
+ if (found) return found.value;
679
+ return tryGenerate(name) ?? match;
680
+ });
681
+ }
682
+
683
+ // core/src/requestBuilder.ts
684
+ var DEFAULT_CONTENT_TYPES = {
685
+ raw: "text/plain",
686
+ text: "text/plain",
687
+ json: "application/json",
688
+ xml: "application/xml",
689
+ html: "text/html",
690
+ graphql: "application/json"
691
+ };
692
+ function effectiveAuth(request3, collection) {
693
+ if (request3.auth?.type === "inherit") {
694
+ return collection?.auth && collection.auth.type !== "inherit" ? collection.auth : { ...request3.auth, type: "none" };
695
+ }
696
+ return request3.auth;
697
+ }
698
+ function encodeQueryComponent(s) {
699
+ return encodeURIComponent(s);
700
+ }
701
+ function appendQuery(url, pairs) {
702
+ if (!pairs.length) return url;
703
+ const hashAt = url.indexOf("#");
704
+ const base = hashAt >= 0 ? url.slice(0, hashAt) : url;
705
+ const hash = hashAt >= 0 ? url.slice(hashAt) : "";
706
+ const query = pairs.map((p) => `${encodeQueryComponent(p.key)}=${encodeQueryComponent(p.value)}`).join("&");
707
+ const sep = base.includes("?") ? base.endsWith("?") || base.endsWith("&") ? "" : "&" : "?";
708
+ return base + sep + query + hash;
709
+ }
710
+ function utf8ToBase64(s) {
711
+ const bytes = new TextEncoder().encode(s);
712
+ let bin = "";
713
+ for (const b of bytes) bin += String.fromCharCode(b);
714
+ return btoa(bin);
715
+ }
716
+ function applyPathVariables(url, vars) {
717
+ if (!vars.length) return url;
718
+ const schemeEnd = url.indexOf("://");
719
+ const pathStart = url.indexOf("/", schemeEnd >= 0 ? schemeEnd + 3 : 0);
720
+ if (pathStart < 0) return url;
721
+ const qAt = url.search(/[?#]/);
722
+ const pathEnd = qAt >= 0 && qAt > pathStart ? qAt : url.length;
723
+ let path4 = url.slice(pathStart, pathEnd);
724
+ for (const v of vars) {
725
+ if (!v.key) continue;
726
+ const key = v.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
727
+ path4 = path4.replace(new RegExp(`(?<=/):${key}(?=[/.;]|$)`, "g"), () => v.value);
728
+ }
729
+ return url.slice(0, pathStart) + path4 + url.slice(pathEnd);
730
+ }
731
+ function isFileField(f) {
732
+ return !!f.isFile || f.value.startsWith("@") || (f.description ?? "").trim().toLowerCase() === "file";
733
+ }
734
+ function buildRequest(request3, opts) {
735
+ const r = (s) => resolveVariables(s ?? "", opts.ctx);
736
+ const method = (request3.method || "GET").trim().toUpperCase();
737
+ let url = r(request3.url).trim();
738
+ url = applyPathVariables(
739
+ url,
740
+ (request3.pathVariables ?? []).map((p) => ({ key: p.key, value: r(p.value) }))
741
+ );
742
+ const query = (request3.queryParams ?? []).filter((p) => p.enabled !== false && p.key?.trim()).map((p) => ({ key: r(p.key), value: r(p.value) }));
743
+ url = appendQuery(url, query);
744
+ const headers = [{ key: "User-Agent", value: YAMLET_USER_AGENT, enabled: true }];
745
+ for (const h of request3.headers ?? []) {
746
+ if (h.enabled === false || !h.key?.trim()) continue;
747
+ const key = r(h.key).trim();
748
+ if (key.toLowerCase() === "user-agent") continue;
749
+ headers.push({ key, value: r(h.value), enabled: true });
750
+ }
751
+ const hasHeader = (name) => headers.some((h) => h.key.toLowerCase() === name.toLowerCase());
752
+ const auth = effectiveAuth(request3, opts.collection);
753
+ switch (auth.type) {
754
+ case "bearer": {
755
+ const token = r(auth.token);
756
+ if (token.trim()) headers.push({ key: "Authorization", value: `Bearer ${token}`, enabled: true });
757
+ break;
758
+ }
759
+ case "basic": {
760
+ const encoded = utf8ToBase64(`${r(auth.username)}:${r(auth.password)}`);
761
+ headers.push({ key: "Authorization", value: `Basic ${encoded}`, enabled: true });
762
+ break;
763
+ }
764
+ case "apikey": {
765
+ const name = r(auth.apiKeyName).trim();
766
+ if (!name) break;
767
+ const value = r(auth.apiKeyValue);
768
+ if (auth.apiKeyIn === "query") url = appendQuery(url, [{ key: name, value }]);
769
+ else headers.push({ key: name, value, enabled: true });
770
+ break;
771
+ }
772
+ case "cookie": {
773
+ const cookie = r(auth.cookie);
774
+ if (cookie.trim()) headers.push({ key: "Cookie", value: cookie, enabled: true });
775
+ break;
776
+ }
777
+ case "oauth2": {
778
+ const o = auth.oauth2;
779
+ const token = opts.oauthToken?.trim() ? opts.oauthToken : r(o.accessToken);
780
+ if (!token?.trim()) break;
781
+ if (o.addTokenTo === "query") url = appendQuery(url, [{ key: "access_token", value: token }]);
782
+ else {
783
+ const prefix = o.headerPrefix?.trim() ? r(o.headerPrefix).trim() : "Bearer";
784
+ headers.push({ key: "Authorization", value: prefix ? `${prefix} ${token}` : token, enabled: true });
785
+ }
786
+ break;
787
+ }
788
+ default:
789
+ break;
790
+ }
791
+ const b = request3.body;
792
+ let body = { kind: "none" };
793
+ switch (b?.type) {
794
+ case "raw":
795
+ case "json":
796
+ case "xml":
797
+ case "text":
798
+ case "html": {
799
+ if (b.raw) body = { kind: "text", text: r(b.raw), contentType: DEFAULT_CONTENT_TYPES[b.type] };
800
+ break;
801
+ }
802
+ case "graphql": {
803
+ const query2 = r(b.graphqlQuery);
804
+ const varsText = r(b.graphqlVariables).trim();
805
+ let variables = void 0;
806
+ if (varsText) {
807
+ try {
808
+ variables = JSON.parse(varsText);
809
+ } catch {
810
+ variables = void 0;
811
+ }
812
+ }
813
+ const payload = { query: query2 };
814
+ if (variables !== void 0) payload.variables = variables;
815
+ body = { kind: "text", text: JSON.stringify(payload), contentType: "application/json" };
816
+ break;
817
+ }
818
+ case "urlencoded": {
819
+ body = {
820
+ kind: "urlencoded",
821
+ fields: (b.fields ?? []).filter((f) => f.enabled !== false && f.key?.trim()).map((f) => ({ key: r(f.key), value: r(f.value) }))
822
+ };
823
+ break;
824
+ }
825
+ case "form-data": {
826
+ body = {
827
+ kind: "form-data",
828
+ fields: (b.fields ?? []).filter((f) => f.enabled !== false && f.key?.trim()).map((f) => {
829
+ const value = r(f.value);
830
+ const file = isFileField({ ...f, value });
831
+ return { key: r(f.key), value: file && value.startsWith("@") ? value.slice(1) : value, isFile: file };
832
+ })
833
+ };
834
+ break;
835
+ }
836
+ case "binary": {
837
+ const file = r(b.binaryFile).trim();
838
+ if (file) body = { kind: "binary", file: file.startsWith("@") ? file.slice(1) : file, contentType: "application/octet-stream" };
839
+ break;
840
+ }
841
+ default:
842
+ break;
843
+ }
844
+ if (!hasHeader("content-type")) {
845
+ if ((body.kind === "text" || body.kind === "binary") && body.contentType) {
846
+ headers.push({ key: "Content-Type", value: body.contentType, enabled: true });
847
+ } else if (body.kind === "urlencoded") {
848
+ headers.push({ key: "Content-Type", value: "application/x-www-form-urlencoded", enabled: true });
849
+ }
850
+ } else if (body.kind === "text" || body.kind === "binary") {
851
+ body = { ...body, contentType: headers.find((h) => h.key.toLowerCase() === "content-type").value };
852
+ }
853
+ return { method, url, headers, body };
854
+ }
855
+ function encodeUrlEncodedBody(fields) {
856
+ return new URLSearchParams(fields.map((f) => [f.key, f.value])).toString();
857
+ }
858
+
859
+ // core/src/scriptRunner.ts
860
+ import { randomUUID } from "node:crypto";
861
+ import { inspect } from "node:util";
862
+ import vm from "node:vm";
863
+ import * as chai from "chai";
864
+ var DEFAULT_SCRIPT_TIMEOUT_MS = 5e3;
865
+ var ScriptVariables = class {
866
+ request;
867
+ iterationData;
868
+ collection;
869
+ environment;
870
+ globals;
871
+ /** pm.variables.set values: highest precedence, never persisted. */
872
+ local = /* @__PURE__ */ new Map();
873
+ dirty = /* @__PURE__ */ new Set();
874
+ constructor(init = {}) {
875
+ this.request = init.request ?? [];
876
+ this.iterationData = init.iterationData;
877
+ this.collection = init.collection;
878
+ this.environment = init.environment;
879
+ this.globals = init.globals;
880
+ }
881
+ toContext() {
882
+ return {
883
+ request: [...this.request, ...this.local.values()],
884
+ iterationData: this.iterationData,
885
+ collection: this.collection,
886
+ environment: this.environment,
887
+ globals: this.globals
888
+ };
889
+ }
890
+ list(scope) {
891
+ return scope === "environment" ? this.environment : scope === "collection" ? this.collection : scope === "globals" ? this.globals : void 0;
892
+ }
893
+ get(scope, key) {
894
+ if (scope === "local") {
895
+ const hit = lookupVariable(key, this.toContext());
896
+ return hit && hit.scope !== "dynamic" ? hit.value : void 0;
897
+ }
898
+ const list = this.list(scope);
899
+ const lk = key.toLowerCase();
900
+ if (!list) return this.local.get(lk)?.value;
901
+ for (let i = list.length - 1; i >= 0; i--) {
902
+ if (list[i].enabled !== false && list[i].key.toLowerCase() === lk) return list[i].value;
903
+ }
904
+ return void 0;
905
+ }
906
+ set(scope, key, value) {
907
+ if (!key?.trim()) return;
908
+ const list = this.list(scope);
909
+ if (scope === "local" || !list) {
910
+ this.local.set(key.toLowerCase(), { key, value, enabled: true });
911
+ this.dirty.add("local");
912
+ return;
913
+ }
914
+ const existing = list.find((v) => v.key.toLowerCase() === key.toLowerCase());
915
+ if (existing) {
916
+ existing.value = value;
917
+ existing.enabled = true;
918
+ } else list.push({ key, value, enabled: true });
919
+ this.dirty.add(scope);
920
+ }
921
+ unset(scope, key) {
922
+ const list = this.list(scope);
923
+ const lk = key.toLowerCase();
924
+ if (scope === "local" || !list) {
925
+ if (this.local.delete(lk)) this.dirty.add("local");
926
+ return;
927
+ }
928
+ const before = list.length;
929
+ for (let i = list.length - 1; i >= 0; i--) if (list[i].key.toLowerCase() === lk) list.splice(i, 1);
930
+ if (list.length !== before) this.dirty.add(scope);
931
+ }
932
+ clear(scope) {
933
+ const list = this.list(scope);
934
+ if (scope === "local" || !list) {
935
+ this.local.clear();
936
+ return;
937
+ }
938
+ if (list.length) {
939
+ list.splice(0, list.length);
940
+ this.dirty.add(scope);
941
+ }
942
+ }
943
+ toObject(scope) {
944
+ if (scope === "local") return mergedVariables(this.toContext());
945
+ const out = {};
946
+ for (const v of this.list(scope) ?? []) if (v.enabled !== false && v.key) out[v.key] = v.value;
947
+ return out;
948
+ }
949
+ /** Full new lists for each persisted scope a script changed. */
950
+ changes() {
951
+ const out = {};
952
+ if (this.dirty.has("environment") && this.environment) out.environment = structuredClone(this.environment);
953
+ if (this.dirty.has("collection") && this.collection) out.collectionVariables = structuredClone(this.collection);
954
+ if (this.dirty.has("globals") && this.globals) out.globals = structuredClone(this.globals);
955
+ return out;
956
+ }
957
+ };
958
+ var RESPONSE = /* @__PURE__ */ Symbol.for("yamlet.scriptResponse");
959
+ var pluginInstalled = false;
960
+ function installResponsePlugin() {
961
+ if (pluginInstalled) return;
962
+ pluginInstalled = true;
963
+ chai.use((c, utils) => {
964
+ const A = c.Assertion;
965
+ const resp = (a) => utils.flag(a, "object")?.[RESPONSE];
966
+ const need = (a) => {
967
+ const r = resp(a);
968
+ if (!r) throw new Error("expected a response object");
969
+ return r;
970
+ };
971
+ A.addMethod("status", function(expected) {
972
+ const r = need(this);
973
+ if (typeof expected === "number")
974
+ this.assert(r.statusCode === expected, "expected response to have status code #{exp} but got #{act}", "expected response to not have status code #{act}", expected, r.statusCode);
975
+ else
976
+ this.assert(r.reasonPhrase === expected, "expected response to have status reason #{exp} but got #{act}", "expected response to not have status reason #{act}", expected, r.reasonPhrase);
977
+ });
978
+ A.addMethod("header", function(name, value) {
979
+ const r = need(this);
980
+ const h = r.headers.find((x) => x.key.toLowerCase() === String(name).toLowerCase());
981
+ if (value === void 0) this.assert(!!h, `expected response to have header ${name}`, `expected response to not have header ${name}`, name, void 0);
982
+ else this.assert(h?.value === value, `expected response header ${name} to be #{exp} but got #{act}`, `expected response header ${name} to not be #{act}`, value, h?.value);
983
+ });
984
+ A.addMethod("body", function(expected) {
985
+ const r = need(this);
986
+ if (expected === void 0) this.assert(r.body.length > 0, "expected response to have a body", "expected response to not have a body", void 0, void 0);
987
+ else if (typeof expected === "string") this.assert(r.body === expected, "expected response body to equal #{exp}", "expected response body to not equal #{exp}", expected, r.body);
988
+ else {
989
+ let parsed;
990
+ try {
991
+ parsed = JSON.parse(r.body);
992
+ } catch {
993
+ parsed = void 0;
994
+ }
995
+ this.assert(utils.eql(parsed, expected), "expected response JSON body to deeply equal #{exp}", "expected response JSON body to not deeply equal #{exp}", expected, parsed);
996
+ }
997
+ });
998
+ A.addMethod("jsonBody", function(pathOrValue, value) {
999
+ const r = need(this);
1000
+ let parsed;
1001
+ let ok = true;
1002
+ try {
1003
+ parsed = JSON.parse(r.body);
1004
+ } catch {
1005
+ ok = false;
1006
+ }
1007
+ if (pathOrValue === void 0) {
1008
+ this.assert(ok, "expected response to have a JSON body", "expected response to not have a JSON body", void 0, void 0);
1009
+ return;
1010
+ }
1011
+ if (typeof pathOrValue === "string") {
1012
+ const got = pathOrValue.split(".").reduce((o, k) => o && typeof o === "object" ? o[k] : void 0, parsed);
1013
+ if (value === void 0) this.assert(got !== void 0, `expected response JSON to have path ${pathOrValue}`, `expected response JSON to not have path ${pathOrValue}`, void 0, void 0);
1014
+ else this.assert(utils.eql(got, value), `expected response JSON at ${pathOrValue} to deeply equal #{exp} but got #{act}`, `expected response JSON at ${pathOrValue} to not deeply equal #{exp}`, value, got);
1015
+ return;
1016
+ }
1017
+ this.assert(utils.eql(parsed, pathOrValue), "expected response JSON body to deeply equal #{exp}", "expected response JSON body to not deeply equal #{exp}", pathOrValue, parsed);
1018
+ });
1019
+ A.addMethod("responseTime", function() {
1020
+ need(this);
1021
+ });
1022
+ A.overwriteProperty(
1023
+ "ok",
1024
+ (_super) => function() {
1025
+ const r = resp(this);
1026
+ if (!r) return _super.call(this);
1027
+ this.assert(r.statusCode >= 200 && r.statusCode < 300, "expected response to be ok (2xx) but got #{act}", "expected response to not be ok but got #{act}", void 0, r.statusCode);
1028
+ }
1029
+ );
1030
+ const statusProps = [
1031
+ ["success", (s) => s >= 200 && s < 300, "successful (2xx)"],
1032
+ ["info", (s) => s >= 100 && s < 200, "informational (1xx)"],
1033
+ ["redirection", (s) => s >= 300 && s < 400, "a redirection (3xx)"],
1034
+ ["clientError", (s) => s >= 400 && s < 500, "a client error (4xx)"],
1035
+ ["serverError", (s) => s >= 500, "a server error (5xx)"],
1036
+ ["error", (s) => s >= 400, "an error (4xx/5xx)"],
1037
+ ["accepted", (s) => s === 202, "accepted (202)"],
1038
+ ["badRequest", (s) => s === 400, "a bad request (400)"],
1039
+ ["unauthorized", (s) => s === 401, "unauthorized (401)"],
1040
+ ["forbidden", (s) => s === 403, "forbidden (403)"],
1041
+ ["notFound", (s) => s === 404, "not found (404)"],
1042
+ ["rateLimited", (s) => s === 429, "rate limited (429)"]
1043
+ ];
1044
+ for (const [name, test, label] of statusProps) {
1045
+ A.addProperty(name, function() {
1046
+ const r = need(this);
1047
+ this.assert(test(r.statusCode), `expected response to be ${label} but got #{act}`, `expected response to not be ${label}`, void 0, r.statusCode);
1048
+ });
1049
+ }
1050
+ A.addProperty("json", function() {
1051
+ const r = need(this);
1052
+ let ok = true;
1053
+ try {
1054
+ JSON.parse(r.body);
1055
+ } catch {
1056
+ ok = false;
1057
+ }
1058
+ this.assert(ok, "expected response body to be valid JSON", "expected response body to not be JSON", void 0, void 0);
1059
+ });
1060
+ A.addProperty("withBody", function() {
1061
+ const r = need(this);
1062
+ this.assert(r.body.length > 0, "expected response to have a body", "expected response to not have a body", void 0, void 0);
1063
+ });
1064
+ });
1065
+ }
1066
+ var errMessage = (e) => e && typeof e === "object" && "message" in e ? String(e.message) : String(e);
1067
+ function decodeBody(r) {
1068
+ return r.bodyEncoding === "base64" ? Buffer.from(r.body, "base64").toString("utf8") : r.body;
1069
+ }
1070
+ function headerList(list) {
1071
+ const find = (name) => list.find((h) => h.key.toLowerCase() === String(name).toLowerCase());
1072
+ return {
1073
+ get: (name) => find(name)?.value,
1074
+ has: (name) => !!find(name),
1075
+ all: () => list.map((h) => ({ key: h.key, value: h.value })),
1076
+ toObject: () => Object.fromEntries(list.map((h) => [h.key, h.value])),
1077
+ each: (fn) => list.forEach((h) => fn({ key: h.key, value: h.value })),
1078
+ count: () => list.length
1079
+ };
1080
+ }
1081
+ function responseFacade(r) {
1082
+ const facade = {
1083
+ [RESPONSE]: r,
1084
+ code: r.statusCode,
1085
+ status: r.reasonPhrase,
1086
+ responseTime: r.durationMs,
1087
+ responseSize: r.sizeBytes,
1088
+ headers: headerList(r.headers),
1089
+ cookies: {
1090
+ get: (name) => r.cookies.find((c) => c.name === name)?.value,
1091
+ has: (name) => r.cookies.some((c) => c.name === name),
1092
+ toObject: () => Object.fromEntries(r.cookies.map((c) => [c.name, c.value])),
1093
+ all: () => r.cookies.map((c) => ({ ...c }))
1094
+ },
1095
+ text: () => decodeBody(r),
1096
+ json: () => JSON.parse(decodeBody(r) || "null"),
1097
+ size: () => ({ body: r.sizeBytes, header: 0, total: r.sizeBytes }),
1098
+ get to() {
1099
+ return chai.expect(facade).to;
1100
+ }
1101
+ };
1102
+ return facade;
1103
+ }
1104
+ function splitUrl(url) {
1105
+ const hashAt = url.indexOf("#");
1106
+ const noHash = hashAt >= 0 ? url.slice(0, hashAt) : url;
1107
+ const qAt = noHash.indexOf("?");
1108
+ if (qAt < 0) return { base: url, query: [] };
1109
+ const decode = (s) => {
1110
+ try {
1111
+ return decodeURIComponent(s.replace(/\+/g, " "));
1112
+ } catch {
1113
+ return s;
1114
+ }
1115
+ };
1116
+ const query = noHash.slice(qAt + 1).split("&").filter(Boolean).map((pair) => {
1117
+ const eq = pair.indexOf("=");
1118
+ return { key: decode(eq >= 0 ? pair.slice(0, eq) : pair), value: eq >= 0 ? decode(pair.slice(eq + 1)) : "", enabled: true };
1119
+ });
1120
+ return { base: noHash.slice(0, qAt), query };
1121
+ }
1122
+ function requestFacade(req) {
1123
+ const upsertHeader = (h, value) => {
1124
+ const key = typeof h === "string" ? h : String(h?.key ?? h?.name ?? "");
1125
+ const val = typeof h === "string" ? value : h?.value;
1126
+ if (!key.trim()) return;
1127
+ const existing = req.headers.find((x) => x.key.toLowerCase() === key.toLowerCase());
1128
+ const text = val === void 0 || val === null ? "" : String(val);
1129
+ if (existing) {
1130
+ existing.value = text;
1131
+ existing.enabled = true;
1132
+ } else req.headers.push({ key, value: text, enabled: true });
1133
+ };
1134
+ const removeHeader = (name) => {
1135
+ const key = (typeof name === "string" ? name : name?.key ?? "").toLowerCase();
1136
+ req.headers = req.headers.filter((h) => h.key.toLowerCase() !== key);
1137
+ };
1138
+ const enabledHeaders = () => req.headers.filter((h) => h.enabled !== false);
1139
+ const queryApi = {
1140
+ add: (p) => req.queryParams.push({ key: String(p.key), value: String(p.value ?? ""), enabled: true }),
1141
+ upsert: (p) => {
1142
+ const hit = req.queryParams.find((q) => q.key === p.key);
1143
+ if (hit) {
1144
+ hit.value = String(p.value ?? "");
1145
+ hit.enabled = true;
1146
+ } else queryApi.add(p);
1147
+ },
1148
+ remove: (key) => {
1149
+ req.queryParams = req.queryParams.filter((q) => q.key !== key);
1150
+ },
1151
+ get: (key) => req.queryParams.find((q) => q.key === key && q.enabled !== false)?.value,
1152
+ has: (key) => req.queryParams.some((q) => q.key === key && q.enabled !== false),
1153
+ all: () => req.queryParams.filter((q) => q.enabled !== false).map((q) => ({ key: q.key, value: q.value })),
1154
+ toObject: () => Object.fromEntries(req.queryParams.filter((q) => q.enabled !== false).map((q) => [q.key, q.value]))
1155
+ };
1156
+ const full = () => appendQuery(req.url, req.queryParams.filter((q) => q.enabled !== false && q.key));
1157
+ const setUrl = (value) => {
1158
+ const { base, query } = splitUrl(String(value ?? ""));
1159
+ req.url = base;
1160
+ if (query.length || String(value).includes("?")) req.queryParams = query;
1161
+ };
1162
+ const urlFacade = {
1163
+ toString: full,
1164
+ update: setUrl,
1165
+ getHost: () => {
1166
+ try {
1167
+ return new URL(full()).hostname;
1168
+ } catch {
1169
+ return req.url.replace(/^[a-z]+:\/\//i, "").split(/[/?#]/)[0];
1170
+ }
1171
+ },
1172
+ getPath: () => req.url.replace(/^[a-z]+:\/\/[^/]+/i, "").replace(/[?#].*$/, "") || "/",
1173
+ getQueryString: () => full().split("?")[1] ?? "",
1174
+ query: queryApi
1175
+ };
1176
+ const body = {
1177
+ get mode() {
1178
+ return req.body.type;
1179
+ },
1180
+ get raw() {
1181
+ return req.body.raw;
1182
+ },
1183
+ set raw(v) {
1184
+ req.body.raw = v === void 0 || v === null ? "" : String(v);
1185
+ },
1186
+ update(v) {
1187
+ if (typeof v === "string") req.body.raw = v;
1188
+ else if (v && typeof v === "object") {
1189
+ const o = v;
1190
+ if (o.mode === "raw" || o.raw !== void 0) {
1191
+ req.body.raw = String(o.raw ?? "");
1192
+ if (req.body.type === "none") req.body.type = "raw";
1193
+ }
1194
+ const fields = o.urlencoded ?? o.formdata;
1195
+ if (fields) {
1196
+ req.body.type = o.urlencoded ? "urlencoded" : "form-data";
1197
+ req.body.fields = fields.map((f) => ({ key: String(f.key), value: String(f.value ?? ""), enabled: true }));
1198
+ }
1199
+ }
1200
+ },
1201
+ toString: () => req.body.raw
1202
+ };
1203
+ const headersApi = {
1204
+ add: upsertHeader,
1205
+ upsert: upsertHeader,
1206
+ remove: removeHeader,
1207
+ get: (name) => enabledHeaders().find((h) => h.key.toLowerCase() === String(name).toLowerCase())?.value,
1208
+ has: (name) => enabledHeaders().some((h) => h.key.toLowerCase() === String(name).toLowerCase()),
1209
+ all: () => enabledHeaders().map((h) => ({ key: h.key, value: h.value })),
1210
+ toObject: () => Object.fromEntries(enabledHeaders().map((h) => [h.key, h.value])),
1211
+ each: (fn) => enabledHeaders().forEach((h) => fn({ key: h.key, value: h.value }))
1212
+ };
1213
+ return {
1214
+ get id() {
1215
+ return req.id;
1216
+ },
1217
+ get name() {
1218
+ return req.name;
1219
+ },
1220
+ get url() {
1221
+ return urlFacade;
1222
+ },
1223
+ set url(v) {
1224
+ setUrl(v);
1225
+ },
1226
+ get method() {
1227
+ return req.method;
1228
+ },
1229
+ set method(v) {
1230
+ const m = String(v ?? "").trim();
1231
+ if (m) req.method = m.toUpperCase();
1232
+ },
1233
+ headers: headersApi,
1234
+ body,
1235
+ addHeader: upsertHeader,
1236
+ removeHeader
1237
+ };
1238
+ }
1239
+ function scopeApi(vars, scope, ctxForReplace, extra = {}) {
1240
+ const text = (v) => v === void 0 || v === null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
1241
+ return {
1242
+ get: (key) => vars.get(scope, String(key)),
1243
+ set: (key, value) => vars.set(scope, String(key), text(value)),
1244
+ unset: (key) => vars.unset(scope, String(key)),
1245
+ has: (key) => vars.get(scope, String(key)) !== void 0,
1246
+ clear: () => vars.clear(scope),
1247
+ toObject: () => vars.toObject(scope),
1248
+ replaceIn: (template) => resolveVariables(text(template), ctxForReplace()),
1249
+ ...extra
1250
+ };
1251
+ }
1252
+ function normalizeSendSpec(input) {
1253
+ if (typeof input === "string") return { url: input, method: "GET", headers: [] };
1254
+ const o = input ?? {};
1255
+ const url = typeof o.url === "string" ? o.url : String(o.url?.toString?.() ?? "");
1256
+ const headers = [];
1257
+ const rawHeaders = o.header ?? o.headers;
1258
+ if (Array.isArray(rawHeaders)) {
1259
+ for (const h of rawHeaders) if (h && typeof h === "object") headers.push({ key: String(h.key ?? h.name ?? ""), value: String(h.value ?? ""), enabled: true });
1260
+ else if (typeof h === "string" && h.includes(":")) headers.push({ key: h.slice(0, h.indexOf(":")).trim(), value: h.slice(h.indexOf(":") + 1).trim(), enabled: true });
1261
+ } else if (rawHeaders && typeof rawHeaders === "object") {
1262
+ for (const [k, v] of Object.entries(rawHeaders)) headers.push({ key: k, value: String(v), enabled: true });
1263
+ }
1264
+ let body;
1265
+ const b = o.body;
1266
+ if (typeof b === "string") body = b;
1267
+ else if (b && typeof b === "object") {
1268
+ const bo = b;
1269
+ if (bo.mode === "urlencoded" && Array.isArray(bo.urlencoded)) {
1270
+ body = new URLSearchParams(bo.urlencoded.map((f) => [String(f.key), String(f.value ?? "")])).toString();
1271
+ if (!headers.some((h) => h.key.toLowerCase() === "content-type"))
1272
+ headers.push({ key: "Content-Type", value: "application/x-www-form-urlencoded", enabled: true });
1273
+ } else if (bo.raw !== void 0) body = typeof bo.raw === "string" ? bo.raw : JSON.stringify(bo.raw);
1274
+ }
1275
+ return { url, method: String(o.method ?? "GET").toUpperCase(), headers, body };
1276
+ }
1277
+ function withDeadline(p, ms, what) {
1278
+ let timer;
1279
+ return Promise.race([
1280
+ p,
1281
+ new Promise((_, reject) => {
1282
+ timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms} ms`)), ms);
1283
+ })
1284
+ ]).finally(() => clearTimeout(timer));
1285
+ }
1286
+ async function runScript(opts) {
1287
+ if (!opts.script?.trim()) return;
1288
+ installResponsePlugin();
1289
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS;
1290
+ const started = Date.now();
1291
+ const { variables: vars, tests, logs } = opts;
1292
+ const pending = [];
1293
+ const asyncFailures = [];
1294
+ const timers = /* @__PURE__ */ new Set();
1295
+ const record = (name, passed, error) => {
1296
+ const r = { name, passed };
1297
+ if (!passed && error) r.error = error;
1298
+ tests.push(r);
1299
+ };
1300
+ const fmt = (args) => args.map((a) => typeof a === "string" ? a : inspect(a, { depth: 4, breakLength: Infinity, compact: true })).join(" ");
1301
+ const consoleApi = {
1302
+ log: (...a) => logs.push(fmt(a)),
1303
+ info: (...a) => logs.push(fmt(a)),
1304
+ debug: (...a) => logs.push(fmt(a)),
1305
+ trace: (...a) => logs.push(fmt(a)),
1306
+ warn: (...a) => logs.push("[warn] " + fmt(a)),
1307
+ error: (...a) => logs.push("[error] " + fmt(a)),
1308
+ clear: () => void 0
1309
+ };
1310
+ const test = (name, fn) => {
1311
+ const testName = String(name);
1312
+ if (typeof fn !== "function") return;
1313
+ try {
1314
+ let ret;
1315
+ if (fn.length > 0) {
1316
+ ret = new Promise((resolve2, reject) => {
1317
+ fn((err) => err ? reject(err) : resolve2());
1318
+ });
1319
+ } else ret = fn();
1320
+ if (ret && typeof ret.then === "function") {
1321
+ pending.push(
1322
+ Promise.resolve(ret).then(
1323
+ () => record(testName, true),
1324
+ (e) => {
1325
+ record(testName, false, errMessage(e));
1326
+ asyncFailures.push(`${testName}: ${errMessage(e)}`);
1327
+ }
1328
+ )
1329
+ );
1330
+ } else record(testName, true);
1331
+ } catch (e) {
1332
+ record(testName, false, errMessage(e));
1333
+ if (opts.throwOnFailure) throw e;
1334
+ }
1335
+ };
1336
+ test.skip = (_name) => void 0;
1337
+ const ctxForReplace = () => vars.toContext();
1338
+ const iterationData = opts.variables.iterationData ?? {};
1339
+ const response = opts.response ? responseFacade(opts.response) : void 0;
1340
+ const sendRequest = (spec, cb) => {
1341
+ const p = (async () => {
1342
+ if (!opts.sendRequest) throw new Error("pm.sendRequest is not available here");
1343
+ const s = normalizeSendSpec(spec);
1344
+ const resolve2 = (t) => resolveVariables(t, ctxForReplace());
1345
+ const res = await opts.sendRequest({
1346
+ url: resolve2(s.url),
1347
+ method: s.method,
1348
+ headers: s.headers.map((h) => ({ ...h, key: resolve2(h.key), value: resolve2(h.value) })),
1349
+ body: s.body === void 0 ? void 0 : resolve2(s.body)
1350
+ });
1351
+ if (res.isError) throw new Error(res.errorMessage ?? "Request failed");
1352
+ return responseFacade(res);
1353
+ })();
1354
+ const settled = p.then(
1355
+ (res) => {
1356
+ cb?.(null, res);
1357
+ return res;
1358
+ },
1359
+ (err) => {
1360
+ if (cb) cb(err, void 0);
1361
+ else throw err;
1362
+ }
1363
+ );
1364
+ pending.push(settled.catch(() => void 0));
1365
+ return settled;
1366
+ };
1367
+ const pm = {
1368
+ info: {
1369
+ eventName: opts.phase === "pre" ? "prerequest" : "test",
1370
+ requestName: opts.info?.requestName ?? opts.request.name,
1371
+ requestId: opts.info?.requestId ?? opts.request.id,
1372
+ iteration: opts.info?.iteration ?? 0,
1373
+ iterationCount: opts.info?.iterationCount ?? 1
1374
+ },
1375
+ variables: scopeApi(vars, "local", ctxForReplace),
1376
+ environment: scopeApi(vars, "environment", ctxForReplace, { name: opts.info?.environmentName ?? "" }),
1377
+ collectionVariables: scopeApi(vars, "collection", ctxForReplace),
1378
+ globals: scopeApi(vars, "globals", ctxForReplace),
1379
+ iterationData: {
1380
+ get: (key) => iterationData[key],
1381
+ has: (key) => key in iterationData,
1382
+ toObject: () => ({ ...iterationData }),
1383
+ toJSON: () => ({ ...iterationData })
1384
+ },
1385
+ replaceIn: (template) => resolveVariables(template == null ? "" : String(template), ctxForReplace()),
1386
+ request: requestFacade(opts.request),
1387
+ response,
1388
+ cookies: response?.cookies ?? { get: () => void 0, has: () => false, toObject: () => ({}), all: () => [] },
1389
+ test,
1390
+ expect: chai.expect,
1391
+ sendRequest
1392
+ };
1393
+ const modules = { chai, uuid: { v4: randomUUID } };
1394
+ const sandbox = {
1395
+ pm,
1396
+ console: consoleApi,
1397
+ atob,
1398
+ btoa,
1399
+ URL,
1400
+ URLSearchParams,
1401
+ TextEncoder,
1402
+ TextDecoder,
1403
+ structuredClone,
1404
+ crypto: globalThis.crypto,
1405
+ setTimeout: (fn, ms, ...args) => {
1406
+ const t = setTimeout(() => {
1407
+ timers.delete(t);
1408
+ try {
1409
+ fn(...args);
1410
+ } catch (e) {
1411
+ logs.push(`[error] ${errMessage(e)}`);
1412
+ }
1413
+ }, ms);
1414
+ timers.add(t);
1415
+ return t;
1416
+ },
1417
+ clearTimeout: (t) => {
1418
+ timers.delete(t);
1419
+ clearTimeout(t);
1420
+ },
1421
+ require: (name) => {
1422
+ if (name in modules) return modules[name];
1423
+ throw new Error(`Module "${name}" is not available in Yamlet scripts`);
1424
+ }
1425
+ };
1426
+ const context = vm.createContext(sandbox, { name: `yamlet-${opts.phase}-script` });
1427
+ const script = new vm.Script(`(async () => {
1428
+ ${opts.script}
1429
+ })()`, { filename: `${opts.phase === "pre" ? "pre-request" : "post-response"}.js` });
1430
+ try {
1431
+ const result = script.runInContext(context, { timeout: timeoutMs });
1432
+ const remaining = () => Math.max(1, timeoutMs - (Date.now() - started));
1433
+ await withDeadline(Promise.resolve(result), remaining(), "Script");
1434
+ while (pending.length || timers.size) {
1435
+ if (pending.length) {
1436
+ const batch = pending.splice(0, pending.length);
1437
+ await withDeadline(Promise.allSettled(batch), remaining(), "Script");
1438
+ } else await withDeadline(new Promise((r) => setTimeout(r, 5)), remaining(), "Script");
1439
+ }
1440
+ } finally {
1441
+ for (const t of timers) clearTimeout(t);
1442
+ timers.clear();
1443
+ }
1444
+ if (opts.throwOnFailure && asyncFailures.length) throw new Error(asyncFailures[0]);
1445
+ }
1446
+
1447
+ // core/src/requestExecutor.ts
1448
+ var MAX_REDIRECTS = 10;
1449
+ var insecure;
1450
+ var insecureAgent = () => insecure ??= new Agent({ connect: { rejectUnauthorized: false } });
1451
+ var errMessage2 = (e) => {
1452
+ if (e && typeof e === "object") {
1453
+ const err = e;
1454
+ const base = String(err.message ?? e);
1455
+ const cause = err.cause?.code ?? err.cause?.message;
1456
+ return cause && !base.includes(String(cause)) ? `${base} (${cause})` : base;
1457
+ }
1458
+ return String(e);
1459
+ };
1460
+ function headerValue(headers, name) {
1461
+ const v = headers[name.toLowerCase()];
1462
+ return Array.isArray(v) ? v.join(", ") : v;
1463
+ }
1464
+ function toHeaderList(headers) {
1465
+ const out = [];
1466
+ for (const [key, v] of Object.entries(headers)) {
1467
+ if (v === void 0) continue;
1468
+ for (const value of Array.isArray(v) ? v : [v]) out.push({ key, value, enabled: true });
1469
+ }
1470
+ return out;
1471
+ }
1472
+ function toUndiciHeaders(list) {
1473
+ const out = {};
1474
+ for (const h of list) {
1475
+ const k = h.key.toLowerCase();
1476
+ const prev = out[k];
1477
+ out[k] = prev === void 0 ? h.value : Array.isArray(prev) ? [...prev, h.value] : [prev, h.value];
1478
+ }
1479
+ return out;
1480
+ }
1481
+ var TEXTUAL = /^text\/|json|xml|javascript|ecmascript|yaml|csv|x-www-form-urlencoded|graphql|html/i;
1482
+ function isTextual(contentType, bytes) {
1483
+ if (contentType) return TEXTUAL.test(contentType);
1484
+ if (bytes.includes(0)) return false;
1485
+ try {
1486
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1487
+ return true;
1488
+ } catch {
1489
+ return false;
1490
+ }
1491
+ }
1492
+ function decodeText(bytes, contentType) {
1493
+ const charset = /charset\s*=\s*"?([^";\s]+)/i.exec(contentType)?.[1];
1494
+ try {
1495
+ return new TextDecoder(charset || "utf-8").decode(bytes);
1496
+ } catch {
1497
+ return new TextDecoder("utf-8").decode(bytes);
1498
+ }
1499
+ }
1500
+ function resolveFile(p, workspaceRoot, requestFile) {
1501
+ if (path.isAbsolute(p)) return p;
1502
+ const fromRoot = path.resolve(workspaceRoot, p);
1503
+ if (existsSync(fromRoot) || !requestFile) return fromRoot;
1504
+ const fromRequest = path.resolve(path.dirname(requestFile), p);
1505
+ return existsSync(fromRequest) ? fromRequest : fromRoot;
1506
+ }
1507
+ async function prepareBody(built, workspaceRoot, requestFile) {
1508
+ const b = built.body;
1509
+ switch (b.kind) {
1510
+ case "text":
1511
+ return { body: b.text, display: b.text };
1512
+ case "urlencoded": {
1513
+ const text = encodeUrlEncodedBody(b.fields);
1514
+ return { body: text, display: text };
1515
+ }
1516
+ case "form-data": {
1517
+ const form = new FormData();
1518
+ const lines = [];
1519
+ for (const f of b.fields) {
1520
+ if (f.isFile) {
1521
+ const file = resolveFile(f.value, workspaceRoot, requestFile);
1522
+ const data = await fs.readFile(file);
1523
+ form.append(f.key, new Blob([data]), path.basename(file));
1524
+ lines.push(`${f.key}: [file ${f.value}, ${data.length} bytes]`);
1525
+ } else {
1526
+ form.append(f.key, f.value);
1527
+ lines.push(`${f.key}: ${f.value}`);
1528
+ }
1529
+ }
1530
+ return { body: form, display: `[multipart form-data]
1531
+ ${lines.join("\n")}` };
1532
+ }
1533
+ case "binary": {
1534
+ const file = resolveFile(b.file, workspaceRoot, requestFile);
1535
+ const data = await fs.readFile(file);
1536
+ return { body: data, display: `[binary file ${b.file}, ${data.length} bytes]` };
1537
+ }
1538
+ default:
1539
+ return {};
1540
+ }
1541
+ }
1542
+ async function exchange(opts) {
1543
+ let { url, method, body } = opts;
1544
+ let headers = opts.headers.filter((h) => !(body instanceof FormData && h.key.toLowerCase() === "content-type" && !/boundary=/i.test(h.value)));
1545
+ let explicitCookie = headers.find((h) => h.key.toLowerCase() === "cookie")?.value;
1546
+ const start = performance.now();
1547
+ for (let hop = 0; ; hop++) {
1548
+ const sent = headers.filter((h) => h.key.toLowerCase() !== "cookie");
1549
+ const fromJar = opts.jar?.cookieHeaderFor(url);
1550
+ const cookie = [explicitCookie, fromJar].filter(Boolean).join("; ");
1551
+ if (cookie) sent.push({ key: "Cookie", value: cookie, enabled: true });
1552
+ new URL(url);
1553
+ const res = await request2(url, {
1554
+ method,
1555
+ headers: toUndiciHeaders(sent),
1556
+ body,
1557
+ dispatcher: opts.dispatcher,
1558
+ signal: opts.signal
1559
+ });
1560
+ const firstByteMs = performance.now() - start;
1561
+ const setCookie = res.headers["set-cookie"];
1562
+ if (opts.jar && setCookie) opts.jar.setFromResponse(url, Array.isArray(setCookie) ? setCookie : [setCookie]);
1563
+ const location = headerValue(res.headers, "location");
1564
+ if (opts.followRedirects && location && [301, 302, 303, 307, 308].includes(res.statusCode) && hop < MAX_REDIRECTS) {
1565
+ await res.body.dump();
1566
+ const next = new URL(location, url);
1567
+ if (next.host !== new URL(url).host) {
1568
+ headers = headers.filter((h) => !["authorization", "cookie"].includes(h.key.toLowerCase()));
1569
+ explicitCookie = void 0;
1570
+ }
1571
+ if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && method === "POST") {
1572
+ if (method !== "HEAD") method = "GET";
1573
+ body = void 0;
1574
+ headers = headers.filter((h) => !["content-type", "content-length"].includes(h.key.toLowerCase()));
1575
+ }
1576
+ url = next.toString();
1577
+ continue;
1578
+ }
1579
+ const bytes = Buffer.from(await res.body.arrayBuffer());
1580
+ return { status: res.statusCode, headers: res.headers, bytes, sentHeaders: sent, firstByteMs, totalMs: performance.now() - start };
1581
+ }
1582
+ }
1583
+ function cookiesOf(url, headers) {
1584
+ const raw = headers["set-cookie"];
1585
+ const list = raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw];
1586
+ let host = "";
1587
+ try {
1588
+ host = new URL(url).hostname;
1589
+ } catch {
1590
+ }
1591
+ return list.map((h) => parseSetCookie(h)).filter((c) => !!c).map((c) => ({ ...c, domain: c.domain ?? host }));
1592
+ }
1593
+ function formatHeaders(list) {
1594
+ return list.length ? list.map((h) => ` ${h.key}: ${h.value}`).join("\n") : " [none]";
1595
+ }
1596
+ function consoleText(r) {
1597
+ const lines = [`${r.method} ${r.resolvedUrl}`, "", "Request Headers", formatHeaders(r.requestHeaders), "", "Request Body", r.requestBody || "[empty]", "", "Response"];
1598
+ if (r.isError) lines.push(`Error: ${r.errorMessage ?? "Request failed."}`);
1599
+ else {
1600
+ lines.push(`HTTP ${r.statusCode} ${r.reasonPhrase}`, "Response Headers", formatHeaders(r.headers), "", "Response Body");
1601
+ lines.push(r.bodyEncoding === "base64" ? `[binary body, ${r.sizeBytes} bytes]` : r.body || "[empty]");
1602
+ }
1603
+ return lines.join("\n");
1604
+ }
1605
+ function emptyResponse(method, url) {
1606
+ return {
1607
+ statusCode: 0,
1608
+ reasonPhrase: "",
1609
+ durationMs: 0,
1610
+ sizeBytes: 0,
1611
+ resolvedUrl: url,
1612
+ method,
1613
+ requestHeaders: [],
1614
+ headers: [],
1615
+ cookies: [],
1616
+ body: "",
1617
+ bodyEncoding: "utf8",
1618
+ contentType: "",
1619
+ timings: { total: 0 },
1620
+ testResults: [],
1621
+ scriptLogs: [],
1622
+ consoleText: "",
1623
+ isError: false
1624
+ };
1625
+ }
1626
+ function errorResponse(base, message) {
1627
+ const r = { ...base, isError: true, errorMessage: message };
1628
+ r.consoleText = consoleText(r);
1629
+ return r;
1630
+ }
1631
+ async function scriptSend(spec, input, dispatcher) {
1632
+ const base = emptyResponse(spec.method, spec.url);
1633
+ try {
1634
+ const headers = spec.headers.some((h) => h.key.toLowerCase() === "user-agent") ? spec.headers : [{ key: "User-Agent", value: YAMLET_USER_AGENT, enabled: true }, ...spec.headers];
1635
+ const ex = await exchange({ url: spec.url, method: spec.method, headers, body: spec.body, dispatcher, signal: input.signal, followRedirects: true, jar: input.cookieJar });
1636
+ const contentType = headerValue(ex.headers, "content-type") ?? "";
1637
+ const textual = isTextual(contentType, ex.bytes);
1638
+ return {
1639
+ ...base,
1640
+ statusCode: ex.status,
1641
+ reasonPhrase: STATUS_CODES[ex.status] ?? "",
1642
+ durationMs: Math.round(ex.totalMs),
1643
+ sizeBytes: ex.bytes.length,
1644
+ requestHeaders: ex.sentHeaders,
1645
+ requestBody: spec.body,
1646
+ headers: toHeaderList(ex.headers),
1647
+ cookies: cookiesOf(spec.url, ex.headers),
1648
+ body: textual ? decodeText(ex.bytes, contentType) : ex.bytes.toString("base64"),
1649
+ bodyEncoding: textual ? "utf8" : "base64",
1650
+ contentType,
1651
+ timings: { total: Math.round(ex.totalMs), firstByte: Math.round(ex.firstByteMs) }
1652
+ };
1653
+ } catch (e) {
1654
+ return errorResponse(base, errMessage2(e));
1655
+ }
1656
+ }
1657
+ async function execute(input) {
1658
+ const req = structuredClone(input.request);
1659
+ const collection = input.collection;
1660
+ const vars = new ScriptVariables({
1661
+ request: req.variables ?? [],
1662
+ iterationData: input.iterationData,
1663
+ collection: collection ? structuredClone(collection.variables ?? []) : void 0,
1664
+ environment: input.environment ? structuredClone(input.environment.variables ?? []) : void 0,
1665
+ globals: structuredClone(input.globals ?? [])
1666
+ });
1667
+ const tests = [];
1668
+ const logs = [];
1669
+ const settings = { ...defaultSettings(), ...req.settings };
1670
+ const dispatcher = input.dispatcher ?? (settings.skipSslVerification ? insecureAgent() : void 0);
1671
+ const scriptBase = {
1672
+ request: req,
1673
+ variables: vars,
1674
+ tests,
1675
+ logs,
1676
+ info: {
1677
+ requestName: req.name,
1678
+ requestId: req.id,
1679
+ environmentName: input.environment?.name,
1680
+ iteration: input.iteration,
1681
+ iterationCount: input.iterationCount
1682
+ },
1683
+ sendRequest: (spec) => scriptSend(spec, input, dispatcher)
1684
+ };
1685
+ const finish = (response2) => {
1686
+ const r = { ...response2, testResults: tests, scriptLogs: logs };
1687
+ r.consoleText = consoleText(r);
1688
+ return { response: r, changes: vars.changes() };
1689
+ };
1690
+ if (collection?.preRequestScript?.trim()) {
1691
+ try {
1692
+ await runScript({ ...scriptBase, script: collection.preRequestScript, phase: "pre" });
1693
+ } catch (e) {
1694
+ logs.push(`[error] Collection pre-request script: ${errMessage2(e)}`);
1695
+ }
1696
+ }
1697
+ if (req.preRequestScript?.trim()) {
1698
+ try {
1699
+ await runScript({ ...scriptBase, script: req.preRequestScript, phase: "pre", throwOnFailure: input.throwOnTestFailure });
1700
+ } catch (e) {
1701
+ return finish(errorResponse(emptyResponse(req.method, req.url), `Pre-request script error: ${errMessage2(e)}`));
1702
+ }
1703
+ }
1704
+ const ctx = vars.toContext();
1705
+ const resolve2 = (s) => resolveVariables(s ?? "", ctx);
1706
+ const auth = effectiveAuth(req, collection);
1707
+ let oauthToken;
1708
+ if (auth.type === "oauth2" && canFetchAutomatically(auth.oauth2, resolve2)) {
1709
+ try {
1710
+ oauthToken = (await getCachedToken(auth.oauth2, resolve2, { dispatcher, signal: input.signal })).accessToken;
1711
+ } catch (e) {
1712
+ return finish(errorResponse(emptyResponse(req.method, resolve2(req.url)), `OAuth 2.0 token request failed: ${errMessage2(e)}`));
1713
+ }
1714
+ }
1715
+ const built = buildRequest(req, { ctx, collection, oauthToken });
1716
+ let response = emptyResponse(built.method, built.url);
1717
+ response.requestHeaders = built.headers;
1718
+ const timeoutMs = settings.timeoutMs > 0 ? settings.timeoutMs : input.defaultTimeoutMs ?? 0;
1719
+ const timeoutSignal = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
1720
+ const signals = [input.signal, timeoutSignal].filter((s) => !!s);
1721
+ const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
1722
+ try {
1723
+ const prepared = await prepareBody(built, input.workspaceRoot, req.sourceFilePath);
1724
+ response.requestBody = prepared.display;
1725
+ const ex = await exchange({
1726
+ url: built.url,
1727
+ method: built.method,
1728
+ headers: built.headers,
1729
+ body: prepared.body,
1730
+ dispatcher,
1731
+ signal,
1732
+ followRedirects: settings.followRedirects !== false,
1733
+ jar: input.cookieJar
1734
+ });
1735
+ const contentType = headerValue(ex.headers, "content-type") ?? "";
1736
+ const textual = isTextual(contentType, ex.bytes);
1737
+ response = {
1738
+ ...response,
1739
+ statusCode: ex.status,
1740
+ reasonPhrase: STATUS_CODES[ex.status] ?? "",
1741
+ durationMs: Math.round(ex.totalMs),
1742
+ sizeBytes: ex.bytes.length,
1743
+ requestHeaders: ex.sentHeaders,
1744
+ headers: toHeaderList(ex.headers),
1745
+ cookies: cookiesOf(built.url, ex.headers),
1746
+ body: textual ? decodeText(ex.bytes, contentType) : ex.bytes.toString("base64"),
1747
+ bodyEncoding: textual ? "utf8" : "base64",
1748
+ contentType,
1749
+ timings: { total: Math.round(ex.totalMs), firstByte: Math.round(ex.firstByteMs), download: Math.max(0, Math.round(ex.totalMs - ex.firstByteMs)) }
1750
+ };
1751
+ } catch (e) {
1752
+ const message = input.signal?.aborted ? "Request was cancelled." : timeoutSignal?.aborted ? `Request timed out after ${timeoutMs} ms` : errMessage2(e);
1753
+ return finish(errorResponse(response, message));
1754
+ }
1755
+ if (req.postResponseScript?.trim()) {
1756
+ const before = tests.length;
1757
+ try {
1758
+ await runScript({ ...scriptBase, script: req.postResponseScript, phase: "post", response, throwOnFailure: input.throwOnTestFailure });
1759
+ } catch (e) {
1760
+ const message = errMessage2(e);
1761
+ const last = tests.at(-1);
1762
+ if (!(tests.length > before && last && !last.passed && last.error === message)) {
1763
+ tests.push({ name: "Post-response script", passed: false, error: message });
1764
+ }
1765
+ logs.push(`[error] Post-response script: ${message}`);
1766
+ }
1767
+ }
1768
+ if (collection?.postResponseScript?.trim()) {
1769
+ try {
1770
+ await runScript({ ...scriptBase, script: collection.postResponseScript, phase: "post", response });
1771
+ } catch (e) {
1772
+ logs.push(`[error] Collection post-response script: ${errMessage2(e)}`);
1773
+ }
1774
+ }
1775
+ return finish(response);
1776
+ }
1777
+
1778
+ // core/src/collectionRunner.ts
1779
+ function walk(collection, node, chain, out) {
1780
+ for (const f of node.folders) walk(collection, f, [...chain, f.name], out);
1781
+ for (const request3 of node.requests) out.push({ request: request3, collection, path: chain });
1782
+ }
1783
+ function findFolderNode(node, id, chain) {
1784
+ for (const f of node.folders) {
1785
+ if (f.id === id) return { folder: f, chain: [...chain, f.name] };
1786
+ const hit = findFolderNode(f, id, [...chain, f.name]);
1787
+ if (hit) return hit;
1788
+ }
1789
+ return void 0;
1790
+ }
1791
+ function planRun(workspace, opts) {
1792
+ const collections = [...workspace.collections].sort((a, b) => a.order - b.order).filter((c) => !opts.collectionIds?.length || opts.collectionIds.includes(c.id));
1793
+ const all = [];
1794
+ if (opts.folderId) {
1795
+ for (const c of collections) {
1796
+ const hit = findFolderNode(c, opts.folderId, []);
1797
+ if (hit) {
1798
+ walk(c, hit.folder, hit.chain, all);
1799
+ break;
1800
+ }
1801
+ }
1802
+ } else for (const c of collections) walk(c, c, [], all);
1803
+ if (!opts.requestIds?.length) return all;
1804
+ const everywhere = [];
1805
+ for (const c of collections) walk(c, c, [], everywhere);
1806
+ const byId = new Map(everywhere.map((p) => [p.request.id, p]));
1807
+ return opts.requestIds.map((id) => byId.get(id)).filter((p) => !!p);
1808
+ }
1809
+ function sleep(ms, signal) {
1810
+ return new Promise((resolve2) => {
1811
+ if (ms <= 0 || signal?.aborted) return resolve2();
1812
+ const t = setTimeout(done, ms);
1813
+ function done() {
1814
+ clearTimeout(t);
1815
+ signal?.removeEventListener("abort", done);
1816
+ resolve2();
1817
+ }
1818
+ signal?.addEventListener("abort", done, { once: true });
1819
+ });
1820
+ }
1821
+ async function runCollection(opts) {
1822
+ const workspace = opts.workspace ?? opts.store?.workspace;
1823
+ if (!workspace) throw new Error("runCollection needs a workspace or a store");
1824
+ const started = Date.now();
1825
+ const plan = planRun(workspace, opts);
1826
+ const iterations = opts.data?.length ? opts.data.length : Math.max(1, Math.floor(opts.iterations ?? 1));
1827
+ let environment = opts.environment ? structuredClone(opts.environment) : void 0;
1828
+ let globals = structuredClone(opts.globals ?? []);
1829
+ const collectionVars = /* @__PURE__ */ new Map();
1830
+ const collectionFor = (c) => {
1831
+ if (!collectionVars.has(c.id)) collectionVars.set(c.id, structuredClone(c.variables ?? []));
1832
+ return { ...c, variables: collectionVars.get(c.id) };
1833
+ };
1834
+ const results = [];
1835
+ let stopped = false;
1836
+ outer: for (let iteration = 0; iteration < iterations; iteration++) {
1837
+ for (const item of plan) {
1838
+ if (opts.signal?.aborted) {
1839
+ stopped = true;
1840
+ break outer;
1841
+ }
1842
+ if (results.length && opts.delayMs) await sleep(opts.delayMs, opts.signal);
1843
+ if (opts.signal?.aborted) {
1844
+ stopped = true;
1845
+ break outer;
1846
+ }
1847
+ const collection = collectionFor(item.collection);
1848
+ const { response, changes } = await execute({
1849
+ request: item.request,
1850
+ collection,
1851
+ environment,
1852
+ globals,
1853
+ workspaceRoot: opts.workspaceRoot,
1854
+ cookieJar: opts.cookieJar,
1855
+ iterationData: opts.data?.[iteration],
1856
+ dispatcher: opts.dispatcher,
1857
+ signal: opts.signal,
1858
+ defaultTimeoutMs: opts.defaultTimeoutMs,
1859
+ iteration,
1860
+ iterationCount: iterations
1861
+ });
1862
+ if (changes.environment && environment) environment = { ...environment, variables: changes.environment };
1863
+ if (changes.globals) globals = changes.globals;
1864
+ if (changes.collectionVariables) collectionVars.set(item.collection.id, changes.collectionVariables);
1865
+ const transportError = response.isError;
1866
+ const statusOk = response.statusCode >= 200 && response.statusCode <= 399;
1867
+ const passed = !transportError && statusOk && response.testResults.every((t) => t.passed);
1868
+ const result = {
1869
+ iteration,
1870
+ requestId: item.request.id,
1871
+ name: item.request.name,
1872
+ path: item.path,
1873
+ collectionId: item.collection.id,
1874
+ collectionName: item.collection.name,
1875
+ method: response.method || item.request.method,
1876
+ url: response.resolvedUrl || item.request.url,
1877
+ status: response.statusCode,
1878
+ durationMs: response.durationMs,
1879
+ sizeBytes: response.sizeBytes,
1880
+ tests: response.testResults,
1881
+ passed
1882
+ };
1883
+ if (transportError) result.error = response.errorMessage ?? "Request failed";
1884
+ else if (!statusOk) result.error = `Unexpected status ${response.statusCode}${response.reasonPhrase ? " " + response.reasonPhrase : ""}`;
1885
+ results.push(result);
1886
+ opts.onResult?.(result);
1887
+ if (opts.bail && !passed) {
1888
+ stopped = true;
1889
+ break outer;
1890
+ }
1891
+ }
1892
+ }
1893
+ const failed = results.filter((r) => !r.passed).length;
1894
+ return {
1895
+ results,
1896
+ total: results.length,
1897
+ passed: results.length - failed,
1898
+ failed,
1899
+ durationMs: Date.now() - started,
1900
+ iterations,
1901
+ assertions: {
1902
+ total: results.reduce((n, r) => n + r.tests.length, 0),
1903
+ failed: results.reduce((n, r) => n + r.tests.filter((t) => !t.passed).length, 0)
1904
+ },
1905
+ variables: { environment: environment?.variables, globals, collections: Object.fromEntries(collectionVars) },
1906
+ stopped
1907
+ };
1908
+ }
1909
+ function parseCsv(text) {
1910
+ const rows = [];
1911
+ let row = [];
1912
+ let field = "";
1913
+ let quoted = false;
1914
+ for (let i = 0; i < text.length; i++) {
1915
+ const ch = text[i];
1916
+ if (quoted) {
1917
+ if (ch === '"') {
1918
+ if (text[i + 1] === '"') {
1919
+ field += '"';
1920
+ i++;
1921
+ } else quoted = false;
1922
+ } else field += ch;
1923
+ continue;
1924
+ }
1925
+ if (ch === '"' && field === "") quoted = true;
1926
+ else if (ch === ",") {
1927
+ row.push(field);
1928
+ field = "";
1929
+ } else if (ch === "\n" || ch === "\r") {
1930
+ if (ch === "\r" && text[i + 1] === "\n") i++;
1931
+ row.push(field);
1932
+ rows.push(row);
1933
+ row = [];
1934
+ field = "";
1935
+ } else field += ch;
1936
+ }
1937
+ if (field !== "" || row.length) {
1938
+ row.push(field);
1939
+ rows.push(row);
1940
+ }
1941
+ return rows.filter((r) => !(r.length === 1 && r[0].trim() === ""));
1942
+ }
1943
+ var cell = (v) => v === null || v === void 0 ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
1944
+ function parseDataFile(text, filename) {
1945
+ const body = text.replace(/^/, "");
1946
+ const isJson = /\.json$/i.test(filename) || !/\.csv$/i.test(filename) && /^\s*[[{]/.test(body);
1947
+ if (isJson) {
1948
+ const parsed = JSON.parse(body);
1949
+ const list = Array.isArray(parsed) ? parsed : [parsed];
1950
+ return list.map((item, i) => {
1951
+ if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error(`Row ${i + 1} is not an object`);
1952
+ return Object.fromEntries(Object.entries(item).map(([k, v]) => [k, cell(v)]));
1953
+ });
1954
+ }
1955
+ const rows = parseCsv(body);
1956
+ if (!rows.length) return [];
1957
+ const header = rows[0].map((h) => h.trim());
1958
+ return rows.slice(1).map((r) => Object.fromEntries(header.map((h, i) => [h, r[i] ?? ""])));
1959
+ }
1960
+
1961
+ // core/src/workspaceStore.ts
1962
+ import { existsSync as existsSync3, promises as fs2, statSync } from "node:fs";
1963
+ import path3 from "node:path";
1964
+
1965
+ // core/src/pathNaming.ts
1966
+ import { existsSync as existsSync2 } from "node:fs";
1967
+ import path2 from "node:path";
1968
+ function slugify(name, fallback = "untitled") {
1969
+ if (!name?.trim()) return fallback;
1970
+ let out = "";
1971
+ let lastHyphen = false;
1972
+ for (const ch of name.trim().toLowerCase()) {
1973
+ if (/[a-z0-9]/.test(ch)) {
1974
+ out += ch;
1975
+ lastHyphen = false;
1976
+ } else if (" -_./".includes(ch)) {
1977
+ if (!lastHyphen && out.length > 0) {
1978
+ out += "-";
1979
+ lastHyphen = true;
1980
+ }
1981
+ }
1982
+ }
1983
+ out = out.replace(/^-+|-+$/g, "");
1984
+ return out || fallback;
1985
+ }
1986
+ function uniqueFilePath(directory, fileName, except) {
1987
+ const ext = path2.extname(fileName);
1988
+ const base = fileName.slice(0, fileName.length - ext.length);
1989
+ let candidate = path2.join(directory, fileName);
1990
+ for (let n = 2; existsSync2(candidate) && candidate !== except; n++) {
1991
+ candidate = path2.join(directory, `${base}-${n}${ext}`);
1992
+ }
1993
+ return candidate;
1994
+ }
1995
+ function uniqueDirectoryPath(parent, name, except) {
1996
+ let candidate = path2.join(parent, name);
1997
+ for (let n = 2; existsSync2(candidate) && candidate !== except; n++) {
1998
+ candidate = path2.join(parent, `${name}-${n}`);
1999
+ }
2000
+ return candidate;
2001
+ }
2002
+
2003
+ // core/src/yamlDtos.ts
2004
+ import { Document, isAlias, isMap, isScalar, isSeq, parseDocument, stringify } from "yaml";
2005
+ function loadYaml(text) {
2006
+ if (!text?.trim()) return {};
2007
+ const doc = parseDocument(text, { uniqueKeys: false });
2008
+ if (doc.errors.length) throw doc.errors[0];
2009
+ const toPlain = (n) => {
2010
+ if (isAlias(n)) return toPlain(n.resolve(doc));
2011
+ if (isMap(n)) {
2012
+ const o = {};
2013
+ for (const p of n.items) {
2014
+ const k = isScalar(p.key) ? String(p.key.value ?? "") : String(p.key ?? "");
2015
+ o[k] = toPlain(p.value);
2016
+ }
2017
+ return o;
2018
+ }
2019
+ if (isSeq(n)) return n.items.map(toPlain);
2020
+ if (isScalar(n)) {
2021
+ if (typeof n.value === "number" || typeof n.value === "bigint") return n.source ?? String(n.value);
2022
+ return n.value;
2023
+ }
2024
+ return n ?? null;
2025
+ };
2026
+ return toPlain(doc.contents) ?? {};
2027
+ }
2028
+ var STRINGIFY_OPTS = { lineWidth: 0 };
2029
+ function dumpYaml(value) {
2030
+ return stringify(value, STRINGIFY_OPTS);
2031
+ }
2032
+ function dumpYamlPreserving(value, original, known) {
2033
+ if (!original?.trim()) return dumpYaml(value);
2034
+ try {
2035
+ const orig = parseDocument(original);
2036
+ if (orig.errors.length || !isMap(orig.contents)) return dumpYaml(value);
2037
+ const doc = new Document(value);
2038
+ const target = doc.contents;
2039
+ if (!isMap(target)) return dumpYaml(value);
2040
+ for (const pair of orig.contents.items) {
2041
+ const key = isScalar(pair.key) ? String(pair.key.value) : void 0;
2042
+ if (!key || known.has(key) || isDropped(key) || doc.has(key)) continue;
2043
+ target.items.push(pair);
2044
+ }
2045
+ return doc.toString(STRINGIFY_OPTS);
2046
+ } catch {
2047
+ return dumpYaml(value);
2048
+ }
2049
+ }
2050
+ var isDropped = (key) => /^_[a-z0-9]+_[a-z_]+$/i.test(key);
2051
+ var isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
2052
+ function str(v) {
2053
+ if (v === null || v === void 0) return "";
2054
+ if (typeof v === "string") return v;
2055
+ if (Array.isArray(v) && v.every((x) => typeof x === "string")) return v.join("\n");
2056
+ if (typeof v === "object") return JSON.stringify(v);
2057
+ return String(v);
2058
+ }
2059
+ var optStr = (v) => v === null || v === void 0 ? void 0 : str(v);
2060
+ function bool(v) {
2061
+ if (v === true || v === false) return v;
2062
+ if (typeof v === "string") {
2063
+ const t = v.trim().toLowerCase();
2064
+ if (t === "true") return true;
2065
+ if (t === "false") return false;
2066
+ }
2067
+ return void 0;
2068
+ }
2069
+ function int(v) {
2070
+ if (typeof v === "number") return Math.trunc(v);
2071
+ if (typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim())) return Math.trunc(Number(v));
2072
+ return void 0;
2073
+ }
2074
+ function readEnabled(o) {
2075
+ const disabled = bool(o.disabled);
2076
+ if (disabled !== void 0) return !disabled;
2077
+ return bool(o.enabled) ?? true;
2078
+ }
2079
+ function readRows(raw) {
2080
+ if (Array.isArray(raw)) {
2081
+ return raw.filter(isObj).map((o) => ({ key: str(o.key ?? o.name), value: str(o.value), description: optStr(o.description), enabled: readEnabled(o), o }));
2082
+ }
2083
+ if (isObj(raw)) {
2084
+ return Object.entries(raw).map(([key, value]) => ({ key, value: str(value), enabled: true, o: {} }));
2085
+ }
2086
+ return [];
2087
+ }
2088
+ function readKeyValues(raw) {
2089
+ return readRows(raw).map((r) => {
2090
+ const kv = { key: r.key, value: r.value, enabled: r.enabled };
2091
+ if (r.description) kv.description = r.description;
2092
+ return kv;
2093
+ });
2094
+ }
2095
+ function readVariables(raw) {
2096
+ return readRows(raw).map((r) => {
2097
+ const v = { key: r.key, value: r.value, enabled: r.enabled };
2098
+ if (str(r.o.type).toLowerCase() === "secret" || bool(r.o.secret) === true) v.secret = true;
2099
+ return v;
2100
+ });
2101
+ }
2102
+ function writeKeyValues(list) {
2103
+ if (!list?.length) return void 0;
2104
+ return list.map((kv) => {
2105
+ const o = { key: kv.key, value: kv.value ?? "" };
2106
+ if (kv.description) o.description = kv.description;
2107
+ if (kv.enabled === false) o.enabled = false;
2108
+ return o;
2109
+ });
2110
+ }
2111
+ function writeVariables(list) {
2112
+ if (!list?.length) return void 0;
2113
+ return list.map((v) => {
2114
+ const o = { key: v.key, value: v.value ?? "" };
2115
+ if (v.secret) o.type = "secret";
2116
+ if (v.enabled === false) o.enabled = false;
2117
+ return o;
2118
+ });
2119
+ }
2120
+ function authType(raw) {
2121
+ switch (str(raw).trim().toLowerCase()) {
2122
+ case "bearer":
2123
+ return "bearer";
2124
+ case "basic":
2125
+ return "basic";
2126
+ case "apikey":
2127
+ case "api-key":
2128
+ case "api_key":
2129
+ return "apikey";
2130
+ case "cookie":
2131
+ return "cookie";
2132
+ case "oauth2":
2133
+ return "oauth2";
2134
+ case "inherit":
2135
+ case "inherited":
2136
+ return "inherit";
2137
+ default:
2138
+ return "none";
2139
+ }
2140
+ }
2141
+ function grantType(raw) {
2142
+ switch (str(raw).trim().toLowerCase()) {
2143
+ case "authorization_code":
2144
+ case "authorization-code":
2145
+ case "authorization_code_with_pkce":
2146
+ return "authorization_code";
2147
+ case "password":
2148
+ case "password_credentials":
2149
+ return "password";
2150
+ default:
2151
+ return "client_credentials";
2152
+ }
2153
+ }
2154
+ function credential(block, key) {
2155
+ if (Array.isArray(block)) {
2156
+ const hit = block.filter(isObj).find((kv) => str(kv.key).toLowerCase() === key.toLowerCase());
2157
+ return hit ? str(hit.value) : void 0;
2158
+ }
2159
+ if (isObj(block)) {
2160
+ const k = Object.keys(block).find((x) => x.toLowerCase() === key.toLowerCase());
2161
+ return k ? str(block[k]) : void 0;
2162
+ }
2163
+ return void 0;
2164
+ }
2165
+ function readOAuth2(o) {
2166
+ const get = (...keys) => {
2167
+ for (const k of keys) {
2168
+ const v = credential(o, k);
2169
+ if (v !== void 0) return v;
2170
+ }
2171
+ return void 0;
2172
+ };
2173
+ const d = defaultOAuth2();
2174
+ const addTo = (get("addTokenTo") ?? "").toLowerCase();
2175
+ const clientAuth = (get("client_authentication", "clientAuthentication") ?? "").toLowerCase();
2176
+ const challenge = get("challengeAlgorithm");
2177
+ return {
2178
+ grantType: grantType(get("grant_type", "grantType")),
2179
+ accessToken: get("accessToken", "access_token") ?? "",
2180
+ refreshToken: get("refreshToken", "refresh_token") ?? "",
2181
+ headerPrefix: get("headerPrefix", "tokenType")?.trim() || d.headerPrefix,
2182
+ accessTokenUrl: get("accessTokenUrl", "tokenUrl", "access_token_url") ?? "",
2183
+ authUrl: get("authUrl", "authorizationUrl", "auth_url") ?? "",
2184
+ clientId: get("clientId", "client_id") ?? "",
2185
+ clientSecret: get("clientSecret", "client_secret") ?? "",
2186
+ scope: get("scope") ?? "",
2187
+ redirectUri: get("redirect_uri", "redirectUri", "redirect_url", "callbackUrl") ?? "",
2188
+ username: get("username") ?? "",
2189
+ password: get("password") ?? "",
2190
+ addTokenTo: addTo === "queryparams" || addTo === "query" ? "query" : "header",
2191
+ clientAuthentication: clientAuth === "body" ? "body" : "basic",
2192
+ challengeAlgorithm: challenge?.trim().toLowerCase() === "plain" ? "plain" : "S256"
2193
+ };
2194
+ }
2195
+ function readAuth(raw, fallback = "none") {
2196
+ if (!isObj(raw)) return defaultAuth(fallback);
2197
+ const type = raw.type === void 0 ? fallback : authType(raw.type);
2198
+ const a = defaultAuth(type);
2199
+ const typed = (name) => raw[name];
2200
+ a.token = credential(typed("bearer"), "token") ?? str(raw.token);
2201
+ a.username = credential(typed("basic"), "username") ?? str(raw.username);
2202
+ a.password = credential(typed("basic"), "password") ?? str(raw.password);
2203
+ a.apiKeyName = credential(typed("apikey"), "key") ?? str(raw.key);
2204
+ a.apiKeyValue = credential(typed("apikey"), "value") ?? str(raw.value);
2205
+ a.apiKeyIn = (credential(typed("apikey"), "in") ?? str(raw.in)).toLowerCase() === "query" ? "query" : "header";
2206
+ a.cookie = str(raw.cookie);
2207
+ if (isObj(raw.credentials)) a.oauth2 = readOAuth2(raw.credentials);
2208
+ else if (raw.oauth2 !== void 0) a.oauth2 = readOAuth2(raw.oauth2);
2209
+ return a;
2210
+ }
2211
+ function writeOAuth2(o) {
2212
+ const out = {
2213
+ grant_type: o.grantType === "authorization_code" ? "authorization_code" : o.grantType === "password" ? "password_credentials" : "client_credentials"
2214
+ };
2215
+ const put = (k, v) => {
2216
+ if (v) out[k] = v;
2217
+ };
2218
+ put("accessTokenUrl", o.accessTokenUrl);
2219
+ put("authUrl", o.authUrl);
2220
+ put("clientId", o.clientId);
2221
+ put("clientSecret", o.clientSecret);
2222
+ put("scope", o.scope);
2223
+ put("redirect_uri", o.redirectUri);
2224
+ put("username", o.username);
2225
+ put("password", o.password);
2226
+ put("accessToken", o.accessToken);
2227
+ put("refreshToken", o.refreshToken);
2228
+ if (o.headerPrefix && o.headerPrefix !== "Bearer") out.tokenType = o.headerPrefix;
2229
+ if (o.addTokenTo === "query") out.addTokenTo = "queryParams";
2230
+ if (o.clientAuthentication === "body") out.client_authentication = "body";
2231
+ if (o.challengeAlgorithm === "plain") out.challengeAlgorithm = "plain";
2232
+ return out;
2233
+ }
2234
+ function writeAuth(a) {
2235
+ const out = { type: a.type === "none" ? "noauth" : a.type };
2236
+ const put = (k, v) => {
2237
+ if (v) out[k] = v;
2238
+ };
2239
+ put("token", a.token);
2240
+ put("username", a.username);
2241
+ put("password", a.password);
2242
+ put("key", a.apiKeyName);
2243
+ put("value", a.apiKeyValue);
2244
+ if (a.type === "apikey") out.in = a.apiKeyIn === "query" ? "query" : "header";
2245
+ put("cookie", a.cookie);
2246
+ if (a.type === "oauth2") out.credentials = writeOAuth2(a.oauth2);
2247
+ return out;
2248
+ }
2249
+ function isPreScriptType(type) {
2250
+ const t = str(type).toLowerCase().replace(/-/g, "");
2251
+ return t.includes("pre") || t.includes("before");
2252
+ }
2253
+ function readScripts(raw) {
2254
+ const pre = [];
2255
+ const post = [];
2256
+ if (Array.isArray(raw)) {
2257
+ for (const s of raw.filter(isObj)) {
2258
+ const code = str(s.code ?? s.exec ?? s.script);
2259
+ if (!code) continue;
2260
+ (isPreScriptType(s.type) ? pre : post).push(code);
2261
+ }
2262
+ } else if (isObj(raw)) {
2263
+ for (const [type, code] of Object.entries(raw)) {
2264
+ const c = str(code);
2265
+ if (c) (isPreScriptType(type) ? pre : post).push(c);
2266
+ }
2267
+ }
2268
+ return { pre: pre.join("\n\n"), post: post.join("\n\n") };
2269
+ }
2270
+ function writeScripts(pre, post) {
2271
+ const list = [];
2272
+ if (pre) list.push({ type: "preRequest", code: pre });
2273
+ if (post) list.push({ type: "afterResponse", code: post });
2274
+ return list.length ? list : void 0;
2275
+ }
2276
+ function bodyType(raw) {
2277
+ switch (str(raw).trim().toLowerCase()) {
2278
+ case "raw":
2279
+ return "raw";
2280
+ case "json":
2281
+ return "json";
2282
+ case "xml":
2283
+ return "xml";
2284
+ case "text":
2285
+ return "text";
2286
+ case "html":
2287
+ return "html";
2288
+ case "graphql":
2289
+ return "graphql";
2290
+ case "form-data":
2291
+ case "formdata":
2292
+ case "multipart-form":
2293
+ case "multipart":
2294
+ return "form-data";
2295
+ case "x-www-form-urlencoded":
2296
+ case "urlencoded":
2297
+ case "form-urlencoded":
2298
+ return "urlencoded";
2299
+ case "binary":
2300
+ case "file":
2301
+ return "binary";
2302
+ default:
2303
+ return "none";
2304
+ }
2305
+ }
2306
+ function readField(o) {
2307
+ const isFile = str(o.type).toLowerCase() === "file" || bool(o.isFile) === true;
2308
+ const src = Array.isArray(o.src) ? str(o.src[0]) : optStr(o.src);
2309
+ const field = {
2310
+ key: str(o.key ?? o.name),
2311
+ value: isFile ? src ?? str(o.value) : str(o.value),
2312
+ enabled: readEnabled(o)
2313
+ };
2314
+ if (o.description !== void 0 && o.description !== null) field.description = str(o.description);
2315
+ if (isFile) field.isFile = true;
2316
+ return field;
2317
+ }
2318
+ function readBody(raw) {
2319
+ const b = defaultBody();
2320
+ if (!isObj(raw)) return b;
2321
+ b.type = bodyType(raw.type ?? raw.mode);
2322
+ const content = raw.content;
2323
+ b.raw = optStr(raw.raw) ?? (typeof content === "string" ? content : "");
2324
+ const fieldsRaw = Array.isArray(raw.fields) ? raw.fields : Array.isArray(content) ? content : void 0;
2325
+ if (fieldsRaw) b.fields = fieldsRaw.filter(isObj).map(readField);
2326
+ else if (isObj(content) && (b.type === "form-data" || b.type === "urlencoded"))
2327
+ b.fields = Object.entries(content).map(([key, value]) => ({ key, value: str(value), enabled: true }));
2328
+ const gql = isObj(raw.graphql) ? raw.graphql : raw;
2329
+ b.graphqlQuery = str(gql.query);
2330
+ b.graphqlVariables = gql.variables === void 0 || gql.variables === null ? "" : typeof gql.variables === "string" ? gql.variables : JSON.stringify(gql.variables, null, 2);
2331
+ if (b.type === "graphql" && !b.graphqlQuery && b.raw) b.graphqlQuery = b.raw;
2332
+ b.binaryFile = str(raw.file ?? raw.binaryFile ?? (b.type === "binary" && typeof raw.src === "string" ? raw.src : ""));
2333
+ if (b.type === "binary" && !b.binaryFile && b.raw) b.binaryFile = b.raw;
2334
+ return b;
2335
+ }
2336
+ function writeField(f) {
2337
+ const o = { type: f.isFile ? "file" : "text", key: f.key };
2338
+ if (f.isFile) o.src = f.value.startsWith("@") ? f.value.slice(1) : f.value;
2339
+ else o.value = f.value ?? "";
2340
+ if (f.description) o.description = f.description;
2341
+ if (f.enabled === false) o.enabled = false;
2342
+ return o;
2343
+ }
2344
+ var BODY_TYPE_NAMES = {
2345
+ none: "none",
2346
+ raw: "raw",
2347
+ json: "json",
2348
+ xml: "xml",
2349
+ text: "text",
2350
+ html: "html",
2351
+ "form-data": "form-data",
2352
+ urlencoded: "x-www-form-urlencoded",
2353
+ graphql: "graphql",
2354
+ binary: "binary"
2355
+ };
2356
+ function writeBody(b) {
2357
+ const out = { type: BODY_TYPE_NAMES[b.type] ?? "none" };
2358
+ if (b.raw) out.raw = b.raw;
2359
+ const fields = b.fields?.filter((f) => f.key?.trim() || f.value);
2360
+ if (fields?.length) out.content = fields.map(writeField);
2361
+ if (b.graphqlQuery || b.graphqlVariables) {
2362
+ const g = {};
2363
+ if (b.graphqlQuery) g.query = b.graphqlQuery;
2364
+ if (b.graphqlVariables) g.variables = b.graphqlVariables;
2365
+ out.graphql = g;
2366
+ }
2367
+ if (b.binaryFile) out.file = b.binaryFile;
2368
+ if (b.type === "none" && Object.keys(out).length === 1) return void 0;
2369
+ return out;
2370
+ }
2371
+ function readSettings(o) {
2372
+ const s = defaultSettings();
2373
+ const raw = isObj(o.settings) ? o.settings : {};
2374
+ s.timeoutMs = int(raw.timeoutMs ?? raw.timeout) ?? 0;
2375
+ s.followRedirects = bool(raw.followRedirects) ?? true;
2376
+ const ssl = isObj(o.ssl) ? o.ssl : {};
2377
+ s.skipSslVerification = bool(raw.skipSslVerification) ?? bool(o.skipSslVerification) ?? bool(ssl.skipVerification) ?? (bool(ssl.verify) === false ? true : void 0) ?? false;
2378
+ return s;
2379
+ }
2380
+ function writeSettings(s) {
2381
+ if (!s) return void 0;
2382
+ const out = {};
2383
+ if (s.timeoutMs > 0) out.timeoutMs = s.timeoutMs;
2384
+ if (s.followRedirects === false) out.followRedirects = false;
2385
+ if (s.skipSslVerification) out.skipSslVerification = true;
2386
+ return Object.keys(out).length ? out : void 0;
2387
+ }
2388
+ function readExamples(raw) {
2389
+ if (!Array.isArray(raw)) return [];
2390
+ return raw.filter(isObj).map((o) => {
2391
+ const e = {
2392
+ id: str(o.id) || newId(),
2393
+ name: str(o.name),
2394
+ status: int(o.status ?? o.code) ?? 0,
2395
+ headers: readKeyValues(o.headers),
2396
+ body: str(o.body)
2397
+ };
2398
+ if (o.contentType) e.contentType = str(o.contentType);
2399
+ if (o.savedAt) e.savedAt = str(o.savedAt);
2400
+ return e;
2401
+ });
2402
+ }
2403
+ function writeExamples(list) {
2404
+ if (!list?.length) return void 0;
2405
+ return list.map((e) => {
2406
+ const o = { id: e.id, name: e.name, status: e.status };
2407
+ const h = writeKeyValues(e.headers);
2408
+ if (h) o.headers = h;
2409
+ if (e.contentType) o.contentType = e.contentType;
2410
+ if (e.body) o.body = e.body;
2411
+ if (e.savedAt) o.savedAt = e.savedAt;
2412
+ return o;
2413
+ });
2414
+ }
2415
+ var REQUEST_KEYS = /* @__PURE__ */ new Set([
2416
+ "id",
2417
+ "name",
2418
+ "description",
2419
+ "order",
2420
+ "method",
2421
+ "url",
2422
+ "queryParams",
2423
+ "headers",
2424
+ "pathVariables",
2425
+ "variables",
2426
+ "auth",
2427
+ "body",
2428
+ "scripts",
2429
+ "settings",
2430
+ "examples",
2431
+ "skipSslVerification",
2432
+ "ssl"
2433
+ ]);
2434
+ function deriveNameFromFile(filePath) {
2435
+ let name = filePath.split(/[\\/]/).pop() ?? filePath;
2436
+ name = name.replace(/\.ya?ml$/i, "");
2437
+ return name.replace(/\.request$/i, "");
2438
+ }
2439
+ function requestFromDto(raw, sourceFilePath) {
2440
+ const o = isObj(raw) ? raw : {};
2441
+ const name = str(o.name).trim() ? str(o.name) : sourceFilePath ? deriveNameFromFile(sourceFilePath) : "";
2442
+ const scripts = readScripts(o.scripts);
2443
+ const req = {
2444
+ id: str(o.id).trim() || newId(),
2445
+ name,
2446
+ method: str(o.method).trim() ? str(o.method).trim().toUpperCase() : "GET",
2447
+ url: str(o.url),
2448
+ headers: readKeyValues(o.headers),
2449
+ queryParams: readKeyValues(o.queryParams),
2450
+ pathVariables: readRows(o.pathVariables).map((r) => {
2451
+ const p = { key: r.key, value: r.value };
2452
+ if (r.description) p.description = r.description;
2453
+ return p;
2454
+ }),
2455
+ body: readBody(o.body),
2456
+ auth: o.auth === void 0 || o.auth === null ? defaultAuth("inherit") : readAuth(o.auth, "none"),
2457
+ variables: readVariables(o.variables),
2458
+ description: str(o.description),
2459
+ preRequestScript: scripts.pre,
2460
+ postResponseScript: scripts.post,
2461
+ settings: readSettings(o),
2462
+ examples: readExamples(o.examples),
2463
+ order: int(o.order) ?? 0
2464
+ };
2465
+ if (sourceFilePath) req.sourceFilePath = sourceFilePath;
2466
+ return req;
2467
+ }
2468
+ function requestToDto(r) {
2469
+ const o = { id: r.id, name: r.name };
2470
+ if (r.description) o.description = r.description;
2471
+ o.order = r.order ?? 0;
2472
+ o.method = (r.method || "GET").toUpperCase();
2473
+ o.url = r.url ?? "";
2474
+ const put = (k, v) => {
2475
+ if (v !== void 0) o[k] = v;
2476
+ };
2477
+ put("queryParams", writeKeyValues(r.queryParams));
2478
+ put("headers", writeKeyValues(r.headers));
2479
+ put(
2480
+ "pathVariables",
2481
+ r.pathVariables?.length ? r.pathVariables.map((p) => {
2482
+ const x = { key: p.key, value: p.value ?? "" };
2483
+ if (p.description) x.description = p.description;
2484
+ return x;
2485
+ }) : void 0
2486
+ );
2487
+ put("variables", writeVariables(r.variables));
2488
+ if (r.auth && r.auth.type !== "inherit") o.auth = writeAuth(r.auth);
2489
+ put("body", r.body ? writeBody(r.body) : void 0);
2490
+ put("scripts", writeScripts(r.preRequestScript, r.postResponseScript));
2491
+ put("settings", writeSettings(r.settings));
2492
+ put("examples", writeExamples(r.examples));
2493
+ return o;
2494
+ }
2495
+ function requestToYaml(r, original) {
2496
+ return dumpYamlPreserving(requestToDto(r), original, REQUEST_KEYS);
2497
+ }
2498
+ var COLLECTION_KEYS = /* @__PURE__ */ new Set([
2499
+ "id",
2500
+ "name",
2501
+ "description",
2502
+ "order",
2503
+ "variables",
2504
+ "auth",
2505
+ "scripts",
2506
+ // Read-only aliases from the collection v2.1 shape; migrated on save.
2507
+ "info",
2508
+ "item",
2509
+ "variable",
2510
+ "event"
2511
+ ]);
2512
+ function eventScript(events, listen) {
2513
+ if (!Array.isArray(events)) return "";
2514
+ const ev = events.filter(isObj).find((e) => str(e.listen).toLowerCase() === listen);
2515
+ if (!ev) return "";
2516
+ const script = isObj(ev.script) ? ev.script : {};
2517
+ const exec = script.exec;
2518
+ return Array.isArray(exec) ? exec.map(str).join("\n") : str(exec);
2519
+ }
2520
+ function applyCollectionMetadata(c, raw) {
2521
+ if (!isObj(raw)) return;
2522
+ const info = isObj(raw.info) ? raw.info : void 0;
2523
+ const legacyIdKey = info ? Object.keys(info).find((k) => /^_[a-z0-9]+_id$/i.test(k)) : void 0;
2524
+ const id = legacyIdKey && str(info[legacyIdKey]) || str(raw.id);
2525
+ const name = info && str(info.name) || str(raw.name);
2526
+ if (id.trim()) c.id = id;
2527
+ if (name.trim()) c.name = name;
2528
+ const description = (info && optStr(info.description)) ?? optStr(raw.description);
2529
+ if (description) c.description = description;
2530
+ const order = int(raw.order);
2531
+ if (order !== void 0) c.order = order;
2532
+ if (isObj(raw.auth)) c.auth = readAuth(raw.auth, "none");
2533
+ const legacyVars = Array.isArray(raw.variable) ? readVariables(raw.variable) : [];
2534
+ const vars = legacyVars.length ? legacyVars : readVariables(raw.variables);
2535
+ if (vars.length) c.variables = vars;
2536
+ const scripts = readScripts(raw.scripts);
2537
+ const pre = eventScript(raw.event, "prerequest") || scripts.pre;
2538
+ const post = eventScript(raw.event, "test") || scripts.post;
2539
+ if (pre) c.preRequestScript = pre;
2540
+ if (post) c.postResponseScript = post;
2541
+ }
2542
+ function applyCollectionDefinition(c, raw) {
2543
+ if (!isObj(raw)) return;
2544
+ if (str(raw.name).trim()) c.name = str(raw.name);
2545
+ if (raw.description) c.description = str(raw.description);
2546
+ const vars = readVariables(raw.variables);
2547
+ if (vars.length) c.variables = vars;
2548
+ const auth = Array.isArray(raw.auth) ? raw.auth.find(isObj) : isObj(raw.auth) ? raw.auth : void 0;
2549
+ if (auth) c.auth = readAuth(auth, "none");
2550
+ const scripts = readScripts(raw.scripts);
2551
+ if (scripts.pre) c.preRequestScript = scripts.pre;
2552
+ if (scripts.post) c.postResponseScript = scripts.post;
2553
+ }
2554
+ function collectionToDto(c) {
2555
+ const o = { id: c.id, name: c.name };
2556
+ if (c.description) o.description = c.description;
2557
+ o.order = c.order ?? 0;
2558
+ const vars = writeVariables(c.variables);
2559
+ if (vars) o.variables = vars;
2560
+ if (c.auth && c.auth.type !== "none" && c.auth.type !== "inherit") o.auth = writeAuth(c.auth);
2561
+ const scripts = writeScripts(c.preRequestScript, c.postResponseScript);
2562
+ if (scripts) o.scripts = scripts;
2563
+ return o;
2564
+ }
2565
+ function collectionToYaml(c, original) {
2566
+ return dumpYamlPreserving(collectionToDto(c), original, COLLECTION_KEYS);
2567
+ }
2568
+ var FOLDER_KEYS = /* @__PURE__ */ new Set(["id", "name", "description", "order"]);
2569
+ function applyFolderMetadata(f, raw) {
2570
+ if (!isObj(raw)) return;
2571
+ if (str(raw.id).trim()) f.id = str(raw.id);
2572
+ if (str(raw.name).trim()) f.name = str(raw.name);
2573
+ if (raw.description) f.description = str(raw.description);
2574
+ const order = int(raw.order);
2575
+ if (order !== void 0) f.order = order;
2576
+ }
2577
+ function folderToDto(f) {
2578
+ const o = { id: f.id, name: f.name };
2579
+ if (f.description) o.description = f.description;
2580
+ o.order = f.order ?? 0;
2581
+ return o;
2582
+ }
2583
+ function folderToYaml(f, original) {
2584
+ return dumpYamlPreserving(folderToDto(f), original, FOLDER_KEYS);
2585
+ }
2586
+ var ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["id", "name", "variables", "values"]);
2587
+ function deriveEnvironmentName(filePath) {
2588
+ return deriveNameFromFile(filePath).replace(/\.environment$/i, "");
2589
+ }
2590
+ function environmentFromDto(raw, filePath) {
2591
+ const o = isObj(raw) ? raw : {};
2592
+ const vars = o.variables !== void 0 && o.variables !== null ? o.variables : o.values;
2593
+ const env = {
2594
+ id: str(o.id).trim() || newId(),
2595
+ name: str(o.name).trim() ? str(o.name) : filePath ? deriveEnvironmentName(filePath) : "",
2596
+ variables: readVariables(vars)
2597
+ };
2598
+ if (filePath) env.filePath = filePath;
2599
+ return env;
2600
+ }
2601
+ function environmentToDto(e) {
2602
+ const o = { id: e.id, name: e.name };
2603
+ const vars = writeVariables(e.variables);
2604
+ if (vars) o.variables = vars;
2605
+ return o;
2606
+ }
2607
+ function environmentFromYaml(text, filePath) {
2608
+ return environmentFromDto(loadYaml(text), filePath);
2609
+ }
2610
+ function environmentToYaml(e, original) {
2611
+ return dumpYamlPreserving(environmentToDto(e), original, ENVIRONMENT_KEYS);
2612
+ }
2613
+ function globalsFromYaml(text) {
2614
+ const o = loadYaml(text);
2615
+ return isObj(o) ? readVariables(o.variables ?? o.values) : [];
2616
+ }
2617
+ function globalsToYaml(vars, original) {
2618
+ return dumpYamlPreserving({ variables: writeVariables(vars) ?? [] }, original, /* @__PURE__ */ new Set(["variables", "values"]));
2619
+ }
2620
+
2621
+ // core/src/workspaceStore.ts
2622
+ var ROOT_DIR_NAME = "yamlet";
2623
+ var COLLECTIONS_DIR = "collections";
2624
+ var ENVIRONMENTS_DIR = "environments";
2625
+ var GLOBALS_DIR = "globals";
2626
+ var GLOBALS_FILE = "globals.yaml";
2627
+ var COLLECTION_FILE = "collection.yaml";
2628
+ var FOLDER_FILE = "folder.yaml";
2629
+ var DEFINITION_FILE = path3.join(".resources", "definition.yaml");
2630
+ var REQUEST_EXTENSION = ".yaml";
2631
+ var isDir = (p) => {
2632
+ try {
2633
+ return statSync(p).isDirectory();
2634
+ } catch {
2635
+ return false;
2636
+ }
2637
+ };
2638
+ var isYamlFile = (name) => /\.ya?ml$/i.test(name);
2639
+ function stableId(prefix, key) {
2640
+ let h = 0xcbf29ce484222325n;
2641
+ for (const b of new TextEncoder().encode(key)) {
2642
+ h ^= BigInt(b);
2643
+ h = h * 0x100000001b3n & 0xffffffffffffffffn;
2644
+ }
2645
+ return `${prefix}-${h.toString(16).padStart(16, "0")}`;
2646
+ }
2647
+ var ordinal = (a, b) => a < b ? -1 : a > b ? 1 : 0;
2648
+ var byOrder = (list) => list.sort((a, b) => a.order - b.order);
2649
+ var nextOrder = (list) => list.reduce((m, x) => Math.max(m, (x.order ?? 0) + 1), 0);
2650
+ async function readIfExists(p) {
2651
+ if (!p) return void 0;
2652
+ try {
2653
+ return await fs2.readFile(p, "utf8");
2654
+ } catch {
2655
+ return void 0;
2656
+ }
2657
+ }
2658
+ async function writeFileAtomic(p, content) {
2659
+ await fs2.mkdir(path3.dirname(p), { recursive: true });
2660
+ const tmp = `${p}.${process.pid}.${Date.now().toString(36)}.tmp`;
2661
+ await fs2.writeFile(tmp, content, "utf8");
2662
+ await fs2.rename(tmp, p);
2663
+ }
2664
+ async function listDir(dir) {
2665
+ try {
2666
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
2667
+ return entries.map((e) => ({ name: e.name, dir: e.isDirectory() })).sort((a, b) => ordinal(a.name, b.name));
2668
+ } catch {
2669
+ return [];
2670
+ }
2671
+ }
2672
+ function repath(p, from, to) {
2673
+ if (!p) return p;
2674
+ if (p === from) return to;
2675
+ return p.startsWith(from + path3.sep) ? to + p.slice(from.length) : p;
2676
+ }
2677
+ function repathTree(node, from, to) {
2678
+ node.directoryPath = repath(node.directoryPath, from, to);
2679
+ if ("filePath" in node) node.filePath = repath(node.filePath, from, to);
2680
+ for (const r of node.requests) r.sourceFilePath = repath(r.sourceFilePath, from, to);
2681
+ for (const f of node.folders) repathTree(f, from, to);
2682
+ }
2683
+ function freshIds(node) {
2684
+ node.id = newId();
2685
+ for (const r of node.requests) {
2686
+ r.id = newId();
2687
+ for (const e of r.examples ?? []) e.id = newId();
2688
+ }
2689
+ for (const f of node.folders) freshIds(f);
2690
+ }
2691
+ function copyName(name) {
2692
+ return `${name} Copy`;
2693
+ }
2694
+ var WorkspaceStore = class _WorkspaceStore {
2695
+ workspace;
2696
+ queue = Promise.resolve();
2697
+ constructor(workspace) {
2698
+ this.workspace = workspace;
2699
+ }
2700
+ // ---- Opening -------------------------------------------------------------
2701
+ /** The picked folder if it already holds collections/ + environments/ (or is named `yamlet`), else `<dir>/yamlet`. */
2702
+ static resolveRoot(dir) {
2703
+ const abs = path3.resolve(dir);
2704
+ const looksLikeRoot = isDir(path3.join(abs, COLLECTIONS_DIR)) && isDir(path3.join(abs, ENVIRONMENTS_DIR));
2705
+ if (looksLikeRoot || path3.basename(abs).toLowerCase() === ROOT_DIR_NAME) return abs;
2706
+ return path3.join(abs, ROOT_DIR_NAME);
2707
+ }
2708
+ static async isWorkspace(dir) {
2709
+ return isDir(path3.join(_WorkspaceStore.resolveRoot(dir), COLLECTIONS_DIR));
2710
+ }
2711
+ /** Opens an existing workspace. Does not create anything on disk. */
2712
+ static async open(dir) {
2713
+ const root = _WorkspaceStore.resolveRoot(dir);
2714
+ if (!isDir(root)) throw new Error(`No Yamlet workspace found at ${dir}`);
2715
+ return new _WorkspaceStore(await loadWorkspace(root));
2716
+ }
2717
+ /** Creates the workspace skeleton (seeding globals) where missing, then opens it. */
2718
+ static async create(dir) {
2719
+ const root = _WorkspaceStore.resolveRoot(dir);
2720
+ await fs2.mkdir(path3.join(root, COLLECTIONS_DIR), { recursive: true });
2721
+ await fs2.mkdir(path3.join(root, ENVIRONMENTS_DIR), { recursive: true });
2722
+ await fs2.mkdir(path3.join(root, GLOBALS_DIR), { recursive: true });
2723
+ const globals = path3.join(root, GLOBALS_DIR, GLOBALS_FILE);
2724
+ if (!existsSync3(globals)) {
2725
+ await writeFileAtomic(globals, globalsToYaml([{ key: "appName", value: "Yamlet", enabled: true }]));
2726
+ }
2727
+ return _WorkspaceStore.open(root);
2728
+ }
2729
+ get rootPath() {
2730
+ return this.workspace.rootPath;
2731
+ }
2732
+ async reload() {
2733
+ return this.enqueue(async () => {
2734
+ this.workspace = await loadWorkspace(this.workspace.rootPath);
2735
+ return this.workspace;
2736
+ });
2737
+ }
2738
+ // ---- Lookups -------------------------------------------------------------
2739
+ findCollection(id) {
2740
+ return this.workspace.collections.find((c) => c.id === id);
2741
+ }
2742
+ findEnvironment(id) {
2743
+ return this.workspace.environments.find((e) => e.id === id);
2744
+ }
2745
+ findRequest(id) {
2746
+ const hit = this.locateRequest(id);
2747
+ return hit && { request: hit.request, collection: hit.collection, folders: hit.folders };
2748
+ }
2749
+ findFolder(id) {
2750
+ const hit = this.locateFolder(id);
2751
+ return hit && { folder: hit.folder, collection: hit.collection, folders: hit.folders };
2752
+ }
2753
+ locateRequest(id) {
2754
+ for (const collection of this.workspace.collections) {
2755
+ const walk2 = (node, chain) => {
2756
+ const request3 = node.requests.find((r) => r.id === id);
2757
+ if (request3) return { request: request3, collection, folders: chain, list: node.requests };
2758
+ for (const f of node.folders) {
2759
+ const hit2 = walk2(f, [...chain, f]);
2760
+ if (hit2) return hit2;
2761
+ }
2762
+ return void 0;
2763
+ };
2764
+ const hit = walk2(collection, []);
2765
+ if (hit) return hit;
2766
+ }
2767
+ return void 0;
2768
+ }
2769
+ locateFolder(id) {
2770
+ for (const collection of this.workspace.collections) {
2771
+ const walk2 = (node, chain) => {
2772
+ for (const f of node.folders) {
2773
+ if (f.id === id) return { folder: f, collection, folders: chain, list: node.folders };
2774
+ const hit2 = walk2(f, [...chain, f]);
2775
+ if (hit2) return hit2;
2776
+ }
2777
+ return void 0;
2778
+ };
2779
+ const hit = walk2(collection, []);
2780
+ if (hit) return hit;
2781
+ }
2782
+ return void 0;
2783
+ }
2784
+ requireCollection(id) {
2785
+ const c = this.findCollection(id);
2786
+ if (!c) throw new Error(`Collection not found: ${id}`);
2787
+ return c;
2788
+ }
2789
+ /** The container (collection root or folder) that new children go into. */
2790
+ container(collectionId, folderId) {
2791
+ const collection = this.requireCollection(collectionId);
2792
+ let folder = null;
2793
+ if (folderId) {
2794
+ const hit = this.locateFolder(folderId);
2795
+ if (!hit || hit.collection !== collection) throw new Error(`Folder not found in collection: ${folderId}`);
2796
+ folder = hit.folder;
2797
+ }
2798
+ const node = folder ?? collection;
2799
+ const dir = node.directoryPath;
2800
+ if (!dir) throw new Error("Container has no directory");
2801
+ return { collection, folder, node, dir };
2802
+ }
2803
+ // ---- Collections ---------------------------------------------------------
2804
+ async createCollection(name) {
2805
+ return this.enqueue(async () => {
2806
+ const ws = this.workspace;
2807
+ await fs2.mkdir(ws.collectionsPath, { recursive: true });
2808
+ const dir = uniqueDirectoryPath(ws.collectionsPath, slugify(name, "collection"));
2809
+ await fs2.mkdir(dir, { recursive: true });
2810
+ const c = newCollection({
2811
+ name: name.trim() || "New Collection",
2812
+ directoryPath: dir,
2813
+ filePath: path3.join(dir, COLLECTION_FILE),
2814
+ order: nextOrder(ws.collections)
2815
+ });
2816
+ await this.writeCollection(c);
2817
+ ws.collections.push(c);
2818
+ return c;
2819
+ });
2820
+ }
2821
+ async updateCollection(id, patch) {
2822
+ return this.enqueue(async () => {
2823
+ const c = this.requireCollection(id);
2824
+ if (patch.name !== void 0 && patch.name.trim() && patch.name !== c.name) {
2825
+ c.name = patch.name;
2826
+ await this.renameNodeDir(c, this.workspace.collectionsPath, slugify(c.name, "collection"));
2827
+ }
2828
+ if (patch.description !== void 0) c.description = patch.description;
2829
+ if (patch.variables !== void 0) c.variables = patch.variables;
2830
+ if (patch.auth !== void 0) c.auth = patch.auth;
2831
+ if (patch.preRequestScript !== void 0) c.preRequestScript = patch.preRequestScript;
2832
+ if (patch.postResponseScript !== void 0) c.postResponseScript = patch.postResponseScript;
2833
+ await this.writeCollection(c);
2834
+ return c;
2835
+ });
2836
+ }
2837
+ async deleteCollection(id) {
2838
+ return this.enqueue(async () => {
2839
+ const c = this.requireCollection(id);
2840
+ if (c.directoryPath) await fs2.rm(c.directoryPath, { recursive: true, force: true });
2841
+ this.workspace.collections = this.workspace.collections.filter((x) => x !== c);
2842
+ return c;
2843
+ });
2844
+ }
2845
+ async duplicateCollection(id) {
2846
+ return this.enqueue(async () => {
2847
+ const src = this.requireCollection(id);
2848
+ if (!src.directoryPath) throw new Error("Collection has no directory");
2849
+ const name = copyName(src.name);
2850
+ const dir = uniqueDirectoryPath(this.workspace.collectionsPath, slugify(name, "collection"));
2851
+ await fs2.cp(src.directoryPath, dir, { recursive: true });
2852
+ const c = structuredClone(src);
2853
+ repathTree(c, src.directoryPath, dir);
2854
+ freshIds(c);
2855
+ c.name = name;
2856
+ c.filePath = path3.join(dir, COLLECTION_FILE);
2857
+ c.order = nextOrder(this.workspace.collections);
2858
+ await this.writeTree(c);
2859
+ this.workspace.collections.push(c);
2860
+ return c;
2861
+ });
2862
+ }
2863
+ /** Writes an in-memory collection (e.g. from an importer) as a new collection with fresh ids. */
2864
+ async importCollection(imported) {
2865
+ return this.enqueue(async () => {
2866
+ const ws = this.workspace;
2867
+ const c = structuredClone(imported);
2868
+ freshIds(c);
2869
+ c.name = c.name?.trim() || "Imported Collection";
2870
+ const dir = uniqueDirectoryPath(ws.collectionsPath, slugify(c.name, "collection"));
2871
+ await fs2.mkdir(dir, { recursive: true });
2872
+ c.directoryPath = dir;
2873
+ c.filePath = path3.join(dir, COLLECTION_FILE);
2874
+ c.order = nextOrder(ws.collections);
2875
+ const placeAndWrite = async (node, nodeDir) => {
2876
+ for (const [i, r] of node.requests.entries()) {
2877
+ r.order = i;
2878
+ r.sourceFilePath = uniqueFilePath(nodeDir, slugify(r.name, "request") + REQUEST_EXTENSION);
2879
+ await writeFileAtomic(r.sourceFilePath, requestToYaml(r));
2880
+ }
2881
+ for (const [i, f] of node.folders.entries()) {
2882
+ f.order = i;
2883
+ f.directoryPath = uniqueDirectoryPath(nodeDir, slugify(f.name, "folder"));
2884
+ await fs2.mkdir(f.directoryPath, { recursive: true });
2885
+ await writeFileAtomic(path3.join(f.directoryPath, FOLDER_FILE), folderToYaml(f));
2886
+ await placeAndWrite(f, f.directoryPath);
2887
+ }
2888
+ };
2889
+ await placeAndWrite(c, dir);
2890
+ await writeFileAtomic(c.filePath, collectionToYaml(c));
2891
+ ws.collections.push(c);
2892
+ return c;
2893
+ });
2894
+ }
2895
+ // ---- Folders -------------------------------------------------------------
2896
+ async createFolder(collectionId, parentFolderId, name) {
2897
+ return this.enqueue(async () => {
2898
+ const { node, dir } = this.container(collectionId, parentFolderId);
2899
+ const folderDir = uniqueDirectoryPath(dir, slugify(name, "folder"));
2900
+ await fs2.mkdir(folderDir, { recursive: true });
2901
+ const f = newFolder({ name: name.trim() || "New Folder", directoryPath: folderDir, order: nextOrder(node.folders) });
2902
+ await this.writeFolder(f);
2903
+ node.folders.push(f);
2904
+ return f;
2905
+ });
2906
+ }
2907
+ async updateFolder(id, patch) {
2908
+ return this.enqueue(async () => {
2909
+ const hit = this.locateFolder(id);
2910
+ if (!hit) throw new Error(`Folder not found: ${id}`);
2911
+ const f = hit.folder;
2912
+ if (patch.name !== void 0 && patch.name.trim() && patch.name !== f.name) {
2913
+ f.name = patch.name;
2914
+ const parentDir = path3.dirname(f.directoryPath);
2915
+ await this.renameNodeDir(f, parentDir, slugify(f.name, "folder"));
2916
+ }
2917
+ if (patch.description !== void 0) f.description = patch.description;
2918
+ await this.writeFolder(f);
2919
+ return f;
2920
+ });
2921
+ }
2922
+ async deleteFolder(id) {
2923
+ return this.enqueue(async () => {
2924
+ const hit = this.locateFolder(id);
2925
+ if (!hit) throw new Error(`Folder not found: ${id}`);
2926
+ if (hit.folder.directoryPath) await fs2.rm(hit.folder.directoryPath, { recursive: true, force: true });
2927
+ hit.list.splice(hit.list.indexOf(hit.folder), 1);
2928
+ return hit.folder;
2929
+ });
2930
+ }
2931
+ async duplicateFolder(id) {
2932
+ return this.enqueue(async () => {
2933
+ const hit = this.locateFolder(id);
2934
+ if (!hit?.folder.directoryPath) throw new Error(`Folder not found: ${id}`);
2935
+ const src = hit.folder;
2936
+ const srcDir = src.directoryPath;
2937
+ const name = copyName(src.name);
2938
+ const dir = uniqueDirectoryPath(path3.dirname(srcDir), slugify(name, "folder"));
2939
+ await fs2.cp(srcDir, dir, { recursive: true });
2940
+ const f = structuredClone(src);
2941
+ repathTree(f, srcDir, dir);
2942
+ freshIds(f);
2943
+ f.name = name;
2944
+ await this.writeTree(f);
2945
+ hit.list.splice(hit.list.indexOf(src) + 1, 0, f);
2946
+ await this.saveContainerOrder(hit.collection, hit.folders.at(-1) ?? null);
2947
+ return f;
2948
+ });
2949
+ }
2950
+ // ---- Requests ------------------------------------------------------------
2951
+ async createRequest(collectionId, parentFolderId, init) {
2952
+ return this.enqueue(async () => {
2953
+ const { node, dir } = this.container(collectionId, parentFolderId);
2954
+ const name = init?.name?.trim() ? init.name : "New Request";
2955
+ const file = uniqueFilePath(dir, slugify(name, "request") + REQUEST_EXTENSION);
2956
+ const id = init?.id && !this.idInUse(init.id) ? init.id : newId();
2957
+ const r = newRequest({ ...init, id, name, sourceFilePath: file, order: nextOrder(node.requests) });
2958
+ r.method = (r.method || "GET").toUpperCase();
2959
+ await writeFileAtomic(file, requestToYaml(r));
2960
+ node.requests.push(r);
2961
+ return r;
2962
+ });
2963
+ }
2964
+ /** Replaces a request by id and rewrites its file, renaming the file when the name changed. */
2965
+ async saveRequest(request3) {
2966
+ return this.enqueue(async () => {
2967
+ const hit = this.locateRequest(request3.id);
2968
+ if (!hit) throw new Error(`Request not found: ${request3.id}`);
2969
+ const existing = hit.request;
2970
+ const updated = {
2971
+ ...structuredClone(request3),
2972
+ method: (request3.method || "GET").toUpperCase(),
2973
+ order: existing.order,
2974
+ sourceFilePath: existing.sourceFilePath
2975
+ };
2976
+ let file = existing.sourceFilePath;
2977
+ if (!file) throw new Error("Request has no file path");
2978
+ if (updated.name !== existing.name && updated.name.trim()) {
2979
+ const target = uniqueFilePath(path3.dirname(file), slugify(updated.name, "request") + REQUEST_EXTENSION, file);
2980
+ if (target !== file) {
2981
+ if (existsSync3(file)) await fs2.rename(file, target);
2982
+ file = target;
2983
+ }
2984
+ }
2985
+ updated.sourceFilePath = file;
2986
+ await writeFileAtomic(file, requestToYaml(updated, await readIfExists(file)));
2987
+ hit.list[hit.list.indexOf(existing)] = updated;
2988
+ return updated;
2989
+ });
2990
+ }
2991
+ async deleteRequest(id) {
2992
+ return this.enqueue(async () => {
2993
+ const hit = this.locateRequest(id);
2994
+ if (!hit) throw new Error(`Request not found: ${id}`);
2995
+ if (hit.request.sourceFilePath) await fs2.rm(hit.request.sourceFilePath, { force: true });
2996
+ hit.list.splice(hit.list.indexOf(hit.request), 1);
2997
+ return hit.request;
2998
+ });
2999
+ }
3000
+ async duplicateRequest(id) {
3001
+ return this.enqueue(async () => {
3002
+ const hit = this.locateRequest(id);
3003
+ if (!hit?.request.sourceFilePath) throw new Error(`Request not found: ${id}`);
3004
+ const src = hit.request;
3005
+ const r = structuredClone(src);
3006
+ r.id = newId();
3007
+ for (const e of r.examples ?? []) e.id = newId();
3008
+ r.name = copyName(src.name);
3009
+ r.sourceFilePath = uniqueFilePath(path3.dirname(src.sourceFilePath), slugify(r.name, "request") + REQUEST_EXTENSION);
3010
+ await writeFileAtomic(r.sourceFilePath, requestToYaml(r, await readIfExists(src.sourceFilePath)));
3011
+ hit.list.splice(hit.list.indexOf(src) + 1, 0, r);
3012
+ await this.saveContainerOrder(hit.collection, hit.folders.at(-1) ?? null);
3013
+ return r;
3014
+ });
3015
+ }
3016
+ // ---- Moving --------------------------------------------------------------
3017
+ /**
3018
+ * Moves a request or folder into a target container at `index` (position in the target's
3019
+ * list after removal from its source). Files/directories move on disk when the parent
3020
+ * changes; both containers are renumbered.
3021
+ */
3022
+ async move(kind, id, targetCollectionId, targetFolderId, index) {
3023
+ return this.enqueue(async () => {
3024
+ const target = this.container(targetCollectionId, targetFolderId);
3025
+ const clampInsert = (list, item) => list.splice(Math.max(0, Math.min(index, list.length)), 0, item);
3026
+ if (kind === "request") {
3027
+ const hit2 = this.locateRequest(id);
3028
+ if (!hit2) throw new Error(`Request not found: ${id}`);
3029
+ const r = hit2.request;
3030
+ hit2.list.splice(hit2.list.indexOf(r), 1);
3031
+ if (r.sourceFilePath && path3.dirname(r.sourceFilePath) !== target.dir) {
3032
+ const dest = uniqueFilePath(target.dir, path3.basename(r.sourceFilePath));
3033
+ await fs2.rename(r.sourceFilePath, dest);
3034
+ r.sourceFilePath = dest;
3035
+ }
3036
+ clampInsert(target.node.requests, r);
3037
+ await this.saveContainerOrder(target.collection, target.folder);
3038
+ const source2 = hit2.folders.at(-1) ?? null;
3039
+ if (hit2.collection !== target.collection || source2 !== target.folder) await this.saveContainerOrder(hit2.collection, source2);
3040
+ return r;
3041
+ }
3042
+ const hit = this.locateFolder(id);
3043
+ if (!hit) throw new Error(`Folder not found: ${id}`);
3044
+ const f = hit.folder;
3045
+ if (targetFolderId === f.id || targetFolderId && this.locateFolder(targetFolderId)?.folders.some((a) => a.id === f.id)) {
3046
+ throw new Error("Cannot move a folder into itself");
3047
+ }
3048
+ hit.list.splice(hit.list.indexOf(f), 1);
3049
+ const from = f.directoryPath;
3050
+ if (path3.dirname(from) !== target.dir) {
3051
+ const dest = uniqueDirectoryPath(target.dir, path3.basename(from));
3052
+ await fs2.rename(from, dest);
3053
+ repathTree(f, from, dest);
3054
+ }
3055
+ clampInsert(target.node.folders, f);
3056
+ await this.saveContainerOrder(target.collection, target.folder);
3057
+ const source = hit.folders.at(-1) ?? null;
3058
+ if (hit.collection !== target.collection || source !== target.folder) await this.saveContainerOrder(hit.collection, source);
3059
+ return f;
3060
+ });
3061
+ }
3062
+ /** Renumbers a container's direct children to their list order; rewrites only changed files. */
3063
+ async saveContainerOrder(collection, folder) {
3064
+ const node = folder ?? collection;
3065
+ for (const [i, r] of node.requests.entries()) {
3066
+ if (r.order === i && existsSync3(r.sourceFilePath ?? "")) continue;
3067
+ r.order = i;
3068
+ if (r.sourceFilePath) await writeFileAtomic(r.sourceFilePath, requestToYaml(r, await readIfExists(r.sourceFilePath)));
3069
+ }
3070
+ for (const [i, f] of node.folders.entries()) {
3071
+ if (f.order === i && existsSync3(path3.join(f.directoryPath ?? "", FOLDER_FILE))) continue;
3072
+ f.order = i;
3073
+ await this.writeFolder(f);
3074
+ }
3075
+ }
3076
+ // ---- Environments and globals --------------------------------------------
3077
+ async createEnvironment(name) {
3078
+ return this.enqueue(async () => {
3079
+ const ws = this.workspace;
3080
+ const file = uniqueFilePath(ws.environmentsPath, slugify(name, "environment") + ".yaml");
3081
+ const env = newEnvironment({ name: name.trim() || "New Environment", filePath: file });
3082
+ await writeFileAtomic(file, environmentToYaml(env));
3083
+ ws.environments.push(env);
3084
+ return env;
3085
+ });
3086
+ }
3087
+ async importEnvironment(imported) {
3088
+ return this.enqueue(async () => {
3089
+ const ws = this.workspace;
3090
+ const env = { ...structuredClone(imported), id: newId(), name: imported.name?.trim() || "Imported Environment" };
3091
+ env.filePath = uniqueFilePath(ws.environmentsPath, slugify(env.name, "environment") + ".yaml");
3092
+ await writeFileAtomic(env.filePath, environmentToYaml(env));
3093
+ ws.environments.push(env);
3094
+ return env;
3095
+ });
3096
+ }
3097
+ async saveEnvironment(env) {
3098
+ return this.enqueue(async () => {
3099
+ const list = this.workspace.environments;
3100
+ const idx = list.findIndex((e) => e.id === env.id);
3101
+ if (idx < 0) throw new Error(`Environment not found: ${env.id}`);
3102
+ const existing = list[idx];
3103
+ let file = existing.filePath ?? uniqueFilePath(this.workspace.environmentsPath, slugify(env.name, "environment") + ".yaml");
3104
+ if (env.name !== existing.name && env.name.trim() && existing.filePath) {
3105
+ const target = uniqueFilePath(path3.dirname(file), slugify(env.name, "environment") + ".yaml", file);
3106
+ if (target !== file) {
3107
+ if (existsSync3(file)) await fs2.rename(file, target);
3108
+ file = target;
3109
+ }
3110
+ }
3111
+ const updated = { ...structuredClone(env), filePath: file };
3112
+ await writeFileAtomic(file, environmentToYaml(updated, await readIfExists(file)));
3113
+ list[idx] = updated;
3114
+ return updated;
3115
+ });
3116
+ }
3117
+ async deleteEnvironment(id) {
3118
+ return this.enqueue(async () => {
3119
+ const list = this.workspace.environments;
3120
+ const env = list.find((e) => e.id === id);
3121
+ if (!env) throw new Error(`Environment not found: ${id}`);
3122
+ if (env.filePath) await fs2.rm(env.filePath, { force: true });
3123
+ list.splice(list.indexOf(env), 1);
3124
+ return env;
3125
+ });
3126
+ }
3127
+ async duplicateEnvironment(id) {
3128
+ return this.enqueue(async () => {
3129
+ const list = this.workspace.environments;
3130
+ const src = list.find((e) => e.id === id);
3131
+ if (!src) throw new Error(`Environment not found: ${id}`);
3132
+ const env = { ...structuredClone(src), id: newId(), name: copyName(src.name) };
3133
+ env.filePath = uniqueFilePath(this.workspace.environmentsPath, slugify(env.name, "environment") + ".yaml");
3134
+ await writeFileAtomic(env.filePath, environmentToYaml(env, await readIfExists(src.filePath)));
3135
+ list.splice(list.indexOf(src) + 1, 0, env);
3136
+ return env;
3137
+ });
3138
+ }
3139
+ async saveGlobals(vars) {
3140
+ return this.enqueue(async () => {
3141
+ const file = path3.join(this.workspace.globalsPath, GLOBALS_FILE);
3142
+ await writeFileAtomic(file, globalsToYaml(vars, await readIfExists(file)));
3143
+ this.workspace.globals = structuredClone(vars);
3144
+ return this.workspace.globals;
3145
+ });
3146
+ }
3147
+ // ---- Internals -----------------------------------------------------------
3148
+ enqueue(fn) {
3149
+ const run2 = this.queue.then(fn, fn);
3150
+ this.queue = run2.catch(() => void 0);
3151
+ return run2;
3152
+ }
3153
+ idInUse(id) {
3154
+ return !!(this.findCollection(id) || this.locateRequest(id) || this.locateFolder(id) || this.findEnvironment(id));
3155
+ }
3156
+ async renameNodeDir(node, parentDir, slug) {
3157
+ const from = node.directoryPath;
3158
+ if (!from || path3.basename(from) === slug) return;
3159
+ const to = uniqueDirectoryPath(parentDir, slug, from);
3160
+ if (to === from) return;
3161
+ await fs2.rename(from, to);
3162
+ repathTree(node, from, to);
3163
+ }
3164
+ async writeCollection(c) {
3165
+ if (!c.directoryPath) throw new Error("Collection has no directory");
3166
+ c.filePath = path3.join(c.directoryPath, COLLECTION_FILE);
3167
+ await writeFileAtomic(c.filePath, collectionToYaml(c, await readIfExists(c.filePath)));
3168
+ }
3169
+ async writeFolder(f) {
3170
+ if (!f.directoryPath) return;
3171
+ const file = path3.join(f.directoryPath, FOLDER_FILE);
3172
+ await writeFileAtomic(file, folderToYaml(f, await readIfExists(file)));
3173
+ }
3174
+ /** Rewrites every metadata and request file under a node (used after copying a directory). */
3175
+ async writeTree(node) {
3176
+ if ("variables" in node) await this.writeCollection(node);
3177
+ else await this.writeFolder(node);
3178
+ for (const r of node.requests) {
3179
+ if (r.sourceFilePath) await writeFileAtomic(r.sourceFilePath, requestToYaml(r, await readIfExists(r.sourceFilePath)));
3180
+ }
3181
+ for (const f of node.folders) await this.writeTree(f);
3182
+ }
3183
+ };
3184
+ async function loadWorkspace(root) {
3185
+ const base = path3.basename(root);
3186
+ const ws = {
3187
+ name: base.toLowerCase() === ROOT_DIR_NAME ? path3.basename(path3.dirname(root)) || base : base,
3188
+ rootPath: root,
3189
+ collectionsPath: path3.join(root, COLLECTIONS_DIR),
3190
+ environmentsPath: path3.join(root, ENVIRONMENTS_DIR),
3191
+ globalsPath: path3.join(root, GLOBALS_DIR),
3192
+ collections: [],
3193
+ environments: [],
3194
+ globals: []
3195
+ };
3196
+ const rel = (p) => path3.relative(root, p).split(path3.sep).join("/");
3197
+ for (const entry of await listDir(ws.collectionsPath)) {
3198
+ if (!entry.dir || entry.name.startsWith(".")) continue;
3199
+ try {
3200
+ ws.collections.push(await loadCollection(path3.join(ws.collectionsPath, entry.name), rel));
3201
+ } catch {
3202
+ }
3203
+ }
3204
+ byOrder(ws.collections);
3205
+ for (const entry of await listDir(ws.environmentsPath)) {
3206
+ if (entry.dir || !isYamlFile(entry.name)) continue;
3207
+ const file = path3.join(ws.environmentsPath, entry.name);
3208
+ try {
3209
+ const raw = loadYaml(await fs2.readFile(file, "utf8"));
3210
+ const env = environmentFromDto(raw, file);
3211
+ if (!isObj(raw) || !String(raw.id ?? "").trim()) env.id = stableId("e", rel(file));
3212
+ ws.environments.push(env);
3213
+ } catch {
3214
+ }
3215
+ }
3216
+ const globalsText = await readIfExists(path3.join(ws.globalsPath, GLOBALS_FILE));
3217
+ if (globalsText) {
3218
+ try {
3219
+ ws.globals = globalsFromYaml(globalsText);
3220
+ } catch {
3221
+ ws.globals = [];
3222
+ }
3223
+ }
3224
+ ensureUniqueIds(ws, rel);
3225
+ return ws;
3226
+ }
3227
+ async function loadCollection(dir, rel) {
3228
+ const c = newCollection({
3229
+ id: stableId("c", rel(dir)),
3230
+ name: path3.basename(dir),
3231
+ directoryPath: dir,
3232
+ filePath: path3.join(dir, COLLECTION_FILE)
3233
+ });
3234
+ const definition = await readIfExists(path3.join(dir, DEFINITION_FILE));
3235
+ if (definition) applyCollectionDefinition(c, loadYaml(definition));
3236
+ const metadata = await readIfExists(c.filePath);
3237
+ if (metadata) applyCollectionMetadata(c, loadYaml(metadata));
3238
+ c.requests = await loadRequests(dir, rel);
3239
+ c.folders = await loadFolders(dir, rel);
3240
+ return c;
3241
+ }
3242
+ async function loadFolders(parentDir, rel) {
3243
+ const folders = [];
3244
+ for (const entry of await listDir(parentDir)) {
3245
+ if (!entry.dir || entry.name.startsWith(".")) continue;
3246
+ const dir = path3.join(parentDir, entry.name);
3247
+ const f = newFolder({ id: stableId("f", rel(dir)), name: entry.name, directoryPath: dir });
3248
+ const meta = await readIfExists(path3.join(dir, FOLDER_FILE));
3249
+ if (meta) {
3250
+ try {
3251
+ applyFolderMetadata(f, loadYaml(meta));
3252
+ } catch {
3253
+ }
3254
+ }
3255
+ f.requests = await loadRequests(dir, rel);
3256
+ f.folders = await loadFolders(dir, rel);
3257
+ folders.push(f);
3258
+ }
3259
+ return byOrder(folders);
3260
+ }
3261
+ async function loadRequests(dir, rel) {
3262
+ const requests = [];
3263
+ for (const entry of await listDir(dir)) {
3264
+ if (entry.dir || !isYamlFile(entry.name)) continue;
3265
+ const lower = entry.name.toLowerCase();
3266
+ if (lower === COLLECTION_FILE || lower === FOLDER_FILE) continue;
3267
+ const file = path3.join(dir, entry.name);
3268
+ try {
3269
+ const raw = loadYaml(await fs2.readFile(file, "utf8"));
3270
+ if (!isObj(raw)) continue;
3271
+ const r = requestFromDto(raw, file);
3272
+ if (!String(raw.id ?? "").trim()) r.id = stableId("r", rel(file));
3273
+ requests.push(r);
3274
+ } catch {
3275
+ }
3276
+ }
3277
+ return byOrder(requests);
3278
+ }
3279
+ function ensureUniqueIds(ws, rel) {
3280
+ const seen = /* @__PURE__ */ new Set();
3281
+ const claim = (current, prefix, key) => {
3282
+ let id = current;
3283
+ for (let n = 0; !id || seen.has(id); n++) id = stableId(prefix, `${key}#${n}`);
3284
+ seen.add(id);
3285
+ return id;
3286
+ };
3287
+ const walk2 = (node) => {
3288
+ for (const r of node.requests) r.id = claim(r.id, "r", rel(r.sourceFilePath ?? r.name));
3289
+ for (const f of node.folders) {
3290
+ f.id = claim(f.id, "f", rel(f.directoryPath ?? f.name));
3291
+ walk2(f);
3292
+ }
3293
+ };
3294
+ for (const c of ws.collections) {
3295
+ c.id = claim(c.id, "c", rel(c.directoryPath ?? c.name));
3296
+ walk2(c);
3297
+ }
3298
+ for (const e of ws.environments) e.id = claim(e.id, "e", rel(e.filePath ?? e.name));
3299
+ }
3300
+
3301
+ // cli/src/index.ts
3302
+ var VERSION = true ? "0.0.0-dev" : "0.0.0-dev";
3303
+ var CliError = class extends Error {
3304
+ };
3305
+ var USAGE = `Yamlet CLI: run Yamlet API collections headlessly.
3306
+
3307
+ Usage:
3308
+ yamlet run <workspace> [options]
3309
+
3310
+ Arguments:
3311
+ <workspace> A Yamlet workspace folder (contains collections/ and
3312
+ environments/), or its parent.
3313
+
3314
+ Options:
3315
+ -e, --env <name|file> Environment to use: a name from the workspace, or a
3316
+ path to an environment YAML file.
3317
+ -g, --globals <file> Use these globals instead of the workspace's.
3318
+ -c, --collection <name> Only run this collection (repeatable).
3319
+ -n, --iterations <n> Run everything n times (default 1).
3320
+ -d, --data <file> CSV or JSON data file; one iteration per row.
3321
+ --delay <ms> Wait between requests.
3322
+ --bail Stop after the first failing request.
3323
+ -r, --reporter <name> table (default), json, or junit.
3324
+ -o, --out <file> Write the json/junit report to a file instead of stdout.
3325
+ --no-color Disable ANSI colors (also honors NO_COLOR).
3326
+ -h, --help Show this help.
3327
+ -v, --version Show the version.
3328
+
3329
+ A request fails on a network error, a status outside 2xx/3xx, or a failed
3330
+ pm.test assertion. The exit code is 1 if anything failed, else 0.
3331
+
3332
+ Example:
3333
+ yamlet run ./my-workspace --env dev --reporter junit --out results.xml`;
3334
+ function parseArgs(argv) {
3335
+ const args = { workspace: "", collections: [], delayMs: 0, bail: false, reporter: "table", color: !process.env.NO_COLOR };
3336
+ const value = (i, flag) => {
3337
+ const v = argv[i + 1];
3338
+ if (v === void 0 || v.startsWith("-")) throw new CliError(`Option '${flag}' requires a value.`);
3339
+ return v;
3340
+ };
3341
+ const int2 = (raw, flag, min) => {
3342
+ const n = Number(raw);
3343
+ if (!Number.isInteger(n) || n < min) throw new CliError(`Option '${flag}' needs a whole number of at least ${min}.`);
3344
+ return n;
3345
+ };
3346
+ for (let i = 0; i < argv.length; i++) {
3347
+ const a = argv[i];
3348
+ switch (a) {
3349
+ case "-e":
3350
+ case "--env":
3351
+ args.env = value(i++, a);
3352
+ break;
3353
+ case "-g":
3354
+ case "--globals":
3355
+ args.globals = value(i++, a);
3356
+ break;
3357
+ case "-c":
3358
+ case "--collection":
3359
+ args.collections.push(value(i++, a));
3360
+ break;
3361
+ case "-n":
3362
+ case "--iterations":
3363
+ args.iterations = int2(value(i++, a), a, 1);
3364
+ break;
3365
+ case "-d":
3366
+ case "--data":
3367
+ args.data = value(i++, a);
3368
+ break;
3369
+ case "--delay":
3370
+ args.delayMs = int2(value(i++, a), a, 0);
3371
+ break;
3372
+ case "--bail":
3373
+ args.bail = true;
3374
+ break;
3375
+ case "-r":
3376
+ case "--reporter": {
3377
+ const r = value(i++, a);
3378
+ if (r !== "table" && r !== "json" && r !== "junit") throw new CliError(`Unknown reporter '${r}'. Use table, json or junit.`);
3379
+ args.reporter = r;
3380
+ break;
3381
+ }
3382
+ case "-o":
3383
+ case "--out":
3384
+ args.out = value(i++, a);
3385
+ break;
3386
+ case "--no-color":
3387
+ args.color = false;
3388
+ break;
3389
+ default:
3390
+ if (a.startsWith("-")) throw new CliError(`Unknown option '${a}'.`);
3391
+ if (args.workspace) throw new CliError(`Unexpected argument '${a}'.`);
3392
+ args.workspace = a;
3393
+ }
3394
+ }
3395
+ if (!args.workspace) throw new CliError("Missing <workspace> path. Usage: yamlet run <workspace> [--env <name>]");
3396
+ return args;
3397
+ }
3398
+ function resolveEnvironment(ws, arg) {
3399
+ if (!arg) return void 0;
3400
+ const path4 = resolve(arg);
3401
+ if (existsSync4(path4) && statSync2(path4).isFile()) return environmentFromYaml(readFileSync(path4, "utf8"), path4);
3402
+ const stem = basename(arg).replace(/\.(environment\.)?ya?ml$/i, "");
3403
+ const match = ws.environments.find((e) => e.name.toLowerCase() === arg.toLowerCase() || e.name.toLowerCase() === stem.toLowerCase());
3404
+ if (!match) {
3405
+ const names = ws.environments.map((e) => e.name).join(", ") || "none";
3406
+ throw new CliError(`Environment '${arg}' not found (no such file, and the workspace has: ${names}).`);
3407
+ }
3408
+ return match;
3409
+ }
3410
+ var SGR = { green: "32", red: "31", cyan: "36", dim: "2", bold: "1" };
3411
+ function paint(text, color, ...tones) {
3412
+ const codes = tones.filter((t) => !!t).map((t) => SGR[t]);
3413
+ return color && codes.length ? `\x1B[${codes.join(";")}m${text}\x1B[0m` : text;
3414
+ }
3415
+ var fmtMs = (ms) => ms >= 1e3 ? `${(ms / 1e3).toFixed(2)} s` : `${Math.round(ms)} ms`;
3416
+ function truncate(s, max) {
3417
+ return s.length <= max ? s : `${s.slice(0, Math.max(1, max - 1))}\u2026`;
3418
+ }
3419
+ function table(headers, right, rows, color, out) {
3420
+ const widths = headers.map((h, c) => Math.max(h.length, ...rows.map((r) => r[c].text.length)));
3421
+ const rule = (l, m, r) => l + widths.map((w) => "\u2500".repeat(w + 2)).join(m) + r;
3422
+ const line = (cells, bold = false) => "\u2502" + cells.map((cell2, c) => ` ${paint(right[c] ? cell2.text.padStart(widths[c]) : cell2.text.padEnd(widths[c]), color, bold ? "bold" : void 0, cell2.tone)} \u2502`).join("");
3423
+ out.push(rule("\u250C", "\u252C", "\u2510"), line(headers.map((text) => ({ text })), true), rule("\u251C", "\u253C", "\u2524"));
3424
+ for (const r of rows) out.push(line(r));
3425
+ out.push(rule("\u2514", "\u2534", "\u2518"));
3426
+ }
3427
+ function formatTable(summary, color, columns = 120) {
3428
+ const out = [];
3429
+ const results = summary.results;
3430
+ const multiCollection = new Set(results.map((r) => r.collectionId)).size > 1;
3431
+ const multiIteration = summary.iterations > 1;
3432
+ const headers = [...multiIteration ? ["#"] : [], "Result", ...multiCollection ? ["Collection"] : [], "Method", "URL", "Status", "Time", "Tests"];
3433
+ const right = headers.map((h) => h === "Time" || h === "Tests" || h === "#");
3434
+ const fixed = 3 * headers.length + 1 + 6 + 7 + 10 + 12 + 6 + (multiCollection ? 18 : 0) + (multiIteration ? 4 : 0);
3435
+ const urlMax = Math.max(24, columns - fixed);
3436
+ const rows = results.map((r) => {
3437
+ const tp = r.tests.filter((t) => t.passed).length;
3438
+ return [
3439
+ ...multiIteration ? [{ text: String(r.iteration + 1) }] : [],
3440
+ r.passed ? { text: "PASS", tone: "green" } : { text: "FAIL", tone: "red" },
3441
+ ...multiCollection ? [{ text: truncate(r.collectionName, 16) }] : [],
3442
+ { text: r.method, tone: "cyan" },
3443
+ { text: truncate(r.url, urlMax) },
3444
+ { text: r.error && !r.status ? "ERROR" : String(r.status), tone: r.passed ? "green" : "red" },
3445
+ { text: fmtMs(r.durationMs) },
3446
+ r.tests.length ? { text: `${tp}/${r.tests.length}`, tone: tp === r.tests.length ? "green" : "red" } : { text: "-" }
3447
+ ];
3448
+ });
3449
+ table(headers, right, rows, color, out);
3450
+ const failed = results.filter((r) => !r.passed);
3451
+ if (failed.length) {
3452
+ out.push("", paint("Failures:", color, "red", "bold"));
3453
+ for (const r of failed) {
3454
+ const label = [...r.path, r.name].join(" / ") + (multiIteration ? ` (iteration ${r.iteration + 1})` : "");
3455
+ const bad = r.tests.filter((t) => !t.passed);
3456
+ for (const t of bad) out.push(" " + paint(`${label} > ${t.name}${t.error ? `: ${t.error}` : ""}`, color, "red"));
3457
+ if (!bad.length) out.push(" " + paint(`${label} (${r.method} ${r.url}): ${r.error ?? `HTTP ${r.status}`}`, color, "red"));
3458
+ }
3459
+ }
3460
+ out.push("");
3461
+ const req = `${summary.total} requests, ${paint(`${summary.passed} passed`, color, summary.passed ? "green" : void 0)}, ${paint(`${summary.failed} failed`, color, summary.failed ? "red" : void 0)}`;
3462
+ const asserts = `${summary.assertions.total} assertions, ${paint(`${summary.assertions.failed} failed`, color, summary.assertions.failed ? "red" : void 0)}`;
3463
+ out.push(`Summary: ${req}; ${asserts}; ${fmtMs(summary.durationMs)} total.`);
3464
+ out.push(summary.failed ? paint("RESULT: FAIL", color, "red", "bold") : paint("RESULT: PASS", color, "green", "bold"));
3465
+ return out.join("\n");
3466
+ }
3467
+ var xml = (s) => s.replace(/[<>&"']/g, (c) => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;", '"': "&quot;", "'": "&apos;" })[c]);
3468
+ function formatJunit(summary) {
3469
+ const byCollection = /* @__PURE__ */ new Map();
3470
+ for (const r of summary.results) byCollection.set(r.collectionName, [...byCollection.get(r.collectionName) ?? [], r]);
3471
+ const lines = [`<?xml version="1.0" encoding="UTF-8"?>`, `<testsuites name="yamlet" tests="${summary.total}" failures="${summary.failed}" time="${(summary.durationMs / 1e3).toFixed(3)}">`];
3472
+ for (const [name, results] of byCollection) {
3473
+ const failures = results.filter((r) => !r.passed).length;
3474
+ const time = results.reduce((t, r) => t + r.durationMs, 0) / 1e3;
3475
+ lines.push(` <testsuite name="${xml(name)}" tests="${results.length}" failures="${failures}" time="${time.toFixed(3)}">`);
3476
+ for (const r of results) {
3477
+ const case_ = `${[...r.path, r.name].join(" / ")}${summary.iterations > 1 ? ` [${r.iteration + 1}]` : ""}`;
3478
+ lines.push(` <testcase classname="${xml(name)}" name="${xml(case_)}" time="${(r.durationMs / 1e3).toFixed(3)}">`);
3479
+ if (!r.passed) {
3480
+ const bad = r.tests.filter((t) => !t.passed);
3481
+ const message = bad.length ? bad.map((t) => `${t.name}${t.error ? `: ${t.error}` : ""}`).join("; ") : r.error ?? `HTTP ${r.status}`;
3482
+ lines.push(` <failure message="${xml(message)}">${xml(`${r.method} ${r.url}
3483
+ ${message}`)}</failure>`);
3484
+ }
3485
+ lines.push(` <system-out>${xml(`${r.method} ${r.url} -> ${r.status || "ERROR"} in ${r.durationMs} ms`)}</system-out>`);
3486
+ lines.push(" </testcase>");
3487
+ }
3488
+ lines.push(" </testsuite>");
3489
+ }
3490
+ lines.push("</testsuites>");
3491
+ return lines.join("\n");
3492
+ }
3493
+ function formatJson(summary) {
3494
+ const { variables: _v, ...rest } = summary;
3495
+ return JSON.stringify(rest, null, 2);
3496
+ }
3497
+ async function run(args) {
3498
+ const dir = resolve(args.workspace);
3499
+ if (!existsSync4(dir) || !statSync2(dir).isDirectory()) throw new CliError(`Workspace folder not found: ${dir}`);
3500
+ if (!await WorkspaceStore.isWorkspace(dir)) throw new CliError(`Not a Yamlet workspace (no collections/ and environments/): ${dir}`);
3501
+ const store = await WorkspaceStore.open(dir);
3502
+ const ws = store.workspace;
3503
+ let globals = ws.globals;
3504
+ if (args.globals) {
3505
+ const path4 = resolve(args.globals);
3506
+ if (!existsSync4(path4)) throw new CliError(`Globals file not found: ${path4}`);
3507
+ globals = globalsFromYaml(readFileSync(path4, "utf8"));
3508
+ }
3509
+ const environment = resolveEnvironment(ws, args.env);
3510
+ let collectionIds;
3511
+ if (args.collections.length) {
3512
+ collectionIds = args.collections.map((name) => {
3513
+ const c2 = ws.collections.find((x) => x.name.toLowerCase() === name.toLowerCase() || basename(x.directoryPath ?? "") === name);
3514
+ if (!c2) throw new CliError(`Collection '${name}' not found. Available: ${ws.collections.map((x) => x.name).join(", ") || "none"}.`);
3515
+ return c2.id;
3516
+ });
3517
+ }
3518
+ let data;
3519
+ if (args.data) {
3520
+ const path4 = resolve(args.data);
3521
+ if (!existsSync4(path4)) throw new CliError(`Data file not found: ${path4}`);
3522
+ try {
3523
+ data = parseDataFile(readFileSync(path4, "utf8"), path4);
3524
+ } catch (err) {
3525
+ throw new CliError(`Could not read data file ${path4}: ${err.message}`);
3526
+ }
3527
+ if (!data.length) throw new CliError(`Data file has no rows: ${path4}`);
3528
+ }
3529
+ const table2 = args.reporter === "table";
3530
+ const log = (s = "") => table2 ? console.log(s) : console.error(s);
3531
+ const c = args.color && (table2 ? process.stdout.isTTY !== false : process.stderr.isTTY !== false);
3532
+ log(paint(`Yamlet ${VERSION}`, c, "bold"));
3533
+ log(`${paint("Workspace: ", c, "dim")} ${ws.rootPath}`);
3534
+ log(`${paint("Environment:", c, "dim")} ${environment?.name ?? "(none)"}`);
3535
+ if (data) log(`${paint("Data: ", c, "dim")} ${basename(args.data)} (${data.length} rows)`);
3536
+ log();
3537
+ const summary = await runCollection({
3538
+ workspace: ws,
3539
+ workspaceRoot: ws.rootPath,
3540
+ collectionIds,
3541
+ environment,
3542
+ globals,
3543
+ iterations: args.iterations,
3544
+ data,
3545
+ delayMs: args.delayMs,
3546
+ bail: args.bail,
3547
+ cookieJar: new CookieJar()
3548
+ });
3549
+ if (summary.total === 0) {
3550
+ console.error("No requests found to run.");
3551
+ return 1;
3552
+ }
3553
+ if (table2) {
3554
+ console.log(formatTable(summary, c, process.stdout.columns || 120));
3555
+ } else {
3556
+ const report = args.reporter === "junit" ? formatJunit(summary) : formatJson(summary);
3557
+ if (args.out) {
3558
+ writeFileSync(resolve(args.out), report + "\n");
3559
+ log(`Wrote ${args.reporter} report to ${resolve(args.out)}`);
3560
+ } else {
3561
+ console.log(report);
3562
+ }
3563
+ log(`${summary.total} requests, ${summary.passed} passed, ${summary.failed} failed; ${fmtMs(summary.durationMs)} total.`);
3564
+ }
3565
+ return summary.failed ? 1 : 0;
3566
+ }
3567
+ async function main(argv) {
3568
+ const [cmd, ...rest] = argv;
3569
+ if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
3570
+ console.log(USAGE);
3571
+ return cmd ? 0 : 1;
3572
+ }
3573
+ if (cmd === "-v" || cmd === "--version") {
3574
+ console.log(VERSION);
3575
+ return 0;
3576
+ }
3577
+ if (cmd !== "run") {
3578
+ console.error(`Unknown command '${cmd}'.
3579
+ `);
3580
+ console.log(USAGE);
3581
+ return 1;
3582
+ }
3583
+ if (rest.includes("-h") || rest.includes("--help")) {
3584
+ console.log(USAGE);
3585
+ return 0;
3586
+ }
3587
+ try {
3588
+ return await run(parseArgs(rest));
3589
+ } catch (err) {
3590
+ if (err instanceof CliError) {
3591
+ console.error(`error: ${err.message}`);
3592
+ return 1;
3593
+ }
3594
+ throw err;
3595
+ }
3596
+ }
3597
+
3598
+ // cli/src/bin.ts
3599
+ main(process.argv.slice(2)).then(
3600
+ (code) => {
3601
+ process.exitCode = code;
3602
+ },
3603
+ (err) => {
3604
+ console.error(err instanceof Error ? err.stack ?? err.message : err);
3605
+ process.exitCode = 1;
3606
+ }
3607
+ );