tryscript 0.0.1 → 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.
package/dist/index.d.cts CHANGED
@@ -2,20 +2,48 @@
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/lib/types.d.ts
5
+
5
6
  declare const TestConfigSchema: z.ZodObject<{
6
- bin: z.ZodOptional<z.ZodString>;
7
+ cwd: z.ZodOptional<z.ZodString>;
8
+ sandbox: z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
9
+ fixtures: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
10
+ source: z.ZodString;
11
+ dest: z.ZodOptional<z.ZodString>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ source: string;
14
+ dest?: string | undefined;
15
+ }, {
16
+ source: string;
17
+ dest?: string | undefined;
18
+ }>]>, "many">>;
19
+ before: z.ZodOptional<z.ZodString>;
20
+ after: z.ZodOptional<z.ZodString>;
7
21
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
8
22
  timeout: z.ZodOptional<z.ZodNumber>;
9
23
  patterns: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>>>;
10
24
  tests: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
11
25
  }, "strip", z.ZodTypeAny, {
12
- bin?: string | undefined;
26
+ cwd?: string | undefined;
27
+ sandbox?: string | boolean | undefined;
28
+ fixtures?: (string | {
29
+ source: string;
30
+ dest?: string | undefined;
31
+ })[] | undefined;
32
+ before?: string | undefined;
33
+ after?: string | undefined;
13
34
  env?: Record<string, string> | undefined;
14
35
  timeout?: number | undefined;
15
36
  patterns?: Record<string, string | RegExp> | undefined;
16
37
  tests?: string[] | undefined;
17
38
  }, {
18
- bin?: string | undefined;
39
+ cwd?: string | undefined;
40
+ sandbox?: string | boolean | undefined;
41
+ fixtures?: (string | {
42
+ source: string;
43
+ dest?: string | undefined;
44
+ })[] | undefined;
45
+ before?: string | undefined;
46
+ after?: string | undefined;
19
47
  env?: Record<string, string> | undefined;
20
48
  timeout?: number | undefined;
21
49
  patterns?: Record<string, string | RegExp> | undefined;
@@ -35,12 +63,18 @@ interface TestBlock {
35
63
  command: string;
36
64
  /** Expected output (may include elision patterns) */
37
65
  expectedOutput: string;
66
+ /** Expected stderr output (lines starting with ! in expected output) */
67
+ expectedStderr?: string;
38
68
  /** Expected exit code (default: 0) */
39
69
  expectedExitCode: number;
40
70
  /** Line number where this block starts (1-indexed, for error reporting) */
41
71
  lineNumber: number;
42
72
  /** Raw content of the block for update mode */
43
73
  rawContent: string;
74
+ /** Skip this test (from <!-- skip --> annotation) */
75
+ skip?: boolean;
76
+ /** Run only this test (from <!-- only --> annotation) */
77
+ only?: boolean;
44
78
  }
45
79
  /**
46
80
  * A parsed test file with all its blocks.
@@ -62,6 +96,10 @@ interface TestBlockResult {
62
96
  block: TestBlock;
63
97
  passed: boolean;
64
98
  actualOutput: string;
99
+ /** Separate stdout (when stderr is captured separately) */
100
+ actualStdout?: string;
101
+ /** Separate stderr (when stderr is captured separately) */
102
+ actualStderr?: string;
65
103
  actualExitCode: number;
66
104
  /** Diff if test failed (unified diff format) */
67
105
  diff?: string;
@@ -69,6 +107,8 @@ interface TestBlockResult {
69
107
  duration: number;
70
108
  /** Error message if execution failed */
71
109
  error?: string;
110
+ /** Test was skipped */
111
+ skipped?: boolean;
72
112
  }
73
113
  /**
74
114
  * Result of running all blocks in a test file.
@@ -92,8 +132,24 @@ interface TestRunSummary {
92
132
  }
93
133
  //#endregion
94
134
  //#region src/lib/config.d.ts
135
+ /** Fixture configuration for copying files to sandbox directory */
136
+ interface Fixture {
137
+ /** Source path (resolved relative to test file) */
138
+ source: string;
139
+ /** Destination path (resolved relative to sandbox dir) */
140
+ dest?: string;
141
+ }
95
142
  interface TryscriptConfig {
96
- bin?: string;
143
+ /** Working directory for commands (default: test file directory) */
144
+ cwd?: string;
145
+ /** Run in isolated sandbox: true = empty temp, path = copy to temp */
146
+ sandbox?: boolean | string;
147
+ /** Fixtures to copy to sandbox directory before tests */
148
+ fixtures?: (string | Fixture)[];
149
+ /** Script to run before first test block */
150
+ before?: string;
151
+ /** Script to run after all test blocks */
152
+ after?: string;
97
153
  env?: Record<string, string>;
98
154
  timeout?: number;
99
155
  patterns?: Record<string, RegExp | string>;
@@ -113,19 +169,27 @@ declare function parseTestFile(content: string, filePath: string): TestFile;
113
169
  //#region src/lib/runner.d.ts
114
170
  /**
115
171
  * Execution context for a test file.
116
- * Created once per file, contains the temp directory.
172
+ * Created once per file, contains directory paths and config.
117
173
  */
118
174
  interface ExecutionContext {
119
175
  /** Temporary directory for this test file (resolved, no symlinks) */
120
176
  tempDir: string;
121
- /** Directory containing the test file (for portable test commands) */
177
+ /** Directory containing the test file */
122
178
  testDir: string;
123
- /** Resolved binary path */
124
- binPath: string;
179
+ /** Working directory for command execution */
180
+ cwd: string;
181
+ /** Whether running in sandbox mode */
182
+ sandbox: boolean;
125
183
  /** Environment variables */
126
184
  env: Record<string, string>;
127
185
  /** Timeout per command */
128
186
  timeout: number;
187
+ /** Before hook script */
188
+ before?: string;
189
+ /** After hook script */
190
+ after?: string;
191
+ /** Whether before hook has been run */
192
+ beforeRan?: boolean;
129
193
  }
130
194
  /**
131
195
  * Create an execution context for a test file.
package/dist/index.d.mts CHANGED
@@ -2,20 +2,48 @@
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/lib/types.d.ts
5
+
5
6
  declare const TestConfigSchema: z.ZodObject<{
6
- bin: z.ZodOptional<z.ZodString>;
7
+ cwd: z.ZodOptional<z.ZodString>;
8
+ sandbox: z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
9
+ fixtures: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
10
+ source: z.ZodString;
11
+ dest: z.ZodOptional<z.ZodString>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ source: string;
14
+ dest?: string | undefined;
15
+ }, {
16
+ source: string;
17
+ dest?: string | undefined;
18
+ }>]>, "many">>;
19
+ before: z.ZodOptional<z.ZodString>;
20
+ after: z.ZodOptional<z.ZodString>;
7
21
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
8
22
  timeout: z.ZodOptional<z.ZodNumber>;
9
23
  patterns: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodType<RegExp, z.ZodTypeDef, RegExp>]>>>;
10
24
  tests: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
11
25
  }, "strip", z.ZodTypeAny, {
12
- bin?: string | undefined;
26
+ cwd?: string | undefined;
27
+ sandbox?: string | boolean | undefined;
28
+ fixtures?: (string | {
29
+ source: string;
30
+ dest?: string | undefined;
31
+ })[] | undefined;
32
+ before?: string | undefined;
33
+ after?: string | undefined;
13
34
  env?: Record<string, string> | undefined;
14
35
  timeout?: number | undefined;
15
36
  patterns?: Record<string, string | RegExp> | undefined;
16
37
  tests?: string[] | undefined;
17
38
  }, {
18
- bin?: string | undefined;
39
+ cwd?: string | undefined;
40
+ sandbox?: string | boolean | undefined;
41
+ fixtures?: (string | {
42
+ source: string;
43
+ dest?: string | undefined;
44
+ })[] | undefined;
45
+ before?: string | undefined;
46
+ after?: string | undefined;
19
47
  env?: Record<string, string> | undefined;
20
48
  timeout?: number | undefined;
21
49
  patterns?: Record<string, string | RegExp> | undefined;
@@ -35,12 +63,18 @@ interface TestBlock {
35
63
  command: string;
36
64
  /** Expected output (may include elision patterns) */
37
65
  expectedOutput: string;
66
+ /** Expected stderr output (lines starting with ! in expected output) */
67
+ expectedStderr?: string;
38
68
  /** Expected exit code (default: 0) */
39
69
  expectedExitCode: number;
40
70
  /** Line number where this block starts (1-indexed, for error reporting) */
41
71
  lineNumber: number;
42
72
  /** Raw content of the block for update mode */
43
73
  rawContent: string;
74
+ /** Skip this test (from <!-- skip --> annotation) */
75
+ skip?: boolean;
76
+ /** Run only this test (from <!-- only --> annotation) */
77
+ only?: boolean;
44
78
  }
45
79
  /**
46
80
  * A parsed test file with all its blocks.
@@ -62,6 +96,10 @@ interface TestBlockResult {
62
96
  block: TestBlock;
63
97
  passed: boolean;
64
98
  actualOutput: string;
99
+ /** Separate stdout (when stderr is captured separately) */
100
+ actualStdout?: string;
101
+ /** Separate stderr (when stderr is captured separately) */
102
+ actualStderr?: string;
65
103
  actualExitCode: number;
66
104
  /** Diff if test failed (unified diff format) */
67
105
  diff?: string;
@@ -69,6 +107,8 @@ interface TestBlockResult {
69
107
  duration: number;
70
108
  /** Error message if execution failed */
71
109
  error?: string;
110
+ /** Test was skipped */
111
+ skipped?: boolean;
72
112
  }
73
113
  /**
74
114
  * Result of running all blocks in a test file.
@@ -92,8 +132,24 @@ interface TestRunSummary {
92
132
  }
93
133
  //#endregion
94
134
  //#region src/lib/config.d.ts
135
+ /** Fixture configuration for copying files to sandbox directory */
136
+ interface Fixture {
137
+ /** Source path (resolved relative to test file) */
138
+ source: string;
139
+ /** Destination path (resolved relative to sandbox dir) */
140
+ dest?: string;
141
+ }
95
142
  interface TryscriptConfig {
96
- bin?: string;
143
+ /** Working directory for commands (default: test file directory) */
144
+ cwd?: string;
145
+ /** Run in isolated sandbox: true = empty temp, path = copy to temp */
146
+ sandbox?: boolean | string;
147
+ /** Fixtures to copy to sandbox directory before tests */
148
+ fixtures?: (string | Fixture)[];
149
+ /** Script to run before first test block */
150
+ before?: string;
151
+ /** Script to run after all test blocks */
152
+ after?: string;
97
153
  env?: Record<string, string>;
98
154
  timeout?: number;
99
155
  patterns?: Record<string, RegExp | string>;
@@ -113,19 +169,27 @@ declare function parseTestFile(content: string, filePath: string): TestFile;
113
169
  //#region src/lib/runner.d.ts
114
170
  /**
115
171
  * Execution context for a test file.
116
- * Created once per file, contains the temp directory.
172
+ * Created once per file, contains directory paths and config.
117
173
  */
118
174
  interface ExecutionContext {
119
175
  /** Temporary directory for this test file (resolved, no symlinks) */
120
176
  tempDir: string;
121
- /** Directory containing the test file (for portable test commands) */
177
+ /** Directory containing the test file */
122
178
  testDir: string;
123
- /** Resolved binary path */
124
- binPath: string;
179
+ /** Working directory for command execution */
180
+ cwd: string;
181
+ /** Whether running in sandbox mode */
182
+ sandbox: boolean;
125
183
  /** Environment variables */
126
184
  env: Record<string, string>;
127
185
  /** Timeout per command */
128
186
  timeout: number;
187
+ /** Before hook script */
188
+ before?: string;
189
+ /** After hook script */
190
+ after?: string;
191
+ /** Whether before hook has been run */
192
+ beforeRan?: boolean;
129
193
  }
130
194
  /**
131
195
  * Create an execution context for a test file.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
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";
2
+ import { a as createExecutionContext, c as parseTestFile, i as cleanupExecutionContext, l as defineConfig, n as matchOutput, r as normalizeOutput, s as runBlock, t as VERSION } from "./src-Ca6X7ul-.mjs";
3
3
 
4
4
  export { VERSION, cleanupExecutionContext, createExecutionContext, defineConfig, matchOutput, normalizeOutput, parseTestFile, runBlock };
@@ -73,7 +73,8 @@ function mergeConfig(base, frontmatter) {
73
73
  patterns: {
74
74
  ...base.patterns,
75
75
  ...frontmatter.patterns
76
- }
76
+ },
77
+ fixtures: [...base.fixtures ?? [], ...frontmatter.fixtures ?? []]
77
78
  };
78
79
  }
79
80
  /**
@@ -91,6 +92,10 @@ const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/;
91
92
  const CODE_BLOCK_REGEX = /```(console|bash)\r?\n([\s\S]*?)```/g;
92
93
  /** Regex to match markdown headings (for test names) */
93
94
  const HEADING_REGEX = /^#+\s+(?:Test:\s*)?(.+)$/m;
95
+ /** Regex to match skip annotation in heading or nearby HTML comment */
96
+ const SKIP_ANNOTATION_REGEX = /<!--\s*skip\s*-->/i;
97
+ /** Regex to match only annotation in heading or nearby HTML comment */
98
+ const ONLY_ANNOTATION_REGEX = /<!--\s*only\s*-->/i;
94
99
  /**
95
100
  * Parse a .tryscript.md file into structured test data.
96
101
  */
@@ -110,15 +115,23 @@ function parseTestFile(content, filePath) {
110
115
  const blockContent = match[2] ?? "";
111
116
  const blockStart = match.index;
112
117
  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();
118
+ const contentBefore = body.slice(0, blockStart);
119
+ const lastHeadingMatch = [...contentBefore.matchAll(new RegExp(HEADING_REGEX.source, "gm"))].pop();
120
+ const name = lastHeadingMatch?.[1]?.trim();
121
+ const headingContext = lastHeadingMatch ? contentBefore.slice(contentBefore.lastIndexOf(lastHeadingMatch[0])) : "";
122
+ const skip = SKIP_ANNOTATION_REGEX.test(headingContext);
123
+ const only = ONLY_ANNOTATION_REGEX.test(headingContext);
114
124
  const parsed = parseBlockContent(blockContent);
115
125
  if (parsed) blocks.push({
116
126
  name,
117
127
  command: parsed.command,
118
128
  expectedOutput: parsed.expectedOutput,
129
+ expectedStderr: parsed.expectedStderr,
119
130
  expectedExitCode: parsed.expectedExitCode,
120
131
  lineNumber,
121
- rawContent: match[0]
132
+ rawContent: match[0],
133
+ skip,
134
+ only
122
135
  });
123
136
  }
124
137
  return {
@@ -135,6 +148,7 @@ function parseBlockContent(content) {
135
148
  const lines = content.split("\n");
136
149
  const commandLines = [];
137
150
  const outputLines = [];
151
+ const stderrLines = [];
138
152
  let expectedExitCode = 0;
139
153
  let inCommand = false;
140
154
  for (const line of lines) if (line.startsWith("$ ")) {
@@ -144,6 +158,9 @@ function parseBlockContent(content) {
144
158
  else if (line.startsWith("? ")) {
145
159
  inCommand = false;
146
160
  expectedExitCode = parseInt(line.slice(2).trim(), 10);
161
+ } else if (line.startsWith("! ")) {
162
+ inCommand = false;
163
+ stderrLines.push(line.slice(2));
147
164
  } else {
148
165
  inCommand = false;
149
166
  outputLines.push(line);
@@ -161,9 +178,16 @@ function parseBlockContent(content) {
161
178
  let expectedOutput = outputLines.join("\n");
162
179
  expectedOutput = expectedOutput.replace(/\n+$/, "");
163
180
  if (expectedOutput) expectedOutput += "\n";
181
+ let expectedStderr;
182
+ if (stderrLines.length > 0) {
183
+ expectedStderr = stderrLines.join("\n");
184
+ expectedStderr = expectedStderr.replace(/\n+$/, "");
185
+ if (expectedStderr) expectedStderr += "\n";
186
+ }
164
187
  return {
165
188
  command: command.trim(),
166
189
  expectedOutput,
190
+ expectedStderr,
167
191
  expectedExitCode
168
192
  };
169
193
  }
@@ -173,17 +197,45 @@ function parseBlockContent(content) {
173
197
  /** Default timeout in milliseconds */
174
198
  const DEFAULT_TIMEOUT = 3e4;
175
199
  /**
200
+ * Normalize fixture config to Fixture object.
201
+ */
202
+ function normalizeFixture(fixture) {
203
+ if (typeof fixture === "string") return { source: fixture };
204
+ return fixture;
205
+ }
206
+ /**
207
+ * Setup fixtures by copying files to sandbox directory.
208
+ */
209
+ async function setupFixtures(fixtures, testDir, sandboxDir) {
210
+ if (!fixtures || fixtures.length === 0) return;
211
+ for (const f of fixtures) {
212
+ const fixture = normalizeFixture(f);
213
+ await (0, node_fs_promises.cp)((0, node_path.resolve)(testDir, fixture.source), (0, node_path.resolve)(sandboxDir, fixture.dest ?? (0, node_path.basename)(fixture.source)), { recursive: true });
214
+ }
215
+ }
216
+ /**
176
217
  * Create an execution context for a test file.
177
218
  */
178
219
  async function createExecutionContext(config, testFilePath) {
179
220
  const tempDir = await (0, node_fs_promises.realpath)(await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "tryscript-")));
180
221
  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);
222
+ let cwd;
223
+ let sandbox = false;
224
+ if (config.sandbox === true) {
225
+ cwd = tempDir;
226
+ sandbox = true;
227
+ } else if (typeof config.sandbox === "string") {
228
+ await (0, node_fs_promises.cp)((0, node_path.resolve)(testDir, config.sandbox), tempDir, { recursive: true });
229
+ cwd = tempDir;
230
+ sandbox = true;
231
+ } else if (config.cwd) cwd = (0, node_path.resolve)(testDir, config.cwd);
232
+ else cwd = testDir;
233
+ if (sandbox && config.fixtures) await setupFixtures(config.fixtures, testDir, tempDir);
183
234
  return {
184
235
  tempDir,
185
236
  testDir,
186
- binPath,
237
+ cwd,
238
+ sandbox,
187
239
  env: {
188
240
  ...process.env,
189
241
  ...config.env,
@@ -191,7 +243,9 @@ async function createExecutionContext(config, testFilePath) {
191
243
  FORCE_COLOR: "0",
192
244
  TRYSCRIPT_TEST_DIR: testDir
193
245
  },
194
- timeout: config.timeout ?? DEFAULT_TIMEOUT
246
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
247
+ before: config.before,
248
+ after: config.after
195
249
  };
196
250
  }
197
251
  /**
@@ -204,16 +258,42 @@ async function cleanupExecutionContext(ctx) {
204
258
  });
205
259
  }
206
260
  /**
261
+ * Run the before hook if it hasn't run yet.
262
+ */
263
+ async function runBeforeHook(ctx) {
264
+ if (ctx.before && !ctx.beforeRan) {
265
+ ctx.beforeRan = true;
266
+ await executeCommand(ctx.before, ctx);
267
+ }
268
+ }
269
+ /**
270
+ * Run the after hook.
271
+ */
272
+ async function runAfterHook(ctx) {
273
+ if (ctx.after) await executeCommand(ctx.after, ctx);
274
+ }
275
+ /**
207
276
  * Run a single test block and return the result.
208
277
  */
209
278
  async function runBlock(block, ctx) {
210
279
  const startTime = Date.now();
280
+ if (block.skip) return {
281
+ block,
282
+ passed: true,
283
+ actualOutput: "",
284
+ actualExitCode: 0,
285
+ duration: 0,
286
+ skipped: true
287
+ };
211
288
  try {
212
- const { output, exitCode } = await executeCommand(block.command, ctx);
289
+ await runBeforeHook(ctx);
290
+ const { output, stdout, stderr, exitCode } = await executeCommand(block.command, ctx);
213
291
  return {
214
292
  block,
215
293
  passed: true,
216
294
  actualOutput: output,
295
+ actualStdout: stdout,
296
+ actualStderr: stderr,
217
297
  actualExitCode: exitCode,
218
298
  duration: Date.now() - startTime
219
299
  };
@@ -235,7 +315,7 @@ async function executeCommand(command, ctx) {
235
315
  return new Promise((resolve$2, reject) => {
236
316
  const proc = (0, node_child_process.spawn)(command, {
237
317
  shell: true,
238
- cwd: ctx.tempDir,
318
+ cwd: ctx.cwd,
239
319
  env: ctx.env,
240
320
  stdio: [
241
321
  "ignore",
@@ -243,9 +323,23 @@ async function executeCommand(command, ctx) {
243
323
  "pipe"
244
324
  ]
245
325
  });
246
- const chunks = [];
247
- proc.stdout.on("data", (data) => chunks.push(data));
248
- proc.stderr.on("data", (data) => chunks.push(data));
326
+ const combinedChunks = [];
327
+ const stdoutChunks = [];
328
+ const stderrChunks = [];
329
+ proc.stdout.on("data", (data) => {
330
+ combinedChunks.push({
331
+ data,
332
+ type: "stdout"
333
+ });
334
+ stdoutChunks.push(data);
335
+ });
336
+ proc.stderr.on("data", (data) => {
337
+ combinedChunks.push({
338
+ data,
339
+ type: "stderr"
340
+ });
341
+ stderrChunks.push(data);
342
+ });
249
343
  const timeoutId = setTimeout(() => {
250
344
  if (proc.pid) (0, tree_kill.default)(proc.pid, "SIGKILL");
251
345
  reject(/* @__PURE__ */ new Error(`Command timed out after ${ctx.timeout}ms`));
@@ -253,7 +347,9 @@ async function executeCommand(command, ctx) {
253
347
  proc.on("close", (code) => {
254
348
  clearTimeout(timeoutId);
255
349
  resolve$2({
256
- output: Buffer.concat(chunks).toString("utf-8"),
350
+ output: Buffer.concat(combinedChunks.map((c) => c.data)).toString("utf-8"),
351
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
352
+ stderr: Buffer.concat(stderrChunks).toString("utf-8"),
257
353
  exitCode: code ?? 0
258
354
  });
259
355
  });
@@ -350,7 +446,7 @@ function matchOutput(actual, expected, context, customPatterns = {}) {
350
446
 
351
447
  //#endregion
352
448
  //#region src/index.ts
353
- const VERSION = "0.0.1";
449
+ const VERSION = "0.1.0";
354
450
 
355
451
  //#endregion
356
452
  Object.defineProperty(exports, 'VERSION', {
@@ -413,10 +509,16 @@ Object.defineProperty(exports, 'parseTestFile', {
413
509
  return parseTestFile;
414
510
  }
415
511
  });
512
+ Object.defineProperty(exports, 'runAfterHook', {
513
+ enumerable: true,
514
+ get: function () {
515
+ return runAfterHook;
516
+ }
517
+ });
416
518
  Object.defineProperty(exports, 'runBlock', {
417
519
  enumerable: true,
418
520
  get: function () {
419
521
  return runBlock;
420
522
  }
421
523
  });
422
- //# sourceMappingURL=src-CeUA446P.cjs.map
524
+ //# sourceMappingURL=src-CP4-Q-U5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-CP4-Q-U5.cjs","names":["module","config: TestConfig","blocks: TestBlock[]","match: RegExpExecArray | null","commandLines: string[]","outputLines: string[]","stderrLines: string[]","expectedStderr: string | undefined","cwd: string","combinedChunks: { data: Buffer; type: 'stdout' | 'stderr' }[]","stdoutChunks: Buffer[]","stderrChunks: 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\n/** Fixture configuration for copying files to sandbox directory */\nexport interface Fixture {\n /** Source path (resolved relative to test file) */\n source: string;\n /** Destination path (resolved relative to sandbox dir) */\n dest?: string;\n}\n\nexport interface TryscriptConfig {\n /** Working directory for commands (default: test file directory) */\n cwd?: string;\n /** Run in isolated sandbox: true = empty temp, path = copy to temp */\n sandbox?: boolean | string;\n /** Fixtures to copy to sandbox directory before tests */\n fixtures?: (string | Fixture)[];\n /** Script to run before first test block */\n before?: string;\n /** Script to run after all test blocks */\n after?: 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 fixtures: [...(base.fixtures ?? []), ...(frontmatter.fixtures ?? [])],\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/** Regex to match skip annotation in heading or nearby HTML comment */\nconst SKIP_ANNOTATION_REGEX = /<!--\\s*skip\\s*-->/i;\n\n/** Regex to match only annotation in heading or nearby HTML comment */\nconst ONLY_ANNOTATION_REGEX = /<!--\\s*only\\s*-->/i;\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 // Check for skip/only annotations in the heading line or nearby comments\n const headingContext = lastHeadingMatch\n ? contentBefore.slice(contentBefore.lastIndexOf(lastHeadingMatch[0]))\n : '';\n const skip = SKIP_ANNOTATION_REGEX.test(headingContext);\n const only = ONLY_ANNOTATION_REGEX.test(headingContext);\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 expectedStderr: parsed.expectedStderr,\n expectedExitCode: parsed.expectedExitCode,\n lineNumber,\n rawContent: match[0],\n skip,\n only,\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 expectedStderr?: string;\n expectedExitCode: number;\n} | null {\n const lines = content.split('\\n');\n const commandLines: string[] = [];\n const outputLines: string[] = [];\n const stderrLines: 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 if (line.startsWith('! ')) {\n // Stderr line (prefixed with !)\n inCommand = false;\n stderrLines.push(line.slice(2));\n } else {\n // Output line (stdout or combined)\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 // Join stderr lines if any\n let expectedStderr: string | undefined;\n if (stderrLines.length > 0) {\n expectedStderr = stderrLines.join('\\n');\n expectedStderr = expectedStderr.replace(/\\n+$/, '');\n if (expectedStderr) {\n expectedStderr += '\\n';\n }\n }\n\n return { command: command.trim(), expectedOutput, expectedStderr, expectedExitCode };\n}\n","import { spawn } from 'node:child_process';\nimport { mkdtemp, realpath, rm, cp } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, dirname, resolve, basename } from 'node:path';\nimport treeKill from 'tree-kill';\nimport type { TestBlock, TestBlockResult } from './types.js';\nimport type { TryscriptConfig, Fixture } 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 directory paths and config.\n */\nexport interface ExecutionContext {\n /** Temporary directory for this test file (resolved, no symlinks) */\n tempDir: string;\n /** Directory containing the test file */\n testDir: string;\n /** Working directory for command execution */\n cwd: string;\n /** Whether running in sandbox mode */\n sandbox: boolean;\n /** Environment variables */\n env: Record<string, string>;\n /** Timeout per command */\n timeout: number;\n /** Before hook script */\n before?: string;\n /** After hook script */\n after?: string;\n /** Whether before hook has been run */\n beforeRan?: boolean;\n}\n\n/**\n * Normalize fixture config to Fixture object.\n */\nfunction normalizeFixture(fixture: string | Fixture): Fixture {\n if (typeof fixture === 'string') {\n return { source: fixture };\n }\n return fixture;\n}\n\n/**\n * Setup fixtures by copying files to sandbox directory.\n */\nasync function setupFixtures(\n fixtures: (string | Fixture)[] | undefined,\n testDir: string,\n sandboxDir: string,\n): Promise<void> {\n if (!fixtures || fixtures.length === 0) {\n return;\n }\n\n for (const f of fixtures) {\n const fixture = normalizeFixture(f);\n const src = resolve(testDir, fixture.source);\n const destName = fixture.dest ?? basename(fixture.source);\n const dst = resolve(sandboxDir, destName);\n await cp(src, dst, { recursive: true });\n }\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 // Determine working directory based on sandbox config\n let cwd: string;\n let sandbox = false;\n\n if (config.sandbox === true) {\n // Empty sandbox: run in temp directory\n cwd = tempDir;\n sandbox = true;\n } else if (typeof config.sandbox === 'string') {\n // Copy directory to sandbox: copy source to temp, run in temp\n const srcPath = resolve(testDir, config.sandbox);\n await cp(srcPath, tempDir, { recursive: true });\n cwd = tempDir;\n sandbox = true;\n } else if (config.cwd) {\n // Run in specified directory (relative to test file)\n cwd = resolve(testDir, config.cwd);\n } else {\n // Default: run in test file directory\n cwd = testDir;\n }\n\n // Copy additional fixtures to sandbox (only if sandbox enabled)\n if (sandbox && config.fixtures) {\n await setupFixtures(config.fixtures, testDir, tempDir);\n }\n\n const ctx: ExecutionContext = {\n tempDir,\n testDir,\n cwd,\n sandbox,\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 before: config.before,\n after: config.after,\n };\n\n return ctx;\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 the before hook if it hasn't run yet.\n */\nexport async function runBeforeHook(ctx: ExecutionContext): Promise<void> {\n if (ctx.before && !ctx.beforeRan) {\n ctx.beforeRan = true;\n await executeCommand(ctx.before, ctx);\n }\n}\n\n/**\n * Run the after hook.\n */\nexport async function runAfterHook(ctx: ExecutionContext): Promise<void> {\n if (ctx.after) {\n await executeCommand(ctx.after, ctx);\n }\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 // Handle skip annotation\n if (block.skip) {\n return {\n block,\n passed: true,\n actualOutput: '',\n actualExitCode: 0,\n duration: 0,\n skipped: true,\n };\n }\n\n try {\n // Run before hook if this is the first test\n await runBeforeHook(ctx);\n\n // Execute command directly (shell handles $VAR expansion)\n const { output, stdout, stderr, 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 actualStdout: stdout,\n actualStderr: stderr,\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/** Command execution result with separate stdout/stderr */\ninterface CommandResult {\n output: string;\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command and capture output.\n */\nasync function executeCommand(command: string, ctx: ExecutionContext): Promise<CommandResult> {\n return new Promise((resolve, reject) => {\n const proc = spawn(command, {\n shell: true,\n cwd: ctx.cwd,\n env: ctx.env as NodeJS.ProcessEnv,\n // Pipe both to capture\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n const combinedChunks: { data: Buffer; type: 'stdout' | 'stderr' }[] = [];\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n\n // Capture data as it comes in to preserve order\n proc.stdout.on('data', (data: Buffer) => {\n combinedChunks.push({ data, type: 'stdout' });\n stdoutChunks.push(data);\n });\n proc.stderr.on('data', (data: Buffer) => {\n combinedChunks.push({ data, type: 'stderr' });\n stderrChunks.push(data);\n });\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(combinedChunks.map((c) => c.data)).toString('utf-8');\n const stdout = Buffer.concat(stdoutChunks).toString('utf-8');\n const stderr = Buffer.concat(stderrChunks).toString('utf-8');\n resolve({\n output,\n stdout,\n stderr,\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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"}