tryscript 0.1.6 → 0.1.7
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 +41 -8
- package/dist/bin.cjs +58 -8
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.mjs +58 -8
- package/dist/bin.mjs.map +1 -1
- package/dist/index.cjs +10 -2
- package/dist/index.d.cts +140 -1
- package/dist/index.d.mts +140 -1
- package/dist/index.mjs +2 -2
- package/dist/{src-1oEnK7GG.cjs → src-BIZMxxIt.cjs} +485 -11
- package/dist/src-BIZMxxIt.cjs.map +1 -0
- package/dist/{src-Bd9-Y0qp.mjs → src-BQxIhzgF.mjs} +439 -13
- package/dist/src-BQxIhzgF.mjs.map +1 -0
- package/docs/tryscript-reference.md +95 -3
- package/package.json +1 -1
- package/dist/src-1oEnK7GG.cjs.map +0 -1
- package/dist/src-Bd9-Y0qp.mjs.map +0 -1
|
@@ -37,6 +37,7 @@ let tree_kill = require("tree-kill");
|
|
|
37
37
|
tree_kill = __toESM(tree_kill);
|
|
38
38
|
let strip_ansi = require("strip-ansi");
|
|
39
39
|
strip_ansi = __toESM(strip_ansi);
|
|
40
|
+
let atomically = require("atomically");
|
|
40
41
|
|
|
41
42
|
//#region src/lib/config.ts
|
|
42
43
|
/** Default coverage configuration values. */
|
|
@@ -120,8 +121,6 @@ function defineConfig(config) {
|
|
|
120
121
|
//#region src/lib/parser.ts
|
|
121
122
|
/** Regex to match YAML frontmatter at the start of a file */
|
|
122
123
|
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/;
|
|
123
|
-
/** Regex to match fenced code blocks with console/bash info string */
|
|
124
|
-
const CODE_BLOCK_REGEX = /```(console|bash)\r?\n([\s\S]*?)```/g;
|
|
125
124
|
/** Regex to match markdown headings (for test names) */
|
|
126
125
|
const HEADING_REGEX = /^#+\s+(?:Test:\s*)?(.+)$/m;
|
|
127
126
|
/** Regex to match skip annotation in heading or nearby HTML comment */
|
|
@@ -129,6 +128,55 @@ const SKIP_ANNOTATION_REGEX = /<!--\s*skip\s*-->/i;
|
|
|
129
128
|
/** Regex to match only annotation in heading or nearby HTML comment */
|
|
130
129
|
const ONLY_ANNOTATION_REGEX = /<!--\s*only\s*-->/i;
|
|
131
130
|
/**
|
|
131
|
+
* Find console/bash fenced code blocks, supporting extended fences (4+ backticks).
|
|
132
|
+
*
|
|
133
|
+
* Extended fences allow embedding triple-backtick blocks in expected output.
|
|
134
|
+
* A closing fence must have at least as many backticks as the opening fence
|
|
135
|
+
* (per CommonMark spec).
|
|
136
|
+
*/
|
|
137
|
+
function findConsoleCodeBlocks(text) {
|
|
138
|
+
const results = [];
|
|
139
|
+
const lines = text.split("\n");
|
|
140
|
+
const offsets = new Array(lines.length);
|
|
141
|
+
offsets[0] = 0;
|
|
142
|
+
for (let j = 1; j < lines.length; j++) offsets[j] = offsets[j - 1] + lines[j - 1].length + 1;
|
|
143
|
+
let i = 0;
|
|
144
|
+
while (i < lines.length) {
|
|
145
|
+
const line = lines[i];
|
|
146
|
+
const trimmed = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
147
|
+
const openMatch = /^(`{3,})(console|bash)\s*$/.exec(trimmed);
|
|
148
|
+
if (!openMatch) {
|
|
149
|
+
i++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const fenceLen = openMatch[1].length;
|
|
153
|
+
const infoString = openMatch[2];
|
|
154
|
+
const openLineIdx = i;
|
|
155
|
+
const closingRe = /* @__PURE__ */ new RegExp(`^\`{${fenceLen},}\\s*$`);
|
|
156
|
+
i++;
|
|
157
|
+
while (i < lines.length) {
|
|
158
|
+
const cur = lines[i];
|
|
159
|
+
const curTrimmed = cur.endsWith("\r") ? cur.slice(0, -1) : cur;
|
|
160
|
+
if (closingRe.test(curTrimmed)) {
|
|
161
|
+
const startOffset = offsets[openLineIdx];
|
|
162
|
+
const endOffset = offsets[i] + lines[i].length;
|
|
163
|
+
const contentStart = offsets[openLineIdx + 1];
|
|
164
|
+
const contentEnd = offsets[i];
|
|
165
|
+
results.push({
|
|
166
|
+
fullMatch: text.slice(startOffset, endOffset),
|
|
167
|
+
infoString,
|
|
168
|
+
content: text.slice(contentStart, contentEnd),
|
|
169
|
+
index: startOffset
|
|
170
|
+
});
|
|
171
|
+
i++;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
i++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return results;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
132
180
|
* Parse a .tryscript.md file into structured test data.
|
|
133
181
|
*/
|
|
134
182
|
function parseTestFile(content, filePath) {
|
|
@@ -141,12 +189,11 @@ function parseTestFile(content, filePath) {
|
|
|
141
189
|
body = content.slice(frontmatterMatch[0].length);
|
|
142
190
|
}
|
|
143
191
|
const blocks = [];
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const lineNumber = content.slice(0, content.indexOf(match[0])).split("\n").length;
|
|
192
|
+
const codeBlocks = findConsoleCodeBlocks(body);
|
|
193
|
+
for (const codeBlock of codeBlocks) {
|
|
194
|
+
const blockContent = codeBlock.content;
|
|
195
|
+
const blockStart = codeBlock.index;
|
|
196
|
+
const lineNumber = content.slice(0, content.indexOf(codeBlock.fullMatch)).split("\n").length;
|
|
150
197
|
const contentBefore = body.slice(0, blockStart);
|
|
151
198
|
const lastHeadingMatch = [...contentBefore.matchAll(new RegExp(HEADING_REGEX.source, "gm"))].pop();
|
|
152
199
|
const name = lastHeadingMatch?.[1]?.trim();
|
|
@@ -161,7 +208,7 @@ function parseTestFile(content, filePath) {
|
|
|
161
208
|
expectedStderr: parsed.expectedStderr,
|
|
162
209
|
expectedExitCode: parsed.expectedExitCode,
|
|
163
210
|
lineNumber,
|
|
164
|
-
rawContent:
|
|
211
|
+
rawContent: codeBlock.fullMatch,
|
|
165
212
|
skip,
|
|
166
213
|
only
|
|
167
214
|
});
|
|
@@ -536,9 +583,15 @@ function patternToRegex(expected, customPatterns = {}) {
|
|
|
536
583
|
const dotdotMarker = getMarker();
|
|
537
584
|
replacements.set(dotdotMarker, "[^\\n]*");
|
|
538
585
|
processed = processed.replaceAll("[..]", dotdotMarker);
|
|
586
|
+
const unknownDotdotMarker = getMarker();
|
|
587
|
+
replacements.set(unknownDotdotMarker, "[^\\n]*");
|
|
588
|
+
processed = processed.replaceAll("[??]", unknownDotdotMarker);
|
|
539
589
|
const ellipsisMarker = getMarker();
|
|
540
590
|
replacements.set(ellipsisMarker, "(?:[^\\n]*\\n)*");
|
|
541
591
|
processed = processed.replace(/\.\.\.\n/g, ellipsisMarker);
|
|
592
|
+
const unknownEllipsisMarker = getMarker();
|
|
593
|
+
replacements.set(unknownEllipsisMarker, "(?:[^\\n]*\\n)*");
|
|
594
|
+
processed = processed.replace(/\?\?\?\n/g, unknownEllipsisMarker);
|
|
542
595
|
const exeMarker = getMarker();
|
|
543
596
|
const exe = process.platform === "win32" ? "\\.exe" : "";
|
|
544
597
|
replacements.set(exeMarker, exe);
|
|
@@ -581,6 +634,82 @@ function normalizeOutput(output) {
|
|
|
581
634
|
return normalized;
|
|
582
635
|
}
|
|
583
636
|
/**
|
|
637
|
+
* Like `patternToRegex()` but wraps each wildcard in a capturing group and
|
|
638
|
+
* returns metadata describing what each group represents.
|
|
639
|
+
*
|
|
640
|
+
* Each occurrence gets a unique marker so that the `groups` array is ordered
|
|
641
|
+
* by position in the string, matching the regex capture group indices.
|
|
642
|
+
*/
|
|
643
|
+
function patternToCapturingRegex(expected, customPatterns = {}) {
|
|
644
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
645
|
+
const markerMeta = /* @__PURE__ */ new Map();
|
|
646
|
+
let markerIndex = 0;
|
|
647
|
+
const getMarker = () => {
|
|
648
|
+
return `${MARKER}${markerIndex++}${MARKER}`;
|
|
649
|
+
};
|
|
650
|
+
const replaceEach = (processed$1, pattern, regexStr, meta) => {
|
|
651
|
+
let result = processed$1;
|
|
652
|
+
if (typeof pattern === "string") while (result.includes(pattern)) {
|
|
653
|
+
const marker = getMarker();
|
|
654
|
+
replacements.set(marker, regexStr);
|
|
655
|
+
markerMeta.set(marker, meta);
|
|
656
|
+
result = result.replace(pattern, marker);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
let m;
|
|
660
|
+
while ((m = pattern.exec(result)) !== null) {
|
|
661
|
+
const marker = getMarker();
|
|
662
|
+
replacements.set(marker, regexStr);
|
|
663
|
+
markerMeta.set(marker, meta);
|
|
664
|
+
result = result.slice(0, m.index) + marker + result.slice(m.index + m[0].length);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return result;
|
|
668
|
+
};
|
|
669
|
+
let processed = expected;
|
|
670
|
+
processed = replaceEach(processed, "[..]", "([^\\n]*)", {
|
|
671
|
+
category: "generic",
|
|
672
|
+
multiline: false
|
|
673
|
+
});
|
|
674
|
+
processed = replaceEach(processed, "[??]", "([^\\n]*)", {
|
|
675
|
+
category: "unknown",
|
|
676
|
+
multiline: false
|
|
677
|
+
});
|
|
678
|
+
processed = replaceEach(processed, /\.\.\.\n/, "((?:[^\\n]*\\n)*)", {
|
|
679
|
+
category: "generic",
|
|
680
|
+
multiline: true
|
|
681
|
+
});
|
|
682
|
+
processed = replaceEach(processed, /\?\?\?\n/, "((?:[^\\n]*\\n)*)", {
|
|
683
|
+
category: "unknown",
|
|
684
|
+
multiline: true
|
|
685
|
+
});
|
|
686
|
+
const exe = process.platform === "win32" ? "\\.exe" : "";
|
|
687
|
+
processed = replaceEach(processed, "[EXE]", exe, null);
|
|
688
|
+
for (const [name, pattern] of Object.entries(customPatterns)) {
|
|
689
|
+
const placeholder = `[${name}]`;
|
|
690
|
+
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
691
|
+
processed = replaceEach(processed, placeholder, `(${patternStr})`, {
|
|
692
|
+
category: "named",
|
|
693
|
+
name,
|
|
694
|
+
multiline: false
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
const sortedEntries = [...replacements.entries()].sort((a, b) => {
|
|
698
|
+
return processed.indexOf(a[0]) - processed.indexOf(b[0]);
|
|
699
|
+
});
|
|
700
|
+
let regex = escapeRegex(processed);
|
|
701
|
+
const groups = [];
|
|
702
|
+
for (const [marker, replacement] of sortedEntries) {
|
|
703
|
+
const meta = markerMeta.get(marker);
|
|
704
|
+
if (meta) groups.push(meta);
|
|
705
|
+
regex = regex.replaceAll(escapeRegex(marker), replacement);
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
regex: new RegExp(`^${regex}$`, "s"),
|
|
709
|
+
groups
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
584
713
|
* Check if actual output matches expected pattern.
|
|
585
714
|
*/
|
|
586
715
|
function matchOutput(actual, expected, context, customPatterns = {}) {
|
|
@@ -589,10 +718,307 @@ function matchOutput(actual, expected, context, customPatterns = {}) {
|
|
|
589
718
|
if (normalizedExpected === "" && normalizedActual === "") return true;
|
|
590
719
|
return patternToRegex(preprocessPaths(normalizedExpected, context), customPatterns).test(normalizedActual);
|
|
591
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* Match actual output against expected pattern and return wildcard captures.
|
|
723
|
+
* Returns `null` if the output does not match.
|
|
724
|
+
*/
|
|
725
|
+
function matchAndCapture(actual, expected, context, customPatterns = {}) {
|
|
726
|
+
const normalizedActual = normalizeOutput(actual);
|
|
727
|
+
const normalizedExpected = normalizeOutput(expected);
|
|
728
|
+
if (normalizedExpected === "" && normalizedActual === "") return { captures: [] };
|
|
729
|
+
const { regex, groups } = patternToCapturingRegex(preprocessPaths(normalizedExpected, context), customPatterns);
|
|
730
|
+
const match = regex.exec(normalizedActual);
|
|
731
|
+
if (!match) return null;
|
|
732
|
+
return { captures: groups.map((meta, i) => ({
|
|
733
|
+
category: meta.category,
|
|
734
|
+
name: meta.name,
|
|
735
|
+
multiline: meta.multiline,
|
|
736
|
+
captured: match[i + 1] ?? ""
|
|
737
|
+
})) };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/lib/expander.ts
|
|
742
|
+
/**
|
|
743
|
+
* Whether a wildcard category should be expanded at the given level.
|
|
744
|
+
*
|
|
745
|
+
* The hierarchy is: unknown < generic < all.
|
|
746
|
+
*/
|
|
747
|
+
function shouldExpandCategory(category, level) {
|
|
748
|
+
switch (level) {
|
|
749
|
+
case "unknown": return category === "unknown";
|
|
750
|
+
case "generic": return category === "unknown" || category === "generic";
|
|
751
|
+
case "all": return true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const WILDCARD_TOKENS = [
|
|
755
|
+
{
|
|
756
|
+
token: "[..]",
|
|
757
|
+
category: "generic",
|
|
758
|
+
multiline: false
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
token: "[??]",
|
|
762
|
+
category: "unknown",
|
|
763
|
+
multiline: false
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
token: /\.\.\.\n/,
|
|
767
|
+
category: "generic",
|
|
768
|
+
multiline: true
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
token: /\?\?\?\n/,
|
|
772
|
+
category: "unknown",
|
|
773
|
+
multiline: true
|
|
774
|
+
}
|
|
775
|
+
];
|
|
776
|
+
/**
|
|
777
|
+
* Expand wildcards in expected output by replacing them with captured actual text.
|
|
778
|
+
*
|
|
779
|
+
* Only wildcards whose category is targeted by `level` are replaced; others are
|
|
780
|
+
* left intact. Returns `null` if actual output doesn't match expected pattern.
|
|
781
|
+
*/
|
|
782
|
+
function expandExpectedOutput(expected, actual, context, level, customPatterns) {
|
|
783
|
+
const normalizedExpected = normalizeOutput(expected);
|
|
784
|
+
const normalizedActual = normalizeOutput(actual);
|
|
785
|
+
if (normalizedExpected === "" && normalizedActual === "") return {
|
|
786
|
+
expandedOutput: "",
|
|
787
|
+
captures: [],
|
|
788
|
+
expandedCount: 0
|
|
789
|
+
};
|
|
790
|
+
const result = matchAndCapture(actual, expected, context, customPatterns);
|
|
791
|
+
if (!result) return null;
|
|
792
|
+
let output = normalizedExpected;
|
|
793
|
+
let expandedCount = 0;
|
|
794
|
+
const tokenPositions = [];
|
|
795
|
+
for (const wt of WILDCARD_TOKENS) {
|
|
796
|
+
let searchFrom = 0;
|
|
797
|
+
if (typeof wt.token === "string") while (true) {
|
|
798
|
+
const pos = output.indexOf(wt.token, searchFrom);
|
|
799
|
+
if (pos === -1) break;
|
|
800
|
+
tokenPositions.push({
|
|
801
|
+
pos,
|
|
802
|
+
length: wt.token.length
|
|
803
|
+
});
|
|
804
|
+
searchFrom = pos + wt.token.length;
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
const re = new RegExp(wt.token.source, "g");
|
|
808
|
+
let m;
|
|
809
|
+
while ((m = re.exec(output)) !== null) tokenPositions.push({
|
|
810
|
+
pos: m.index,
|
|
811
|
+
length: m[0].length
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (customPatterns) for (const name of Object.keys(customPatterns)) {
|
|
816
|
+
const placeholder = `[${name}]`;
|
|
817
|
+
let searchFrom = 0;
|
|
818
|
+
while (true) {
|
|
819
|
+
const pos = output.indexOf(placeholder, searchFrom);
|
|
820
|
+
if (pos === -1) break;
|
|
821
|
+
tokenPositions.push({
|
|
822
|
+
pos,
|
|
823
|
+
length: placeholder.length
|
|
824
|
+
});
|
|
825
|
+
searchFrom = pos + placeholder.length;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
tokenPositions.sort((a, b) => a.pos - b.pos);
|
|
829
|
+
const replacements = tokenPositions.map((tp, i) => ({
|
|
830
|
+
...tp,
|
|
831
|
+
capture: result.captures[i]
|
|
832
|
+
}));
|
|
833
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
834
|
+
const r = replacements[i];
|
|
835
|
+
if (shouldExpandCategory(r.capture.category, level)) {
|
|
836
|
+
const replacement = r.capture.captured;
|
|
837
|
+
output = output.slice(0, r.pos) + replacement + output.slice(r.pos + r.length);
|
|
838
|
+
expandedCount++;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
expandedOutput: output,
|
|
843
|
+
captures: result.captures,
|
|
844
|
+
expandedCount
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Expand wildcards in a test file in place.
|
|
849
|
+
*
|
|
850
|
+
* Uses the same reverse-order strategy as `updater.ts` to maintain correct
|
|
851
|
+
* string offsets when modifying multiple blocks.
|
|
852
|
+
*/
|
|
853
|
+
async function expandTestFile(file, results, level, context, customPatterns) {
|
|
854
|
+
let content = file.rawContent;
|
|
855
|
+
const changes = [];
|
|
856
|
+
let totalExpanded = 0;
|
|
857
|
+
const resultByBlock = new Map(results.map((result) => [result.block, result]));
|
|
858
|
+
const blocksWithResults = [...file.blocks].map((block) => ({
|
|
859
|
+
block,
|
|
860
|
+
result: resultByBlock.get(block)
|
|
861
|
+
})).reverse();
|
|
862
|
+
for (const { block, result } of blocksWithResults) {
|
|
863
|
+
if (!result || !block.expectedOutput) continue;
|
|
864
|
+
const expansion = expandExpectedOutput(block.expectedOutput, result.actualOutput, context, level, customPatterns);
|
|
865
|
+
if (!expansion || expansion.expandedCount === 0) continue;
|
|
866
|
+
const fence = "`".repeat(/^(`+)/.exec(block.rawContent)?.[1]?.length ?? 3);
|
|
867
|
+
const commandLines = block.command.split("\n").map((line, i) => {
|
|
868
|
+
return i === 0 ? `$ ${line}` : `> ${line}`;
|
|
869
|
+
});
|
|
870
|
+
const lines = [`${fence}console`, ...commandLines];
|
|
871
|
+
const trimmedOutput = expansion.expandedOutput.trimEnd();
|
|
872
|
+
if (trimmedOutput) lines.push(trimmedOutput);
|
|
873
|
+
lines.push(`? ${block.expectedExitCode ?? result.actualExitCode}`, fence);
|
|
874
|
+
const newBlockContent = lines.join("\n");
|
|
875
|
+
const blockStart = content.indexOf(block.rawContent);
|
|
876
|
+
if (blockStart !== -1) {
|
|
877
|
+
content = content.slice(0, blockStart) + newBlockContent + content.slice(blockStart + block.rawContent.length);
|
|
878
|
+
changes.push(block.name ?? `Line ${block.lineNumber}`);
|
|
879
|
+
totalExpanded += expansion.expandedCount;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (changes.length > 0) await (0, atomically.writeFile)(file.path, content);
|
|
883
|
+
return {
|
|
884
|
+
expanded: changes.length > 0,
|
|
885
|
+
expandedCount: totalExpanded,
|
|
886
|
+
changes
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/lib/yaml-utils.ts
|
|
892
|
+
/**
|
|
893
|
+
* Manual key order comparator for YAML `sortMapEntries`.
|
|
894
|
+
*
|
|
895
|
+
* Keys listed in `order` appear first (in that order); unlisted keys sort
|
|
896
|
+
* to the end alphabetically. Adapted from tbd sorting patterns
|
|
897
|
+
* (`ordering.manual`).
|
|
898
|
+
*/
|
|
899
|
+
function manualKeyOrder(order) {
|
|
900
|
+
const orderMap = new Map(order.map((key, index) => [key, index]));
|
|
901
|
+
return (a, b) => {
|
|
902
|
+
const indexA = orderMap.get(a.key.value);
|
|
903
|
+
const indexB = orderMap.get(b.key.value);
|
|
904
|
+
if (indexA === void 0 && indexB === void 0) return a.key.value.localeCompare(b.key.value);
|
|
905
|
+
if (indexA === void 0) return 1;
|
|
906
|
+
if (indexB === void 0) return -1;
|
|
907
|
+
return indexA - indexB;
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
const DEFAULT_YAML_LINE_WIDTH = 88;
|
|
911
|
+
const YAML_STRINGIFY_OPTIONS = {
|
|
912
|
+
lineWidth: DEFAULT_YAML_LINE_WIDTH,
|
|
913
|
+
defaultStringType: "PLAIN",
|
|
914
|
+
defaultKeyType: "PLAIN"
|
|
915
|
+
};
|
|
916
|
+
function stringifyYaml(data, options) {
|
|
917
|
+
return (0, yaml.stringify)(data, {
|
|
918
|
+
...YAML_STRINGIFY_OPTIONS,
|
|
919
|
+
...options
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
//#endregion
|
|
924
|
+
//#region src/lib/capture-log.ts
|
|
925
|
+
const TOP_LEVEL_ORDER = manualKeyOrder(["generated", "files"]);
|
|
926
|
+
const FILE_ORDER = manualKeyOrder(["path", "blocks"]);
|
|
927
|
+
const BLOCK_ORDER = manualKeyOrder([
|
|
928
|
+
"name",
|
|
929
|
+
"command",
|
|
930
|
+
"expected_exit_code",
|
|
931
|
+
"actual_exit_code",
|
|
932
|
+
"expected_output",
|
|
933
|
+
"actual_output",
|
|
934
|
+
"captures",
|
|
935
|
+
"passed"
|
|
936
|
+
]);
|
|
937
|
+
const CAPTURE_ORDER = manualKeyOrder([
|
|
938
|
+
"category",
|
|
939
|
+
"name",
|
|
940
|
+
"multiline",
|
|
941
|
+
"matched"
|
|
942
|
+
]);
|
|
943
|
+
/**
|
|
944
|
+
* Sort comparator for `yaml.stringify`'s `sortMapEntries`.
|
|
945
|
+
*
|
|
946
|
+
* Dispatches to the correct field ordering based on which keys are present,
|
|
947
|
+
* since the `yaml` package calls this for every map node in the document.
|
|
948
|
+
*/
|
|
949
|
+
const BLOCK_KEYS = new Set([
|
|
950
|
+
"name",
|
|
951
|
+
"command",
|
|
952
|
+
"expected_exit_code",
|
|
953
|
+
"actual_exit_code",
|
|
954
|
+
"expected_output",
|
|
955
|
+
"actual_output",
|
|
956
|
+
"captures",
|
|
957
|
+
"passed"
|
|
958
|
+
]);
|
|
959
|
+
const CAPTURE_KEYS = new Set([
|
|
960
|
+
"category",
|
|
961
|
+
"name",
|
|
962
|
+
"multiline",
|
|
963
|
+
"matched"
|
|
964
|
+
]);
|
|
965
|
+
function captureLogSortMapEntries(a, b) {
|
|
966
|
+
const aKey = a.key.value;
|
|
967
|
+
const bKey = b.key.value;
|
|
968
|
+
if (aKey === "generated" || aKey === "files" || bKey === "generated" || bKey === "files") return TOP_LEVEL_ORDER(a, b);
|
|
969
|
+
if (aKey === "blocks" || bKey === "blocks") return FILE_ORDER(a, b);
|
|
970
|
+
if (BLOCK_KEYS.has(aKey) && BLOCK_KEYS.has(bKey)) return BLOCK_ORDER(a, b);
|
|
971
|
+
if (CAPTURE_KEYS.has(aKey) && CAPTURE_KEYS.has(bKey)) return CAPTURE_ORDER(a, b);
|
|
972
|
+
return aKey.localeCompare(bKey);
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Build the capture log document structure from test results.
|
|
976
|
+
*
|
|
977
|
+
* `customPatterns` can be a static object or a per-file callback.
|
|
978
|
+
* Separated from `writeCaptureLog` for testability.
|
|
979
|
+
*/
|
|
980
|
+
function buildCaptureLogDoc(fileResults, matchContext, customPatterns) {
|
|
981
|
+
const files = fileResults.map((fr) => {
|
|
982
|
+
const ctx = matchContext(fr.file);
|
|
983
|
+
const patterns = typeof customPatterns === "function" ? customPatterns(fr.file) : customPatterns;
|
|
984
|
+
const blocks = fr.results.map((r) => {
|
|
985
|
+
const captures = (matchAndCapture(r.actualOutput, r.block.expectedOutput, ctx, patterns)?.captures ?? []).map((c) => ({
|
|
986
|
+
category: c.category,
|
|
987
|
+
...c.name ? { name: c.name } : {},
|
|
988
|
+
multiline: c.multiline,
|
|
989
|
+
matched: c.captured
|
|
990
|
+
}));
|
|
991
|
+
return {
|
|
992
|
+
name: r.block.name,
|
|
993
|
+
command: r.block.command,
|
|
994
|
+
expected_exit_code: r.block.expectedExitCode,
|
|
995
|
+
actual_exit_code: r.actualExitCode,
|
|
996
|
+
expected_output: r.block.expectedOutput,
|
|
997
|
+
actual_output: r.actualOutput,
|
|
998
|
+
captures,
|
|
999
|
+
passed: r.passed
|
|
1000
|
+
};
|
|
1001
|
+
});
|
|
1002
|
+
return {
|
|
1003
|
+
path: fr.file.path,
|
|
1004
|
+
blocks
|
|
1005
|
+
};
|
|
1006
|
+
});
|
|
1007
|
+
return {
|
|
1008
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1009
|
+
files
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Write a YAML capture log file recording wildcard captures and execution metadata.
|
|
1014
|
+
*/
|
|
1015
|
+
async function writeCaptureLog(path, fileResults, matchContext, customPatterns) {
|
|
1016
|
+
await (0, atomically.writeFile)(path, "# tryscript capture log\n" + stringifyYaml(buildCaptureLogDoc(fileResults, matchContext, customPatterns), { sortMapEntries: captureLogSortMapEntries }));
|
|
1017
|
+
}
|
|
592
1018
|
|
|
593
1019
|
//#endregion
|
|
594
1020
|
//#region src/index.ts
|
|
595
|
-
const VERSION = "0.1.
|
|
1021
|
+
const VERSION = "0.1.7";
|
|
596
1022
|
|
|
597
1023
|
//#endregion
|
|
598
1024
|
Object.defineProperty(exports, 'VERSION', {
|
|
@@ -607,6 +1033,12 @@ Object.defineProperty(exports, '__toESM', {
|
|
|
607
1033
|
return __toESM;
|
|
608
1034
|
}
|
|
609
1035
|
});
|
|
1036
|
+
Object.defineProperty(exports, 'buildCaptureLogDoc', {
|
|
1037
|
+
enumerable: true,
|
|
1038
|
+
get: function () {
|
|
1039
|
+
return buildCaptureLogDoc;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
610
1042
|
Object.defineProperty(exports, 'cleanupExecutionContext', {
|
|
611
1043
|
enumerable: true,
|
|
612
1044
|
get: function () {
|
|
@@ -625,12 +1057,36 @@ Object.defineProperty(exports, 'defineConfig', {
|
|
|
625
1057
|
return defineConfig;
|
|
626
1058
|
}
|
|
627
1059
|
});
|
|
1060
|
+
Object.defineProperty(exports, 'expandExpectedOutput', {
|
|
1061
|
+
enumerable: true,
|
|
1062
|
+
get: function () {
|
|
1063
|
+
return expandExpectedOutput;
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
Object.defineProperty(exports, 'expandTestFile', {
|
|
1067
|
+
enumerable: true,
|
|
1068
|
+
get: function () {
|
|
1069
|
+
return expandTestFile;
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
628
1072
|
Object.defineProperty(exports, 'loadConfig', {
|
|
629
1073
|
enumerable: true,
|
|
630
1074
|
get: function () {
|
|
631
1075
|
return loadConfig;
|
|
632
1076
|
}
|
|
633
1077
|
});
|
|
1078
|
+
Object.defineProperty(exports, 'manualKeyOrder', {
|
|
1079
|
+
enumerable: true,
|
|
1080
|
+
get: function () {
|
|
1081
|
+
return manualKeyOrder;
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
Object.defineProperty(exports, 'matchAndCapture', {
|
|
1085
|
+
enumerable: true,
|
|
1086
|
+
get: function () {
|
|
1087
|
+
return matchAndCapture;
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
634
1090
|
Object.defineProperty(exports, 'matchOutput', {
|
|
635
1091
|
enumerable: true,
|
|
636
1092
|
get: function () {
|
|
@@ -673,4 +1129,22 @@ Object.defineProperty(exports, 'runBlock', {
|
|
|
673
1129
|
return runBlock;
|
|
674
1130
|
}
|
|
675
1131
|
});
|
|
676
|
-
|
|
1132
|
+
Object.defineProperty(exports, 'shouldExpandCategory', {
|
|
1133
|
+
enumerable: true,
|
|
1134
|
+
get: function () {
|
|
1135
|
+
return shouldExpandCategory;
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
Object.defineProperty(exports, 'stringifyYaml', {
|
|
1139
|
+
enumerable: true,
|
|
1140
|
+
get: function () {
|
|
1141
|
+
return stringifyYaml;
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
Object.defineProperty(exports, 'writeCaptureLog', {
|
|
1145
|
+
enumerable: true,
|
|
1146
|
+
get: function () {
|
|
1147
|
+
return writeCaptureLog;
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
//# sourceMappingURL=src-BIZMxxIt.cjs.map
|