playwright-order-manager 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +240 -0
  3. package/bin/run.js +133 -0
  4. package/dist/constants/index.d.ts +72 -0
  5. package/dist/constants/index.d.ts.map +1 -0
  6. package/dist/constants/index.js +93 -0
  7. package/dist/constants/index.js.map +1 -0
  8. package/dist/core/OrderedExecution.d.ts +52 -0
  9. package/dist/core/OrderedExecution.d.ts.map +1 -0
  10. package/dist/core/OrderedExecution.js +253 -0
  11. package/dist/core/OrderedExecution.js.map +1 -0
  12. package/dist/core/OrderedReportParser.d.ts +38 -0
  13. package/dist/core/OrderedReportParser.d.ts.map +1 -0
  14. package/dist/core/OrderedReportParser.js +169 -0
  15. package/dist/core/OrderedReportParser.js.map +1 -0
  16. package/dist/core/OrderedSummary.d.ts +20 -0
  17. package/dist/core/OrderedSummary.d.ts.map +1 -0
  18. package/dist/core/OrderedSummary.js +747 -0
  19. package/dist/core/OrderedSummary.js.map +1 -0
  20. package/dist/fixtures/index.d.ts +30 -0
  21. package/dist/fixtures/index.d.ts.map +1 -0
  22. package/dist/fixtures/index.js +212 -0
  23. package/dist/fixtures/index.js.map +1 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +31 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/runner/TestOrderManager.d.ts +62 -0
  29. package/dist/runner/TestOrderManager.d.ts.map +1 -0
  30. package/dist/runner/TestOrderManager.js +490 -0
  31. package/dist/runner/TestOrderManager.js.map +1 -0
  32. package/dist/types/index.d.ts +215 -0
  33. package/dist/types/index.d.ts.map +1 -0
  34. package/dist/types/index.js +6 -0
  35. package/dist/types/index.js.map +1 -0
  36. package/package.json +65 -0
  37. package/templates/playwright.merge.config.ts +57 -0
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderedExecution = void 0;
4
+ const constants_1 = require("../constants");
5
+ // =============================================================================
6
+ // INTERNAL HELPERS
7
+ // These are private to this file. Not exported. Not part of the public API.
8
+ // =============================================================================
9
+ /**
10
+ * Returns true if the test has ANY of the given tags.
11
+ *
12
+ * Why a separate helper?
13
+ * Tag matching happens in multiple places (boundary detection, priority grouping).
14
+ * Centralizing it means if Playwright ever changes how tags work, we fix one place.
15
+ */
16
+ function hasAnyTag(test, tags) {
17
+ return test.tags.some((tag) => tags.includes(tag));
18
+ }
19
+ /**
20
+ * Returns the highest-priority tag found on a test.
21
+ *
22
+ * "Highest priority" means earliest in the PRIORITY_TAGS array.
23
+ * So if a test has both '@P2' and '@P3', this returns '@P2'.
24
+ *
25
+ * Returns undefined if the test has no priority tag at all.
26
+ */
27
+ function getHighestPriorityTag(test) {
28
+ return constants_1.RunnerConstants.PRIORITY_TAGS.find((tag) => test.tags.includes(tag));
29
+ }
30
+ // =============================================================================
31
+ // BUCKET BUILDERS — one function per OrderMode
32
+ // =============================================================================
33
+ /**
34
+ * Builds buckets for 'basic' mode.
35
+ *
36
+ * Basic mode does the minimum: boundary tests (@runFirst / @runLast) get
37
+ * their own buckets, everything else goes into one single bucket in the
38
+ * middle. No priority ordering within that middle bucket.
39
+ */
40
+ function buildBasicBuckets(tests, runFirstTags, runLastTags) {
41
+ const runFirstTests = [];
42
+ const middleTests = [];
43
+ const runLastTests = [];
44
+ for (const test of tests) {
45
+ if (hasAnyTag(test, runFirstTags)) {
46
+ runFirstTests.push(test);
47
+ }
48
+ else if (hasAnyTag(test, runLastTags)) {
49
+ runLastTests.push(test);
50
+ }
51
+ else {
52
+ middleTests.push(test);
53
+ }
54
+ }
55
+ const buckets = [];
56
+ if (runFirstTests.length > 0) {
57
+ buckets.push({
58
+ key: constants_1.RunnerConstants.BUCKET_KEYS.RUN_FIRST,
59
+ label: 'Run First',
60
+ kind: 'boundary',
61
+ critical: true,
62
+ tests: runFirstTests,
63
+ });
64
+ }
65
+ if (middleTests.length > 0) {
66
+ buckets.push({
67
+ key: constants_1.RunnerConstants.BUCKET_KEYS.NO_PRIORITY,
68
+ label: 'All Tests',
69
+ kind: 'none',
70
+ critical: false,
71
+ tests: middleTests,
72
+ });
73
+ }
74
+ if (runLastTests.length > 0) {
75
+ buckets.push({
76
+ key: constants_1.RunnerConstants.BUCKET_KEYS.RUN_LAST,
77
+ label: 'Run Last',
78
+ kind: 'boundary',
79
+ critical: false,
80
+ tests: runLastTests,
81
+ });
82
+ }
83
+ return buckets;
84
+ }
85
+ /**
86
+ * Builds buckets for 'priority' mode.
87
+ *
88
+ * Priority mode creates one bucket per priority level that has at least
89
+ * one test. So if you have @P1 and @P3 tests but no @P2, you get two
90
+ * priority buckets (not three). Tests with no priority tag go into the
91
+ * NoPriority bucket, which runs after all priority buckets.
92
+ *
93
+ * Full execution order:
94
+ * [runFirst] → [@P1] → [@P2] → [@P3] → [@P4] → [NoPriority] → [runLast]
95
+ *
96
+ * Empty buckets are omitted entirely.
97
+ */
98
+ function buildPriorityBuckets(tests, runFirstTags, runLastTags) {
99
+ const runFirstTests = [];
100
+ const runLastTests = [];
101
+ const noPriorityTests = [];
102
+ // One array per priority tag, keyed by tag name
103
+ const priorityMap = new Map(constants_1.RunnerConstants.PRIORITY_TAGS.map((tag) => [tag, []]));
104
+ // Single pass through all tests — assign each to exactly one bucket
105
+ for (const test of tests) {
106
+ if (hasAnyTag(test, runFirstTags)) {
107
+ runFirstTests.push(test);
108
+ continue;
109
+ }
110
+ if (hasAnyTag(test, runLastTags)) {
111
+ runLastTests.push(test);
112
+ continue;
113
+ }
114
+ const priorityTag = getHighestPriorityTag(test);
115
+ if (priorityTag) {
116
+ // Non-null assertion safe here: we just found the tag in PRIORITY_TAGS
117
+ priorityMap.get(priorityTag).push(test);
118
+ }
119
+ else {
120
+ noPriorityTests.push(test);
121
+ }
122
+ }
123
+ const buckets = [];
124
+ // 1. runFirst bucket (critical — if this fails, we stop)
125
+ if (runFirstTests.length > 0) {
126
+ buckets.push({
127
+ key: constants_1.RunnerConstants.BUCKET_KEYS.RUN_FIRST,
128
+ label: 'Run First',
129
+ kind: 'boundary',
130
+ critical: true,
131
+ tests: runFirstTests,
132
+ });
133
+ }
134
+ // 2. One bucket per priority tag, in P1 → P4 order
135
+ // Skip empty priority buckets entirely
136
+ for (const tag of constants_1.RunnerConstants.PRIORITY_TAGS) {
137
+ const testsForTag = priorityMap.get(tag);
138
+ if (testsForTag.length === 0)
139
+ continue;
140
+ // '@P1' → 'P1', '@P2' → 'P2', etc.
141
+ const shortTag = tag.replace('@', '');
142
+ buckets.push({
143
+ key: `priority-${shortTag}`,
144
+ label: `Priority ${shortTag}`,
145
+ kind: 'priority',
146
+ critical: false,
147
+ tests: testsForTag,
148
+ });
149
+ }
150
+ // 3. NoPriority bucket — tests with no @Px tag
151
+ if (noPriorityTests.length > 0) {
152
+ buckets.push({
153
+ key: constants_1.RunnerConstants.BUCKET_KEYS.NO_PRIORITY,
154
+ label: constants_1.RunnerConstants.NO_PRIORITY_TOKEN,
155
+ kind: 'none',
156
+ critical: false,
157
+ tests: noPriorityTests,
158
+ });
159
+ }
160
+ // 4. runLast bucket (not critical — cleanup should always attempt to run)
161
+ if (runLastTests.length > 0) {
162
+ buckets.push({
163
+ key: constants_1.RunnerConstants.BUCKET_KEYS.RUN_LAST,
164
+ label: 'Run Last',
165
+ kind: 'boundary',
166
+ critical: false,
167
+ tests: runLastTests,
168
+ });
169
+ }
170
+ return buckets;
171
+ }
172
+ // =============================================================================
173
+ // PUBLIC API
174
+ // =============================================================================
175
+ class OrderedExecution {
176
+ /**
177
+ * Takes a flat list of discovered tests and groups them into ordered buckets.
178
+ *
179
+ * This is the core algorithm of the entire package. The buckets returned
180
+ * here define the execution order — the runner executes them one by one,
181
+ * in the order they appear in this array.
182
+ *
183
+ * @param options - See BuildBucketOptions in types/index.ts
184
+ * @returns An ordered array of BucketPlan objects, ready for execution
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * const buckets = OrderedExecution.buildBuckets({
189
+ * tests: discoveredTests,
190
+ * orderMode: 'priority',
191
+ * });
192
+ * // buckets[0] = runFirst tests (if any)
193
+ * // buckets[1] = @P1 tests (if any)
194
+ * // buckets[2] = @P2 tests (if any)
195
+ * // ...and so on
196
+ * ```
197
+ */
198
+ static buildBuckets(options) {
199
+ const { tests, orderMode, runFirstTags = [constants_1.RunnerConstants.RUN_FIRST_TAG], runLastTags = [constants_1.RunnerConstants.RUN_LAST_TAG], } = options;
200
+ // Guard: nothing to do with an empty test list
201
+ if (tests.length === 0) {
202
+ return [];
203
+ }
204
+ switch (orderMode) {
205
+ case 'basic':
206
+ return buildBasicBuckets(tests, runFirstTags, runLastTags);
207
+ case 'priority':
208
+ return buildPriorityBuckets(tests, runFirstTags, runLastTags);
209
+ case 'custom':
210
+ // v2 — not implemented yet
211
+ // We throw instead of silently falling back so the user knows immediately
212
+ throw new Error(`OrderMode 'custom' is not implemented in v1. ` +
213
+ `Use 'basic' or 'priority'.`);
214
+ default: {
215
+ // This branch should be unreachable if TypeScript types are respected.
216
+ // But if someone passes an invalid value via JavaScript or env vars,
217
+ // we want a clear error message.
218
+ const exhaustiveCheck = orderMode;
219
+ throw new Error(`Unknown orderMode: '${exhaustiveCheck}'. ` +
220
+ `Valid values are: 'basic', 'priority'.`);
221
+ }
222
+ }
223
+ }
224
+ /**
225
+ * Splits an array of buckets into three phases for the runner to process.
226
+ *
227
+ * Why split into phases?
228
+ * The runner needs to handle the runFirst bucket specially — if it fails
229
+ * and the FailurePolicy is 'critical', we stop before running any middle
230
+ * buckets. Similarly, runLast should always attempt to run (cleanup)
231
+ * even if middle buckets failed.
232
+ *
233
+ * @param buckets - The output from buildBuckets()
234
+ * @returns An object with three arrays: runFirst, middle, runLast
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * const { runFirst, middle, runLast } = OrderedExecution.groupBuckets(buckets);
239
+ * // Run runFirst phase, check failure policy
240
+ * // Run middle phase buckets in order
241
+ * // Run runLast phase regardless of middle results
242
+ * ```
243
+ */
244
+ static groupBuckets(buckets) {
245
+ const runFirst = buckets.filter((b) => b.key === constants_1.RunnerConstants.BUCKET_KEYS.RUN_FIRST);
246
+ const runLast = buckets.filter((b) => b.key === constants_1.RunnerConstants.BUCKET_KEYS.RUN_LAST);
247
+ const middle = buckets.filter((b) => b.key !== constants_1.RunnerConstants.BUCKET_KEYS.RUN_FIRST &&
248
+ b.key !== constants_1.RunnerConstants.BUCKET_KEYS.RUN_LAST);
249
+ return { runFirst, middle, runLast };
250
+ }
251
+ }
252
+ exports.OrderedExecution = OrderedExecution;
253
+ //# sourceMappingURL=OrderedExecution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrderedExecution.js","sourceRoot":"","sources":["../../src/core/OrderedExecution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { DiscoveredTestCase, ExecutedTestResult } from '../types';
2
+ export declare class OrderedReportParser {
3
+ /**
4
+ * Parses Playwright's execution JSON report into our typed ExecutedTestResult array.
5
+ *
6
+ * Call this after a Playwright run completes and writes its JSON report.
7
+ * The result is what OrderedSummary uses to build the final summary.
8
+ *
9
+ * @param rawJson - The parsed contents of Playwright's JSON report file.
10
+ * Pass the result of JSON.parse(fs.readFileSync(reportPath, 'utf8')).
11
+ * @returns A flat array of ExecutedTestResult, one entry per test per project.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const raw = JSON.parse(fs.readFileSync('./test-results/report.json', 'utf8'));
16
+ * const results = OrderedReportParser.parseExecutionReport(raw);
17
+ * ```
18
+ */
19
+ static parseExecutionReport(rawJson: unknown): ExecutedTestResult[];
20
+ /**
21
+ * Parses the discovery JSON written during `playwright --list` phase.
22
+ *
23
+ * During discovery, our fixture writes a JSON file containing the list
24
+ * of all tests Playwright found. This method reads that file's contents
25
+ * and converts them into DiscoveredTestCase objects.
26
+ *
27
+ * @param rawJson - The parsed contents of the discovery JSON file.
28
+ * @returns A flat array of DiscoveredTestCase objects.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const raw = JSON.parse(fs.readFileSync('./ordered-results/ordered-discovery.json', 'utf8'));
33
+ * const tests = OrderedReportParser.parseDiscoveryReport(raw);
34
+ * ```
35
+ */
36
+ static parseDiscoveryReport(rawJson: unknown): DiscoveredTestCase[];
37
+ }
38
+ //# sourceMappingURL=OrderedReportParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrderedReportParser.d.ts","sourceRoot":"","sources":["../../src/core/OrderedReportParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAEnB,MAAM,UAAU,CAAC;AA0JlB,qBAAa,mBAAmB;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,EAAE;IA0DnE;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,EAAE;CA8BpE"}
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderedReportParser = void 0;
4
+ // =============================================================================
5
+ // INTERNAL HELPERS
6
+ // =============================================================================
7
+ /**
8
+ * Normalises whatever status string Playwright gives us into our TestStatus type.
9
+ * Playwright uses 'expected', 'unexpected', 'flaky', 'skipped' — we map those
10
+ * to our simpler 'passed' | 'failed' | 'skipped' | 'timedOut' | 'interrupted'.
11
+ */
12
+ function normaliseStatus(raw) {
13
+ switch (raw) {
14
+ case 'expected':
15
+ case 'passed':
16
+ return 'passed';
17
+ case 'skipped':
18
+ return 'skipped';
19
+ case 'timedOut':
20
+ return 'timedOut';
21
+ case 'interrupted':
22
+ return 'interrupted';
23
+ // 'unexpected', 'flaky', or anything else we don't recognise → failed
24
+ default:
25
+ return 'failed';
26
+ }
27
+ }
28
+ /**
29
+ * Extracts the most useful error message from a Playwright error array.
30
+ * Playwright can report multiple errors per test — we take the first non-empty one.
31
+ */
32
+ function extractErrorMessage(errors) {
33
+ if (!errors || errors.length === 0)
34
+ return undefined;
35
+ for (const error of errors) {
36
+ const msg = error.message ?? error.value;
37
+ if (msg && msg.trim().length > 0) {
38
+ // Trim to first 500 chars — full stack traces belong in the HTML report,
39
+ // not in our summary JSON
40
+ return msg.trim().slice(0, 500);
41
+ }
42
+ }
43
+ return undefined;
44
+ }
45
+ /**
46
+ * Recursively walks a Playwright suite tree and collects all specs.
47
+ * Suites can be nested arbitrarily deep (file → describe → describe → spec).
48
+ *
49
+ * @param suite - The current suite node to walk
50
+ * @param filePath - The file path, carried down from the top-level suite
51
+ * @param collected - Accumulator array — specs are pushed here
52
+ */
53
+ function collectSpecs(suite, filePath, collected) {
54
+ // Collect specs at this level
55
+ if (suite.specs) {
56
+ for (const spec of suite.specs) {
57
+ collected.push({ spec, file: filePath });
58
+ }
59
+ }
60
+ // Recurse into nested suites
61
+ if (suite.suites) {
62
+ for (const child of suite.suites) {
63
+ // Use the child's own file path if it has one, otherwise inherit from parent
64
+ collectSpecs(child, child.file ?? filePath, collected);
65
+ }
66
+ }
67
+ }
68
+ // =============================================================================
69
+ // PUBLIC API
70
+ // =============================================================================
71
+ class OrderedReportParser {
72
+ /**
73
+ * Parses Playwright's execution JSON report into our typed ExecutedTestResult array.
74
+ *
75
+ * Call this after a Playwright run completes and writes its JSON report.
76
+ * The result is what OrderedSummary uses to build the final summary.
77
+ *
78
+ * @param rawJson - The parsed contents of Playwright's JSON report file.
79
+ * Pass the result of JSON.parse(fs.readFileSync(reportPath, 'utf8')).
80
+ * @returns A flat array of ExecutedTestResult, one entry per test per project.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const raw = JSON.parse(fs.readFileSync('./test-results/report.json', 'utf8'));
85
+ * const results = OrderedReportParser.parseExecutionReport(raw);
86
+ * ```
87
+ */
88
+ static parseExecutionReport(rawJson) {
89
+ // Validate that we got something that looks like a Playwright report
90
+ if (!rawJson || typeof rawJson !== 'object') {
91
+ throw new Error('OrderedReportParser.parseExecutionReport: ' +
92
+ 'expected a JSON object, got ' + typeof rawJson);
93
+ }
94
+ const report = rawJson;
95
+ if (!Array.isArray(report.suites)) {
96
+ throw new Error('OrderedReportParser.parseExecutionReport: ' +
97
+ 'JSON does not look like a Playwright report (missing "suites" array)');
98
+ }
99
+ const results = [];
100
+ // Collect all specs from the entire suite tree
101
+ const allSpecs = [];
102
+ for (const topSuite of report.suites) {
103
+ collectSpecs(topSuite, topSuite.file ?? '', allSpecs);
104
+ }
105
+ // Convert each spec + its tests into ExecutedTestResult entries
106
+ for (const { spec, file } of allSpecs) {
107
+ if (!spec.tests || spec.tests.length === 0)
108
+ continue;
109
+ for (const test of spec.tests) {
110
+ // How many retries were used?
111
+ // results array has one entry per attempt. retries = attempts - 1.
112
+ const retries = test.results
113
+ ? Math.max(0, test.results.length - 1)
114
+ : 0;
115
+ // The final error comes from the last result attempt (if it failed)
116
+ const lastResult = test.results?.[test.results.length - 1];
117
+ const errorMessage = extractErrorMessage(lastResult?.errors ?? test.errors);
118
+ results.push({
119
+ title: spec.title,
120
+ file,
121
+ status: normaliseStatus(test.status),
122
+ duration: test.duration,
123
+ retries,
124
+ errorMessage,
125
+ });
126
+ }
127
+ }
128
+ return results;
129
+ }
130
+ /**
131
+ * Parses the discovery JSON written during `playwright --list` phase.
132
+ *
133
+ * During discovery, our fixture writes a JSON file containing the list
134
+ * of all tests Playwright found. This method reads that file's contents
135
+ * and converts them into DiscoveredTestCase objects.
136
+ *
137
+ * @param rawJson - The parsed contents of the discovery JSON file.
138
+ * @returns A flat array of DiscoveredTestCase objects.
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const raw = JSON.parse(fs.readFileSync('./ordered-results/ordered-discovery.json', 'utf8'));
143
+ * const tests = OrderedReportParser.parseDiscoveryReport(raw);
144
+ * ```
145
+ */
146
+ static parseDiscoveryReport(rawJson) {
147
+ if (!rawJson || typeof rawJson !== 'object') {
148
+ throw new Error('OrderedReportParser.parseDiscoveryReport: ' +
149
+ 'expected a JSON object, got ' + typeof rawJson);
150
+ }
151
+ const data = rawJson;
152
+ if (!Array.isArray(data.tests)) {
153
+ throw new Error('OrderedReportParser.parseDiscoveryReport: ' +
154
+ 'discovery JSON missing "tests" array. ' +
155
+ 'Make sure the orderedDiscovery fixture is set up correctly.');
156
+ }
157
+ // Validate and cast each entry
158
+ return data.tests
159
+ .filter((entry) => {
160
+ return (entry !== null &&
161
+ typeof entry === 'object' &&
162
+ typeof entry.title === 'string' &&
163
+ typeof entry.file === 'string' &&
164
+ Array.isArray(entry.tags));
165
+ });
166
+ }
167
+ }
168
+ exports.OrderedReportParser = OrderedReportParser;
169
+ //# sourceMappingURL=OrderedReportParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrderedReportParser.js","sourceRoot":"","sources":["../../src/core/OrderedReportParser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { OrderedRunSummary, BucketExecutionRecord } from '../types';
2
+ export declare class OrderedSummary {
3
+ /**
4
+ * Writes the ordered run summary to disk as both JSON and HTML.
5
+ *
6
+ * @param summary - The complete run summary to write
7
+ * @param reportRoot - Directory to write into. Defaults to RunnerConstants.DEFAULTS.REPORT_ROOT
8
+ * @returns Absolute paths of the files written: { jsonPath, htmlPath }
9
+ */
10
+ static write(summary: OrderedRunSummary, reportRoot?: string): {
11
+ jsonPath: string;
12
+ htmlPath: string;
13
+ };
14
+ /**
15
+ * Builds an OrderedRunSummary from raw bucket execution records.
16
+ * Call this after all buckets finish, then pass the result to write().
17
+ */
18
+ static buildSummary(buckets: BucketExecutionRecord[], startedAt: string, orderMode: string, failurePolicy: string): OrderedRunSummary;
19
+ }
20
+ //# sourceMappingURL=OrderedSummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrderedSummary.d.ts","sourceRoot":"","sources":["../../src/core/OrderedSummary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EAEtB,MAAM,UAAU,CAAC;AAyqBlB,qBAAa,cAAc;IACzB;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CACV,OAAO,EAAE,iBAAiB,EAC1B,UAAU,GAAE,MAA6C,GACxD;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IA2BzC;;;OAGG;IACH,MAAM,CAAC,YAAY,CACjB,OAAO,EAAE,qBAAqB,EAAE,EAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,iBAAiB;CA6BrB"}