apitestcenter 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD-PARTY-NOTICES-UI.txt +1 -1
- package/dist/THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/cli.mjs +722 -388
- package/dist/ui/404/index.html +1 -1
- package/dist/ui/404.html +1 -1
- package/dist/ui/__next.__PAGE__.txt +11 -11
- package/dist/ui/__next._full.txt +12 -12
- package/dist/ui/__next._tree.txt +2 -2
- package/dist/ui/_next/static/chunks/0gi0-hmzkqhl_.js +9 -0
- package/dist/ui/_next/static/chunks/0k57pe238zzo_.js +119 -0
- package/dist/ui/_next/static/chunks/1xkm-ij6841fr.js +19 -0
- package/dist/ui/_next/static/chunks/2alsmi687ind7.css +3 -0
- package/dist/ui/_next/static/chunks/3nzrtm85qus90.js +1 -0
- package/dist/ui/_next/static/chunks/{388t7tbteqx_j.js → 3rlhnherpqpp5.js} +73 -73
- package/dist/ui/_not-found/__next._full.txt +11 -11
- package/dist/ui/_not-found/__next._not-found.__PAGE__.txt +10 -10
- package/dist/ui/_not-found/__next._tree.txt +2 -2
- package/dist/ui/_not-found/index.html +1 -1
- package/dist/ui/_not-found/index.txt +11 -11
- package/dist/ui/compare/__next._full.txt +12 -12
- package/dist/ui/compare/__next._tree.txt +2 -2
- package/dist/ui/compare/__next.compare.__PAGE__.txt +11 -11
- package/dist/ui/compare/index.html +1 -1
- package/dist/ui/compare/index.txt +12 -12
- package/dist/ui/history/__next._full.txt +13 -13
- package/dist/ui/history/__next._tree.txt +2 -2
- package/dist/ui/history/__next.history.__PAGE__.txt +12 -12
- package/dist/ui/history/index.html +1 -1
- package/dist/ui/history/index.txt +13 -13
- package/dist/ui/index.html +1 -1
- package/dist/ui/index.txt +12 -12
- package/dist/ui/preview/__next._full.txt +13 -13
- package/dist/ui/preview/__next._tree.txt +2 -2
- package/dist/ui/preview/__next.preview.__PAGE__.txt +12 -12
- package/dist/ui/preview/index.html +1 -1
- package/dist/ui/preview/index.txt +13 -13
- package/dist/ui/suite/__next._full.txt +12 -12
- package/dist/ui/suite/__next._tree.txt +2 -2
- package/dist/ui/suite/__next.suite.__PAGE__.txt +11 -11
- package/dist/ui/suite/index.html +1 -1
- package/dist/ui/suite/index.txt +12 -12
- package/dist/ui/tests/__next._full.txt +11 -11
- package/dist/ui/tests/__next._tree.txt +2 -2
- package/dist/ui/tests/__next.tests.__PAGE__.txt +10 -10
- package/dist/ui/tests/index.html +1 -1
- package/dist/ui/tests/index.txt +11 -11
- package/package.json +1 -1
- package/dist/ui/_next/static/chunks/1-6_oo-9egb_t.js +0 -1
- package/dist/ui/_next/static/chunks/1_537szc_614o.js +0 -1
- package/dist/ui/_next/static/chunks/1_q4bnjjia0y5.js +0 -19
- package/dist/ui/_next/static/chunks/3_q1wiq483s-m.css +0 -3
- package/dist/ui/_next/static/chunks/3jywp_61ylz0z.js +0 -119
- /package/dist/ui/_next/static/{Tiehe_bgMIxGzSdltdXXB → 9J5wOX3Ij2I3Gph37vpQ-}/_buildManifest.js +0 -0
- /package/dist/ui/_next/static/{Tiehe_bgMIxGzSdltdXXB → 9J5wOX3Ij2I3Gph37vpQ-}/_clientMiddlewareManifest.js +0 -0
- /package/dist/ui/_next/static/{Tiehe_bgMIxGzSdltdXXB → 9J5wOX3Ij2I3Gph37vpQ-}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -16866,7 +16866,7 @@ function loadFromLabKnobs(knobs) {
|
|
|
16866
16866
|
...extras
|
|
16867
16867
|
};
|
|
16868
16868
|
}
|
|
16869
|
-
var httpMethodSchema, durationSchema, headerNameSchema, keyValueSchema, requestHeaderSchema, bodySchema, httpRequestSchema, loadStageSchema, LOAD_EXECUTORS, loadConfigSchema, runTargetsSchema, thresholdSchema, httpClientOptionsSchema, oauthGrants, oauthConfigSchema, sessionAuthSchema, VARIABLE_NAME, variableNameSchema, extractRuleSchema, setupStepSchema, testDataSchema, testDefinitionSchema, requestScriptsSchema, labRequestSchema, labRunSchema, loadExtras, LOAD_PRESETS, LOAD_KNOB_PRESETS;
|
|
16869
|
+
var httpMethodSchema, durationSchema, headerNameSchema, keyValueSchema, requestHeaderSchema, fileRefSchema, multipartPartSchema, bodySchema, httpRequestSchema, loadStageSchema, LOAD_EXECUTORS, loadConfigSchema, runTargetsSchema, thresholdSchema, httpClientOptionsSchema, oauthGrants, oauthConfigSchema, sessionAuthSchema, VARIABLE_NAME, variableNameSchema, extractRuleSchema, setupStepSchema, testDataSchema, testDefinitionSchema, requestScriptsSchema, labRequestSchema, labRunSchema, loadExtras, LOAD_PRESETS, LOAD_KNOB_PRESETS;
|
|
16870
16870
|
var init_definition = __esm({
|
|
16871
16871
|
"../shared/src/definition.ts"() {
|
|
16872
16872
|
"use strict";
|
|
@@ -16894,6 +16894,16 @@ var init_definition = __esm({
|
|
|
16894
16894
|
key: headerNameSchema,
|
|
16895
16895
|
value: external_exports.string()
|
|
16896
16896
|
});
|
|
16897
|
+
fileRefSchema = external_exports.object({
|
|
16898
|
+
id: external_exports.string().regex(/^[a-z0-9-]{8,64}$/, "not a file id"),
|
|
16899
|
+
name: external_exports.string().min(1).max(255),
|
|
16900
|
+
size: external_exports.number().int().nonnegative(),
|
|
16901
|
+
contentType: external_exports.string().max(200).default("application/octet-stream")
|
|
16902
|
+
});
|
|
16903
|
+
multipartPartSchema = external_exports.discriminatedUnion("kind", [
|
|
16904
|
+
external_exports.object({ kind: external_exports.literal("text"), key: external_exports.string().min(1), value: external_exports.string() }).strict(),
|
|
16905
|
+
external_exports.object({ kind: external_exports.literal("file"), key: external_exports.string().min(1), file: fileRefSchema }).strict()
|
|
16906
|
+
]);
|
|
16897
16907
|
bodySchema = external_exports.discriminatedUnion("mode", [
|
|
16898
16908
|
external_exports.object({ mode: external_exports.literal("none") }).strict(),
|
|
16899
16909
|
external_exports.object({ mode: external_exports.literal("json"), json: external_exports.unknown() }).strict(),
|
|
@@ -16906,6 +16916,17 @@ var init_definition = __esm({
|
|
|
16906
16916
|
mode: external_exports.literal("raw"),
|
|
16907
16917
|
raw: external_exports.string(),
|
|
16908
16918
|
contentType: external_exports.string().min(1)
|
|
16919
|
+
}).strict(),
|
|
16920
|
+
// A form with files (multipart/form-data): text fields and files, in order.
|
|
16921
|
+
external_exports.object({
|
|
16922
|
+
mode: external_exports.literal("multipart"),
|
|
16923
|
+
parts: external_exports.array(multipartPartSchema).max(100)
|
|
16924
|
+
}).strict(),
|
|
16925
|
+
// One file as the whole body (an image, a PDF, a ZIP…).
|
|
16926
|
+
external_exports.object({
|
|
16927
|
+
mode: external_exports.literal("file"),
|
|
16928
|
+
file: fileRefSchema,
|
|
16929
|
+
contentType: external_exports.string().min(1)
|
|
16909
16930
|
}).strict()
|
|
16910
16931
|
]);
|
|
16911
16932
|
httpRequestSchema = external_exports.object({
|
|
@@ -17203,7 +17224,7 @@ function fillLab(lab, vars2, { builtins = true } = {}) {
|
|
|
17203
17224
|
const deep = (v) => fillDeep(v, vars2, builtins);
|
|
17204
17225
|
const { request: request3, auth } = lab;
|
|
17205
17226
|
const body = request3.body;
|
|
17206
|
-
const filledBody = body.mode === "json" ? { ...body, json: deep(body.json) } : body.mode === "text" ? { ...body, text: raw(body.text) } : body.mode === "form" ? { ...body, fields: body.fields.map((f) => ({ key: raw(f.key), value: raw(f.value) })) } : body.mode === "raw" ? { ...body, raw: raw(body.raw) } : body;
|
|
17227
|
+
const filledBody = body.mode === "json" ? { ...body, json: deep(body.json) } : body.mode === "text" ? { ...body, text: raw(body.text) } : body.mode === "form" ? { ...body, fields: body.fields.map((f) => ({ key: raw(f.key), value: raw(f.value) })) } : body.mode === "raw" ? { ...body, raw: raw(body.raw) } : body.mode === "multipart" ? { ...body, parts: body.parts.map((p) => p.kind === "text" ? { ...p, key: raw(p.key), value: raw(p.value) } : { ...p, key: raw(p.key) }) } : body;
|
|
17207
17228
|
const filledAuth = auth.mode === "bearer" ? { ...auth, token: raw(auth.token) } : auth.mode === "basic" ? { ...auth, username: raw(auth.username), password: raw(auth.password) } : auth.mode === "api_key" ? { ...auth, name: raw(auth.name), value: raw(auth.value) } : auth;
|
|
17208
17229
|
return {
|
|
17209
17230
|
...lab,
|
|
@@ -20740,9 +20761,9 @@ var require_stringifyDocument = __commonJS({
|
|
|
20740
20761
|
const lines = [];
|
|
20741
20762
|
let hasDirectives = options.directives === true;
|
|
20742
20763
|
if (options.directives !== false && doc.directives) {
|
|
20743
|
-
const
|
|
20744
|
-
if (
|
|
20745
|
-
lines.push(
|
|
20764
|
+
const dir3 = doc.directives.toString(doc);
|
|
20765
|
+
if (dir3) {
|
|
20766
|
+
lines.push(dir3);
|
|
20746
20767
|
hasDirectives = true;
|
|
20747
20768
|
} else if (doc.directives.docStart)
|
|
20748
20769
|
hasDirectives = true;
|
|
@@ -24856,6 +24877,9 @@ function defaultRequestDraft() {
|
|
|
24856
24877
|
graphqlOperation: "",
|
|
24857
24878
|
graphqlErrorsFail: true,
|
|
24858
24879
|
bodyText: "",
|
|
24880
|
+
formRows: [],
|
|
24881
|
+
bodyFile: null,
|
|
24882
|
+
bodyFileType: "",
|
|
24859
24883
|
authMode: "inherit",
|
|
24860
24884
|
oauth: defaultOAuth(),
|
|
24861
24885
|
oauth1: defaultOAuth1(),
|
|
@@ -24905,6 +24929,17 @@ function checksFrom(value) {
|
|
|
24905
24929
|
return parsed.success ? [parsed.data] : [];
|
|
24906
24930
|
});
|
|
24907
24931
|
}
|
|
24932
|
+
function fileFrom(value) {
|
|
24933
|
+
const parsed = fileRefSchema.safeParse(value);
|
|
24934
|
+
return parsed.success ? parsed.data : null;
|
|
24935
|
+
}
|
|
24936
|
+
function formRowsFrom(value) {
|
|
24937
|
+
if (!Array.isArray(value)) return [];
|
|
24938
|
+
return value.slice(0, 100).map((row) => {
|
|
24939
|
+
const item = row ?? {};
|
|
24940
|
+
return { key: typeof item.key === "string" ? item.key : "", value: typeof item.value === "string" ? item.value : "", kind: item.kind === "file" ? "file" : "text", file: fileFrom(item.file) };
|
|
24941
|
+
});
|
|
24942
|
+
}
|
|
24908
24943
|
function rows(value) {
|
|
24909
24944
|
if (!Array.isArray(value)) return [];
|
|
24910
24945
|
return value.map((row) => {
|
|
@@ -24919,7 +24954,7 @@ function requestFromUnknown(value) {
|
|
|
24919
24954
|
const base = defaultRequestDraft();
|
|
24920
24955
|
if (!value || typeof value !== "object") return base;
|
|
24921
24956
|
const item = value;
|
|
24922
|
-
const bodyMode =
|
|
24957
|
+
const bodyMode = BODY_MODES.includes(item.bodyMode) ? item.bodyMode : "none";
|
|
24923
24958
|
const authMode2 = item.authMode === "bearer" || item.authMode === "basic" || item.authMode === "api_key" || item.authMode === "inherit" || item.authMode === "oauth2" || item.authMode === "digest" || item.authMode === "ntlm" || item.authMode === "aws_sigv4" || item.authMode === "jwt" || item.authMode === "oauth1" ? item.authMode : item.authMode === void 0 ? "inherit" : "none";
|
|
24924
24959
|
return {
|
|
24925
24960
|
method: METHODS.includes(item.method) ? item.method : "GET",
|
|
@@ -24930,6 +24965,9 @@ function requestFromUnknown(value) {
|
|
|
24930
24965
|
headers: rows(item.headers),
|
|
24931
24966
|
bodyMode,
|
|
24932
24967
|
bodyText: str(item.bodyText, ""),
|
|
24968
|
+
formRows: formRowsFrom(item.formRows),
|
|
24969
|
+
bodyFile: fileFrom(item.bodyFile),
|
|
24970
|
+
bodyFileType: str(item.bodyFileType, ""),
|
|
24933
24971
|
graphqlQuery: str(item.graphqlQuery, ""),
|
|
24934
24972
|
graphqlVariables: str(item.graphqlVariables, ""),
|
|
24935
24973
|
graphqlOperation: str(item.graphqlOperation, ""),
|
|
@@ -25085,6 +25123,20 @@ function requestParts(draft) {
|
|
|
25085
25123
|
body = { mode: "json", json: { query: draft.graphqlQuery, variables, ...draft.graphqlOperation.trim() ? { operationName: draft.graphqlOperation.trim() } : {} } };
|
|
25086
25124
|
} else if (hasBody(draft.method) && draft.bodyMode === "text") {
|
|
25087
25125
|
body = { mode: "text", text: draft.bodyText };
|
|
25126
|
+
} else if (hasBody(draft.method) && draft.bodyMode === "form") {
|
|
25127
|
+
body = { mode: "form", fields: draft.formRows.filter((r) => r.kind === "text" && r.key.trim()).map((r) => ({ key: r.key, value: r.value })) };
|
|
25128
|
+
} else if (hasBody(draft.method) && draft.bodyMode === "multipart") {
|
|
25129
|
+
const parts2 = [];
|
|
25130
|
+
for (const r of draft.formRows) {
|
|
25131
|
+
if (!r.key.trim()) continue;
|
|
25132
|
+
if (r.kind === "text") parts2.push({ kind: "text", key: r.key, value: r.value });
|
|
25133
|
+
else if (r.file) parts2.push({ kind: "file", key: r.key, file: r.file });
|
|
25134
|
+
else throw new Error(`Choose a file for "${r.key}" in the body`);
|
|
25135
|
+
}
|
|
25136
|
+
body = { mode: "multipart", parts: parts2 };
|
|
25137
|
+
} else if (hasBody(draft.method) && draft.bodyMode === "file") {
|
|
25138
|
+
if (!draft.bodyFile) throw new Error("Choose the file to send as the body");
|
|
25139
|
+
body = { mode: "file", file: draft.bodyFile, contentType: draft.bodyFileType.trim() || draft.bodyFile.contentType || "application/octet-stream" };
|
|
25088
25140
|
}
|
|
25089
25141
|
return {
|
|
25090
25142
|
request: {
|
|
@@ -25250,7 +25302,7 @@ function oauthWire(o) {
|
|
|
25250
25302
|
assertion: o.assertion.trim()
|
|
25251
25303
|
};
|
|
25252
25304
|
}
|
|
25253
|
-
var METHODS, DEFAULT_LOAD, str, num, TEMPLATED, DURATION;
|
|
25305
|
+
var METHODS, BODY_MODES, DEFAULT_LOAD, str, num, TEMPLATED, DURATION;
|
|
25254
25306
|
var init_draft = __esm({
|
|
25255
25307
|
"../shared/src/draft.ts"() {
|
|
25256
25308
|
"use strict";
|
|
@@ -25263,6 +25315,7 @@ var init_draft = __esm({
|
|
|
25263
25315
|
init_url_parts();
|
|
25264
25316
|
init_workspace();
|
|
25265
25317
|
METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
25318
|
+
BODY_MODES = ["none", "json", "text", "graphql", "form", "multipart", "file"];
|
|
25266
25319
|
DEFAULT_LOAD = { preset: "smoke", ...LOAD_KNOB_PRESETS.smoke };
|
|
25267
25320
|
str = (v, fallback) => typeof v === "string" ? v : fallback;
|
|
25268
25321
|
num = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
@@ -26500,6 +26553,91 @@ var init_graphql = __esm({
|
|
|
26500
26553
|
}
|
|
26501
26554
|
});
|
|
26502
26555
|
|
|
26556
|
+
// ../shared/src/multipart.ts
|
|
26557
|
+
function multipartBoundary() {
|
|
26558
|
+
let s = "";
|
|
26559
|
+
for (let i = 0; i < 24; i++) s += "0123456789abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 36)];
|
|
26560
|
+
return `----ApiTestCenter${s}`;
|
|
26561
|
+
}
|
|
26562
|
+
function quoted(name) {
|
|
26563
|
+
return `"${name.replace(/"/g, "%22").replace(/\r/g, "%0D").replace(/\n/g, "%0A")}"`;
|
|
26564
|
+
}
|
|
26565
|
+
function partHead(part, boundary) {
|
|
26566
|
+
if (part.kind === "text") return `--${boundary}\r
|
|
26567
|
+
Content-Disposition: form-data; name=${quoted(part.key)}\r
|
|
26568
|
+
\r
|
|
26569
|
+
`;
|
|
26570
|
+
return `--${boundary}\r
|
|
26571
|
+
Content-Disposition: form-data; name=${quoted(part.key)}; filename=${quoted(part.file.name)}\r
|
|
26572
|
+
Content-Type: ${part.file.contentType || "application/octet-stream"}\r
|
|
26573
|
+
\r
|
|
26574
|
+
`;
|
|
26575
|
+
}
|
|
26576
|
+
function multipartContentType(boundary) {
|
|
26577
|
+
return `multipart/form-data; boundary=${boundary}`;
|
|
26578
|
+
}
|
|
26579
|
+
function buildMultipart(parts, boundary) {
|
|
26580
|
+
const enc = new TextEncoder();
|
|
26581
|
+
const chunks = [];
|
|
26582
|
+
for (const part of parts) {
|
|
26583
|
+
chunks.push(enc.encode(partHead(part, boundary)));
|
|
26584
|
+
chunks.push(part.kind === "text" ? enc.encode(part.value) : part.bytes);
|
|
26585
|
+
chunks.push(enc.encode("\r\n"));
|
|
26586
|
+
}
|
|
26587
|
+
chunks.push(enc.encode(`--${boundary}--\r
|
|
26588
|
+
`));
|
|
26589
|
+
const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
|
|
26590
|
+
let at = 0;
|
|
26591
|
+
for (const c of chunks) {
|
|
26592
|
+
out.set(c, at);
|
|
26593
|
+
at += c.length;
|
|
26594
|
+
}
|
|
26595
|
+
return out;
|
|
26596
|
+
}
|
|
26597
|
+
function formatBytes(n) {
|
|
26598
|
+
if (n < 1024) return `${n} B`;
|
|
26599
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
26600
|
+
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
26601
|
+
}
|
|
26602
|
+
function multipartPreview(parts, boundary) {
|
|
26603
|
+
let out = "";
|
|
26604
|
+
for (const part of parts) out += partHead(part, boundary) + (part.kind === "text" ? part.value : `<file: ${part.file.name}, ${formatBytes(part.file.size)}>`) + "\r\n";
|
|
26605
|
+
return `${out}--${boundary}--\r
|
|
26606
|
+
`;
|
|
26607
|
+
}
|
|
26608
|
+
function guessContentType(name) {
|
|
26609
|
+
const ext = /\.([a-z0-9]+)$/i.exec(name)?.[1]?.toLowerCase() ?? "";
|
|
26610
|
+
const types = {
|
|
26611
|
+
json: "application/json",
|
|
26612
|
+
xml: "application/xml",
|
|
26613
|
+
pdf: "application/pdf",
|
|
26614
|
+
zip: "application/zip",
|
|
26615
|
+
gz: "application/gzip",
|
|
26616
|
+
csv: "text/csv",
|
|
26617
|
+
txt: "text/plain",
|
|
26618
|
+
html: "text/html",
|
|
26619
|
+
png: "image/png",
|
|
26620
|
+
jpg: "image/jpeg",
|
|
26621
|
+
jpeg: "image/jpeg",
|
|
26622
|
+
gif: "image/gif",
|
|
26623
|
+
webp: "image/webp",
|
|
26624
|
+
svg: "image/svg+xml",
|
|
26625
|
+
mp4: "video/mp4",
|
|
26626
|
+
mp3: "audio/mpeg",
|
|
26627
|
+
wav: "audio/wav",
|
|
26628
|
+
doc: "application/msword",
|
|
26629
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
26630
|
+
xls: "application/vnd.ms-excel",
|
|
26631
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
26632
|
+
};
|
|
26633
|
+
return types[ext] ?? "application/octet-stream";
|
|
26634
|
+
}
|
|
26635
|
+
var init_multipart = __esm({
|
|
26636
|
+
"../shared/src/multipart.ts"() {
|
|
26637
|
+
"use strict";
|
|
26638
|
+
}
|
|
26639
|
+
});
|
|
26640
|
+
|
|
26503
26641
|
// ../shared/src/export-fields.ts
|
|
26504
26642
|
function roundN(n, digits = 2) {
|
|
26505
26643
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -27462,6 +27600,7 @@ var init_src = __esm({
|
|
|
27462
27600
|
init_sigv4();
|
|
27463
27601
|
init_oauth1();
|
|
27464
27602
|
init_graphql();
|
|
27603
|
+
init_multipart();
|
|
27465
27604
|
init_latency_bins();
|
|
27466
27605
|
init_checks3();
|
|
27467
27606
|
init_definition();
|
|
@@ -27527,8 +27666,8 @@ token, password, or key.
|
|
|
27527
27666
|
entries = /* @__PURE__ */ new Map();
|
|
27528
27667
|
loaded = false;
|
|
27529
27668
|
queue = Promise.resolve();
|
|
27530
|
-
constructor(
|
|
27531
|
-
this.dir =
|
|
27669
|
+
constructor(dir3 = resolveDataDir()) {
|
|
27670
|
+
this.dir = dir3;
|
|
27532
27671
|
}
|
|
27533
27672
|
/** Run one mutation at a time, so two saves never race on index.json or a baseline flip. */
|
|
27534
27673
|
serial(task) {
|
|
@@ -27855,6 +27994,61 @@ var init_k6 = __esm({
|
|
|
27855
27994
|
}
|
|
27856
27995
|
});
|
|
27857
27996
|
|
|
27997
|
+
// ../../apps/engine/src/file-store.ts
|
|
27998
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
27999
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
28000
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
28001
|
+
import { join as join4 } from "node:path";
|
|
28002
|
+
function useFileStore(dataDir) {
|
|
28003
|
+
dir = join4(dataDir, "files");
|
|
28004
|
+
}
|
|
28005
|
+
function filesDir() {
|
|
28006
|
+
return dir || join4(resolveDataDir(), "files");
|
|
28007
|
+
}
|
|
28008
|
+
async function saveFile(name, bytes2, contentType) {
|
|
28009
|
+
if (bytes2.length > FILE_MAX) throw Object.assign(new Error("A file can be up to 50 MB"), { statusCode: 413 });
|
|
28010
|
+
const id = randomUUID2();
|
|
28011
|
+
await mkdir2(filesDir(), { recursive: true });
|
|
28012
|
+
await writeFile2(join4(filesDir(), id), bytes2);
|
|
28013
|
+
const clean = name.replace(/[\\/]/g, "_").trim().slice(0, 255) || "file";
|
|
28014
|
+
const type = contentType && contentType !== "application/octet-stream" ? contentType : guessContentType(clean);
|
|
28015
|
+
return { id, name: clean, size: bytes2.length, contentType: type };
|
|
28016
|
+
}
|
|
28017
|
+
function fileBytes(file2) {
|
|
28018
|
+
const path = join4(filesDir(), file2.id);
|
|
28019
|
+
if (!/^[a-z0-9-]{8,64}$/.test(file2.id) || !existsSync2(path)) {
|
|
28020
|
+
throw new Error(`The file "${file2.name}" isn't in this data folder (${filesDir()}) \u2014 choose it again in the Body tab`);
|
|
28021
|
+
}
|
|
28022
|
+
return readFileSync(path);
|
|
28023
|
+
}
|
|
28024
|
+
function encodeMultipart(parts) {
|
|
28025
|
+
const boundary = multipartBoundary();
|
|
28026
|
+
const filled = parts.map((p) => p.kind === "text" ? p : { ...p, bytes: fileBytes(p.file) });
|
|
28027
|
+
return { bytes: Buffer.from(buildMultipart(filled, boundary)), contentType: multipartContentType(boundary), preview: multipartPreview(parts, boundary) };
|
|
28028
|
+
}
|
|
28029
|
+
function registerFileRoutes(api) {
|
|
28030
|
+
api.addContentTypeParser("*", { parseAs: "buffer", bodyLimit: FILE_MAX }, (_req, body, done) => done(null, body));
|
|
28031
|
+
api.post("/files", { bodyLimit: FILE_MAX }, async (request3, reply) => {
|
|
28032
|
+
const body = request3.body;
|
|
28033
|
+
if (!Buffer.isBuffer(body)) return reply.code(400).send({ error: "Send the file's bytes as the body" });
|
|
28034
|
+
try {
|
|
28035
|
+
return await saveFile(String(request3.query.name ?? "file"), body, request3.query.type || void 0);
|
|
28036
|
+
} catch (err) {
|
|
28037
|
+
return reply.code(err.statusCode ?? 500).send({ error: err instanceof Error ? err.message : "Could not keep the file" });
|
|
28038
|
+
}
|
|
28039
|
+
});
|
|
28040
|
+
}
|
|
28041
|
+
var FILE_MAX, dir;
|
|
28042
|
+
var init_file_store = __esm({
|
|
28043
|
+
"../../apps/engine/src/file-store.ts"() {
|
|
28044
|
+
"use strict";
|
|
28045
|
+
init_src();
|
|
28046
|
+
init_history_store();
|
|
28047
|
+
FILE_MAX = 50 * 1024 * 1024;
|
|
28048
|
+
dir = "";
|
|
28049
|
+
}
|
|
28050
|
+
});
|
|
28051
|
+
|
|
27858
28052
|
// ../shared/src/k6-script.ts
|
|
27859
28053
|
function targetUrl(run2) {
|
|
27860
28054
|
const url2 = new URL(run2.request.url);
|
|
@@ -27874,18 +28068,22 @@ function publicHeaders(run2) {
|
|
|
27874
28068
|
if (pair.key) headers[pair.key] = pair.value;
|
|
27875
28069
|
}
|
|
27876
28070
|
const body = run2.request.body;
|
|
28071
|
+
if (body.mode === "multipart") {
|
|
28072
|
+
for (const k of Object.keys(headers)) if (k.toLowerCase() === "content-type") delete headers[k];
|
|
28073
|
+
}
|
|
27877
28074
|
const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
|
|
27878
28075
|
if (!hasContentType) {
|
|
27879
28076
|
if (body.mode === "json") headers["content-type"] = "application/json";
|
|
27880
28077
|
else if (body.mode === "text") headers["content-type"] = "text/plain";
|
|
27881
28078
|
else if (body.mode === "form") headers["content-type"] = "application/x-www-form-urlencoded";
|
|
27882
28079
|
else if (body.mode === "raw") headers["content-type"] = body.contentType;
|
|
28080
|
+
else if (body.mode === "file") headers["content-type"] = body.contentType;
|
|
27883
28081
|
}
|
|
27884
28082
|
return headers;
|
|
27885
28083
|
}
|
|
27886
28084
|
function encodeBody(run2) {
|
|
27887
28085
|
const { body } = run2.request;
|
|
27888
|
-
if (body.mode === "none") return "";
|
|
28086
|
+
if (body.mode === "none" || body.mode === "multipart" || body.mode === "file") return "";
|
|
27889
28087
|
if (body.mode === "json") {
|
|
27890
28088
|
return typeof body.json === "string" ? body.json : JSON.stringify(body.json ?? {});
|
|
27891
28089
|
}
|
|
@@ -28017,6 +28215,35 @@ function generateK6Script(run2, extra2 = {}) {
|
|
|
28017
28215
|
}
|
|
28018
28216
|
const bodyMode = run2.request.body.mode;
|
|
28019
28217
|
env.ATC_BODY_FILL = bodyMode === "json" ? "json" : bodyMode === "form" ? "url" : "raw";
|
|
28218
|
+
const reqBody = run2.request.body;
|
|
28219
|
+
const bytesOf = (id, name) => {
|
|
28220
|
+
const bytes2 = extra2.fileBytes?.[id];
|
|
28221
|
+
if (!bytes2) throw new Error(`The file "${name}" isn't available for the load test`);
|
|
28222
|
+
return bytes2;
|
|
28223
|
+
};
|
|
28224
|
+
const partFiles = [];
|
|
28225
|
+
let bodyBin = false;
|
|
28226
|
+
if (reqBody.mode === "file") {
|
|
28227
|
+
files["body.bin"] = bytesOf(reqBody.file.id, reqBody.file.name);
|
|
28228
|
+
bodyBin = true;
|
|
28229
|
+
} else if (reqBody.mode === "multipart" && run2.auth.mode === "aws_sigv4") {
|
|
28230
|
+
const boundary = multipartBoundary();
|
|
28231
|
+
const parts = reqBody.parts.map((p) => p.kind === "text" ? p : { ...p, bytes: bytesOf(p.file.id, p.file.name) });
|
|
28232
|
+
files["body.bin"] = buildMultipart(parts, boundary);
|
|
28233
|
+
const headers = JSON.parse(env.ATC_HEADERS);
|
|
28234
|
+
headers["content-type"] = multipartContentType(boundary);
|
|
28235
|
+
env.ATC_HEADERS = JSON.stringify(headers);
|
|
28236
|
+
bodyBin = true;
|
|
28237
|
+
} else if (reqBody.mode === "multipart") {
|
|
28238
|
+
const parts = reqBody.parts.map((p, i) => {
|
|
28239
|
+
if (p.kind === "text") return { key: p.key, value: p.value };
|
|
28240
|
+
const name = `file-${i}.bin`;
|
|
28241
|
+
files[name] = bytesOf(p.file.id, p.file.name);
|
|
28242
|
+
partFiles.push(name);
|
|
28243
|
+
return { key: p.key, file: name, name: p.file.name, type: p.file.contentType };
|
|
28244
|
+
});
|
|
28245
|
+
files["parts.json"] = JSON.stringify(parts);
|
|
28246
|
+
}
|
|
28020
28247
|
if (data) {
|
|
28021
28248
|
files["data.json"] = JSON.stringify(data.rows);
|
|
28022
28249
|
env.ATC_DATA_COLUMNS = JSON.stringify(data.columns);
|
|
@@ -28070,7 +28297,13 @@ function __atcKeep(out) {
|
|
|
28070
28297
|
}
|
|
28071
28298
|
` : ""}${run2.auth.mode === "aws_sigv4" ? `
|
|
28072
28299
|
const AWS = JSON.parse(__ENV.ATC_AWS);
|
|
28073
|
-
${SIGV4_JS}` : ""}${
|
|
28300
|
+
${SIGV4_JS}` : ""}${bodyBin ? `
|
|
28301
|
+
// The body: a file, read once.
|
|
28302
|
+
const __atcBodyBin = open("./body.bin", "b");` : ""}${files["parts.json"] ? `
|
|
28303
|
+
// A form with files: the fields, and the files, read once.
|
|
28304
|
+
const __atcParts = JSON.parse(open("./parts.json"));
|
|
28305
|
+
const __atcPartFiles = {};
|
|
28306
|
+
${partFiles.map((n) => `__atcPartFiles[${JSON.stringify(n)}] = open(${JSON.stringify(`./${n}`)}, "b");`).join("\n")}` : ""}${run2.auth.mode === "oauth1" ? `
|
|
28074
28307
|
const OAUTH1 = JSON.parse(__ENV.ATC_OAUTH1);
|
|
28075
28308
|
${OAUTH1_JS}` : ""}
|
|
28076
28309
|
const CHECKS = __ENV.ATC_CHECKS_FILE ? JSON.parse(open("./checks.json")) : JSON.parse(__ENV.ATC_CHECKS || "[]");
|
|
@@ -28127,7 +28360,11 @@ export default ${scripts ? "async " : ""}function () {
|
|
|
28127
28360
|
body = out.request.body === undefined ? null : f(out.request.body, __ENV.ATC_BODY_FILL);
|
|
28128
28361
|
if (out.error) check(null, { "pre-request script ran": () => false });
|
|
28129
28362
|
}` : ""}
|
|
28130
|
-
|
|
28363
|
+
${bodyBin ? ` body = __atcBodyBin;
|
|
28364
|
+
` : files["parts.json"] ? ` // Test data and built-in values fill the text fields, for each request.
|
|
28365
|
+
body = {};
|
|
28366
|
+
for (const p of __atcParts) body[f(p.key)] = p.file ? http.file(__atcPartFiles[p.file], p.name, p.type) : f(p.value);
|
|
28367
|
+
` : ""} if (__ENV.ATC_COOKIE) headers.Cookie = __ENV.ATC_COOKIE;
|
|
28131
28368
|
if (__ENV.ATC_TOKEN) headers.Authorization = "Bearer " + f(__atcFreshToken());
|
|
28132
28369
|
if (__ENV.ATC_BASIC_USER !== undefined) headers.Authorization = "Basic " + encoding.b64encode(f(__ENV.ATC_BASIC_USER) + ":" + f(__ENV.ATC_BASIC_PASS || ""));
|
|
28133
28370
|
if (__ENV.ATC_API_NAME && __ENV.ATC_API_KEY) headers[f(__ENV.ATC_API_NAME)] = f(__ENV.ATC_API_KEY);
|
|
@@ -28149,7 +28386,7 @@ export default ${scripts ? "async " : ""}function () {
|
|
|
28149
28386
|
const named = {};
|
|
28150
28387
|
for (const item of CHECKS) named[item.label] = () => __atcCheck(item.check, view);${scripts ? `
|
|
28151
28388
|
if (__atcPost) {
|
|
28152
|
-
const out = await __atcScriptRunAsync(__atcPost, { phase: "post", name: __ENV.ATC_NAME, iteration: __atcIteration, request: { method: method, url: target, headers: [], body: body ===
|
|
28389
|
+
const out = await __atcScriptRunAsync(__atcPost, { phase: "post", name: __ENV.ATC_NAME, iteration: __atcIteration, request: { method: method, url: target, headers: [], body: typeof body === "string" ? body : undefined }, response: { code: res.status, headers: lower, body: view.body || "", responseTime: res.timings.duration }, vars: vars }, __atcHost);
|
|
28153
28390
|
__atcKeep(out);
|
|
28154
28391
|
// Each pm.test is a check; a test that throws in one request fails in that one only.
|
|
28155
28392
|
for (const t of out.tests) named[t.name] = () => t.ok;
|
|
@@ -28178,6 +28415,7 @@ var init_k6_script = __esm({
|
|
|
28178
28415
|
init_scripts();
|
|
28179
28416
|
init_oauth1();
|
|
28180
28417
|
init_sigv4();
|
|
28418
|
+
init_multipart();
|
|
28181
28419
|
init_definition();
|
|
28182
28420
|
}
|
|
28183
28421
|
});
|
|
@@ -32588,9 +32826,9 @@ var require_sha256 = __commonJS({
|
|
|
32588
32826
|
var forge2 = require_forge();
|
|
32589
32827
|
require_md();
|
|
32590
32828
|
require_util();
|
|
32591
|
-
var
|
|
32592
|
-
forge2.md.sha256 = forge2.md.algorithms.sha256 =
|
|
32593
|
-
|
|
32829
|
+
var sha2562 = module.exports = forge2.sha256 = forge2.sha256 || {};
|
|
32830
|
+
forge2.md.sha256 = forge2.md.algorithms.sha256 = sha2562;
|
|
32831
|
+
sha2562.create = function() {
|
|
32594
32832
|
if (!_initialized) {
|
|
32595
32833
|
_init();
|
|
32596
32834
|
}
|
|
@@ -46008,14 +46246,14 @@ var require_lib = __commonJS({
|
|
|
46008
46246
|
});
|
|
46009
46247
|
|
|
46010
46248
|
// ../../apps/engine/src/certificates.ts
|
|
46011
|
-
import { randomUUID as
|
|
46012
|
-
import { mkdir as
|
|
46013
|
-
import { join as
|
|
46249
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
46250
|
+
import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
|
|
46251
|
+
import { join as join5 } from "node:path";
|
|
46014
46252
|
import { X509Certificate } from "node:crypto";
|
|
46015
46253
|
import * as tlsModule from "node:tls";
|
|
46016
46254
|
import { connect as tlsSocket, createSecureContext, rootCertificates } from "node:tls";
|
|
46017
46255
|
function useCertificateStore(dataDir, store3) {
|
|
46018
|
-
|
|
46256
|
+
dir2 = dataDir;
|
|
46019
46257
|
secrets = store3;
|
|
46020
46258
|
}
|
|
46021
46259
|
function addTemporaryCertificate(host2, material) {
|
|
@@ -46039,9 +46277,9 @@ function hostMatches(pattern, url2) {
|
|
|
46039
46277
|
return name === host2;
|
|
46040
46278
|
}
|
|
46041
46279
|
async function readStore() {
|
|
46042
|
-
if (!
|
|
46280
|
+
if (!dir2) return { certificates: [], cas: [] };
|
|
46043
46281
|
try {
|
|
46044
|
-
const raw = JSON.parse(await readFile2(
|
|
46282
|
+
const raw = JSON.parse(await readFile2(join5(dir2, "certificates.json"), "utf8"));
|
|
46045
46283
|
return {
|
|
46046
46284
|
certificates: Array.isArray(raw.certificates) ? raw.certificates.filter((c) => c && typeof c.id === "string" && typeof c.host === "string") : [],
|
|
46047
46285
|
cas: Array.isArray(raw.cas) ? raw.cas.filter((c) => c && typeof c.id === "string" && typeof c.pem === "string") : []
|
|
@@ -46054,10 +46292,10 @@ async function readAll() {
|
|
|
46054
46292
|
return (await readStore()).certificates;
|
|
46055
46293
|
}
|
|
46056
46294
|
async function writeStore(next) {
|
|
46057
|
-
await
|
|
46058
|
-
const file2 =
|
|
46295
|
+
await mkdir3(dir2, { recursive: true });
|
|
46296
|
+
const file2 = join5(dir2, "certificates.json");
|
|
46059
46297
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
46060
|
-
await
|
|
46298
|
+
await writeFile3(tmp, `${JSON.stringify({ version: 1, ...next }, null, 2)}
|
|
46061
46299
|
`, "utf8");
|
|
46062
46300
|
await rename2(tmp, file2);
|
|
46063
46301
|
}
|
|
@@ -46081,7 +46319,7 @@ async function addCas(pemText) {
|
|
|
46081
46319
|
if (!x.ca) throw new Error(`${x.subject.split("\n").find((l) => l.startsWith("CN="))?.slice(3) ?? "That certificate"} is not a certificate authority \u2014 add the CA that issued the server's certificate`);
|
|
46082
46320
|
if (store3.cas.some((c) => c.pem.replace(/\s/g, "") === pem.replace(/\s/g, ""))) continue;
|
|
46083
46321
|
const cn = x.subject.split("\n").find((l) => l.startsWith("CN="))?.slice(3) ?? x.subject.replace(/\n/g, ", ");
|
|
46084
|
-
store3.cas.push({ id:
|
|
46322
|
+
store3.cas.push({ id: randomUUID3(), subject: cn, expires: new Date(x.validTo).toISOString(), pem });
|
|
46085
46323
|
}
|
|
46086
46324
|
await writeStore(store3);
|
|
46087
46325
|
casCache = null;
|
|
@@ -46169,7 +46407,7 @@ async function saveCertificate(input) {
|
|
|
46169
46407
|
if (input.kind === "pem" && (!material.cert || !material.key)) throw new Error("Choose the certificate file and its private key file");
|
|
46170
46408
|
checkMaterial(material);
|
|
46171
46409
|
const all = await readAll();
|
|
46172
|
-
const id = input.id && all.some((c) => c.id === input.id) ? input.id :
|
|
46410
|
+
const id = input.id && all.some((c) => c.id === input.id) ? input.id : randomUUID3();
|
|
46173
46411
|
await secrets.deletePrefix(`cert:${id}:`);
|
|
46174
46412
|
if (material.key) await secrets.set(`cert:${id}:key`, material.key);
|
|
46175
46413
|
if (material.pfx) await secrets.set(`cert:${id}:pfx`, material.pfx.toString("base64"));
|
|
@@ -46288,13 +46526,13 @@ function registerCertificateRoutes(app2) {
|
|
|
46288
46526
|
return { host: m?.host ?? null };
|
|
46289
46527
|
});
|
|
46290
46528
|
}
|
|
46291
|
-
var import_node_forge,
|
|
46529
|
+
var import_node_forge, dir2, secrets, extra, casCache, systemCache, saveBody;
|
|
46292
46530
|
var init_certificates = __esm({
|
|
46293
46531
|
"../../apps/engine/src/certificates.ts"() {
|
|
46294
46532
|
"use strict";
|
|
46295
46533
|
import_node_forge = __toESM(require_lib(), 1);
|
|
46296
46534
|
init_zod();
|
|
46297
|
-
|
|
46535
|
+
dir2 = "";
|
|
46298
46536
|
secrets = null;
|
|
46299
46537
|
extra = [];
|
|
46300
46538
|
casCache = null;
|
|
@@ -61284,7 +61522,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
61284
61522
|
var require_snapshot_recorder = __commonJS({
|
|
61285
61523
|
"../../node_modules/.pnpm/undici@8.10.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
61286
61524
|
"use strict";
|
|
61287
|
-
var { writeFile:
|
|
61525
|
+
var { writeFile: writeFile12, readFile: readFile12, mkdir: mkdir9 } = __require("node:fs/promises");
|
|
61288
61526
|
var { dirname: dirname4, resolve: resolve5 } = __require("node:path");
|
|
61289
61527
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
61290
61528
|
var { InvalidArgumentError, UndiciError } = require_errors2();
|
|
@@ -61531,12 +61769,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
61531
61769
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
61532
61770
|
}
|
|
61533
61771
|
const resolvedPath = resolve5(path);
|
|
61534
|
-
await
|
|
61772
|
+
await mkdir9(dirname4(resolvedPath), { recursive: true });
|
|
61535
61773
|
const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot2]) => ({
|
|
61536
61774
|
hash: hash2,
|
|
61537
61775
|
snapshot: snapshot2
|
|
61538
61776
|
}));
|
|
61539
|
-
await
|
|
61777
|
+
await writeFile12(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
61540
61778
|
}
|
|
61541
61779
|
/**
|
|
61542
61780
|
* Clears all recorded snapshots
|
|
@@ -73430,7 +73668,7 @@ ${captureLines}` : capture.stack;
|
|
|
73430
73668
|
});
|
|
73431
73669
|
|
|
73432
73670
|
// ../../apps/engine/src/oauth.ts
|
|
73433
|
-
import { X509Certificate as X509Certificate2, createHash as createHash2, randomBytes, randomUUID as
|
|
73671
|
+
import { X509Certificate as X509Certificate2, createHash as createHash2, randomBytes, randomUUID as randomUUID4 } from "node:crypto";
|
|
73434
73672
|
import { createServer } from "node:http";
|
|
73435
73673
|
function useOAuthStore(secrets2) {
|
|
73436
73674
|
store2 = secrets2;
|
|
@@ -73473,7 +73711,7 @@ function signedAssertion(c, claims, now) {
|
|
|
73473
73711
|
const x5t = thumbprint(c.certificate);
|
|
73474
73712
|
try {
|
|
73475
73713
|
return signJwt(
|
|
73476
|
-
{ algorithm: c.assertionAlgorithm, key: c.privateKey, payload: JSON.stringify({ jti:
|
|
73714
|
+
{ algorithm: c.assertionAlgorithm, key: c.privateKey, payload: JSON.stringify({ jti: randomUUID4(), ...claims }), lifetimeSec: 300, kid: c.kid, headerName: "", prefix: "" },
|
|
73477
73715
|
now,
|
|
73478
73716
|
x5t ? { x5t } : {}
|
|
73479
73717
|
);
|
|
@@ -73627,7 +73865,7 @@ async function startAuthorization(c) {
|
|
|
73627
73865
|
}
|
|
73628
73866
|
const port2 = Number(redirect.port || 80);
|
|
73629
73867
|
for (const [id2, p] of pending) if (p.port === port2 && p.status === "waiting") stopPending(id2, "replaced by a new sign-in");
|
|
73630
|
-
const id =
|
|
73868
|
+
const id = randomUUID4();
|
|
73631
73869
|
const state = b64url2(randomBytes(24));
|
|
73632
73870
|
const verifier = b64url2(randomBytes(48));
|
|
73633
73871
|
const url2 = new URL(c.authUrl);
|
|
@@ -77972,7 +78210,7 @@ var init_script_sandbox = __esm({
|
|
|
77972
78210
|
});
|
|
77973
78211
|
|
|
77974
78212
|
// ../../apps/engine/src/realtime.ts
|
|
77975
|
-
import { randomUUID as
|
|
78213
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
77976
78214
|
function add(s, e) {
|
|
77977
78215
|
s.events.push({ at: Date.now() - s.started, ...e, text: e.text.length > 1e5 ? `${e.text.slice(0, 1e5)}\u2026 (cut)` : e.text });
|
|
77978
78216
|
if (s.events.length > MAX_EVENTS) {
|
|
@@ -77982,7 +78220,7 @@ function add(s, e) {
|
|
|
77982
78220
|
}
|
|
77983
78221
|
function newSession(kind, url2) {
|
|
77984
78222
|
if (sessions.size >= MAX_SESSIONS) [...sessions.values()].sort((a, b) => a.started - b.started)[0]?.close();
|
|
77985
|
-
const s = { id:
|
|
78223
|
+
const s = { id: randomUUID5(), kind, url: url2, started: Date.now(), status: "connecting", events: [], dropped: 0, lastRead: Date.now(), close: () => void 0 };
|
|
77986
78224
|
sessions.set(s.id, s);
|
|
77987
78225
|
return s;
|
|
77988
78226
|
}
|
|
@@ -78199,6 +78437,11 @@ function applyAuth(headers, url2, auth) {
|
|
|
78199
78437
|
}
|
|
78200
78438
|
function encodeBody2(lab) {
|
|
78201
78439
|
const { body } = lab.request;
|
|
78440
|
+
if (body.mode === "multipart") {
|
|
78441
|
+
const encoded = encodeMultipart(body.parts);
|
|
78442
|
+
return { body: encoded.bytes, contentType: encoded.contentType, preview: encoded.preview, ownType: true };
|
|
78443
|
+
}
|
|
78444
|
+
if (body.mode === "file") return { body: fileBytes(body.file), contentType: body.contentType, preview: `<file: ${body.file.name}, ${formatBytes(body.file.size)}>` };
|
|
78202
78445
|
if (body.mode === "none") return {};
|
|
78203
78446
|
if (body.mode === "json") {
|
|
78204
78447
|
return {
|
|
@@ -78319,8 +78562,21 @@ function scriptSender(lab) {
|
|
|
78319
78562
|
}
|
|
78320
78563
|
function scriptRequest(lab) {
|
|
78321
78564
|
const url2 = fullUrl(lab);
|
|
78565
|
+
const text = bodyText(lab);
|
|
78566
|
+
return { method: lab.request.method, url: url2.toString(), headers: lab.request.headers.map((h) => [h.key, h.value]), ...text !== void 0 ? { body: text } : {} };
|
|
78567
|
+
}
|
|
78568
|
+
function bodyShape(lab) {
|
|
78569
|
+
const { body } = lab.request;
|
|
78570
|
+
if (body.mode === "multipart") return { form: body.parts.map((p) => p.kind === "text" ? { name: p.key, value: p.value } : { name: p.key, file: p.file.name, contentType: p.file.contentType }) };
|
|
78571
|
+
if (body.mode === "file") return { file: { name: body.file.name, contentType: body.contentType } };
|
|
78572
|
+
return void 0;
|
|
78573
|
+
}
|
|
78574
|
+
function bodyText(lab) {
|
|
78575
|
+
const { body } = lab.request;
|
|
78576
|
+
if (body.mode === "multipart") return multipartPreview(body.parts, "----ApiTestCenter");
|
|
78577
|
+
if (body.mode === "file") return `<file: ${body.file.name}, ${formatBytes(body.file.size)}>`;
|
|
78322
78578
|
const encoded = encodeBody2(lab);
|
|
78323
|
-
return
|
|
78579
|
+
return typeof encoded.body === "string" ? encoded.body : void 0;
|
|
78324
78580
|
}
|
|
78325
78581
|
function applyScriptRequest(lab, out, vars2) {
|
|
78326
78582
|
const before = scriptRequest(lab);
|
|
@@ -78330,7 +78586,7 @@ function applyScriptRequest(lab, out, vars2) {
|
|
|
78330
78586
|
let body = lab.request.body;
|
|
78331
78587
|
const newBody = r.body === void 0 ? void 0 : fill(r.body);
|
|
78332
78588
|
if (newBody !== before.body || newBody !== r.body) {
|
|
78333
|
-
const contentType = headers.find((h) => h.key.toLowerCase() === "content-type")?.value ?? encodeBody2(lab).contentType ?? "text/plain";
|
|
78589
|
+
const contentType = headers.find((h) => h.key.toLowerCase() === "content-type")?.value ?? (lab.request.body.mode === "multipart" || lab.request.body.mode === "file" ? void 0 : encodeBody2(lab).contentType) ?? "text/plain";
|
|
78334
78590
|
body = newBody === void 0 ? { mode: "none" } : { mode: "raw", raw: newBody, contentType };
|
|
78335
78591
|
}
|
|
78336
78592
|
const urlChanged = r.url !== before.url;
|
|
@@ -78459,7 +78715,16 @@ async function sendRaw(lab, extra2 = {}) {
|
|
|
78459
78715
|
headers.authorization = extra2.authorization;
|
|
78460
78716
|
secretNames.push("authorization");
|
|
78461
78717
|
}
|
|
78462
|
-
|
|
78718
|
+
let encoded;
|
|
78719
|
+
try {
|
|
78720
|
+
encoded = encodeBody2(lab);
|
|
78721
|
+
} catch (err) {
|
|
78722
|
+
const error51 = err instanceof Error ? err.message : String(err);
|
|
78723
|
+
return { raw: "", lowered: {}, result: { ok: false, status: 0, statusText: "error", headers: {}, body: "", truncated: false, durationMs: 0, url: url2.toString(), error: error51 } };
|
|
78724
|
+
}
|
|
78725
|
+
if (encoded.ownType) {
|
|
78726
|
+
for (const k of Object.keys(headers)) if (k.toLowerCase() === "content-type") delete headers[k];
|
|
78727
|
+
}
|
|
78463
78728
|
if (encoded.contentType && !Object.keys(headers).some((k) => k.toLowerCase() === "content-type")) {
|
|
78464
78729
|
headers["content-type"] = encoded.contentType;
|
|
78465
78730
|
}
|
|
@@ -78467,7 +78732,7 @@ async function sendRaw(lab, extra2 = {}) {
|
|
|
78467
78732
|
if (lab.auth.mode === "oauth1") {
|
|
78468
78733
|
const { mode: _mode, ...creds } = lab.auth;
|
|
78469
78734
|
const contentType = Object.entries(headers).find(([k]) => k.toLowerCase() === "content-type")?.[1];
|
|
78470
|
-
const signed = signOAuth1(creds, { method: lab.request.method, url: url2.toString(), contentType, body: encoded.body });
|
|
78735
|
+
const signed = signOAuth1(creds, { method: lab.request.method, url: url2.toString(), contentType, body: typeof encoded.body === "string" ? encoded.body : void 0 });
|
|
78471
78736
|
if (signed.authorization) {
|
|
78472
78737
|
headers.authorization = signed.authorization;
|
|
78473
78738
|
secretNames.push("authorization");
|
|
@@ -78502,7 +78767,7 @@ async function sendRaw(lab, extra2 = {}) {
|
|
|
78502
78767
|
const jar2 = await cookieHeader(current);
|
|
78503
78768
|
if (jar2) sendHeaders.cookie = jar2;
|
|
78504
78769
|
}
|
|
78505
|
-
if (!sent) sent = describeSent(method, current, sendHeaders, body, [...secretNames, ...ALWAYS_SECRET], secretQuery, options.tlsSkipVerify);
|
|
78770
|
+
if (!sent) sent = describeSent(method, current, sendHeaders, body, [...secretNames, ...ALWAYS_SECRET], secretQuery, options.tlsSkipVerify, encoded.preview, bodyShape(lab));
|
|
78506
78771
|
else (sent.redirects ??= []).push(`${method} ${current.toString()}`);
|
|
78507
78772
|
response = await (0, import_undici3.request)(current, { method, headers: sendHeaders, body, headersTimeout: timeout, bodyTimeout: timeout, dispatcher });
|
|
78508
78773
|
const setCookie = response.headers["set-cookie"];
|
|
@@ -78594,14 +78859,16 @@ async function sendRaw(lab, extra2 = {}) {
|
|
|
78594
78859
|
if (!extra2.dispatcher && !keepOpen) await dispatcher.close();
|
|
78595
78860
|
}
|
|
78596
78861
|
}
|
|
78597
|
-
function describeSent(method, url2, headers,
|
|
78862
|
+
function describeSent(method, url2, headers, bytes2, secretHeaders, secretQuery, insecure, preview, shape) {
|
|
78598
78863
|
const list = [["host", url2.host], ...Object.entries(headers)];
|
|
78599
|
-
if (
|
|
78864
|
+
if (bytes2 !== void 0) list.push(["content-length", String(typeof bytes2 === "string" ? Buffer.byteLength(bytes2) : bytes2.length)]);
|
|
78865
|
+
const body = bytes2 === void 0 ? void 0 : preview ?? (typeof bytes2 === "string" ? bytes2 : `<${bytes2.length} bytes>`);
|
|
78600
78866
|
return {
|
|
78601
78867
|
method,
|
|
78602
78868
|
url: url2.toString(),
|
|
78603
78869
|
headers: list,
|
|
78604
78870
|
...body !== void 0 ? { body: body.length > BODY_LIMIT ? body.slice(0, BODY_LIMIT) : body, bodyTruncated: body.length > BODY_LIMIT || void 0 } : {},
|
|
78871
|
+
...shape ?? {},
|
|
78605
78872
|
secretHeaders: [...new Set(secretHeaders.map((h) => h.toLowerCase()))],
|
|
78606
78873
|
secretQuery,
|
|
78607
78874
|
// Only https has a certificate to skip.
|
|
@@ -78623,6 +78890,7 @@ var init_probe = __esm({
|
|
|
78623
78890
|
init_oauth();
|
|
78624
78891
|
init_script_sandbox();
|
|
78625
78892
|
init_realtime();
|
|
78893
|
+
init_file_store();
|
|
78626
78894
|
BODY_LIMIT = 64 * 1024;
|
|
78627
78895
|
IMAGE_LIMIT = 5 * 1024 * 1024;
|
|
78628
78896
|
ALWAYS_SECRET = ["authorization", "proxy-authorization", "cookie", "x-api-key", "api-key", "x-amz-security-token"];
|
|
@@ -78896,9 +79164,9 @@ var init_k6_metrics = __esm({
|
|
|
78896
79164
|
|
|
78897
79165
|
// ../../apps/engine/src/load-run.ts
|
|
78898
79166
|
import { spawn as spawn2 } from "node:child_process";
|
|
78899
|
-
import { mkdtemp, readFile as readFile3, rm, writeFile as
|
|
79167
|
+
import { mkdtemp, readFile as readFile3, rm, writeFile as writeFile4 } from "node:fs/promises";
|
|
78900
79168
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
78901
|
-
import { join as
|
|
79169
|
+
import { join as join6 } from "node:path";
|
|
78902
79170
|
function onRunFinished(listener) {
|
|
78903
79171
|
finishedListeners.push(listener);
|
|
78904
79172
|
}
|
|
@@ -79197,9 +79465,9 @@ async function startRun(lab, repoRoot2) {
|
|
|
79197
79465
|
filled = fillLab(lab, step.vars, { builtins: false });
|
|
79198
79466
|
}
|
|
79199
79467
|
const id = crypto.randomUUID();
|
|
79200
|
-
const
|
|
79201
|
-
const scriptPath =
|
|
79202
|
-
const summaryPath =
|
|
79468
|
+
const dir3 = await mkdtemp(join6(tmpdir2(), "atc-run-"));
|
|
79469
|
+
const scriptPath = join6(dir3, "script.js");
|
|
79470
|
+
const summaryPath = join6(dir3, "summary.json");
|
|
79203
79471
|
let refresh = null;
|
|
79204
79472
|
if (filled.auth.mode === "oauth2") {
|
|
79205
79473
|
const config2 = filled.auth.oauth;
|
|
@@ -79233,17 +79501,24 @@ async function startRun(lab, repoRoot2) {
|
|
|
79233
79501
|
if (bundle && process.platform !== "linux" && !filled.options.tlsSkipVerify && await needsTrustedCa(filled.request.url)) {
|
|
79234
79502
|
filled = { ...filled, options: { ...filled.options, tlsSkipVerify: true } };
|
|
79235
79503
|
}
|
|
79236
|
-
const
|
|
79504
|
+
const fileBytesById = {};
|
|
79505
|
+
const body = filled.request.body;
|
|
79506
|
+
try {
|
|
79507
|
+
for (const file2 of body.mode === "file" ? [body.file] : body.mode === "multipart" ? body.parts.flatMap((p) => p.kind === "file" ? [p.file] : []) : []) fileBytesById[file2.id] = fileBytes(file2);
|
|
79508
|
+
} catch (err) {
|
|
79509
|
+
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { statusCode: 422 });
|
|
79510
|
+
}
|
|
79511
|
+
const generated = generateK6Script(filled, { oauthRefresh: refresh, tlsAuth, jwt: jwt2, fileBytes: fileBytesById });
|
|
79237
79512
|
if (filled.options.cookies !== false && !filled.request.headers.some((h) => h.key.toLowerCase() === "cookie")) {
|
|
79238
79513
|
const jar2 = await cookieHeader(filled.request.url);
|
|
79239
79514
|
if (jar2) generated.env.ATC_COOKIE = jar2;
|
|
79240
79515
|
}
|
|
79241
79516
|
if (bundle && process.platform === "linux") {
|
|
79242
|
-
await
|
|
79243
|
-
generated.env.SSL_CERT_FILE =
|
|
79517
|
+
await writeFile4(join6(dir3, "ca-bundle.pem"), bundle, "utf8");
|
|
79518
|
+
generated.env.SSL_CERT_FILE = join6(dir3, "ca-bundle.pem");
|
|
79244
79519
|
}
|
|
79245
|
-
await
|
|
79246
|
-
for (const [name,
|
|
79520
|
+
await writeFile4(scriptPath, generated.script, "utf8");
|
|
79521
|
+
for (const [name, content] of Object.entries(generated.files)) await writeFile4(join6(dir3, name), content);
|
|
79247
79522
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
79248
79523
|
const run2 = {
|
|
79249
79524
|
id,
|
|
@@ -79251,7 +79526,7 @@ async function startRun(lab, repoRoot2) {
|
|
|
79251
79526
|
status: "running",
|
|
79252
79527
|
startedAt,
|
|
79253
79528
|
child: null,
|
|
79254
|
-
dir:
|
|
79529
|
+
dir: dir3,
|
|
79255
79530
|
summaryPath,
|
|
79256
79531
|
aggregator: Object.assign(new LiveAggregator(Date.parse(startedAt)), { skipChallenges: lab.auth.mode === "digest" || lab.auth.mode === "ntlm" }),
|
|
79257
79532
|
report: {
|
|
@@ -79306,7 +79581,7 @@ async function startRun(lab, repoRoot2) {
|
|
|
79306
79581
|
scriptPath
|
|
79307
79582
|
],
|
|
79308
79583
|
{
|
|
79309
|
-
cwd:
|
|
79584
|
+
cwd: dir3,
|
|
79310
79585
|
env: { ...process.env, ...generated.env },
|
|
79311
79586
|
windowsHide: true
|
|
79312
79587
|
}
|
|
@@ -79385,6 +79660,7 @@ var init_load_run = __esm({
|
|
|
79385
79660
|
"../../apps/engine/src/load-run.ts"() {
|
|
79386
79661
|
"use strict";
|
|
79387
79662
|
init_src();
|
|
79663
|
+
init_file_store();
|
|
79388
79664
|
init_k6_script();
|
|
79389
79665
|
init_src();
|
|
79390
79666
|
init_certificates();
|
|
@@ -79403,8 +79679,8 @@ var init_load_run = __esm({
|
|
|
79403
79679
|
|
|
79404
79680
|
// ../../apps/engine/src/secret-store.ts
|
|
79405
79681
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes4 } from "node:crypto";
|
|
79406
|
-
import { readFile as readFile4, rename as rename3, writeFile as
|
|
79407
|
-
import { join as
|
|
79682
|
+
import { readFile as readFile4, rename as rename3, writeFile as writeFile5 } from "node:fs/promises";
|
|
79683
|
+
import { join as join7 } from "node:path";
|
|
79408
79684
|
var SecretStore;
|
|
79409
79685
|
var init_secret_store = __esm({
|
|
79410
79686
|
"../../apps/engine/src/secret-store.ts"() {
|
|
@@ -79414,8 +79690,8 @@ var init_secret_store = __esm({
|
|
|
79414
79690
|
loaded = false;
|
|
79415
79691
|
key;
|
|
79416
79692
|
file;
|
|
79417
|
-
constructor(
|
|
79418
|
-
this.file =
|
|
79693
|
+
constructor(dir3, keyBase64 = process.env.ATC_SECRET_KEY) {
|
|
79694
|
+
this.file = join7(dir3, "secrets.enc");
|
|
79419
79695
|
const key = keyBase64 ? Buffer.from(keyBase64, "base64") : null;
|
|
79420
79696
|
this.key = key && key.length === 32 ? key : null;
|
|
79421
79697
|
}
|
|
@@ -79443,7 +79719,7 @@ var init_secret_store = __esm({
|
|
|
79443
79719
|
const cipher = createCipheriv("aes-256-gcm", this.key, iv);
|
|
79444
79720
|
const body = Buffer.concat([cipher.update(JSON.stringify(Object.fromEntries(this.values)), "utf8"), cipher.final()]);
|
|
79445
79721
|
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
79446
|
-
await
|
|
79722
|
+
await writeFile5(tmp, Buffer.concat([iv, cipher.getAuthTag(), body]));
|
|
79447
79723
|
await rename3(tmp, this.file);
|
|
79448
79724
|
}
|
|
79449
79725
|
async get(id) {
|
|
@@ -79466,9 +79742,9 @@ var init_secret_store = __esm({
|
|
|
79466
79742
|
});
|
|
79467
79743
|
|
|
79468
79744
|
// ../../apps/engine/src/suite-run.ts
|
|
79469
|
-
import { randomUUID as
|
|
79470
|
-
import { mkdir as
|
|
79471
|
-
import { join as
|
|
79745
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
79746
|
+
import { mkdir as mkdir4, readFile as readFile5, rename as rename4, writeFile as writeFile6 } from "node:fs/promises";
|
|
79747
|
+
import { join as join8 } from "node:path";
|
|
79472
79748
|
async function resolveSaved(workspace, saved, vars2, envName, patch = {}) {
|
|
79473
79749
|
const applied = applyFolders({ ...requestFromUnknown(saved.request), ...patch }, await workspace.folderChain(saved.collection));
|
|
79474
79750
|
return resolveDraft(applied.draft, { ...applied.vars, ...vars2 }, envName);
|
|
@@ -79483,7 +79759,7 @@ async function runSuite(workspace, plans, opts, onChange = () => {
|
|
|
79483
79759
|
if (!inCollection.length) throw new StepProblem(opts.collection ? `No saved requests in "${opts.collection}"` : "No saved requests outside a collection");
|
|
79484
79760
|
const ordered = orderSuite(inCollection, await plans.get(opts.collection));
|
|
79485
79761
|
const run2 = {
|
|
79486
|
-
id:
|
|
79762
|
+
id: randomUUID6(),
|
|
79487
79763
|
collection: opts.collection,
|
|
79488
79764
|
environment: env?.name ?? null,
|
|
79489
79765
|
status: "running",
|
|
@@ -79674,8 +79950,8 @@ var init_suite_run = __esm({
|
|
|
79674
79950
|
SuitePlans = class {
|
|
79675
79951
|
file;
|
|
79676
79952
|
queue = Promise.resolve();
|
|
79677
|
-
constructor(
|
|
79678
|
-
this.file =
|
|
79953
|
+
constructor(dir3) {
|
|
79954
|
+
this.file = join8(dir3, "suites.json");
|
|
79679
79955
|
}
|
|
79680
79956
|
async readAll() {
|
|
79681
79957
|
try {
|
|
@@ -79693,9 +79969,9 @@ var init_suite_run = __esm({
|
|
|
79693
79969
|
const task = async () => {
|
|
79694
79970
|
const all = await this.readAll();
|
|
79695
79971
|
const next2 = Object.fromEntries(Object.entries(all).map(([k, v]) => [k === from || k.startsWith(`${from}/`) ? to + k.slice(from.length) : k, v]));
|
|
79696
|
-
await
|
|
79972
|
+
await mkdir4(join8(this.file, ".."), { recursive: true });
|
|
79697
79973
|
const tmp = `${this.file}.tmp`;
|
|
79698
|
-
await
|
|
79974
|
+
await writeFile6(tmp, `${JSON.stringify({ version: 1, collections: next2 }, null, 2)}
|
|
79699
79975
|
`, "utf8");
|
|
79700
79976
|
await rename4(tmp, this.file);
|
|
79701
79977
|
};
|
|
@@ -79707,9 +79983,9 @@ var init_suite_run = __esm({
|
|
|
79707
79983
|
const task = async () => {
|
|
79708
79984
|
const all = await this.readAll();
|
|
79709
79985
|
all[collection] = normalizeSuitePlan(plan);
|
|
79710
|
-
await
|
|
79986
|
+
await mkdir4(join8(this.file, ".."), { recursive: true });
|
|
79711
79987
|
const tmp = `${this.file}.tmp`;
|
|
79712
|
-
await
|
|
79988
|
+
await writeFile6(tmp, `${JSON.stringify({ version: 1, collections: all }, null, 2)}
|
|
79713
79989
|
`, "utf8");
|
|
79714
79990
|
await rename4(tmp, this.file);
|
|
79715
79991
|
return all[collection];
|
|
@@ -79733,9 +80009,9 @@ var init_suite_run = __esm({
|
|
|
79733
80009
|
});
|
|
79734
80010
|
|
|
79735
80011
|
// ../../apps/engine/src/workspace-store.ts
|
|
79736
|
-
import { randomUUID as
|
|
79737
|
-
import { mkdir as
|
|
79738
|
-
import { dirname, join as
|
|
80012
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
80013
|
+
import { mkdir as mkdir5, readdir as readdir2, readFile as readFile6, rename as rename5, rm as rm2, rmdir, stat as stat2, unlink as unlink2, writeFile as writeFile7 } from "node:fs/promises";
|
|
80014
|
+
import { dirname, join as join9, relative, sep } from "node:path";
|
|
79739
80015
|
function putSecret(target, field, value) {
|
|
79740
80016
|
const dot = field.indexOf(".");
|
|
79741
80017
|
if (dot > 0) {
|
|
@@ -79774,11 +80050,11 @@ var init_workspace_store = __esm({
|
|
|
79774
80050
|
root;
|
|
79775
80051
|
envFile;
|
|
79776
80052
|
queue = Promise.resolve();
|
|
79777
|
-
constructor(
|
|
79778
|
-
this.dir =
|
|
80053
|
+
constructor(dir3, secrets2) {
|
|
80054
|
+
this.dir = dir3;
|
|
79779
80055
|
this.secrets = secrets2;
|
|
79780
|
-
this.root =
|
|
79781
|
-
this.envFile =
|
|
80056
|
+
this.root = join9(dir3, "requests");
|
|
80057
|
+
this.envFile = join9(dir3, "environments.json");
|
|
79782
80058
|
}
|
|
79783
80059
|
serial(task) {
|
|
79784
80060
|
const next = this.queue.then(task, task);
|
|
@@ -79797,7 +80073,7 @@ var init_workspace_store = __esm({
|
|
|
79797
80073
|
return;
|
|
79798
80074
|
}
|
|
79799
80075
|
for (const e of entries) {
|
|
79800
|
-
const path =
|
|
80076
|
+
const path = join9(folder, e.name);
|
|
79801
80077
|
if (e.isDirectory() && depth < MAX_FOLDER_DEPTH) {
|
|
79802
80078
|
folders.push(this.pathOf(path));
|
|
79803
80079
|
await walk(path, depth + 1);
|
|
@@ -79811,12 +80087,12 @@ var init_workspace_store = __esm({
|
|
|
79811
80087
|
return (await this.scan()).files;
|
|
79812
80088
|
}
|
|
79813
80089
|
/** A folder on disk → its path, "Orders/Admin". */
|
|
79814
|
-
pathOf(
|
|
79815
|
-
const rel = relative(this.root,
|
|
80090
|
+
pathOf(dir3) {
|
|
80091
|
+
const rel = relative(this.root, dir3);
|
|
79816
80092
|
return rel && rel !== "." ? rel.split(sep).join("/") : "";
|
|
79817
80093
|
}
|
|
79818
80094
|
dirOf(path) {
|
|
79819
|
-
return path ?
|
|
80095
|
+
return path ? join9(this.root, ...path.split("/")) : this.root;
|
|
79820
80096
|
}
|
|
79821
80097
|
async readRequest(path) {
|
|
79822
80098
|
try {
|
|
@@ -79887,7 +80163,7 @@ var init_workspace_store = __esm({
|
|
|
79887
80163
|
const secrets2 = [];
|
|
79888
80164
|
const folders = new Set([...picked.values()].flatMap((f) => folderChain(f.file.collection)));
|
|
79889
80165
|
for (const path of [...folders].sort()) {
|
|
79890
|
-
const file2 =
|
|
80166
|
+
const file2 = join9(this.dirOf(path), FOLDER_FILE);
|
|
79891
80167
|
try {
|
|
79892
80168
|
const text = await readFile6(file2, "utf8");
|
|
79893
80169
|
files.push({ path: relative(this.dir, file2).split(sep).join("/"), text });
|
|
@@ -79918,7 +80194,7 @@ var init_workspace_store = __esm({
|
|
|
79918
80194
|
}
|
|
79919
80195
|
saveRequest(input) {
|
|
79920
80196
|
return this.serial(async () => {
|
|
79921
|
-
const id = input.id ||
|
|
80197
|
+
const id = input.id || randomUUID7();
|
|
79922
80198
|
const existing = input.id ? await this.find(input.id) : null;
|
|
79923
80199
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
79924
80200
|
const request3 = { ...input.request };
|
|
@@ -79935,10 +80211,10 @@ var init_workspace_store = __esm({
|
|
|
79935
80211
|
secretFields: secretFields.length ? secretFields : void 0
|
|
79936
80212
|
};
|
|
79937
80213
|
const folder = this.dirOf(collection);
|
|
79938
|
-
await
|
|
79939
|
-
const path =
|
|
80214
|
+
await mkdir5(folder, { recursive: true });
|
|
80215
|
+
const path = join9(folder, `${safeSegment(file2.name)}_${id.slice(0, 8)}.json`);
|
|
79940
80216
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
79941
|
-
await
|
|
80217
|
+
await writeFile7(tmp, `${JSON.stringify(file2, null, 2)}
|
|
79942
80218
|
`, "utf8");
|
|
79943
80219
|
await rename5(tmp, path);
|
|
79944
80220
|
if (existing && existing.path !== path) await unlink2(existing.path).catch(() => void 0);
|
|
@@ -79957,7 +80233,7 @@ var init_workspace_store = __esm({
|
|
|
79957
80233
|
}
|
|
79958
80234
|
async readFolder(path) {
|
|
79959
80235
|
try {
|
|
79960
|
-
const raw = JSON.parse(await readFile6(
|
|
80236
|
+
const raw = JSON.parse(await readFile6(join9(this.dirOf(path), FOLDER_FILE), "utf8"));
|
|
79961
80237
|
return { ...folderFromUnknown(raw), id: typeof raw.id === "string" ? raw.id : "", secretFields: Array.isArray(raw.secretFields) ? raw.secretFields : void 0 };
|
|
79962
80238
|
} catch {
|
|
79963
80239
|
return null;
|
|
@@ -79991,13 +80267,13 @@ var init_workspace_store = __esm({
|
|
|
79991
80267
|
return this.serial(async () => {
|
|
79992
80268
|
const path = folderPath(input);
|
|
79993
80269
|
if (!path) throw Object.assign(new Error("The top level has no folder settings"), { statusCode: 400 });
|
|
79994
|
-
const
|
|
79995
|
-
await
|
|
80270
|
+
const dir3 = this.dirOf(path);
|
|
80271
|
+
await mkdir5(dir3, { recursive: true });
|
|
79996
80272
|
const settings = folderFromUnknown(raw);
|
|
79997
80273
|
const old = await this.readFolder(path);
|
|
79998
|
-
const id = old?.id ||
|
|
80274
|
+
const id = old?.id || randomUUID7();
|
|
79999
80275
|
await this.secrets.deletePrefix(`folder:${id}:`);
|
|
80000
|
-
const file2 =
|
|
80276
|
+
const file2 = join9(dir3, FOLDER_FILE);
|
|
80001
80277
|
if (isEmptyFolder(settings)) {
|
|
80002
80278
|
await unlink2(file2).catch(() => void 0);
|
|
80003
80279
|
return { path, settings };
|
|
@@ -80013,7 +80289,7 @@ var init_workspace_store = __esm({
|
|
|
80013
80289
|
}
|
|
80014
80290
|
if (secretFields.length) onDisk.secretFields = secretFields;
|
|
80015
80291
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
80016
|
-
await
|
|
80292
|
+
await writeFile7(tmp, `${JSON.stringify(onDisk, null, 2)}
|
|
80017
80293
|
`, "utf8");
|
|
80018
80294
|
await rename5(tmp, file2);
|
|
80019
80295
|
return await this.getFolder(path);
|
|
@@ -80030,7 +80306,7 @@ var init_workspace_store = __esm({
|
|
|
80030
80306
|
if (inFolder(to, from)) throw bad("A folder can't move inside itself");
|
|
80031
80307
|
if (await stat2(this.dirOf(to)).then(() => true, () => false)) throw bad(`There is already a folder "${to}"`);
|
|
80032
80308
|
if (!await stat2(this.dirOf(from)).then(() => true, () => false)) throw Object.assign(new Error(`No folder "${from}"`), { statusCode: 404 });
|
|
80033
|
-
await
|
|
80309
|
+
await mkdir5(dirname(this.dirOf(to)), { recursive: true });
|
|
80034
80310
|
await rename5(this.dirOf(from), this.dirOf(to));
|
|
80035
80311
|
return { from, to };
|
|
80036
80312
|
});
|
|
@@ -80085,9 +80361,9 @@ var init_workspace_store = __esm({
|
|
|
80085
80361
|
}
|
|
80086
80362
|
onDisk.push({ id: env.id, name: env.name, variables });
|
|
80087
80363
|
}
|
|
80088
|
-
await
|
|
80364
|
+
await mkdir5(this.dir, { recursive: true });
|
|
80089
80365
|
const tmp = `${this.envFile}.${process.pid}.tmp`;
|
|
80090
|
-
await
|
|
80366
|
+
await writeFile7(tmp, `${JSON.stringify(onDisk, null, 2)}
|
|
80091
80367
|
`, "utf8");
|
|
80092
80368
|
await rename5(tmp, this.envFile);
|
|
80093
80369
|
return this.environments();
|
|
@@ -80097,6 +80373,247 @@ var init_workspace_store = __esm({
|
|
|
80097
80373
|
}
|
|
80098
80374
|
});
|
|
80099
80375
|
|
|
80376
|
+
// ../../apps/engine/src/practice-live.ts
|
|
80377
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
80378
|
+
function frame(opcode, data) {
|
|
80379
|
+
const len = data.length;
|
|
80380
|
+
const head = len < 126 ? Buffer.from([128 | opcode, len]) : len < 65536 ? Buffer.from([128 | opcode, 126, len >> 8, len & 255]) : Buffer.concat([Buffer.from([128 | opcode, 127]), (() => {
|
|
80381
|
+
const b = Buffer.alloc(8);
|
|
80382
|
+
b.writeBigUInt64BE(BigInt(len));
|
|
80383
|
+
return b;
|
|
80384
|
+
})()]);
|
|
80385
|
+
return Buffer.concat([head, data]);
|
|
80386
|
+
}
|
|
80387
|
+
function echo(req, socket) {
|
|
80388
|
+
const key = req.headers["sec-websocket-key"];
|
|
80389
|
+
if (typeof key !== "string") return socket.destroy();
|
|
80390
|
+
const accept = createHash5("sha1").update(key + GUID).digest("base64");
|
|
80391
|
+
socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
80392
|
+
Upgrade: websocket\r
|
|
80393
|
+
Connection: Upgrade\r
|
|
80394
|
+
Sec-WebSocket-Accept: ${accept}\r
|
|
80395
|
+
\r
|
|
80396
|
+
`);
|
|
80397
|
+
const sendText = (value) => socket.write(frame(1, Buffer.from(JSON.stringify(value))));
|
|
80398
|
+
sendText({ type: "welcome", message: "Practice WebSocket: send a message and it comes back." });
|
|
80399
|
+
let buf = Buffer.alloc(0);
|
|
80400
|
+
socket.on("data", (chunk) => {
|
|
80401
|
+
buf = Buffer.concat([buf, chunk]);
|
|
80402
|
+
for (; ; ) {
|
|
80403
|
+
if (buf.length < 2) return;
|
|
80404
|
+
const opcode = buf[0] & 15;
|
|
80405
|
+
const masked = (buf[1] & 128) !== 0;
|
|
80406
|
+
let len = buf[1] & 127;
|
|
80407
|
+
let at = 2;
|
|
80408
|
+
if (len === 126) {
|
|
80409
|
+
if (buf.length < 4) return;
|
|
80410
|
+
len = buf.readUInt16BE(2);
|
|
80411
|
+
at = 4;
|
|
80412
|
+
} else if (len === 127) {
|
|
80413
|
+
if (buf.length < 10) return;
|
|
80414
|
+
len = Number(buf.readBigUInt64BE(2));
|
|
80415
|
+
at = 10;
|
|
80416
|
+
}
|
|
80417
|
+
if (len > 1e6) return socket.destroy();
|
|
80418
|
+
const mask = masked ? buf.subarray(at, at + 4) : null;
|
|
80419
|
+
at += masked ? 4 : 0;
|
|
80420
|
+
if (buf.length < at + len) return;
|
|
80421
|
+
const payload = Buffer.from(buf.subarray(at, at + len));
|
|
80422
|
+
if (mask) for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i % 4];
|
|
80423
|
+
buf = buf.subarray(at + len);
|
|
80424
|
+
if (opcode === 8) {
|
|
80425
|
+
socket.end(frame(8, payload.subarray(0, 2)));
|
|
80426
|
+
return;
|
|
80427
|
+
}
|
|
80428
|
+
if (opcode === 9) socket.write(frame(10, payload));
|
|
80429
|
+
else if (opcode === 1) {
|
|
80430
|
+
const text = payload.toString("utf8");
|
|
80431
|
+
let parsed = text;
|
|
80432
|
+
try {
|
|
80433
|
+
parsed = JSON.parse(text);
|
|
80434
|
+
} catch {
|
|
80435
|
+
}
|
|
80436
|
+
sendText({ type: "echo", you: parsed, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
80437
|
+
}
|
|
80438
|
+
}
|
|
80439
|
+
});
|
|
80440
|
+
socket.on("error", () => socket.destroy());
|
|
80441
|
+
}
|
|
80442
|
+
function attachPracticeWebSocket(server, path) {
|
|
80443
|
+
server.on("upgrade", (req, socket) => {
|
|
80444
|
+
if ((req.url ?? "").split("?")[0] !== path) return;
|
|
80445
|
+
echo(req, socket);
|
|
80446
|
+
});
|
|
80447
|
+
}
|
|
80448
|
+
function practiceEvents(res, onGone, count = 10, everyMs = 1e3) {
|
|
80449
|
+
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
|
|
80450
|
+
res.write(": practice event stream \u2014 a tick a second\n\n");
|
|
80451
|
+
let n = 0;
|
|
80452
|
+
const timer = setInterval(() => {
|
|
80453
|
+
n++;
|
|
80454
|
+
res.write(`event: tick
|
|
80455
|
+
id: ${n}
|
|
80456
|
+
data: ${JSON.stringify({ n, at: (/* @__PURE__ */ new Date()).toISOString(), price: Math.round((40 + Math.random() * 5) * 100) / 100 })}
|
|
80457
|
+
|
|
80458
|
+
`);
|
|
80459
|
+
if (n >= count) {
|
|
80460
|
+
clearInterval(timer);
|
|
80461
|
+
res.end();
|
|
80462
|
+
}
|
|
80463
|
+
}, everyMs);
|
|
80464
|
+
onGone(() => clearInterval(timer));
|
|
80465
|
+
}
|
|
80466
|
+
var GUID;
|
|
80467
|
+
var init_practice_live = __esm({
|
|
80468
|
+
"../../apps/engine/src/practice-live.ts"() {
|
|
80469
|
+
"use strict";
|
|
80470
|
+
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
80471
|
+
}
|
|
80472
|
+
});
|
|
80473
|
+
|
|
80474
|
+
// ../../apps/engine/src/demo-api.ts
|
|
80475
|
+
import { createHash as createHash6, randomBytes as randomBytes5 } from "node:crypto";
|
|
80476
|
+
function parseMultipart(body, boundary) {
|
|
80477
|
+
const parts = [];
|
|
80478
|
+
const delimiter = Buffer.from(`--${boundary}`);
|
|
80479
|
+
let at = body.indexOf(delimiter);
|
|
80480
|
+
while (at >= 0) {
|
|
80481
|
+
const start = at + delimiter.length;
|
|
80482
|
+
if (body.subarray(start, start + 2).toString() === "--") break;
|
|
80483
|
+
const headEnd = body.indexOf("\r\n\r\n", start);
|
|
80484
|
+
const next = body.indexOf(delimiter, start);
|
|
80485
|
+
if (headEnd < 0 || next < 0) break;
|
|
80486
|
+
const head = body.subarray(start, headEnd).toString("utf8");
|
|
80487
|
+
const param = (key) => {
|
|
80488
|
+
const m = new RegExp(`;\\s*${key}=(?:"([^"]*)"|([^;\\s]+))`, "i").exec(head);
|
|
80489
|
+
return m ? m[1] ?? m[2] : void 0;
|
|
80490
|
+
};
|
|
80491
|
+
const name = param("name");
|
|
80492
|
+
const filename = param("filename");
|
|
80493
|
+
const type = /content-type:\s*([^\r\n]+)/i.exec(head)?.[1]?.trim();
|
|
80494
|
+
if (name !== void 0) parts.push({ name, ...filename !== void 0 ? { filename } : {}, ...type ? { type } : {}, data: body.subarray(headEnd + 4, next - 2) });
|
|
80495
|
+
at = next;
|
|
80496
|
+
}
|
|
80497
|
+
return parts;
|
|
80498
|
+
}
|
|
80499
|
+
function registerDemoApi(api) {
|
|
80500
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
80501
|
+
const created = /* @__PURE__ */ new Map();
|
|
80502
|
+
let nextOrder = 2001;
|
|
80503
|
+
const signedIn = (auth) => {
|
|
80504
|
+
const header = auth ?? "";
|
|
80505
|
+
return header.startsWith("Bearer ") ? tokens.get(header.slice(7)) : void 0;
|
|
80506
|
+
};
|
|
80507
|
+
api.get("/products", async () => {
|
|
80508
|
+
await jitter(5, 25);
|
|
80509
|
+
return { items: PRODUCTS, total: PRODUCTS.length };
|
|
80510
|
+
});
|
|
80511
|
+
api.get("/products/:id", async (request3, reply) => {
|
|
80512
|
+
await jitter(5, 20);
|
|
80513
|
+
const product = PRODUCTS.find((p) => String(p.id) === request3.params.id);
|
|
80514
|
+
if (!product) return reply.code(404).send({ error: `No product ${request3.params.id}. Try 1 to ${PRODUCTS.length}.` });
|
|
80515
|
+
return product;
|
|
80516
|
+
});
|
|
80517
|
+
api.post("/login", async (request3, reply) => {
|
|
80518
|
+
await jitter(30, 40);
|
|
80519
|
+
const username = typeof request3.body?.username === "string" ? request3.body.username.trim() : "";
|
|
80520
|
+
if (!username || request3.body?.password !== "demo") {
|
|
80521
|
+
return reply.code(401).send({ error: 'Use any username with the password "demo".' });
|
|
80522
|
+
}
|
|
80523
|
+
if (tokens.size > 5e3) tokens.clear();
|
|
80524
|
+
const token = `demo-${randomBytes5(12).toString("hex")}`;
|
|
80525
|
+
tokens.set(token, username);
|
|
80526
|
+
return { data: { token, user: username, expiresIn: 3600 } };
|
|
80527
|
+
});
|
|
80528
|
+
api.get("/orders", async (request3, reply) => {
|
|
80529
|
+
await jitter(10, 30);
|
|
80530
|
+
const auth = request3.headers.authorization ?? "";
|
|
80531
|
+
const user = auth.startsWith("Bearer ") ? tokens.get(auth.slice(7)) : void 0;
|
|
80532
|
+
if (!user) return reply.code(401).send({ error: "Sign in first: POST /login, then send the token as a Bearer token." });
|
|
80533
|
+
return {
|
|
80534
|
+
user,
|
|
80535
|
+
orders: [
|
|
80536
|
+
{ id: "A-1001", items: [PRODUCTS[0].name, PRODUCTS[1].name], total: PRODUCTS[0].price + PRODUCTS[1].price },
|
|
80537
|
+
{ id: "A-1002", items: [PRODUCTS[3].name], total: PRODUCTS[3].price }
|
|
80538
|
+
]
|
|
80539
|
+
};
|
|
80540
|
+
});
|
|
80541
|
+
api.post("/orders", async (request3, reply) => {
|
|
80542
|
+
await jitter(15, 30);
|
|
80543
|
+
const user = signedIn(request3.headers.authorization);
|
|
80544
|
+
if (!user) return reply.code(401).send({ error: "Sign in first: POST /login, then send the token as a Bearer token." });
|
|
80545
|
+
const product = PRODUCTS.find((p) => p.id === Number(request3.body?.productId));
|
|
80546
|
+
if (!product) return reply.code(400).send({ error: `Send { "productId": 1 } \u2014 a product id from 1 to ${PRODUCTS.length}.` });
|
|
80547
|
+
const quantity = Math.min(99, Math.max(1, Math.floor(Number(request3.body?.quantity ?? 1)) || 1));
|
|
80548
|
+
if (created.size > 5e3) created.clear();
|
|
80549
|
+
const order = { id: `A-${nextOrder++}`, user, productId: product.id, quantity, total: product.price * quantity, status: "new" };
|
|
80550
|
+
created.set(order.id, order);
|
|
80551
|
+
return reply.code(201).send(order);
|
|
80552
|
+
});
|
|
80553
|
+
api.get("/orders/:id", async (request3, reply) => {
|
|
80554
|
+
await jitter(5, 20);
|
|
80555
|
+
if (!signedIn(request3.headers.authorization)) return reply.code(401).send({ error: "Sign in first: POST /login, then send the token as a Bearer token." });
|
|
80556
|
+
const order = created.get(request3.params.id);
|
|
80557
|
+
if (!order) return reply.code(404).send({ error: `No order ${request3.params.id}. Create one with POST /orders.` });
|
|
80558
|
+
return order;
|
|
80559
|
+
});
|
|
80560
|
+
api.get("/slow", async (request3) => {
|
|
80561
|
+
const ms3 = Math.min(3e3, Math.max(0, Number(request3.query.ms ?? 300) || 0));
|
|
80562
|
+
await jitter(ms3, ms3 * 0.2);
|
|
80563
|
+
return { waitedMs: ms3 };
|
|
80564
|
+
});
|
|
80565
|
+
api.get("/flaky", async (_request, reply) => {
|
|
80566
|
+
await jitter(5, 30);
|
|
80567
|
+
if (Math.random() < 0.1) return reply.code(500).send({ error: "Something went wrong (on purpose, about 1 in 10)." });
|
|
80568
|
+
return { ok: true };
|
|
80569
|
+
});
|
|
80570
|
+
api.addContentTypeParser("*", { parseAs: "buffer", bodyLimit: 20 * 1024 * 1024 }, (_req, body, done) => done(null, body));
|
|
80571
|
+
api.post("/upload", { bodyLimit: 20 * 1024 * 1024 }, async (request3, reply) => {
|
|
80572
|
+
await jitter(10, 20);
|
|
80573
|
+
const type = String(request3.headers["content-type"] ?? "");
|
|
80574
|
+
const raw = Buffer.isBuffer(request3.body) ? request3.body : Buffer.from(typeof request3.body === "string" ? request3.body : JSON.stringify(request3.body ?? ""));
|
|
80575
|
+
const boundary = /^multipart\/form-data;.*boundary="?([^";]+)"?/i.exec(type)?.[1];
|
|
80576
|
+
if (/^multipart\/form-data/i.test(type) && !boundary) return reply.code(400).send({ error: "A multipart/form-data body needs a boundary in its Content-Type \u2014 leave Content-Type out and the app sets it." });
|
|
80577
|
+
if (!boundary) return { kind: "file", contentType: type || null, size: raw.length, sha256: sha256(raw) };
|
|
80578
|
+
const parts = parseMultipart(raw, boundary);
|
|
80579
|
+
if (!parts.length) return reply.code(400).send({ error: "No fields found in the form." });
|
|
80580
|
+
return {
|
|
80581
|
+
kind: "form",
|
|
80582
|
+
fields: Object.fromEntries(parts.filter((p) => p.filename === void 0).map((p) => [p.name, p.data.toString("utf8")])),
|
|
80583
|
+
files: parts.filter((p) => p.filename !== void 0).map((p) => ({ field: p.name, name: p.filename, contentType: p.type ?? null, size: p.data.length, sha256: sha256(p.data) }))
|
|
80584
|
+
};
|
|
80585
|
+
});
|
|
80586
|
+
api.get("/events", async (request3, reply) => {
|
|
80587
|
+
reply.hijack();
|
|
80588
|
+
practiceEvents(reply.raw, (stop) => request3.raw.on("close", stop));
|
|
80589
|
+
});
|
|
80590
|
+
attachPracticeWebSocket(api.server, `${api.prefix}/ws`);
|
|
80591
|
+
}
|
|
80592
|
+
var PRODUCTS, sha256, sleep, jitter;
|
|
80593
|
+
var init_demo_api = __esm({
|
|
80594
|
+
"../../apps/engine/src/demo-api.ts"() {
|
|
80595
|
+
"use strict";
|
|
80596
|
+
init_practice_live();
|
|
80597
|
+
PRODUCTS = [
|
|
80598
|
+
["Desk lamp", 39],
|
|
80599
|
+
["Notebook", 6],
|
|
80600
|
+
["Coffee mug", 12],
|
|
80601
|
+
["Headphones", 129],
|
|
80602
|
+
["Backpack", 74],
|
|
80603
|
+
["Water bottle", 18],
|
|
80604
|
+
["Keyboard", 89],
|
|
80605
|
+
["Plant pot", 15],
|
|
80606
|
+
["Phone stand", 22],
|
|
80607
|
+
["Sunglasses", 58],
|
|
80608
|
+
["Umbrella", 27],
|
|
80609
|
+
["Wall clock", 34]
|
|
80610
|
+
].map(([name, price], i) => ({ id: i + 1, name, price, inStock: i % 4 !== 3 }));
|
|
80611
|
+
sha256 = (data) => createHash6("sha256").update(data).digest("hex");
|
|
80612
|
+
sleep = (ms3) => new Promise((r) => setTimeout(r, ms3));
|
|
80613
|
+
jitter = (base, spread) => sleep(base + Math.random() * spread);
|
|
80614
|
+
}
|
|
80615
|
+
});
|
|
80616
|
+
|
|
80100
80617
|
// ../../apps/engine/src/mock-server.ts
|
|
80101
80618
|
import { createServer as createServer2 } from "node:http";
|
|
80102
80619
|
var MAX_ROUTES, MockServer;
|
|
@@ -88049,7 +88566,7 @@ var require_sonic_boom = __commonJS({
|
|
|
88049
88566
|
if (!(this instanceof SonicBoom)) {
|
|
88050
88567
|
return new SonicBoom(opts);
|
|
88051
88568
|
}
|
|
88052
|
-
let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append = true, mkdir:
|
|
88569
|
+
let { fd, dest, minLength, maxLength, maxWrite, periodicFlush, sync, append = true, mkdir: mkdir9, retryEAGAIN, fsync, contentMode, mode } = opts || {};
|
|
88053
88570
|
fd = fd || dest;
|
|
88054
88571
|
this._len = 0;
|
|
88055
88572
|
this.fd = -1;
|
|
@@ -88074,7 +88591,7 @@ var require_sonic_boom = __commonJS({
|
|
|
88074
88591
|
this.append = append || false;
|
|
88075
88592
|
this.mode = mode;
|
|
88076
88593
|
this.retryEAGAIN = retryEAGAIN || (() => true);
|
|
88077
|
-
this.mkdir =
|
|
88594
|
+
this.mkdir = mkdir9 || false;
|
|
88078
88595
|
let fsWriteSync;
|
|
88079
88596
|
let fsWrite;
|
|
88080
88597
|
if (contentMode === kContentModeBuffer) {
|
|
@@ -88786,7 +89303,7 @@ var require_thread_stream = __commonJS({
|
|
|
88786
89303
|
var { version: version2 } = require_package();
|
|
88787
89304
|
var { EventEmitter } = __require("events");
|
|
88788
89305
|
var { Worker: Worker2 } = __require("worker_threads");
|
|
88789
|
-
var { join:
|
|
89306
|
+
var { join: join14 } = __require("path");
|
|
88790
89307
|
var { pathToFileURL } = __require("url");
|
|
88791
89308
|
var { wait } = require_wait();
|
|
88792
89309
|
var {
|
|
@@ -88837,7 +89354,7 @@ var require_thread_stream = __commonJS({
|
|
|
88837
89354
|
function createWorker(stream, opts) {
|
|
88838
89355
|
const { filename, workerData } = opts;
|
|
88839
89356
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
88840
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
89357
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join14(__dirname, "lib", "worker.js");
|
|
88841
89358
|
const worker = new Worker2(toExecute, {
|
|
88842
89359
|
...opts.workerOpts,
|
|
88843
89360
|
name: opts.workerOpts?.name || "thread-stream",
|
|
@@ -89303,9 +89820,9 @@ var require_transport = __commonJS({
|
|
|
89303
89820
|
"../../node_modules/.pnpm/pino@10.3.1/node_modules/pino/lib/transport.js"(exports, module) {
|
|
89304
89821
|
"use strict";
|
|
89305
89822
|
var { createRequire } = __require("module");
|
|
89306
|
-
var { existsSync:
|
|
89823
|
+
var { existsSync: existsSync4 } = __require("node:fs");
|
|
89307
89824
|
var getCallers = require_caller();
|
|
89308
|
-
var { join:
|
|
89825
|
+
var { join: join14, isAbsolute, sep: sep3 } = __require("node:path");
|
|
89309
89826
|
var { fileURLToPath: fileURLToPath3 } = __require("node:url");
|
|
89310
89827
|
var sleep2 = require_atomic_sleep();
|
|
89311
89828
|
var onExit = require_on_exit_leak_free();
|
|
@@ -89377,7 +89894,7 @@ var require_transport = __commonJS({
|
|
|
89377
89894
|
return false;
|
|
89378
89895
|
}
|
|
89379
89896
|
}
|
|
89380
|
-
return isAbsolute(path) && !
|
|
89897
|
+
return isAbsolute(path) && !existsSync4(path);
|
|
89381
89898
|
}
|
|
89382
89899
|
function stripQuotes(value) {
|
|
89383
89900
|
const first = value[0];
|
|
@@ -89458,7 +89975,7 @@ var require_transport = __commonJS({
|
|
|
89458
89975
|
throw new Error("only one of target or targets can be specified");
|
|
89459
89976
|
}
|
|
89460
89977
|
if (targets) {
|
|
89461
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
89978
|
+
target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
|
|
89462
89979
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
89463
89980
|
return {
|
|
89464
89981
|
...dest,
|
|
@@ -89476,7 +89993,7 @@ var require_transport = __commonJS({
|
|
|
89476
89993
|
});
|
|
89477
89994
|
});
|
|
89478
89995
|
} else if (pipeline) {
|
|
89479
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
89996
|
+
target = bundlerOverrides["pino-worker"] || join14(__dirname, "worker.js");
|
|
89480
89997
|
options.pipelines = [pipeline.map((dest) => {
|
|
89481
89998
|
return {
|
|
89482
89999
|
...dest,
|
|
@@ -89499,7 +90016,7 @@ var require_transport = __commonJS({
|
|
|
89499
90016
|
return origin;
|
|
89500
90017
|
}
|
|
89501
90018
|
if (origin === "pino/file") {
|
|
89502
|
-
return
|
|
90019
|
+
return join14(__dirname, "..", "file.js");
|
|
89503
90020
|
}
|
|
89504
90021
|
let fixTarget2;
|
|
89505
90022
|
for (const filePath of callers) {
|
|
@@ -90479,7 +90996,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
90479
90996
|
return circularValue;
|
|
90480
90997
|
}
|
|
90481
90998
|
let res = "";
|
|
90482
|
-
let
|
|
90999
|
+
let join14 = ",";
|
|
90483
91000
|
const originalIndentation = indentation;
|
|
90484
91001
|
if (Array.isArray(value)) {
|
|
90485
91002
|
if (value.length === 0) {
|
|
@@ -90493,7 +91010,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
90493
91010
|
indentation += spacer;
|
|
90494
91011
|
res += `
|
|
90495
91012
|
${indentation}`;
|
|
90496
|
-
|
|
91013
|
+
join14 = `,
|
|
90497
91014
|
${indentation}`;
|
|
90498
91015
|
}
|
|
90499
91016
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -90501,13 +91018,13 @@ ${indentation}`;
|
|
|
90501
91018
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
90502
91019
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
90503
91020
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
90504
|
-
res +=
|
|
91021
|
+
res += join14;
|
|
90505
91022
|
}
|
|
90506
91023
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
90507
91024
|
res += tmp !== void 0 ? tmp : "null";
|
|
90508
91025
|
if (value.length - 1 > maximumBreadth) {
|
|
90509
91026
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
90510
|
-
res += `${
|
|
91027
|
+
res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
90511
91028
|
}
|
|
90512
91029
|
if (spacer !== "") {
|
|
90513
91030
|
res += `
|
|
@@ -90528,7 +91045,7 @@ ${originalIndentation}`;
|
|
|
90528
91045
|
let separator = "";
|
|
90529
91046
|
if (spacer !== "") {
|
|
90530
91047
|
indentation += spacer;
|
|
90531
|
-
|
|
91048
|
+
join14 = `,
|
|
90532
91049
|
${indentation}`;
|
|
90533
91050
|
whitespace = " ";
|
|
90534
91051
|
}
|
|
@@ -90542,13 +91059,13 @@ ${indentation}`;
|
|
|
90542
91059
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
90543
91060
|
if (tmp !== void 0) {
|
|
90544
91061
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
90545
|
-
separator =
|
|
91062
|
+
separator = join14;
|
|
90546
91063
|
}
|
|
90547
91064
|
}
|
|
90548
91065
|
if (keyLength > maximumBreadth) {
|
|
90549
91066
|
const removedKeys = keyLength - maximumBreadth;
|
|
90550
91067
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
90551
|
-
separator =
|
|
91068
|
+
separator = join14;
|
|
90552
91069
|
}
|
|
90553
91070
|
if (spacer !== "" && separator.length > 1) {
|
|
90554
91071
|
res = `
|
|
@@ -90589,7 +91106,7 @@ ${originalIndentation}`;
|
|
|
90589
91106
|
}
|
|
90590
91107
|
const originalIndentation = indentation;
|
|
90591
91108
|
let res = "";
|
|
90592
|
-
let
|
|
91109
|
+
let join14 = ",";
|
|
90593
91110
|
if (Array.isArray(value)) {
|
|
90594
91111
|
if (value.length === 0) {
|
|
90595
91112
|
return "[]";
|
|
@@ -90602,7 +91119,7 @@ ${originalIndentation}`;
|
|
|
90602
91119
|
indentation += spacer;
|
|
90603
91120
|
res += `
|
|
90604
91121
|
${indentation}`;
|
|
90605
|
-
|
|
91122
|
+
join14 = `,
|
|
90606
91123
|
${indentation}`;
|
|
90607
91124
|
}
|
|
90608
91125
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -90610,13 +91127,13 @@ ${indentation}`;
|
|
|
90610
91127
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
90611
91128
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
90612
91129
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
90613
|
-
res +=
|
|
91130
|
+
res += join14;
|
|
90614
91131
|
}
|
|
90615
91132
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
90616
91133
|
res += tmp !== void 0 ? tmp : "null";
|
|
90617
91134
|
if (value.length - 1 > maximumBreadth) {
|
|
90618
91135
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
90619
|
-
res += `${
|
|
91136
|
+
res += `${join14}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
90620
91137
|
}
|
|
90621
91138
|
if (spacer !== "") {
|
|
90622
91139
|
res += `
|
|
@@ -90629,7 +91146,7 @@ ${originalIndentation}`;
|
|
|
90629
91146
|
let whitespace = "";
|
|
90630
91147
|
if (spacer !== "") {
|
|
90631
91148
|
indentation += spacer;
|
|
90632
|
-
|
|
91149
|
+
join14 = `,
|
|
90633
91150
|
${indentation}`;
|
|
90634
91151
|
whitespace = " ";
|
|
90635
91152
|
}
|
|
@@ -90638,7 +91155,7 @@ ${indentation}`;
|
|
|
90638
91155
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
90639
91156
|
if (tmp !== void 0) {
|
|
90640
91157
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
90641
|
-
separator =
|
|
91158
|
+
separator = join14;
|
|
90642
91159
|
}
|
|
90643
91160
|
}
|
|
90644
91161
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -90696,20 +91213,20 @@ ${originalIndentation}`;
|
|
|
90696
91213
|
indentation += spacer;
|
|
90697
91214
|
let res2 = `
|
|
90698
91215
|
${indentation}`;
|
|
90699
|
-
const
|
|
91216
|
+
const join15 = `,
|
|
90700
91217
|
${indentation}`;
|
|
90701
91218
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
90702
91219
|
let i = 0;
|
|
90703
91220
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
90704
91221
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
90705
91222
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
90706
|
-
res2 +=
|
|
91223
|
+
res2 += join15;
|
|
90707
91224
|
}
|
|
90708
91225
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
90709
91226
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
90710
91227
|
if (value.length - 1 > maximumBreadth) {
|
|
90711
91228
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
90712
|
-
res2 += `${
|
|
91229
|
+
res2 += `${join15}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
90713
91230
|
}
|
|
90714
91231
|
res2 += `
|
|
90715
91232
|
${originalIndentation}`;
|
|
@@ -90725,16 +91242,16 @@ ${originalIndentation}`;
|
|
|
90725
91242
|
return '"[Object]"';
|
|
90726
91243
|
}
|
|
90727
91244
|
indentation += spacer;
|
|
90728
|
-
const
|
|
91245
|
+
const join14 = `,
|
|
90729
91246
|
${indentation}`;
|
|
90730
91247
|
let res = "";
|
|
90731
91248
|
let separator = "";
|
|
90732
91249
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
90733
91250
|
if (isTypedArrayWithEntries(value)) {
|
|
90734
|
-
res += stringifyTypedArray(value,
|
|
91251
|
+
res += stringifyTypedArray(value, join14, maximumBreadth);
|
|
90735
91252
|
keys = keys.slice(value.length);
|
|
90736
91253
|
maximumPropertiesToStringify -= value.length;
|
|
90737
|
-
separator =
|
|
91254
|
+
separator = join14;
|
|
90738
91255
|
}
|
|
90739
91256
|
if (deterministic) {
|
|
90740
91257
|
keys = sort(keys, comparator);
|
|
@@ -90745,13 +91262,13 @@ ${indentation}`;
|
|
|
90745
91262
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
90746
91263
|
if (tmp !== void 0) {
|
|
90747
91264
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
90748
|
-
separator =
|
|
91265
|
+
separator = join14;
|
|
90749
91266
|
}
|
|
90750
91267
|
}
|
|
90751
91268
|
if (keyLength > maximumBreadth) {
|
|
90752
91269
|
const removedKeys = keyLength - maximumBreadth;
|
|
90753
91270
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
90754
|
-
separator =
|
|
91271
|
+
separator = join14;
|
|
90755
91272
|
}
|
|
90756
91273
|
if (separator !== "") {
|
|
90757
91274
|
res = `
|
|
@@ -114475,7 +114992,7 @@ var require_parse_url = __commonJS({
|
|
|
114475
114992
|
var require_form_data = __commonJS({
|
|
114476
114993
|
"../../node_modules/.pnpm/light-my-request@6.6.0/node_modules/light-my-request/lib/form-data.js"(exports, module) {
|
|
114477
114994
|
"use strict";
|
|
114478
|
-
var { randomUUID:
|
|
114995
|
+
var { randomUUID: randomUUID10 } = __require("node:crypto");
|
|
114479
114996
|
var { Readable } = __require("node:stream");
|
|
114480
114997
|
var textEncoder;
|
|
114481
114998
|
function isFormDataLike(payload) {
|
|
@@ -114483,7 +115000,7 @@ var require_form_data = __commonJS({
|
|
|
114483
115000
|
}
|
|
114484
115001
|
function formDataToStream(formdata) {
|
|
114485
115002
|
textEncoder = textEncoder ?? new TextEncoder();
|
|
114486
|
-
const boundary = `----formdata-${
|
|
115003
|
+
const boundary = `----formdata-${randomUUID10()}`;
|
|
114487
115004
|
const prefix = `--${boundary}\r
|
|
114488
115005
|
Content-Disposition: form-data`;
|
|
114489
115006
|
const escape2 = (str3) => str3.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22");
|
|
@@ -116918,7 +117435,7 @@ ${body}`);
|
|
|
116918
117435
|
|
|
116919
117436
|
// ../../apps/engine/src/serve-ui.ts
|
|
116920
117437
|
import { readFile as readFile7, stat as stat3 } from "node:fs/promises";
|
|
116921
|
-
import { extname, join as
|
|
117438
|
+
import { extname, join as join10, normalize, resolve as resolve2, sep as sep2 } from "node:path";
|
|
116922
117439
|
async function isFile(path) {
|
|
116923
117440
|
try {
|
|
116924
117441
|
return (await stat3(path)).isFile();
|
|
@@ -116926,20 +117443,20 @@ async function isFile(path) {
|
|
|
116926
117443
|
return false;
|
|
116927
117444
|
}
|
|
116928
117445
|
}
|
|
116929
|
-
function serveUi(app2,
|
|
116930
|
-
const root2 = resolve2(
|
|
117446
|
+
function serveUi(app2, dir3) {
|
|
117447
|
+
const root2 = resolve2(dir3);
|
|
116931
117448
|
app2.get("/*", async (request3, reply) => {
|
|
116932
117449
|
const urlPath = decodeURIComponent((request3.url.split("?")[0] ?? "/").replace(/\/+$/, "") || "/");
|
|
116933
|
-
const target = normalize(
|
|
117450
|
+
const target = normalize(join10(root2, urlPath));
|
|
116934
117451
|
if (target !== root2 && !target.startsWith(root2 + sep2)) return reply.code(403).send("Forbidden");
|
|
116935
|
-
const candidates = extname(target) ? [target] : [
|
|
117452
|
+
const candidates = extname(target) ? [target] : [join10(target, "index.html"), `${target}.html`];
|
|
116936
117453
|
for (const file2 of candidates) {
|
|
116937
117454
|
if (!await isFile(file2)) continue;
|
|
116938
117455
|
const type = TYPES[extname(file2).toLowerCase()] ?? "application/octet-stream";
|
|
116939
117456
|
const immutable = file2.includes(`${sep2}_next${sep2}static${sep2}`);
|
|
116940
117457
|
return reply.header("content-type", type).header("cache-control", immutable ? "public, max-age=31536000, immutable" : "no-cache").send(await readFile7(file2));
|
|
116941
117458
|
}
|
|
116942
|
-
const notFound =
|
|
117459
|
+
const notFound = join10(root2, "404.html");
|
|
116943
117460
|
return reply.code(404).header("content-type", TYPES[".html"]).send(await isFile(notFound) ? await readFile7(notFound) : "Not found");
|
|
116944
117461
|
});
|
|
116945
117462
|
}
|
|
@@ -117049,10 +117566,10 @@ var init_history_routes = __esm({
|
|
|
117049
117566
|
});
|
|
117050
117567
|
|
|
117051
117568
|
// ../../apps/engine/src/settings-routes.ts
|
|
117052
|
-
import { mkdir as
|
|
117053
|
-
import { join as
|
|
117569
|
+
import { mkdir as mkdir6, readFile as readFile8, rename as rename6, writeFile as writeFile8 } from "node:fs/promises";
|
|
117570
|
+
import { join as join11 } from "node:path";
|
|
117054
117571
|
function registerSettingsRoutes(app2, dataDir) {
|
|
117055
|
-
const file2 =
|
|
117572
|
+
const file2 = join11(dataDir, "views.json");
|
|
117056
117573
|
async function read() {
|
|
117057
117574
|
try {
|
|
117058
117575
|
return normalizeViewsFile(JSON.parse(await readFile8(file2, "utf8")));
|
|
@@ -117064,9 +117581,9 @@ function registerSettingsRoutes(app2, dataDir) {
|
|
|
117064
117581
|
app2.put("/settings/views", async (request3, reply) => {
|
|
117065
117582
|
const next = normalizeViewsFile(request3.body);
|
|
117066
117583
|
if (next.views.length > 100) return reply.code(400).send({ error: "At most 100 saved views" });
|
|
117067
|
-
await
|
|
117584
|
+
await mkdir6(dataDir, { recursive: true });
|
|
117068
117585
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
117069
|
-
await
|
|
117586
|
+
await writeFile8(tmp, `${JSON.stringify(next, null, 2)}
|
|
117070
117587
|
`, "utf8");
|
|
117071
117588
|
await rename6(tmp, file2);
|
|
117072
117589
|
return next;
|
|
@@ -117168,212 +117685,11 @@ var init_workspace_routes = __esm({
|
|
|
117168
117685
|
}
|
|
117169
117686
|
});
|
|
117170
117687
|
|
|
117171
|
-
// ../../apps/engine/src/practice-live.ts
|
|
117172
|
-
import { createHash as createHash5 } from "node:crypto";
|
|
117173
|
-
function frame(opcode, data) {
|
|
117174
|
-
const len = data.length;
|
|
117175
|
-
const head = len < 126 ? Buffer.from([128 | opcode, len]) : len < 65536 ? Buffer.from([128 | opcode, 126, len >> 8, len & 255]) : Buffer.concat([Buffer.from([128 | opcode, 127]), (() => {
|
|
117176
|
-
const b = Buffer.alloc(8);
|
|
117177
|
-
b.writeBigUInt64BE(BigInt(len));
|
|
117178
|
-
return b;
|
|
117179
|
-
})()]);
|
|
117180
|
-
return Buffer.concat([head, data]);
|
|
117181
|
-
}
|
|
117182
|
-
function echo(req, socket) {
|
|
117183
|
-
const key = req.headers["sec-websocket-key"];
|
|
117184
|
-
if (typeof key !== "string") return socket.destroy();
|
|
117185
|
-
const accept = createHash5("sha1").update(key + GUID).digest("base64");
|
|
117186
|
-
socket.write(`HTTP/1.1 101 Switching Protocols\r
|
|
117187
|
-
Upgrade: websocket\r
|
|
117188
|
-
Connection: Upgrade\r
|
|
117189
|
-
Sec-WebSocket-Accept: ${accept}\r
|
|
117190
|
-
\r
|
|
117191
|
-
`);
|
|
117192
|
-
const sendText = (value) => socket.write(frame(1, Buffer.from(JSON.stringify(value))));
|
|
117193
|
-
sendText({ type: "welcome", message: "Practice WebSocket: send a message and it comes back." });
|
|
117194
|
-
let buf = Buffer.alloc(0);
|
|
117195
|
-
socket.on("data", (chunk) => {
|
|
117196
|
-
buf = Buffer.concat([buf, chunk]);
|
|
117197
|
-
for (; ; ) {
|
|
117198
|
-
if (buf.length < 2) return;
|
|
117199
|
-
const opcode = buf[0] & 15;
|
|
117200
|
-
const masked = (buf[1] & 128) !== 0;
|
|
117201
|
-
let len = buf[1] & 127;
|
|
117202
|
-
let at = 2;
|
|
117203
|
-
if (len === 126) {
|
|
117204
|
-
if (buf.length < 4) return;
|
|
117205
|
-
len = buf.readUInt16BE(2);
|
|
117206
|
-
at = 4;
|
|
117207
|
-
} else if (len === 127) {
|
|
117208
|
-
if (buf.length < 10) return;
|
|
117209
|
-
len = Number(buf.readBigUInt64BE(2));
|
|
117210
|
-
at = 10;
|
|
117211
|
-
}
|
|
117212
|
-
if (len > 1e6) return socket.destroy();
|
|
117213
|
-
const mask = masked ? buf.subarray(at, at + 4) : null;
|
|
117214
|
-
at += masked ? 4 : 0;
|
|
117215
|
-
if (buf.length < at + len) return;
|
|
117216
|
-
const payload = Buffer.from(buf.subarray(at, at + len));
|
|
117217
|
-
if (mask) for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i % 4];
|
|
117218
|
-
buf = buf.subarray(at + len);
|
|
117219
|
-
if (opcode === 8) {
|
|
117220
|
-
socket.end(frame(8, payload.subarray(0, 2)));
|
|
117221
|
-
return;
|
|
117222
|
-
}
|
|
117223
|
-
if (opcode === 9) socket.write(frame(10, payload));
|
|
117224
|
-
else if (opcode === 1) {
|
|
117225
|
-
const text = payload.toString("utf8");
|
|
117226
|
-
let parsed = text;
|
|
117227
|
-
try {
|
|
117228
|
-
parsed = JSON.parse(text);
|
|
117229
|
-
} catch {
|
|
117230
|
-
}
|
|
117231
|
-
sendText({ type: "echo", you: parsed, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
117232
|
-
}
|
|
117233
|
-
}
|
|
117234
|
-
});
|
|
117235
|
-
socket.on("error", () => socket.destroy());
|
|
117236
|
-
}
|
|
117237
|
-
function attachPracticeWebSocket(server, path) {
|
|
117238
|
-
server.on("upgrade", (req, socket) => {
|
|
117239
|
-
if ((req.url ?? "").split("?")[0] !== path) return;
|
|
117240
|
-
echo(req, socket);
|
|
117241
|
-
});
|
|
117242
|
-
}
|
|
117243
|
-
function practiceEvents(res, onGone, count = 10, everyMs = 1e3) {
|
|
117244
|
-
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
|
|
117245
|
-
res.write(": practice event stream \u2014 a tick a second\n\n");
|
|
117246
|
-
let n = 0;
|
|
117247
|
-
const timer = setInterval(() => {
|
|
117248
|
-
n++;
|
|
117249
|
-
res.write(`event: tick
|
|
117250
|
-
id: ${n}
|
|
117251
|
-
data: ${JSON.stringify({ n, at: (/* @__PURE__ */ new Date()).toISOString(), price: Math.round((40 + Math.random() * 5) * 100) / 100 })}
|
|
117252
|
-
|
|
117253
|
-
`);
|
|
117254
|
-
if (n >= count) {
|
|
117255
|
-
clearInterval(timer);
|
|
117256
|
-
res.end();
|
|
117257
|
-
}
|
|
117258
|
-
}, everyMs);
|
|
117259
|
-
onGone(() => clearInterval(timer));
|
|
117260
|
-
}
|
|
117261
|
-
var GUID;
|
|
117262
|
-
var init_practice_live = __esm({
|
|
117263
|
-
"../../apps/engine/src/practice-live.ts"() {
|
|
117264
|
-
"use strict";
|
|
117265
|
-
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
117266
|
-
}
|
|
117267
|
-
});
|
|
117268
|
-
|
|
117269
|
-
// ../../apps/engine/src/demo-api.ts
|
|
117270
|
-
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
117271
|
-
function registerDemoApi(api) {
|
|
117272
|
-
const tokens = /* @__PURE__ */ new Map();
|
|
117273
|
-
const created = /* @__PURE__ */ new Map();
|
|
117274
|
-
let nextOrder = 2001;
|
|
117275
|
-
const signedIn = (auth) => {
|
|
117276
|
-
const header = auth ?? "";
|
|
117277
|
-
return header.startsWith("Bearer ") ? tokens.get(header.slice(7)) : void 0;
|
|
117278
|
-
};
|
|
117279
|
-
api.get("/products", async () => {
|
|
117280
|
-
await jitter(5, 25);
|
|
117281
|
-
return { items: PRODUCTS, total: PRODUCTS.length };
|
|
117282
|
-
});
|
|
117283
|
-
api.get("/products/:id", async (request3, reply) => {
|
|
117284
|
-
await jitter(5, 20);
|
|
117285
|
-
const product = PRODUCTS.find((p) => String(p.id) === request3.params.id);
|
|
117286
|
-
if (!product) return reply.code(404).send({ error: `No product ${request3.params.id}. Try 1 to ${PRODUCTS.length}.` });
|
|
117287
|
-
return product;
|
|
117288
|
-
});
|
|
117289
|
-
api.post("/login", async (request3, reply) => {
|
|
117290
|
-
await jitter(30, 40);
|
|
117291
|
-
const username = typeof request3.body?.username === "string" ? request3.body.username.trim() : "";
|
|
117292
|
-
if (!username || request3.body?.password !== "demo") {
|
|
117293
|
-
return reply.code(401).send({ error: 'Use any username with the password "demo".' });
|
|
117294
|
-
}
|
|
117295
|
-
if (tokens.size > 5e3) tokens.clear();
|
|
117296
|
-
const token = `demo-${randomBytes5(12).toString("hex")}`;
|
|
117297
|
-
tokens.set(token, username);
|
|
117298
|
-
return { data: { token, user: username, expiresIn: 3600 } };
|
|
117299
|
-
});
|
|
117300
|
-
api.get("/orders", async (request3, reply) => {
|
|
117301
|
-
await jitter(10, 30);
|
|
117302
|
-
const auth = request3.headers.authorization ?? "";
|
|
117303
|
-
const user = auth.startsWith("Bearer ") ? tokens.get(auth.slice(7)) : void 0;
|
|
117304
|
-
if (!user) return reply.code(401).send({ error: "Sign in first: POST /login, then send the token as a Bearer token." });
|
|
117305
|
-
return {
|
|
117306
|
-
user,
|
|
117307
|
-
orders: [
|
|
117308
|
-
{ id: "A-1001", items: [PRODUCTS[0].name, PRODUCTS[1].name], total: PRODUCTS[0].price + PRODUCTS[1].price },
|
|
117309
|
-
{ id: "A-1002", items: [PRODUCTS[3].name], total: PRODUCTS[3].price }
|
|
117310
|
-
]
|
|
117311
|
-
};
|
|
117312
|
-
});
|
|
117313
|
-
api.post("/orders", async (request3, reply) => {
|
|
117314
|
-
await jitter(15, 30);
|
|
117315
|
-
const user = signedIn(request3.headers.authorization);
|
|
117316
|
-
if (!user) return reply.code(401).send({ error: "Sign in first: POST /login, then send the token as a Bearer token." });
|
|
117317
|
-
const product = PRODUCTS.find((p) => p.id === Number(request3.body?.productId));
|
|
117318
|
-
if (!product) return reply.code(400).send({ error: `Send { "productId": 1 } \u2014 a product id from 1 to ${PRODUCTS.length}.` });
|
|
117319
|
-
const quantity = Math.min(99, Math.max(1, Math.floor(Number(request3.body?.quantity ?? 1)) || 1));
|
|
117320
|
-
if (created.size > 5e3) created.clear();
|
|
117321
|
-
const order = { id: `A-${nextOrder++}`, user, productId: product.id, quantity, total: product.price * quantity, status: "new" };
|
|
117322
|
-
created.set(order.id, order);
|
|
117323
|
-
return reply.code(201).send(order);
|
|
117324
|
-
});
|
|
117325
|
-
api.get("/orders/:id", async (request3, reply) => {
|
|
117326
|
-
await jitter(5, 20);
|
|
117327
|
-
if (!signedIn(request3.headers.authorization)) return reply.code(401).send({ error: "Sign in first: POST /login, then send the token as a Bearer token." });
|
|
117328
|
-
const order = created.get(request3.params.id);
|
|
117329
|
-
if (!order) return reply.code(404).send({ error: `No order ${request3.params.id}. Create one with POST /orders.` });
|
|
117330
|
-
return order;
|
|
117331
|
-
});
|
|
117332
|
-
api.get("/slow", async (request3) => {
|
|
117333
|
-
const ms3 = Math.min(3e3, Math.max(0, Number(request3.query.ms ?? 300) || 0));
|
|
117334
|
-
await jitter(ms3, ms3 * 0.2);
|
|
117335
|
-
return { waitedMs: ms3 };
|
|
117336
|
-
});
|
|
117337
|
-
api.get("/flaky", async (_request, reply) => {
|
|
117338
|
-
await jitter(5, 30);
|
|
117339
|
-
if (Math.random() < 0.1) return reply.code(500).send({ error: "Something went wrong (on purpose, about 1 in 10)." });
|
|
117340
|
-
return { ok: true };
|
|
117341
|
-
});
|
|
117342
|
-
api.get("/events", async (request3, reply) => {
|
|
117343
|
-
reply.hijack();
|
|
117344
|
-
practiceEvents(reply.raw, (stop) => request3.raw.on("close", stop));
|
|
117345
|
-
});
|
|
117346
|
-
attachPracticeWebSocket(api.server, `${api.prefix}/ws`);
|
|
117347
|
-
}
|
|
117348
|
-
var PRODUCTS, sleep, jitter;
|
|
117349
|
-
var init_demo_api = __esm({
|
|
117350
|
-
"../../apps/engine/src/demo-api.ts"() {
|
|
117351
|
-
"use strict";
|
|
117352
|
-
init_practice_live();
|
|
117353
|
-
PRODUCTS = [
|
|
117354
|
-
["Desk lamp", 39],
|
|
117355
|
-
["Notebook", 6],
|
|
117356
|
-
["Coffee mug", 12],
|
|
117357
|
-
["Headphones", 129],
|
|
117358
|
-
["Backpack", 74],
|
|
117359
|
-
["Water bottle", 18],
|
|
117360
|
-
["Keyboard", 89],
|
|
117361
|
-
["Plant pot", 15],
|
|
117362
|
-
["Phone stand", 22],
|
|
117363
|
-
["Sunglasses", 58],
|
|
117364
|
-
["Umbrella", 27],
|
|
117365
|
-
["Wall clock", 34]
|
|
117366
|
-
].map(([name, price], i) => ({ id: i + 1, name, price, inStock: i % 4 !== 3 }));
|
|
117367
|
-
sleep = (ms3) => new Promise((r) => setTimeout(r, ms3));
|
|
117368
|
-
jitter = (base, spread) => sleep(base + Math.random() * spread);
|
|
117369
|
-
}
|
|
117370
|
-
});
|
|
117371
|
-
|
|
117372
117688
|
// ../../apps/engine/src/usage.ts
|
|
117373
|
-
import { randomUUID as
|
|
117374
|
-
import { mkdir as
|
|
117689
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
117690
|
+
import { mkdir as mkdir7, readFile as readFile9, rename as rename7, writeFile as writeFile9 } from "node:fs/promises";
|
|
117375
117691
|
import { arch, platform } from "node:os";
|
|
117376
|
-
import { join as
|
|
117692
|
+
import { join as join12 } from "node:path";
|
|
117377
117693
|
var EMPTY, KEEP_DAYS, UsageStats;
|
|
117378
117694
|
var init_usage = __esm({
|
|
117379
117695
|
"../../apps/engine/src/usage.ts"() {
|
|
@@ -117385,8 +117701,8 @@ var init_usage = __esm({
|
|
|
117385
117701
|
opts;
|
|
117386
117702
|
state = null;
|
|
117387
117703
|
writing = Promise.resolve();
|
|
117388
|
-
constructor(
|
|
117389
|
-
this.file =
|
|
117704
|
+
constructor(dir3, opts) {
|
|
117705
|
+
this.file = join12(dir3, "usage.json");
|
|
117390
117706
|
this.opts = opts;
|
|
117391
117707
|
}
|
|
117392
117708
|
async load() {
|
|
@@ -117395,20 +117711,20 @@ var init_usage = __esm({
|
|
|
117395
117711
|
const raw = JSON.parse(await readFile9(this.file, "utf8"));
|
|
117396
117712
|
this.state = {
|
|
117397
117713
|
enabled: typeof raw.enabled === "boolean" ? raw.enabled : null,
|
|
117398
|
-
installId: typeof raw.installId === "string" && raw.installId ? raw.installId :
|
|
117714
|
+
installId: typeof raw.installId === "string" && raw.installId ? raw.installId : randomUUID8(),
|
|
117399
117715
|
days: raw.days && typeof raw.days === "object" ? raw.days : {}
|
|
117400
117716
|
};
|
|
117401
117717
|
} catch {
|
|
117402
|
-
this.state = { enabled: null, installId:
|
|
117718
|
+
this.state = { enabled: null, installId: randomUUID8(), days: {} };
|
|
117403
117719
|
}
|
|
117404
117720
|
return this.state;
|
|
117405
117721
|
}
|
|
117406
117722
|
save() {
|
|
117407
117723
|
this.writing = this.writing.then(async () => {
|
|
117408
117724
|
if (!this.state) return;
|
|
117409
|
-
await
|
|
117725
|
+
await mkdir7(join12(this.file, ".."), { recursive: true });
|
|
117410
117726
|
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
117411
|
-
await
|
|
117727
|
+
await writeFile9(tmp, `${JSON.stringify(this.state, null, 2)}
|
|
117412
117728
|
`, "utf8");
|
|
117413
117729
|
await rename7(tmp, this.file);
|
|
117414
117730
|
}).catch(() => void 0);
|
|
@@ -117448,7 +117764,7 @@ var init_usage = __esm({
|
|
|
117448
117764
|
const s = await this.load();
|
|
117449
117765
|
s.enabled = enabled;
|
|
117450
117766
|
if (!enabled) {
|
|
117451
|
-
s.installId =
|
|
117767
|
+
s.installId = randomUUID8();
|
|
117452
117768
|
s.days = {};
|
|
117453
117769
|
}
|
|
117454
117770
|
await this.save();
|
|
@@ -117632,8 +117948,8 @@ var init_local_servers = __esm({
|
|
|
117632
117948
|
});
|
|
117633
117949
|
|
|
117634
117950
|
// ../../apps/engine/src/response-history.ts
|
|
117635
|
-
import { mkdir as
|
|
117636
|
-
import { join as
|
|
117951
|
+
import { mkdir as mkdir8, readFile as readFile10, rm as rm3, writeFile as writeFile10 } from "node:fs/promises";
|
|
117952
|
+
import { join as join13 } from "node:path";
|
|
117637
117953
|
function maskSecrets(body) {
|
|
117638
117954
|
const t = body.trim();
|
|
117639
117955
|
if (!(t.startsWith("{") || t.startsWith("["))) return { body, masked: false };
|
|
@@ -117671,11 +117987,11 @@ var init_response_history = __esm({
|
|
|
117671
117987
|
dir;
|
|
117672
117988
|
queue = Promise.resolve();
|
|
117673
117989
|
constructor(dataDir) {
|
|
117674
|
-
this.dir =
|
|
117990
|
+
this.dir = join13(dataDir, "responses");
|
|
117675
117991
|
}
|
|
117676
117992
|
file(id) {
|
|
117677
117993
|
if (!/^[\w-]{8,64}$/.test(id)) throw new Error("bad request id");
|
|
117678
|
-
return
|
|
117994
|
+
return join13(this.dir, `${id}.json`);
|
|
117679
117995
|
}
|
|
117680
117996
|
async read(id) {
|
|
117681
117997
|
try {
|
|
@@ -117703,8 +118019,8 @@ var init_response_history = __esm({
|
|
|
117703
118019
|
...masked ? { masked } : {}
|
|
117704
118020
|
};
|
|
117705
118021
|
const answers2 = [answer, ...await this.read(id)].slice(0, KEEP);
|
|
117706
|
-
await
|
|
117707
|
-
await
|
|
118022
|
+
await mkdir8(this.dir, { recursive: true });
|
|
118023
|
+
await writeFile10(this.file(id), JSON.stringify({ version: 1, answers: answers2 }, null, 1), "utf8");
|
|
117708
118024
|
});
|
|
117709
118025
|
this.queue = next.catch(() => void 0);
|
|
117710
118026
|
return next;
|
|
@@ -117826,6 +118142,8 @@ async function routes(api) {
|
|
|
117826
118142
|
useOAuthStore(secrets2);
|
|
117827
118143
|
registerOAuthRoutes(api);
|
|
117828
118144
|
useCertificateStore(history.dir, secrets2);
|
|
118145
|
+
useFileStore(history.dir);
|
|
118146
|
+
await api.register(async (scope) => registerFileRoutes(scope));
|
|
117829
118147
|
useCookieStore(secrets2);
|
|
117830
118148
|
registerCookieRoutes(api);
|
|
117831
118149
|
registerCertificateRoutes(api);
|
|
@@ -117941,6 +118259,7 @@ var init_index = __esm({
|
|
|
117941
118259
|
init_suite_run();
|
|
117942
118260
|
init_oauth();
|
|
117943
118261
|
init_certificates();
|
|
118262
|
+
init_file_store();
|
|
117944
118263
|
init_cookies();
|
|
117945
118264
|
init_local_servers();
|
|
117946
118265
|
init_mock_server();
|
|
@@ -117998,9 +118317,9 @@ init_history_store();
|
|
|
117998
118317
|
init_k6();
|
|
117999
118318
|
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
118000
118319
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
118001
|
-
import { randomUUID as
|
|
118002
|
-
import { existsSync as
|
|
118003
|
-
import { readFile as readFile11, writeFile as
|
|
118320
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
118321
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
118322
|
+
import { readFile as readFile11, writeFile as writeFile11 } from "node:fs/promises";
|
|
118004
118323
|
import { dirname as dirname3, resolve as resolve4 } from "node:path";
|
|
118005
118324
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
118006
118325
|
import { parseArgs } from "node:util";
|
|
@@ -118051,9 +118370,9 @@ async function downloadK6(log = () => {
|
|
|
118051
118370
|
const tar = process.platform === "win32" && existsSync(systemTar) ? systemTar : "tar";
|
|
118052
118371
|
execFileSync(tar, ["-xf", asset], { cwd: work, stdio: "ignore" });
|
|
118053
118372
|
const exe = process.platform === "win32" ? "k6.exe" : "k6";
|
|
118054
|
-
const find = (
|
|
118055
|
-
for (const name of readdirSync(
|
|
118056
|
-
const p = join3(
|
|
118373
|
+
const find = (dir4) => {
|
|
118374
|
+
for (const name of readdirSync(dir4)) {
|
|
118375
|
+
const p = join3(dir4, name);
|
|
118057
118376
|
if (statSync(p).isDirectory()) {
|
|
118058
118377
|
const hit = find(p);
|
|
118059
118378
|
if (hit) return hit;
|
|
@@ -118063,9 +118382,9 @@ async function downloadK6(log = () => {
|
|
|
118063
118382
|
};
|
|
118064
118383
|
const found = find(work);
|
|
118065
118384
|
if (!found) throw new Error(`${exe} was not in ${asset}`);
|
|
118066
|
-
const
|
|
118067
|
-
mkdirSync(
|
|
118068
|
-
const dest = join3(
|
|
118385
|
+
const dir3 = k6CacheDir();
|
|
118386
|
+
mkdirSync(dir3, { recursive: true });
|
|
118387
|
+
const dest = join3(dir3, exe);
|
|
118069
118388
|
copyFileSync(found, dest);
|
|
118070
118389
|
if (process.platform !== "win32") chmodSync(dest, 493);
|
|
118071
118390
|
log(`k6 saved to ${dest}`);
|
|
@@ -118082,8 +118401,10 @@ init_secret_store();
|
|
|
118082
118401
|
init_suite_run();
|
|
118083
118402
|
init_oauth();
|
|
118084
118403
|
init_certificates();
|
|
118404
|
+
init_file_store();
|
|
118085
118405
|
init_cookies();
|
|
118086
118406
|
init_workspace_store();
|
|
118407
|
+
init_demo_api();
|
|
118087
118408
|
init_mock_server();
|
|
118088
118409
|
|
|
118089
118410
|
// ../../apps/engine/src/mcp.ts
|
|
@@ -118442,7 +118763,7 @@ async function serveMcp(o) {
|
|
|
118442
118763
|
// ../../apps/engine/src/cli.ts
|
|
118443
118764
|
var repoRoot = resolve4(dirname3(fileURLToPath2(import.meta.url)), "../../..");
|
|
118444
118765
|
var EXIT = { passed: 0, failed: 1, error: 2 };
|
|
118445
|
-
var VERSION = "0.
|
|
118766
|
+
var VERSION = "0.13.0";
|
|
118446
118767
|
var HELP = `APITestCenter CLI ${VERSION} \u2014 https://apitestcenter.com/docs/cli-and-ci.html
|
|
118447
118768
|
|
|
118448
118769
|
Usage (apitestcenter, or the short name atc once installed)
|
|
@@ -118550,7 +118871,7 @@ async function main() {
|
|
|
118550
118871
|
process.exit(values.help ? EXIT.passed : EXIT.error);
|
|
118551
118872
|
}
|
|
118552
118873
|
if (values.data && command !== "init") {
|
|
118553
|
-
if (!
|
|
118874
|
+
if (!existsSync3(values.data)) fail(`no folder "${values.data}"`);
|
|
118554
118875
|
process.env.ATC_DATA_DIR = resolve4(values.data);
|
|
118555
118876
|
}
|
|
118556
118877
|
if (command === "init") return init(target, values.data ?? "api-tests");
|
|
@@ -118560,6 +118881,7 @@ async function main() {
|
|
|
118560
118881
|
const workspace = new WorkspaceStore(dataDir, secrets2);
|
|
118561
118882
|
useOAuthStore(secrets2);
|
|
118562
118883
|
useCertificateStore(dataDir, secrets2);
|
|
118884
|
+
useFileStore(dataDir);
|
|
118563
118885
|
useCookieStore(secrets2);
|
|
118564
118886
|
for (const spec of values.cert ?? []) {
|
|
118565
118887
|
const eq = spec.indexOf("=");
|
|
@@ -118604,7 +118926,7 @@ Environments (${index.environments.length})`);
|
|
|
118604
118926
|
let raw;
|
|
118605
118927
|
let name;
|
|
118606
118928
|
let collection = "";
|
|
118607
|
-
if (
|
|
118929
|
+
if (existsSync3(target) && target.toLowerCase().endsWith(".json")) {
|
|
118608
118930
|
const file2 = JSON.parse(await readFile11(target, "utf8"));
|
|
118609
118931
|
raw = file2.request ?? file2;
|
|
118610
118932
|
name = file2.name ?? target;
|
|
@@ -118621,7 +118943,7 @@ Environments (${index.environments.length})`);
|
|
|
118621
118943
|
}
|
|
118622
118944
|
const env = values.env ? index.environments.find((e) => e.name === values.env || e.id === values.env) : void 0;
|
|
118623
118945
|
if (values.env && !env) fail(`no environment "${values.env}". Run "atc list".`);
|
|
118624
|
-
const vars2 = variablesOf(env);
|
|
118946
|
+
const vars2 = { ...JSON.stringify(raw).includes("{{practice") ? await practiceVars() : {}, ...variablesOf(env) };
|
|
118625
118947
|
for (const pair of values.var ?? []) {
|
|
118626
118948
|
const i = pair.indexOf("=");
|
|
118627
118949
|
if (i < 1) fail(`--var needs key=value, got "${pair}"`);
|
|
@@ -118742,12 +119064,12 @@ compared with baseline \u201C${baseline.testName}\u201D (${baseline.startedAt.sl
|
|
|
118742
119064
|
console.log(` ${compared.headline}`);
|
|
118743
119065
|
console.log(` ${gate.failed ? "\u2715" : "\u2713"} ${gate.reason}`);
|
|
118744
119066
|
}
|
|
118745
|
-
if (values.json) await
|
|
118746
|
-
if (values.html) await
|
|
119067
|
+
if (values.json) await writeFile11(values.json, exportRunJson(report), "utf8");
|
|
119068
|
+
if (values.html) await writeFile11(values.html, exportRunHtml(report, { slo, before: baseline }), "utf8");
|
|
118747
119069
|
const extra2 = gate ? [{ classname: "baseline", name: `no slower than the baseline by more than ${Math.round(maxSlower * 100)}%`, failure: gate.failed ? gate.reason : void 0 }] : [];
|
|
118748
|
-
if (values.junit) await
|
|
119070
|
+
if (values.junit) await writeFile11(values.junit, exportRunJunit(report, verdict, targets?.checksRate ?? 0.99, extra2), "utf8");
|
|
118749
119071
|
if (values["save-baseline"]) {
|
|
118750
|
-
await
|
|
119072
|
+
await writeFile11(values["save-baseline"], exportRunJson(report), "utf8");
|
|
118751
119073
|
log(` saved this run as a baseline \u2192 ${values["save-baseline"]}`);
|
|
118752
119074
|
}
|
|
118753
119075
|
for (const [flag, file2] of [["json", values.json], ["html", values.html], ["junit", values.junit]]) if (file2) log(` wrote ${flag} \u2192 ${file2}`);
|
|
@@ -118756,7 +119078,7 @@ compared with baseline \u201C${baseline.testName}\u201D (${baseline.startedAt.sl
|
|
|
118756
119078
|
}
|
|
118757
119079
|
async function ui(collection, dataDir, noOpen, inBrowserTab = false) {
|
|
118758
119080
|
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
118759
|
-
const uiDir2 = [process.env.ATC_UI_DIR, resolve4(here, "ui"), resolve4(repoRoot, "apps/web/.next-desktop")].find((d) => Boolean(d) &&
|
|
119081
|
+
const uiDir2 = [process.env.ATC_UI_DIR, resolve4(here, "ui"), resolve4(repoRoot, "apps/web/.next-desktop")].find((d) => Boolean(d) && existsSync3(resolve4(d, "index.html")));
|
|
118760
119082
|
if (!uiDir2) fail("this build has no app screens. Use the npm package (npx apitestcenter ui), or run: pnpm --filter @apitestcenter/desktop build:ui");
|
|
118761
119083
|
Object.assign(process.env, {
|
|
118762
119084
|
ATC_UI_DIR: uiDir2,
|
|
@@ -118812,7 +119134,7 @@ function findAppBrowser() {
|
|
|
118812
119134
|
const candidates = process.platform === "win32" ? [env["ProgramFiles(x86)"], env.ProgramFiles, env.LOCALAPPDATA].flatMap(
|
|
118813
119135
|
(root2) => root2 ? [`${root2}\\Microsoft\\Edge\\Application\\msedge.exe`, `${root2}\\Google\\Chrome\\Application\\chrome.exe`, `${root2}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`] : []
|
|
118814
119136
|
) : process.platform === "darwin" ? ["/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"] : ["/usr/bin/microsoft-edge", "/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/usr/bin/brave-browser", "/snap/bin/chromium"];
|
|
118815
|
-
return candidates.find((p) =>
|
|
119137
|
+
return candidates.find((p) => existsSync3(p)) ?? null;
|
|
118816
119138
|
}
|
|
118817
119139
|
function openAppWindow(url2) {
|
|
118818
119140
|
const bin = findAppBrowser();
|
|
@@ -118844,24 +119166,24 @@ function openBrowser(url2) {
|
|
|
118844
119166
|
}
|
|
118845
119167
|
async function init(collection, folder) {
|
|
118846
119168
|
const name = collection?.trim() || "My API";
|
|
118847
|
-
const
|
|
118848
|
-
if (
|
|
119169
|
+
const dir3 = resolve4(folder);
|
|
119170
|
+
if (existsSync3(resolve4(dir3, "requests")) && readdirSync2(resolve4(dir3, "requests")).length) {
|
|
118849
119171
|
fail(`${folder} already has saved requests. Open them with: npx apitestcenter ui --data ${folder}`);
|
|
118850
119172
|
}
|
|
118851
|
-
mkdirSync2(
|
|
118852
|
-
const store3 = new WorkspaceStore(
|
|
118853
|
-
await store3.saveEnvironments([{ id:
|
|
119173
|
+
mkdirSync2(dir3, { recursive: true });
|
|
119174
|
+
const store3 = new WorkspaceStore(dir3, new SecretStore(dir3));
|
|
119175
|
+
await store3.saveEnvironments([{ id: randomUUID9(), name: "Local", variables: [{ key: "baseUrl", value: "http://localhost:3000", secret: false }] }]);
|
|
118854
119176
|
await store3.saveRequest({
|
|
118855
119177
|
name: "Health check",
|
|
118856
119178
|
collection: name,
|
|
118857
119179
|
request: { method: "GET", url: "{{baseUrl}}/health", checks: [{ type: "status", expect: "2xx" }] }
|
|
118858
119180
|
});
|
|
118859
119181
|
const data = folder.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
118860
|
-
writeFileSync2(resolve4(
|
|
118861
|
-
const ignore = resolve4(
|
|
118862
|
-
if (!
|
|
119182
|
+
writeFileSync2(resolve4(dir3, "README.md"), ciReadme({ collection: name, environment: "Local", data }));
|
|
119183
|
+
const ignore = resolve4(dir3, ".gitignore");
|
|
119184
|
+
if (!existsSync3(ignore)) writeFileSync2(ignore, ["# Kept by APITestCenter on this computer \u2014 not tests, so not in git", "responses/", "runs/", "compares/", "usage.json", "secrets.enc", ""].join("\n"));
|
|
118863
119185
|
const workflow = resolve4(".github/workflows/api-checks.yml");
|
|
118864
|
-
const wroteWorkflow = !
|
|
119186
|
+
const wroteWorkflow = !existsSync3(workflow);
|
|
118865
119187
|
if (wroteWorkflow) {
|
|
118866
119188
|
mkdirSync2(dirname3(workflow), { recursive: true });
|
|
118867
119189
|
writeFileSync2(workflow, ciWorkflow({ collection: name, environment: "Local", data }));
|
|
@@ -118877,6 +119199,8 @@ async function init(collection, folder) {
|
|
|
118877
119199
|
async function suite(collection, values, workspace, dataDir) {
|
|
118878
119200
|
if (collection === void 0) fail('suite needs a collection name, or "" for requests outside any collection. Run "atc list".');
|
|
118879
119201
|
const vars2 = {};
|
|
119202
|
+
const inside = (await workspace.index()).requests.filter((r) => collection === "" ? r.collection === "" : r.collection === collection || r.collection.startsWith(`${collection}/`));
|
|
119203
|
+
if (inside.some((r) => r.url.includes("{{practice"))) Object.assign(vars2, await practiceVars());
|
|
118880
119204
|
for (const pair of values.var ?? []) {
|
|
118881
119205
|
const i = pair.indexOf("=");
|
|
118882
119206
|
if (i < 1) fail(`--var needs key=value, got "${pair}"`);
|
|
@@ -118913,12 +119237,22 @@ async function suite(collection, values, workspace, dataDir) {
|
|
|
118913
119237
|
const ok = suitePassed(run2);
|
|
118914
119238
|
console.log(`
|
|
118915
119239
|
${ok ? "PASS" : "FAIL"} ${suiteHeadline(run2)}`);
|
|
118916
|
-
if (values.json) await
|
|
118917
|
-
if (values.html) await
|
|
118918
|
-
if (values.junit) await
|
|
119240
|
+
if (values.json) await writeFile11(values.json, exportSuiteJson(run2), "utf8");
|
|
119241
|
+
if (values.html) await writeFile11(values.html, exportSuiteHtml(run2), "utf8");
|
|
119242
|
+
if (values.junit) await writeFile11(values.junit, exportSuiteJunit(run2), "utf8");
|
|
118919
119243
|
for (const [flag, file2] of [["json", values.json], ["html", values.html], ["junit", values.junit]]) if (file2) log(` wrote ${flag} \u2192 ${file2}`);
|
|
118920
119244
|
return ok ? EXIT.passed : EXIT.failed;
|
|
118921
119245
|
}
|
|
119246
|
+
async function practiceVars() {
|
|
119247
|
+
const { default: Fastify2 } = await Promise.resolve().then(() => __toESM(require_fastify(), 1));
|
|
119248
|
+
const app2 = Fastify2({ logger: false });
|
|
119249
|
+
await app2.register(async (demo) => registerDemoApi(demo), { prefix: "/demo-api" });
|
|
119250
|
+
await app2.listen({ host: "127.0.0.1", port: 0 });
|
|
119251
|
+
app2.server.unref();
|
|
119252
|
+
const address = app2.server.address();
|
|
119253
|
+
const port2 = typeof address === "object" && address ? address.port : 0;
|
|
119254
|
+
return { practiceApi: `http://127.0.0.1:${port2}/demo-api`, practiceWs: `ws://127.0.0.1:${port2}/demo-api` };
|
|
119255
|
+
}
|
|
118922
119256
|
async function mock(collection, portText, workspace) {
|
|
118923
119257
|
if (collection === void 0) fail('say which collection to mock, e.g. atc mock "Orders API" (or "" for all requests)');
|
|
118924
119258
|
const port2 = portText === void 0 ? 4010 : Number(portText);
|