curltrim 0.1.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.
Files changed (47) hide show
  1. package/README.md +96 -0
  2. package/dist/src/assertions.d.ts +6 -0
  3. package/dist/src/assertions.js +176 -0
  4. package/dist/src/assertions.js.map +1 -0
  5. package/dist/src/candidates.d.ts +3 -0
  6. package/dist/src/candidates.js +74 -0
  7. package/dist/src/candidates.js.map +1 -0
  8. package/dist/src/cli.d.ts +4 -0
  9. package/dist/src/cli.js +168 -0
  10. package/dist/src/cli.js.map +1 -0
  11. package/dist/src/config.d.ts +5 -0
  12. package/dist/src/config.js +87 -0
  13. package/dist/src/config.js.map +1 -0
  14. package/dist/src/curl-parser.d.ts +2 -0
  15. package/dist/src/curl-parser.js +110 -0
  16. package/dist/src/curl-parser.js.map +1 -0
  17. package/dist/src/index.d.ts +10 -0
  18. package/dist/src/index.js +11 -0
  19. package/dist/src/index.js.map +1 -0
  20. package/dist/src/inspect.d.ts +2 -0
  21. package/dist/src/inspect.js +29 -0
  22. package/dist/src/inspect.js.map +1 -0
  23. package/dist/src/interactive.d.ts +56 -0
  24. package/dist/src/interactive.js +181 -0
  25. package/dist/src/interactive.js.map +1 -0
  26. package/dist/src/json-output.d.ts +3 -0
  27. package/dist/src/json-output.js +27 -0
  28. package/dist/src/json-output.js.map +1 -0
  29. package/dist/src/minimizer.d.ts +10 -0
  30. package/dist/src/minimizer.js +61 -0
  31. package/dist/src/minimizer.js.map +1 -0
  32. package/dist/src/render-curl.d.ts +2 -0
  33. package/dist/src/render-curl.js +53 -0
  34. package/dist/src/render-curl.js.map +1 -0
  35. package/dist/src/replayer.d.ts +2 -0
  36. package/dist/src/replayer.js +82 -0
  37. package/dist/src/replayer.js.map +1 -0
  38. package/dist/src/request-rendering.d.ts +6 -0
  39. package/dist/src/request-rendering.js +31 -0
  40. package/dist/src/request-rendering.js.map +1 -0
  41. package/dist/src/terminal.d.ts +19 -0
  42. package/dist/src/terminal.js +187 -0
  43. package/dist/src/terminal.js.map +1 -0
  44. package/dist/src/types.d.ts +121 -0
  45. package/dist/src/types.js +2 -0
  46. package/dist/src/types.js.map +1 -0
  47. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # curltrim
