tryscript 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -0
- package/dist/bin.cjs +390 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.d.cts +2 -0
- package/dist/bin.d.mts +2 -0
- package/dist/bin.mjs +389 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.cjs +11 -0
- package/dist/index.d.cts +165 -0
- package/dist/index.d.mts +165 -0
- package/dist/index.mjs +4 -0
- package/dist/src-CeUA446P.cjs +422 -0
- package/dist/src-CeUA446P.cjs.map +1 -0
- package/dist/src-UjaSQrqA.mjs +328 -0
- package/dist/src-UjaSQrqA.mjs.map +1 -0
- package/docs/tryscript-reference.md +163 -0
- package/package.json +76 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/types.d.ts
|
|
5
|
+
declare const TestConfigSchema: z.ZodObject<{
|
|
6
|
+
bin: z.ZodOptional<z.ZodString>;
|
|
7
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
8
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
patterns: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>>>;
|
|
10
|
+
tests: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
bin?: string | undefined;
|
|
13
|
+
env?: Record<string, string> | undefined;
|
|
14
|
+
timeout?: number | undefined;
|
|
15
|
+
patterns?: Record<string, string | RegExp> | undefined;
|
|
16
|
+
tests?: string[] | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
bin?: string | undefined;
|
|
19
|
+
env?: Record<string, string> | undefined;
|
|
20
|
+
timeout?: number | undefined;
|
|
21
|
+
patterns?: Record<string, string | RegExp> | undefined;
|
|
22
|
+
tests?: string[] | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for a test file or global config.
|
|
26
|
+
*/
|
|
27
|
+
type TestConfig = z.infer<typeof TestConfigSchema>;
|
|
28
|
+
/**
|
|
29
|
+
* A single command block within a test file.
|
|
30
|
+
*/
|
|
31
|
+
interface TestBlock {
|
|
32
|
+
/** Optional test name from preceding markdown heading */
|
|
33
|
+
name?: string;
|
|
34
|
+
/** The command to execute (may span multiple lines with > continuation) */
|
|
35
|
+
command: string;
|
|
36
|
+
/** Expected output (may include elision patterns) */
|
|
37
|
+
expectedOutput: string;
|
|
38
|
+
/** Expected exit code (default: 0) */
|
|
39
|
+
expectedExitCode: number;
|
|
40
|
+
/** Line number where this block starts (1-indexed, for error reporting) */
|
|
41
|
+
lineNumber: number;
|
|
42
|
+
/** Raw content of the block for update mode */
|
|
43
|
+
rawContent: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A parsed test file with all its blocks.
|
|
47
|
+
*/
|
|
48
|
+
interface TestFile {
|
|
49
|
+
/** Absolute path to the test file */
|
|
50
|
+
path: string;
|
|
51
|
+
/** Merged configuration (global + frontmatter) */
|
|
52
|
+
config: TestConfig;
|
|
53
|
+
/** Parsed test blocks in order */
|
|
54
|
+
blocks: TestBlock[];
|
|
55
|
+
/** Raw file content for update mode */
|
|
56
|
+
rawContent: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Result of running a single test block.
|
|
60
|
+
*/
|
|
61
|
+
interface TestBlockResult {
|
|
62
|
+
block: TestBlock;
|
|
63
|
+
passed: boolean;
|
|
64
|
+
actualOutput: string;
|
|
65
|
+
actualExitCode: number;
|
|
66
|
+
/** Diff if test failed (unified diff format) */
|
|
67
|
+
diff?: string;
|
|
68
|
+
/** Duration in milliseconds */
|
|
69
|
+
duration: number;
|
|
70
|
+
/** Error message if execution failed */
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Result of running all blocks in a test file.
|
|
75
|
+
*/
|
|
76
|
+
interface TestFileResult {
|
|
77
|
+
file: TestFile;
|
|
78
|
+
results: TestBlockResult[];
|
|
79
|
+
passed: boolean;
|
|
80
|
+
/** Total duration in milliseconds */
|
|
81
|
+
duration: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Summary of running multiple test files.
|
|
85
|
+
*/
|
|
86
|
+
interface TestRunSummary {
|
|
87
|
+
files: TestFileResult[];
|
|
88
|
+
totalPassed: number;
|
|
89
|
+
totalFailed: number;
|
|
90
|
+
totalBlocks: number;
|
|
91
|
+
duration: number;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/lib/config.d.ts
|
|
95
|
+
interface TryscriptConfig {
|
|
96
|
+
bin?: string;
|
|
97
|
+
env?: Record<string, string>;
|
|
98
|
+
timeout?: number;
|
|
99
|
+
patterns?: Record<string, RegExp | string>;
|
|
100
|
+
tests?: string[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Helper for typed config files.
|
|
104
|
+
*/
|
|
105
|
+
declare function defineConfig(config: TryscriptConfig): TryscriptConfig;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/lib/parser.d.ts
|
|
108
|
+
/**
|
|
109
|
+
* Parse a .tryscript.md file into structured test data.
|
|
110
|
+
*/
|
|
111
|
+
declare function parseTestFile(content: string, filePath: string): TestFile;
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/lib/runner.d.ts
|
|
114
|
+
/**
|
|
115
|
+
* Execution context for a test file.
|
|
116
|
+
* Created once per file, contains the temp directory.
|
|
117
|
+
*/
|
|
118
|
+
interface ExecutionContext {
|
|
119
|
+
/** Temporary directory for this test file (resolved, no symlinks) */
|
|
120
|
+
tempDir: string;
|
|
121
|
+
/** Directory containing the test file (for portable test commands) */
|
|
122
|
+
testDir: string;
|
|
123
|
+
/** Resolved binary path */
|
|
124
|
+
binPath: string;
|
|
125
|
+
/** Environment variables */
|
|
126
|
+
env: Record<string, string>;
|
|
127
|
+
/** Timeout per command */
|
|
128
|
+
timeout: number;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create an execution context for a test file.
|
|
132
|
+
*/
|
|
133
|
+
declare function createExecutionContext(config: TryscriptConfig, testFilePath: string): Promise<ExecutionContext>;
|
|
134
|
+
/**
|
|
135
|
+
* Clean up execution context (remove temp directory).
|
|
136
|
+
*/
|
|
137
|
+
declare function cleanupExecutionContext(ctx: ExecutionContext): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Run a single test block and return the result.
|
|
140
|
+
*/
|
|
141
|
+
declare function runBlock(block: TestBlock, ctx: ExecutionContext): Promise<TestBlockResult>;
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/lib/matcher.d.ts
|
|
144
|
+
/**
|
|
145
|
+
* Normalize actual output for comparison.
|
|
146
|
+
* - Remove ANSI escape codes (colors, etc.)
|
|
147
|
+
* - Normalize line endings to \n
|
|
148
|
+
* - Normalize paths (Windows backslashes to forward slashes)
|
|
149
|
+
* - Trim trailing whitespace from lines
|
|
150
|
+
* - Ensure single trailing newline
|
|
151
|
+
*/
|
|
152
|
+
declare function normalizeOutput(output: string): string;
|
|
153
|
+
/**
|
|
154
|
+
* Check if actual output matches expected pattern.
|
|
155
|
+
*/
|
|
156
|
+
declare function matchOutput(actual: string, expected: string, context: {
|
|
157
|
+
root: string;
|
|
158
|
+
cwd: string;
|
|
159
|
+
}, customPatterns?: Record<string, string | RegExp>): boolean;
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/index.d.ts
|
|
162
|
+
declare const VERSION: string;
|
|
163
|
+
//#endregion
|
|
164
|
+
export { type ExecutionContext, type TestBlock, type TestBlockResult, type TestConfig, type TestFile, type TestFileResult, type TestRunSummary, type TryscriptConfig, VERSION, cleanupExecutionContext, createExecutionContext, defineConfig, matchOutput, normalizeOutput, parseTestFile, runBlock };
|
|
165
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
|
|
2
|
+
import { a as createExecutionContext, c as defineConfig, i as cleanupExecutionContext, n as matchOutput, o as runBlock, r as normalizeOutput, s as parseTestFile, t as VERSION } from "./src-UjaSQrqA.mjs";
|
|
3
|
+
|
|
4
|
+
export { VERSION, cleanupExecutionContext, createExecutionContext, defineConfig, matchOutput, normalizeOutput, parseTestFile, runBlock };
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
|
|
2
|
+
//#region rolldown:runtime
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let node_url = require("node:url");
|
|
30
|
+
let node_fs = require("node:fs");
|
|
31
|
+
let node_path = require("node:path");
|
|
32
|
+
let yaml = require("yaml");
|
|
33
|
+
let node_child_process = require("node:child_process");
|
|
34
|
+
let node_fs_promises = require("node:fs/promises");
|
|
35
|
+
let node_os = require("node:os");
|
|
36
|
+
let tree_kill = require("tree-kill");
|
|
37
|
+
tree_kill = __toESM(tree_kill);
|
|
38
|
+
let strip_ansi = require("strip-ansi");
|
|
39
|
+
strip_ansi = __toESM(strip_ansi);
|
|
40
|
+
|
|
41
|
+
//#region src/lib/config.ts
|
|
42
|
+
const CONFIG_FILES = [
|
|
43
|
+
"tryscript.config.ts",
|
|
44
|
+
"tryscript.config.js",
|
|
45
|
+
"tryscript.config.mjs"
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Load config file using dynamic import.
|
|
49
|
+
* Supports both TypeScript (via tsx/ts-node) and JavaScript configs.
|
|
50
|
+
*/
|
|
51
|
+
async function loadConfig(baseDir) {
|
|
52
|
+
for (const filename of CONFIG_FILES) {
|
|
53
|
+
const configPath = (0, node_path.resolve)(baseDir, filename);
|
|
54
|
+
if ((0, node_fs.existsSync)(configPath)) {
|
|
55
|
+
const module$1 = await import((0, node_url.pathToFileURL)(configPath).href);
|
|
56
|
+
return module$1.default ?? module$1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Merge config with frontmatter overrides.
|
|
63
|
+
* Frontmatter takes precedence over config file.
|
|
64
|
+
*/
|
|
65
|
+
function mergeConfig(base, frontmatter) {
|
|
66
|
+
return {
|
|
67
|
+
...base,
|
|
68
|
+
...frontmatter,
|
|
69
|
+
env: {
|
|
70
|
+
...base.env,
|
|
71
|
+
...frontmatter.env
|
|
72
|
+
},
|
|
73
|
+
patterns: {
|
|
74
|
+
...base.patterns,
|
|
75
|
+
...frontmatter.patterns
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Helper for typed config files.
|
|
81
|
+
*/
|
|
82
|
+
function defineConfig(config) {
|
|
83
|
+
return config;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/lib/parser.ts
|
|
88
|
+
/** Regex to match YAML frontmatter at the start of a file */
|
|
89
|
+
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/;
|
|
90
|
+
/** Regex to match fenced code blocks with console/bash info string */
|
|
91
|
+
const CODE_BLOCK_REGEX = /```(console|bash)\r?\n([\s\S]*?)```/g;
|
|
92
|
+
/** Regex to match markdown headings (for test names) */
|
|
93
|
+
const HEADING_REGEX = /^#+\s+(?:Test:\s*)?(.+)$/m;
|
|
94
|
+
/**
|
|
95
|
+
* Parse a .tryscript.md file into structured test data.
|
|
96
|
+
*/
|
|
97
|
+
function parseTestFile(content, filePath) {
|
|
98
|
+
const rawContent = content;
|
|
99
|
+
let config = {};
|
|
100
|
+
let body = content;
|
|
101
|
+
const frontmatterMatch = FRONTMATTER_REGEX.exec(content);
|
|
102
|
+
if (frontmatterMatch) {
|
|
103
|
+
config = (0, yaml.parse)(frontmatterMatch[1] ?? "");
|
|
104
|
+
body = content.slice(frontmatterMatch[0].length);
|
|
105
|
+
}
|
|
106
|
+
const blocks = [];
|
|
107
|
+
CODE_BLOCK_REGEX.lastIndex = 0;
|
|
108
|
+
let match;
|
|
109
|
+
while ((match = CODE_BLOCK_REGEX.exec(body)) !== null) {
|
|
110
|
+
const blockContent = match[2] ?? "";
|
|
111
|
+
const blockStart = match.index;
|
|
112
|
+
const lineNumber = content.slice(0, content.indexOf(match[0])).split("\n").length;
|
|
113
|
+
const name = [...body.slice(0, blockStart).matchAll(new RegExp(HEADING_REGEX.source, "gm"))].pop()?.[1]?.trim();
|
|
114
|
+
const parsed = parseBlockContent(blockContent);
|
|
115
|
+
if (parsed) blocks.push({
|
|
116
|
+
name,
|
|
117
|
+
command: parsed.command,
|
|
118
|
+
expectedOutput: parsed.expectedOutput,
|
|
119
|
+
expectedExitCode: parsed.expectedExitCode,
|
|
120
|
+
lineNumber,
|
|
121
|
+
rawContent: match[0]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
path: filePath,
|
|
126
|
+
config,
|
|
127
|
+
blocks,
|
|
128
|
+
rawContent
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Parse the content of a single console block.
|
|
133
|
+
*/
|
|
134
|
+
function parseBlockContent(content) {
|
|
135
|
+
const lines = content.split("\n");
|
|
136
|
+
const commandLines = [];
|
|
137
|
+
const outputLines = [];
|
|
138
|
+
let expectedExitCode = 0;
|
|
139
|
+
let inCommand = false;
|
|
140
|
+
for (const line of lines) if (line.startsWith("$ ")) {
|
|
141
|
+
inCommand = true;
|
|
142
|
+
commandLines.push(line.slice(2));
|
|
143
|
+
} else if (line.startsWith("> ") && inCommand) commandLines.push(line.slice(2));
|
|
144
|
+
else if (line.startsWith("? ")) {
|
|
145
|
+
inCommand = false;
|
|
146
|
+
expectedExitCode = parseInt(line.slice(2).trim(), 10);
|
|
147
|
+
} else {
|
|
148
|
+
inCommand = false;
|
|
149
|
+
outputLines.push(line);
|
|
150
|
+
}
|
|
151
|
+
if (commandLines.length === 0) return null;
|
|
152
|
+
let command = "";
|
|
153
|
+
for (let i = 0; i < commandLines.length; i++) {
|
|
154
|
+
const line = commandLines[i] ?? "";
|
|
155
|
+
if (line.endsWith("\\")) command += line.slice(0, -1) + " ";
|
|
156
|
+
else {
|
|
157
|
+
command += line;
|
|
158
|
+
if (i < commandLines.length - 1) command += " ";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
let expectedOutput = outputLines.join("\n");
|
|
162
|
+
expectedOutput = expectedOutput.replace(/\n+$/, "");
|
|
163
|
+
if (expectedOutput) expectedOutput += "\n";
|
|
164
|
+
return {
|
|
165
|
+
command: command.trim(),
|
|
166
|
+
expectedOutput,
|
|
167
|
+
expectedExitCode
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/lib/runner.ts
|
|
173
|
+
/** Default timeout in milliseconds */
|
|
174
|
+
const DEFAULT_TIMEOUT = 3e4;
|
|
175
|
+
/**
|
|
176
|
+
* Create an execution context for a test file.
|
|
177
|
+
*/
|
|
178
|
+
async function createExecutionContext(config, testFilePath) {
|
|
179
|
+
const tempDir = await (0, node_fs_promises.realpath)(await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "tryscript-")));
|
|
180
|
+
const testDir = (0, node_path.resolve)((0, node_path.dirname)(testFilePath));
|
|
181
|
+
let binPath = config.bin ?? "";
|
|
182
|
+
if (binPath && !binPath.startsWith("/")) binPath = (0, node_path.join)(testDir, binPath);
|
|
183
|
+
return {
|
|
184
|
+
tempDir,
|
|
185
|
+
testDir,
|
|
186
|
+
binPath,
|
|
187
|
+
env: {
|
|
188
|
+
...process.env,
|
|
189
|
+
...config.env,
|
|
190
|
+
NO_COLOR: config.env?.NO_COLOR ?? "1",
|
|
191
|
+
FORCE_COLOR: "0",
|
|
192
|
+
TRYSCRIPT_TEST_DIR: testDir
|
|
193
|
+
},
|
|
194
|
+
timeout: config.timeout ?? DEFAULT_TIMEOUT
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Clean up execution context (remove temp directory).
|
|
199
|
+
*/
|
|
200
|
+
async function cleanupExecutionContext(ctx) {
|
|
201
|
+
await (0, node_fs_promises.rm)(ctx.tempDir, {
|
|
202
|
+
recursive: true,
|
|
203
|
+
force: true
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Run a single test block and return the result.
|
|
208
|
+
*/
|
|
209
|
+
async function runBlock(block, ctx) {
|
|
210
|
+
const startTime = Date.now();
|
|
211
|
+
try {
|
|
212
|
+
const { output, exitCode } = await executeCommand(block.command, ctx);
|
|
213
|
+
return {
|
|
214
|
+
block,
|
|
215
|
+
passed: true,
|
|
216
|
+
actualOutput: output,
|
|
217
|
+
actualExitCode: exitCode,
|
|
218
|
+
duration: Date.now() - startTime
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
block,
|
|
223
|
+
passed: false,
|
|
224
|
+
actualOutput: "",
|
|
225
|
+
actualExitCode: -1,
|
|
226
|
+
duration: Date.now() - startTime,
|
|
227
|
+
error: error instanceof Error ? error.message : String(error)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Execute a command and capture output.
|
|
233
|
+
*/
|
|
234
|
+
async function executeCommand(command, ctx) {
|
|
235
|
+
return new Promise((resolve$2, reject) => {
|
|
236
|
+
const proc = (0, node_child_process.spawn)(command, {
|
|
237
|
+
shell: true,
|
|
238
|
+
cwd: ctx.tempDir,
|
|
239
|
+
env: ctx.env,
|
|
240
|
+
stdio: [
|
|
241
|
+
"ignore",
|
|
242
|
+
"pipe",
|
|
243
|
+
"pipe"
|
|
244
|
+
]
|
|
245
|
+
});
|
|
246
|
+
const chunks = [];
|
|
247
|
+
proc.stdout.on("data", (data) => chunks.push(data));
|
|
248
|
+
proc.stderr.on("data", (data) => chunks.push(data));
|
|
249
|
+
const timeoutId = setTimeout(() => {
|
|
250
|
+
if (proc.pid) (0, tree_kill.default)(proc.pid, "SIGKILL");
|
|
251
|
+
reject(/* @__PURE__ */ new Error(`Command timed out after ${ctx.timeout}ms`));
|
|
252
|
+
}, ctx.timeout);
|
|
253
|
+
proc.on("close", (code) => {
|
|
254
|
+
clearTimeout(timeoutId);
|
|
255
|
+
resolve$2({
|
|
256
|
+
output: Buffer.concat(chunks).toString("utf-8"),
|
|
257
|
+
exitCode: code ?? 0
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
proc.on("error", (err) => {
|
|
261
|
+
clearTimeout(timeoutId);
|
|
262
|
+
reject(err);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/lib/matcher.ts
|
|
269
|
+
/**
|
|
270
|
+
* Escape special regex characters in a string.
|
|
271
|
+
*/
|
|
272
|
+
function escapeRegex(str) {
|
|
273
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
274
|
+
}
|
|
275
|
+
const MARKER = "";
|
|
276
|
+
/**
|
|
277
|
+
* Convert expected output with elision patterns to a regex.
|
|
278
|
+
*
|
|
279
|
+
* Handles (matching trycmd):
|
|
280
|
+
* - [..] — matches any characters on the same line (trycmd: [^\n]*?)
|
|
281
|
+
* - ... — matches zero or more complete lines (trycmd: \n(([^\n]*\n)*)?)
|
|
282
|
+
* - [EXE] — matches .exe on Windows, empty otherwise
|
|
283
|
+
* - [ROOT] — replaced with test root directory (pre-processed)
|
|
284
|
+
* - [CWD] — replaced with current working directory (pre-processed)
|
|
285
|
+
* - Custom [NAME] patterns from config (trycmd: TestCases::insert_var)
|
|
286
|
+
*/
|
|
287
|
+
function patternToRegex(expected, customPatterns = {}) {
|
|
288
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
289
|
+
let markerIndex = 0;
|
|
290
|
+
const getMarker = () => {
|
|
291
|
+
return `${MARKER}${markerIndex++}${MARKER}`;
|
|
292
|
+
};
|
|
293
|
+
let processed = expected;
|
|
294
|
+
const dotdotMarker = getMarker();
|
|
295
|
+
replacements.set(dotdotMarker, "[^\\n]*");
|
|
296
|
+
processed = processed.replaceAll("[..]", dotdotMarker);
|
|
297
|
+
const ellipsisMarker = getMarker();
|
|
298
|
+
replacements.set(ellipsisMarker, "(?:[^\\n]*\\n)*");
|
|
299
|
+
processed = processed.replace(/\.\.\.\n/g, ellipsisMarker);
|
|
300
|
+
const exeMarker = getMarker();
|
|
301
|
+
const exe = process.platform === "win32" ? "\\.exe" : "";
|
|
302
|
+
replacements.set(exeMarker, exe);
|
|
303
|
+
processed = processed.replaceAll("[EXE]", exeMarker);
|
|
304
|
+
for (const [name, pattern] of Object.entries(customPatterns)) {
|
|
305
|
+
const placeholder = `[${name}]`;
|
|
306
|
+
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
307
|
+
const marker = getMarker();
|
|
308
|
+
replacements.set(marker, `(${patternStr})`);
|
|
309
|
+
processed = processed.replaceAll(placeholder, marker);
|
|
310
|
+
}
|
|
311
|
+
let regex = escapeRegex(processed);
|
|
312
|
+
for (const [marker, replacement] of replacements) regex = regex.replaceAll(escapeRegex(marker), replacement);
|
|
313
|
+
return new RegExp(`^${regex}$`, "s");
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Pre-process expected output to replace path placeholders with actual paths.
|
|
317
|
+
* This happens BEFORE pattern matching.
|
|
318
|
+
*/
|
|
319
|
+
function preprocessPaths(expected, context) {
|
|
320
|
+
let result = expected;
|
|
321
|
+
const normalizedRoot = context.root.replace(/\\/g, "/");
|
|
322
|
+
const normalizedCwd = context.cwd.replace(/\\/g, "/");
|
|
323
|
+
result = result.replaceAll("[ROOT]", normalizedRoot);
|
|
324
|
+
result = result.replaceAll("[CWD]", normalizedCwd);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Normalize actual output for comparison.
|
|
329
|
+
* - Remove ANSI escape codes (colors, etc.)
|
|
330
|
+
* - Normalize line endings to \n
|
|
331
|
+
* - Normalize paths (Windows backslashes to forward slashes)
|
|
332
|
+
* - Trim trailing whitespace from lines
|
|
333
|
+
* - Ensure single trailing newline
|
|
334
|
+
*/
|
|
335
|
+
function normalizeOutput(output) {
|
|
336
|
+
let normalized = (0, strip_ansi.default)(output);
|
|
337
|
+
normalized = normalized.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n+$/, "\n");
|
|
338
|
+
if (normalized === "\n") normalized = "";
|
|
339
|
+
return normalized;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Check if actual output matches expected pattern.
|
|
343
|
+
*/
|
|
344
|
+
function matchOutput(actual, expected, context, customPatterns = {}) {
|
|
345
|
+
const normalizedActual = normalizeOutput(actual);
|
|
346
|
+
const normalizedExpected = normalizeOutput(expected);
|
|
347
|
+
if (normalizedExpected === "" && normalizedActual === "") return true;
|
|
348
|
+
return patternToRegex(preprocessPaths(normalizedExpected, context), customPatterns).test(normalizedActual);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/index.ts
|
|
353
|
+
const VERSION = "0.0.1";
|
|
354
|
+
|
|
355
|
+
//#endregion
|
|
356
|
+
Object.defineProperty(exports, 'VERSION', {
|
|
357
|
+
enumerable: true,
|
|
358
|
+
get: function () {
|
|
359
|
+
return VERSION;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
Object.defineProperty(exports, '__toESM', {
|
|
363
|
+
enumerable: true,
|
|
364
|
+
get: function () {
|
|
365
|
+
return __toESM;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
Object.defineProperty(exports, 'cleanupExecutionContext', {
|
|
369
|
+
enumerable: true,
|
|
370
|
+
get: function () {
|
|
371
|
+
return cleanupExecutionContext;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
Object.defineProperty(exports, 'createExecutionContext', {
|
|
375
|
+
enumerable: true,
|
|
376
|
+
get: function () {
|
|
377
|
+
return createExecutionContext;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
Object.defineProperty(exports, 'defineConfig', {
|
|
381
|
+
enumerable: true,
|
|
382
|
+
get: function () {
|
|
383
|
+
return defineConfig;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
Object.defineProperty(exports, 'loadConfig', {
|
|
387
|
+
enumerable: true,
|
|
388
|
+
get: function () {
|
|
389
|
+
return loadConfig;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
Object.defineProperty(exports, 'matchOutput', {
|
|
393
|
+
enumerable: true,
|
|
394
|
+
get: function () {
|
|
395
|
+
return matchOutput;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
Object.defineProperty(exports, 'mergeConfig', {
|
|
399
|
+
enumerable: true,
|
|
400
|
+
get: function () {
|
|
401
|
+
return mergeConfig;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
Object.defineProperty(exports, 'normalizeOutput', {
|
|
405
|
+
enumerable: true,
|
|
406
|
+
get: function () {
|
|
407
|
+
return normalizeOutput;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
Object.defineProperty(exports, 'parseTestFile', {
|
|
411
|
+
enumerable: true,
|
|
412
|
+
get: function () {
|
|
413
|
+
return parseTestFile;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
Object.defineProperty(exports, 'runBlock', {
|
|
417
|
+
enumerable: true,
|
|
418
|
+
get: function () {
|
|
419
|
+
return runBlock;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
//# sourceMappingURL=src-CeUA446P.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-CeUA446P.cjs","names":["module","config: TestConfig","blocks: TestBlock[]","match: RegExpExecArray | null","commandLines: string[]","outputLines: string[]","chunks: Buffer[]","VERSION: string"],"sources":["../src/lib/config.ts","../src/lib/parser.ts","../src/lib/runner.ts","../src/lib/matcher.ts","../src/index.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { TestConfig } from './types.js';\n\nexport interface TryscriptConfig {\n bin?: string;\n env?: Record<string, string>;\n timeout?: number;\n patterns?: Record<string, RegExp | string>;\n tests?: string[];\n}\n\nconst CONFIG_FILES = ['tryscript.config.ts', 'tryscript.config.js', 'tryscript.config.mjs'];\n\n/**\n * Load config file using dynamic import.\n * Supports both TypeScript (via tsx/ts-node) and JavaScript configs.\n */\nexport async function loadConfig(baseDir: string): Promise<TryscriptConfig> {\n for (const filename of CONFIG_FILES) {\n const configPath = resolve(baseDir, filename);\n if (existsSync(configPath)) {\n const configUrl = pathToFileURL(configPath).href;\n const module = (await import(configUrl)) as { default?: TryscriptConfig } | TryscriptConfig;\n return (module as { default?: TryscriptConfig }).default ?? (module as TryscriptConfig);\n }\n }\n return {};\n}\n\n/**\n * Merge config with frontmatter overrides.\n * Frontmatter takes precedence over config file.\n */\nexport function mergeConfig(base: TryscriptConfig, frontmatter: TestConfig): TryscriptConfig {\n return {\n ...base,\n ...frontmatter,\n env: { ...base.env, ...frontmatter.env },\n patterns: { ...base.patterns, ...frontmatter.patterns },\n };\n}\n\n/**\n * Helper for typed config files.\n */\nexport function defineConfig(config: TryscriptConfig): TryscriptConfig {\n return config;\n}\n","import { parse as parseYaml } from 'yaml';\nimport type { TestConfig, TestBlock, TestFile } from './types.js';\n\n/** Regex to match YAML frontmatter at the start of a file */\nconst FRONTMATTER_REGEX = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n/;\n\n/** Regex to match fenced code blocks with console/bash info string */\nconst CODE_BLOCK_REGEX = /```(console|bash)\\r?\\n([\\s\\S]*?)```/g;\n\n/** Regex to match markdown headings (for test names) */\nconst HEADING_REGEX = /^#+\\s+(?:Test:\\s*)?(.+)$/m;\n\n/**\n * Parse a .tryscript.md file into structured test data.\n */\nexport function parseTestFile(content: string, filePath: string): TestFile {\n const rawContent = content;\n let config: TestConfig = {};\n let body = content;\n\n // Extract frontmatter if present\n const frontmatterMatch = FRONTMATTER_REGEX.exec(content);\n if (frontmatterMatch) {\n const yamlContent = frontmatterMatch[1] ?? '';\n config = parseYaml(yamlContent) as TestConfig;\n body = content.slice(frontmatterMatch[0].length);\n }\n\n // Parse all console blocks\n const blocks: TestBlock[] = [];\n\n // Reset regex lastIndex\n CODE_BLOCK_REGEX.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = CODE_BLOCK_REGEX.exec(body)) !== null) {\n const blockContent = match[2] ?? '';\n const blockStart = match.index;\n\n // Find the line number (1-indexed)\n const precedingContent = content.slice(0, content.indexOf(match[0]));\n const lineNumber = precedingContent.split('\\n').length;\n\n // Look for a heading before this block (for test name)\n const contentBefore = body.slice(0, blockStart);\n const lastHeadingMatch = [\n ...contentBefore.matchAll(new RegExp(HEADING_REGEX.source, 'gm')),\n ].pop();\n const name = lastHeadingMatch?.[1]?.trim();\n\n // Parse the block content\n const parsed = parseBlockContent(blockContent);\n if (parsed) {\n blocks.push({\n name,\n command: parsed.command,\n expectedOutput: parsed.expectedOutput,\n expectedExitCode: parsed.expectedExitCode,\n lineNumber,\n rawContent: match[0],\n });\n }\n }\n\n return { path: filePath, config, blocks, rawContent };\n}\n\n/**\n * Parse the content of a single console block.\n */\nfunction parseBlockContent(content: string): {\n command: string;\n expectedOutput: string;\n expectedExitCode: number;\n} | null {\n const lines = content.split('\\n');\n const commandLines: string[] = [];\n const outputLines: string[] = [];\n let expectedExitCode = 0;\n let inCommand = false;\n\n for (const line of lines) {\n if (line.startsWith('$ ')) {\n // Start of a command\n inCommand = true;\n commandLines.push(line.slice(2));\n } else if (line.startsWith('> ') && inCommand) {\n // Continuation of a multi-line command\n commandLines.push(line.slice(2));\n } else if (line.startsWith('? ')) {\n // Exit code specification\n inCommand = false;\n expectedExitCode = parseInt(line.slice(2).trim(), 10);\n } else {\n // Output line\n inCommand = false;\n outputLines.push(line);\n }\n }\n\n if (commandLines.length === 0) {\n return null;\n }\n\n // Join command lines, handling shell continuations\n let command = '';\n for (let i = 0; i < commandLines.length; i++) {\n const line = commandLines[i] ?? '';\n if (line.endsWith('\\\\')) {\n command += line.slice(0, -1) + ' ';\n } else {\n command += line;\n if (i < commandLines.length - 1) {\n command += ' ';\n }\n }\n }\n\n // Join output lines, preserving blank lines but trimming trailing empty lines\n let expectedOutput = outputLines.join('\\n');\n expectedOutput = expectedOutput.replace(/\\n+$/, '');\n if (expectedOutput) {\n expectedOutput += '\\n';\n }\n\n return { command: command.trim(), expectedOutput, expectedExitCode };\n}\n","import { spawn } from 'node:child_process';\nimport { mkdtemp, realpath, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, dirname, resolve } from 'node:path';\nimport treeKill from 'tree-kill';\nimport type { TestBlock, TestBlockResult } from './types.js';\nimport type { TryscriptConfig } from './config.js';\n\n/** Default timeout in milliseconds */\nconst DEFAULT_TIMEOUT = 30_000;\n\n/**\n * Execution context for a test file.\n * Created once per file, contains the temp directory.\n */\nexport interface ExecutionContext {\n /** Temporary directory for this test file (resolved, no symlinks) */\n tempDir: string;\n /** Directory containing the test file (for portable test commands) */\n testDir: string;\n /** Resolved binary path */\n binPath: string;\n /** Environment variables */\n env: Record<string, string>;\n /** Timeout per command */\n timeout: number;\n}\n\n/**\n * Create an execution context for a test file.\n */\nexport async function createExecutionContext(\n config: TryscriptConfig,\n testFilePath: string,\n): Promise<ExecutionContext> {\n // Create temp directory and resolve symlinks (e.g., /var -> /private/var on macOS)\n // This ensures [CWD] and [ROOT] patterns match pwd output\n const rawTempDir = await mkdtemp(join(tmpdir(), 'tryscript-'));\n const tempDir = await realpath(rawTempDir);\n\n // Resolve test file directory for portable test commands\n const testDir = resolve(dirname(testFilePath));\n\n // Resolve binary path relative to test file directory\n let binPath = config.bin ?? '';\n if (binPath && !binPath.startsWith('/')) {\n binPath = join(testDir, binPath);\n }\n\n return {\n tempDir,\n testDir,\n binPath,\n env: {\n ...process.env,\n ...config.env,\n // Disable colors by default for deterministic output\n NO_COLOR: config.env?.NO_COLOR ?? '1',\n FORCE_COLOR: '0',\n // Provide test directory for portable test commands\n TRYSCRIPT_TEST_DIR: testDir,\n } as Record<string, string>,\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n };\n}\n\n/**\n * Clean up execution context (remove temp directory).\n */\nexport async function cleanupExecutionContext(ctx: ExecutionContext): Promise<void> {\n await rm(ctx.tempDir, { recursive: true, force: true });\n}\n\n/**\n * Run a single test block and return the result.\n */\nexport async function runBlock(block: TestBlock, ctx: ExecutionContext): Promise<TestBlockResult> {\n const startTime = Date.now();\n\n try {\n const { output, exitCode } = await executeCommand(block.command, ctx);\n\n const duration = Date.now() - startTime;\n\n return {\n block,\n passed: true, // Matching handled separately\n actualOutput: output,\n actualExitCode: exitCode,\n duration,\n };\n } catch (error) {\n const duration = Date.now() - startTime;\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n block,\n passed: false,\n actualOutput: '',\n actualExitCode: -1,\n duration,\n error: message,\n };\n }\n}\n\n/**\n * Execute a command and capture output.\n */\nasync function executeCommand(\n command: string,\n ctx: ExecutionContext,\n): Promise<{ output: string; exitCode: number }> {\n return new Promise((resolve, reject) => {\n const proc = spawn(command, {\n shell: true,\n cwd: ctx.tempDir,\n env: ctx.env as NodeJS.ProcessEnv,\n // Pipe both to capture\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n const chunks: Buffer[] = [];\n\n // Capture data as it comes in to preserve order\n proc.stdout.on('data', (data: Buffer) => chunks.push(data));\n proc.stderr.on('data', (data: Buffer) => chunks.push(data));\n\n const timeoutId = setTimeout(() => {\n if (proc.pid) {\n treeKill(proc.pid, 'SIGKILL');\n }\n reject(new Error(`Command timed out after ${ctx.timeout}ms`));\n }, ctx.timeout);\n\n proc.on('close', (code) => {\n clearTimeout(timeoutId);\n const output = Buffer.concat(chunks).toString('utf-8');\n resolve({\n output,\n exitCode: code ?? 0,\n });\n });\n\n proc.on('error', (err) => {\n clearTimeout(timeoutId);\n reject(err);\n });\n });\n}\n","import stripAnsi from 'strip-ansi';\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n// Marker prefix for patterns (uses Unicode private use chars that won't appear in normal output)\nconst MARKER = '\\uE000';\n\n/**\n * Convert expected output with elision patterns to a regex.\n *\n * Handles (matching trycmd):\n * - [..] — matches any characters on the same line (trycmd: [^\\n]*?)\n * - ... — matches zero or more complete lines (trycmd: \\n(([^\\n]*\\n)*)?)\n * - [EXE] — matches .exe on Windows, empty otherwise\n * - [ROOT] — replaced with test root directory (pre-processed)\n * - [CWD] — replaced with current working directory (pre-processed)\n * - Custom [NAME] patterns from config (trycmd: TestCases::insert_var)\n */\nfunction patternToRegex(\n expected: string,\n customPatterns: Record<string, string | RegExp> = {},\n): RegExp {\n // Build a map of markers to their regex replacements\n const replacements = new Map<string, string>();\n let markerIndex = 0;\n\n const getMarker = (): string => {\n return `${MARKER}${markerIndex++}${MARKER}`;\n };\n\n let processed = expected;\n\n // Replace [..] with marker\n const dotdotMarker = getMarker();\n replacements.set(dotdotMarker, '[^\\\\n]*');\n processed = processed.replaceAll('[..]', dotdotMarker);\n\n // Replace ... (followed by newline) with marker\n const ellipsisMarker = getMarker();\n replacements.set(ellipsisMarker, '(?:[^\\\\n]*\\\\n)*');\n processed = processed.replace(/\\.\\.\\.\\n/g, ellipsisMarker);\n\n // Replace [EXE] with marker\n const exeMarker = getMarker();\n const exe = process.platform === 'win32' ? '\\\\.exe' : '';\n replacements.set(exeMarker, exe);\n processed = processed.replaceAll('[EXE]', exeMarker);\n\n // Replace custom patterns with markers\n for (const [name, pattern] of Object.entries(customPatterns)) {\n const placeholder = `[${name}]`;\n const patternStr = pattern instanceof RegExp ? pattern.source : pattern;\n const marker = getMarker();\n replacements.set(marker, `(${patternStr})`);\n processed = processed.replaceAll(placeholder, marker);\n }\n\n // Escape special regex characters\n let regex = escapeRegex(processed);\n\n // Restore markers to their regex replacements\n for (const [marker, replacement] of replacements) {\n regex = regex.replaceAll(escapeRegex(marker), replacement);\n }\n\n // Match the entire string (dotall mode for . to match newlines if needed)\n return new RegExp(`^${regex}$`, 's');\n}\n\n/**\n * Pre-process expected output to replace path placeholders with actual paths.\n * This happens BEFORE pattern matching.\n */\nfunction preprocessPaths(expected: string, context: { root: string; cwd: string }): string {\n let result = expected;\n // Normalize paths for comparison (use forward slashes)\n const normalizedRoot = context.root.replace(/\\\\/g, '/');\n const normalizedCwd = context.cwd.replace(/\\\\/g, '/');\n result = result.replaceAll('[ROOT]', normalizedRoot);\n result = result.replaceAll('[CWD]', normalizedCwd);\n return result;\n}\n\n/**\n * Normalize actual output for comparison.\n * - Remove ANSI escape codes (colors, etc.)\n * - Normalize line endings to \\n\n * - Normalize paths (Windows backslashes to forward slashes)\n * - Trim trailing whitespace from lines\n * - Ensure single trailing newline\n */\nexport function normalizeOutput(output: string): string {\n // Remove ANSI escape codes first\n let normalized = stripAnsi(output);\n\n normalized = normalized\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n')\n .map((line) => line.trimEnd())\n .join('\\n')\n .replace(/\\n+$/, '\\n');\n\n // Handle empty output\n if (normalized === '\\n') {\n normalized = '';\n }\n\n return normalized;\n}\n\n/**\n * Check if actual output matches expected pattern.\n */\nexport function matchOutput(\n actual: string,\n expected: string,\n context: { root: string; cwd: string },\n customPatterns: Record<string, string | RegExp> = {},\n): boolean {\n const normalizedActual = normalizeOutput(actual);\n const normalizedExpected = normalizeOutput(expected);\n\n // Empty expected matches empty actual\n if (normalizedExpected === '' && normalizedActual === '') {\n return true;\n }\n\n const preprocessed = preprocessPaths(normalizedExpected, context);\n const regex = patternToRegex(preprocessed, customPatterns);\n return regex.test(normalizedActual);\n}\n","// Public API exports\n\n// Version constant (injected at build time)\ndeclare const __VERSION__: string;\nexport const VERSION: string = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'development';\n\n// Config helper\nexport { defineConfig } from './lib/config.js';\nexport type { TryscriptConfig } from './lib/config.js';\n\n// Types\nexport type {\n TestConfig,\n TestBlock,\n TestFile,\n TestBlockResult,\n TestFileResult,\n TestRunSummary,\n} from './lib/types.js';\n\n// Core functions (for programmatic use)\nexport { parseTestFile } from './lib/parser.js';\nexport { runBlock, createExecutionContext, cleanupExecutionContext } from './lib/runner.js';\nexport type { ExecutionContext } from './lib/runner.js';\nexport { matchOutput, normalizeOutput } from './lib/matcher.js';\n"],"mappings":"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"}
|