2
+
3
+ `curltrim` 会重放一条 curl 请求,并逐项移除不影响成功重放的请求参数,最终输出仍然可成功重放的最小 curl。
4
+
5
+ ## 开发
6
+
7
+ 本项目使用 pnpm 管理依赖:
8
+
9
+ ```bash
10
+ corepack enable
11
+ pnpm install
12
+ pnpm verify
13
+ ```
14
+
15
+ ## 使用方式
16
+
17
+ 直接传入 curl:
18
+
19
+ ```bash
20
+ curltrim 'curl "https://example.com/api" -H "Cookie: sid=abc; theme=dark"'
21
+ ```
22
+
23
+ 从文件读取:
24
+
25
+ ```bash
26
+ curltrim --file ./request.curl --targets cookies,headers --expect-status 200-299
27
+ ```
28
+
29
+ 无参数进入交互式配置:
30
+
31
+ ```bash
32
+ curltrim
33
+ ```
34
+
35
+ Inspect 模式只解析 curl,不发真实请求:
36
+
37
+ ```bash
38
+ curltrim --inspect 'curl "https://example.com/api" -H "Cookie: sid=abc"'
39
+ ```
40
+
41
+ JSON 输出适合脚本和 CI:
42
+
43
+ ```bash
44
+ curltrim --json 'curl "https://example.com/api" -H "Cookie: sid=abc"'
45
+ curltrim --inspect --json 'curl "https://example.com/api"'
46
+ ```
47
+
48
+ 也可以先指定输出意图,再进入交互式采集 curl:
49
+
50
+ ```bash
51
+ curltrim --inspect
52
+ curltrim --json
53
+ curltrim --inspect --json
54
+ ```
55
+
56
+ `--json` 会让 stdout 只包含 JSON;即使同时传入 `--plain`,也以 JSON 输出优先。
57
+
58
+ 直接参数模式要求把整条 curl 作为一个 shell 参数传入。复杂或从浏览器复制的 curl 建议使用引号包裹、`--file`,或进入交互式模式。
59
+
60
+ 交互式模式只保留两类 curl 来源:粘贴 curl 文本和从文件读取。粘贴文本时会打开编辑器,因此可以直接粘贴多行 curl,保存并退出后继续。拿到 curl 文本后,程序会先做初步解析校验;校验通过后会继续选择运行模式(裁剪请求或仅 inspect)和输出格式(终端渲染或 JSON)。如果选择仅 inspect,会跳过 targets、断言、delay 和开始重放确认;如果命令行已传入 `--inspect` 或 `--json`,进入交互式后会保留这些输出设置。
61
+
62
+ ## 成功断言
63
+
64
+ ```bash
65
+ --expect-status 200,201,204,300-399
66
+ --expect-text success
67
+ --expect-regex '"ok":\s*true'
68
+ --expect-json '$.code = 0'
69
+ --expect-json '$.count >= 1'
70
+ ```
71
+
72
+ 多个断言会以 AND 关系组合。未提供任何断言时,默认使用 HTTP 状态码 `200-299` 判断成功。如果已经提供文本、正则或 JSONPath 断言,并且仍希望状态码参与判断,需要显式添加 `--expect-status`。
73
+
74
+ ## 裁剪目标
75
+
76
+ 默认裁剪:
77
+
78
+ ```bash
79
+ --targets cookies,headers
80
+ ```
81
+
82
+ 支持的目标:
83
+
84
+ ```text
85
+ cookies,headers,query,body
86
+ ```
87
+
88
+ 第一版为了稳定性只做串行逐项删除验证,暂不提供 `--concurrency`。
89
+
90
+ ## 终端渲染
91
+
92
+ TTY 输出会使用彩色终端渲染,并在重放请求时显示 ASCII loading 动画。CI、日志或需要复制结果时,可以使用 `--plain` 输出纯文本;使用 `--json` 时 stdout 始终只输出 JSON,不包含 loading 或彩色装饰。
93
+
94
+ ## 安全提醒
95
+
96
+ 只对可以重复发送的请求使用 `curltrim`。它会向目标服务发送真实 HTTP 请求。
@@ -0,0 +1,6 @@
1
+ import type { AssertionConfig, ReplayResponse } from "./types.js";
2
+ export interface AssertionResult {
3
+ ok: boolean;
4
+ reasons: string[];
5
+ }
6
+ export declare function assertReplaySuccess(response: ReplayResponse, config: AssertionConfig): AssertionResult;
@@ -0,0 +1,176 @@
1
+ import { JSONPath } from "jsonpath-plus";
2
+ import { parseJsonExpectation, parseStatusExpectation } from "./config.js";
3
+ export function assertReplaySuccess(response, config) {
4
+ const reasons = [];
5
+ const hasStatusExpectation = config.status !== undefined;
6
+ const hasOtherExpectations = config.text.length > 0 || config.regex.length > 0 || config.json.length > 0;
7
+ const shouldCheckStatus = hasStatusExpectation || !hasOtherExpectations;
8
+ if (shouldCheckStatus) {
9
+ const statusExpression = config.status ?? "200-299";
10
+ const statusMatcher = parseStatusMatcher(statusExpression, reasons);
11
+ if (statusMatcher && !statusMatcher(response.status)) {
12
+ reasons.push(`status ${response.status} did not match ${statusExpression}`);
13
+ }
14
+ }
15
+ for (const expectedText of config.text) {
16
+ if (!response.bodyText.includes(expectedText)) {
17
+ reasons.push(`response text did not include ${expectedText}`);
18
+ }
19
+ }
20
+ for (const pattern of config.regex) {
21
+ const regex = parseRegex(pattern, `invalid regex: ${pattern}`, reasons);
22
+ if (regex && !regex.test(response.bodyText)) {
23
+ reasons.push(`response text did not match /${pattern}/`);
24
+ }
25
+ }
26
+ if (config.json.length > 0) {
27
+ const parsed = parseJsonBody(response.bodyText, reasons);
28
+ if (parsed.ok) {
29
+ for (const expression of config.json) {
30
+ const expectation = parseJsonExpectationSafely(expression, reasons);
31
+ if (expectation) {
32
+ const result = evaluateJsonExpectation(parsed.value, expectation);
33
+ if (!result.ok) {
34
+ reasons.push(result.reason ?? `json expectation failed: ${expression}`);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ return { ok: reasons.length === 0, reasons };
41
+ }
42
+ function parseStatusMatcher(expression, reasons) {
43
+ try {
44
+ return parseStatusExpectation(expression);
45
+ }
46
+ catch (error) {
47
+ reasons.push(`invalid status expectation: ${formatError(error)}`);
48
+ return undefined;
49
+ }
50
+ }
51
+ function parseRegex(pattern, reasonPrefix, reasons) {
52
+ try {
53
+ return new RegExp(pattern);
54
+ }
55
+ catch (error) {
56
+ reasons.push(`${reasonPrefix}: ${formatError(error)}`);
57
+ return undefined;
58
+ }
59
+ }
60
+ function parseJsonBody(bodyText, reasons) {
61
+ try {
62
+ return { ok: true, value: JSON.parse(bodyText) };
63
+ }
64
+ catch {
65
+ reasons.push("response body was not valid JSON");
66
+ return { ok: false };
67
+ }
68
+ }
69
+ function parseJsonExpectationSafely(expression, reasons) {
70
+ try {
71
+ return parseJsonExpectation(expression);
72
+ }
73
+ catch (error) {
74
+ reasons.push(`invalid json expectation: ${formatError(error)}`);
75
+ return undefined;
76
+ }
77
+ }
78
+ function evaluateJsonExpectation(body, expectation) {
79
+ const values = readJsonPath(body, expectation.path);
80
+ if (!values.ok) {
81
+ return values;
82
+ }
83
+ if (expectation.operator === "exists") {
84
+ return { ok: values.value.length > 0 };
85
+ }
86
+ if (values.value.length === 0 || expectation.expected === undefined) {
87
+ return { ok: false };
88
+ }
89
+ for (const actual of values.value) {
90
+ const result = compareValue(actual, expectation.operator, expectation.expected);
91
+ if (!result.ok && result.reason) {
92
+ return result;
93
+ }
94
+ if (result.ok) {
95
+ return { ok: true };
96
+ }
97
+ }
98
+ return { ok: false };
99
+ }
100
+ function readJsonPath(body, path) {
101
+ try {
102
+ return { ok: true, value: JSONPath({ path, json: body }) };
103
+ }
104
+ catch (error) {
105
+ return { ok: false, reason: `invalid json path: ${formatError(error)}` };
106
+ }
107
+ }
108
+ function compareValue(actual, operator, expected) {
109
+ if (operator === "=")
110
+ return { ok: comparableText(actual) === expected };
111
+ if (operator === "!=")
112
+ return { ok: comparableText(actual) !== expected };
113
+ if (operator === "contains") {
114
+ if (Array.isArray(actual)) {
115
+ return { ok: actual.some((item) => comparableText(item) === expected) };
116
+ }
117
+ if (isJsonComposite(actual)) {
118
+ return { ok: false };
119
+ }
120
+ return { ok: String(actual).includes(expected) };
121
+ }
122
+ if (operator === "matches") {
123
+ const regex = parseRegexValue(expected);
124
+ if (!regex.ok) {
125
+ return regex;
126
+ }
127
+ if (isJsonComposite(actual)) {
128
+ return { ok: false };
129
+ }
130
+ return { ok: regex.value.test(String(actual)) };
131
+ }
132
+ if (!isNumericComparablePrimitive(actual)) {
133
+ return { ok: false };
134
+ }
135
+ const actualNumber = Number(actual);
136
+ const expectedNumber = Number(expected);
137
+ if (!Number.isFinite(actualNumber) || !Number.isFinite(expectedNumber)) {
138
+ return { ok: false };
139
+ }
140
+ if (operator === ">")
141
+ return { ok: actualNumber > expectedNumber };
142
+ if (operator === ">=")
143
+ return { ok: actualNumber >= expectedNumber };
144
+ if (operator === "<")
145
+ return { ok: actualNumber < expectedNumber };
146
+ if (operator === "<=")
147
+ return { ok: actualNumber <= expectedNumber };
148
+ return { ok: false };
149
+ }
150
+ function parseRegexValue(pattern) {
151
+ try {
152
+ return { ok: true, value: new RegExp(pattern) };
153
+ }
154
+ catch (error) {
155
+ return {
156
+ ok: false,
157
+ reason: `invalid json matches regex: ${pattern}: ${formatError(error)}`
158
+ };
159
+ }
160
+ }
161
+ function comparableText(value) {
162
+ if (isJsonComposite(value)) {
163
+ return JSON.stringify(value) ?? String(value);
164
+ }
165
+ return String(value);
166
+ }
167
+ function isJsonComposite(value) {
168
+ return value !== null && typeof value === "object";
169
+ }
170
+ function isNumericComparablePrimitive(value) {
171
+ return typeof value === "number" || typeof value === "string" || typeof value === "boolean";
172
+ }
173
+ function formatError(error) {
174
+ return error instanceof Error ? error.message : String(error);
175
+ }
176
+ //# sourceMappingURL=assertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.js","sourceRoot":"","sources":["../../src/assertions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Candidate, RequestModel, Target } from "./types.js";
2
+ export declare function createCandidates(request: RequestModel, targets: Target[]): Candidate[];
3
+ export declare function removeCandidate(request: RequestModel, candidate: Candidate): RequestModel;
@@ -0,0 +1,74 @@
1
+ export function createCandidates(request, targets) {
2
+ const selectedTargets = new Set(targets);
3
+ const candidates = [];
4
+ if (selectedTargets.has("cookies")) {
5
+ for (const key of Object.keys(request.cookies)) {
6
+ candidates.push({ target: "cookies", key, label: `cookie: ${key}` });
7
+ }
8
+ }
9
+ if (selectedTargets.has("headers")) {
10
+ for (const key of Object.keys(request.headers)) {
11
+ if (key.toLowerCase() !== "cookie") {
12
+ candidates.push({ target: "headers", key, label: `header: ${key}` });
13
+ }
14
+ }
15
+ }
16
+ if (selectedTargets.has("query")) {
17
+ for (const key of Object.keys(request.query)) {
18
+ candidates.push({ target: "query", key, label: `query: ${key}` });
19
+ }
20
+ }
21
+ if (selectedTargets.has("body") && isObjectBody(request.body)) {
22
+ for (const key of Object.keys(request.body)) {
23
+ candidates.push({ target: "body", key, label: `body: ${key}` });
24
+ }
25
+ }
26
+ return candidates;
27
+ }
28
+ export function removeCandidate(request, candidate) {
29
+ const next = cloneRequest(request);
30
+ if (candidate.target === "cookies") {
31
+ delete next.cookies[candidate.key];
32
+ }
33
+ else if (candidate.target === "headers") {
34
+ delete next.headers[candidate.key];
35
+ }
36
+ else if (candidate.target === "query") {
37
+ delete next.query[candidate.key];
38
+ }
39
+ else if (candidate.target === "body" && isObjectBody(next.body)) {
40
+ delete next.body[candidate.key];
41
+ }
42
+ return next;
43
+ }
44
+ function cloneRequest(request) {
45
+ const base = {
46
+ ...request,
47
+ headers: { ...request.headers },
48
+ cookies: { ...request.cookies },
49
+ query: cloneQuery(request.query)
50
+ };
51
+ if (request.bodyType === "none") {
52
+ return { ...base, bodyType: "none", body: undefined };
53
+ }
54
+ if (request.bodyType === "raw") {
55
+ return { ...base, bodyType: "raw", body: request.body };
56
+ }
57
+ if (request.bodyType === "json") {
58
+ return { ...base, bodyType: "json", body: cloneBody(request.body) };
59
+ }
60
+ if (request.bodyType === "form") {
61
+ return { ...base, bodyType: "form", body: cloneBody(request.body) };
62
+ }
63
+ return { ...base, bodyType: "urlencoded", body: cloneBody(request.body) };
64
+ }
65
+ function cloneQuery(query) {
66
+ return Object.fromEntries(Object.entries(query).map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]));
67
+ }
68
+ function cloneBody(body) {
69
+ return body !== null && typeof body === "object" ? structuredClone(body) : body;
70
+ }
71
+ function isObjectBody(body) {
72
+ return body !== null && typeof body === "object" && !Array.isArray(body);
73
+ }
74
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../src/candidates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import type { CliConfig } from "./types.js";
3
+ export declare function buildCliConfig(argv: string[]): Promise<CliConfig | undefined>;
4
+ export declare function run(argv?: string[]): Promise<void>;
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import { pathToFileURL } from "node:url";
4
+ import { Command } from "commander";
5
+ import { parseTargets } from "./config.js";
6
+ import { parseCurlCommand } from "./curl-parser.js";
7
+ import { buildInspectSummary } from "./inspect.js";
8
+ import { renderInspectJson, renderTrimJson } from "./json-output.js";
9
+ import { minimizeRequest } from "./minimizer.js";
10
+ import { replayRequest } from "./replayer.js";
11
+ import { createProgressRenderer, formatProgressEvent, renderInspectReport, renderReport } from "./terminal.js";
12
+ function collect(value, previous) {
13
+ return previous.concat(value);
14
+ }
15
+ function createCliProgram() {
16
+ const program = new Command();
17
+ return program
18
+ .name("curltrim")
19
+ .description("Trim curl requests to the minimum parameters needed for a successful replay. Direct mode expects the full curl command as one argument; use --file for unquoted commands.")
20
+ .version("0.1.0")
21
+ .argument("[curlCommand]", "full curl command string, passed as one argument")
22
+ .option("--file <path>", "read curl command from a file")
23
+ .option("--targets <targets>", "comma-separated targets to trim", "cookies,headers")
24
+ .option("--delay <ms>", "delay between replay attempts in milliseconds", "0")
25
+ .option("--expect-status <status>", "expected status expression")
26
+ .option("--expect-text <text>", "expected response text", collect, [])
27
+ .option("--expect-regex <regex>", "expected response regex", collect, [])
28
+ .option("--expect-json <json>", "expected JSON expression", collect, [])
29
+ .option("--plain", "render plain output")
30
+ .option("--inspect", "parse and summarize the curl request without replaying it")
31
+ .option("--json", "render machine-readable JSON output")
32
+ .exitOverride();
33
+ }
34
+ async function parseCliInput(argv) {
35
+ const program = createCliProgram();
36
+ await program.parseAsync(argv, { from: "user" });
37
+ return {
38
+ options: program.opts(),
39
+ positionalCurlCommand: program.args[0]
40
+ };
41
+ }
42
+ async function buildInteractiveOptions(argv) {
43
+ const { options } = await parseCliInput(argv);
44
+ return {
45
+ plain: Boolean(options.plain),
46
+ inspect: options.inspect,
47
+ json: options.json
48
+ };
49
+ }
50
+ export async function buildCliConfig(argv) {
51
+ const { options, positionalCurlCommand } = await parseCliInput(argv);
52
+ if (options.file !== undefined && positionalCurlCommand !== undefined) {
53
+ throw new Error("Use either --file or a direct curl command, not both");
54
+ }
55
+ const delayMs = Number(options.delay);
56
+ if (!Number.isInteger(delayMs) || delayMs < 0) {
57
+ throw new Error("Delay must be a non-negative integer");
58
+ }
59
+ const curlCommand = options.file === undefined ? positionalCurlCommand : await readFile(options.file, "utf8");
60
+ if (!curlCommand) {
61
+ return undefined;
62
+ }
63
+ return {
64
+ curlCommand,
65
+ targets: parseTargets(options.targets),
66
+ assertions: {
67
+ status: options.expectStatus,
68
+ text: options.expectText ?? [],
69
+ regex: options.expectRegex ?? [],
70
+ json: options.expectJson ?? []
71
+ },
72
+ delayMs,
73
+ plain: Boolean(options.plain),
74
+ inspect: Boolean(options.inspect),
75
+ json: Boolean(options.json)
76
+ };
77
+ }
78
+ export async function run(argv = process.argv.slice(2)) {
79
+ let config;
80
+ try {
81
+ config = await buildCliConfig(argv);
82
+ }
83
+ catch (error) {
84
+ if (isDisplayOnlyCommanderError(error)) {
85
+ return;
86
+ }
87
+ throw error;
88
+ }
89
+ if (config === undefined) {
90
+ const { runInteractive } = await import("./interactive.js");
91
+ config = await runInteractive(await buildInteractiveOptions(argv));
92
+ }
93
+ const request = parseCurlCommand(config.curlCommand);
94
+ const inspectSummary = buildInspectSummary(request, config.targets, config.assertions);
95
+ if (config.json === true && config.inspect === true) {
96
+ process.stdout.write(renderInspectJson(inspectSummary));
97
+ return;
98
+ }
99
+ if (config.inspect === true) {
100
+ process.stdout.write(renderInspectReport(inspectSummary, { plain: config.plain }));
101
+ return;
102
+ }
103
+ if (config.json === true) {
104
+ const result = await minimizeRequest({
105
+ request,
106
+ targets: config.targets,
107
+ assertions: config.assertions,
108
+ delayMs: config.delayMs,
109
+ replay: replayRequest
110
+ });
111
+ process.stdout.write(renderTrimJson(result));
112
+ return;
113
+ }
114
+ const progress = createProgressRenderer({ plain: config.plain });
115
+ try {
116
+ const result = await minimizeRequest({
117
+ request,
118
+ targets: config.targets,
119
+ assertions: config.assertions,
120
+ delayMs: config.delayMs,
121
+ replay: replayRequest,
122
+ onProgress: (event) => progress.update(formatProgressEvent(event))
123
+ });
124
+ progress.succeed("Trim complete");
125
+ process.stdout.write(renderReport(result, { plain: config.plain }));
126
+ }
127
+ catch (error) {
128
+ progress.fail("Trim failed");
129
+ throw error;
130
+ }
131
+ }
132
+ function getCommanderError(error) {
133
+ if (typeof error !== "object" || error === null || !("code" in error) || !("exitCode" in error)) {
134
+ return undefined;
135
+ }
136
+ const { code, exitCode } = error;
137
+ if (typeof code !== "string" || !code.startsWith("commander.")) {
138
+ return undefined;
139
+ }
140
+ if (typeof exitCode !== "number") {
141
+ return undefined;
142
+ }
143
+ return { code, exitCode };
144
+ }
145
+ function isDisplayOnlyCommanderError(error) {
146
+ const commanderError = getCommanderError(error);
147
+ return (commanderError?.code === "commander.helpDisplayed" ||
148
+ commanderError?.code === "commander.version");
149
+ }
150
+ function formatError(error) {
151
+ return error instanceof Error ? error.message : String(error);
152
+ }
153
+ function isDirectExecution() {
154
+ const entry = process.argv[1];
155
+ return entry !== undefined && import.meta.url === pathToFileURL(entry).href;
156
+ }
157
+ if (isDirectExecution()) {
158
+ run().catch((error) => {
159
+ const commanderError = getCommanderError(error);
160
+ if (commanderError) {
161
+ process.exitCode = commanderError.exitCode;
162
+ return;
163
+ }
164
+ console.error(formatError(error));
165
+ process.exitCode = 1;
166
+ });
167
+ }
168
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { JsonExpectation, Target } from "./types.js";
2
+ export declare const DEFAULT_TARGETS: Target[];
3
+ export declare function parseTargets(value: string | undefined): Target[];
4
+ export declare function parseStatusExpectation(expression: string): (status: number) => boolean;
5
+ export declare function parseJsonExpectation(expression: string): JsonExpectation;
@@ -0,0 +1,87 @@
1
+ export const DEFAULT_TARGETS = ["cookies", "headers"];
2
+ const VALID_TARGETS = new Set(["cookies", "headers", "query", "body"]);
3
+ const JSON_OPERATORS = [
4
+ "exists",
5
+ ">=",
6
+ "<=",
7
+ "!=",
8
+ "=",
9
+ ">",
10
+ "<",
11
+ "contains",
12
+ "matches"
13
+ ];
14
+ export function parseTargets(value) {
15
+ if (!value || value.trim() === "") {
16
+ return [...DEFAULT_TARGETS];
17
+ }
18
+ const targets = value
19
+ .split(",")
20
+ .map((item) => item.trim())
21
+ .filter(Boolean);
22
+ const invalid = targets.filter((target) => !VALID_TARGETS.has(target));
23
+ if (invalid.length > 0) {
24
+ throw new Error(`Invalid target: ${invalid.join(", ")}`);
25
+ }
26
+ return [...new Set(targets)];
27
+ }
28
+ export function parseStatusExpectation(expression) {
29
+ const parts = expression.split(",").map((part) => part.trim());
30
+ if (parts.some((part) => part === "")) {
31
+ throw new Error("Invalid status expression: empty value");
32
+ }
33
+ const matchers = parts.map((part) => {
34
+ const range = part.match(/^(\d{3})-(\d{3})$/);
35
+ if (range) {
36
+ const start = Number(range[1]);
37
+ const end = Number(range[2]);
38
+ if (start > end) {
39
+ throw new Error(`Invalid status expression: ${part}`);
40
+ }
41
+ return (status) => status >= start && status <= end;
42
+ }
43
+ if (/^\d{3}$/.test(part)) {
44
+ const expected = Number(part);
45
+ return (status) => status === expected;
46
+ }
47
+ throw new Error(`Invalid status expression: ${part}`);
48
+ });
49
+ return (status) => matchers.some((matcher) => matcher(status));
50
+ }
51
+ export function parseJsonExpectation(expression) {
52
+ const trimmed = expression.trim();
53
+ const existsMatch = trimmed.match(/^(.+?)\s+exists$/);
54
+ if (existsMatch) {
55
+ return { path: existsMatch[1].trim(), operator: "exists" };
56
+ }
57
+ const operatorMatch = JSON_OPERATORS.reduce((bestMatch, operator) => {
58
+ if (operator === "exists") {
59
+ return bestMatch;
60
+ }
61
+ const marker = ` ${operator} `;
62
+ const index = trimmed.indexOf(marker);
63
+ if (index <= 0) {
64
+ return bestMatch;
65
+ }
66
+ if (!bestMatch ||
67
+ index < bestMatch.index ||
68
+ (index === bestMatch.index && operator.length > bestMatch.operator.length)) {
69
+ return { index, marker, operator };
70
+ }
71
+ return bestMatch;
72
+ }, undefined);
73
+ if (operatorMatch) {
74
+ const { index, marker, operator } = operatorMatch;
75
+ const path = trimmed.slice(0, index).trim();
76
+ const expected = trimmed.slice(index + marker.length).trim();
77
+ if (path !== "" && expected !== "") {
78
+ return {
79
+ path,
80
+ operator,
81
+ expected
82
+ };
83
+ }
84
+ }
85
+ throw new Error(`Invalid JSON expectation: ${expression}`);
86
+ }
87
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}