indexwright 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 (77) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +201 -0
  3. package/README.md +184 -0
  4. package/SPEC.md +386 -0
  5. package/dist/args.d.ts +26 -0
  6. package/dist/args.d.ts.map +1 -0
  7. package/dist/args.js +170 -0
  8. package/dist/args.js.map +1 -0
  9. package/dist/cli.d.ts +12 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +96 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/collections.d.ts +6 -0
  14. package/dist/collections.d.ts.map +1 -0
  15. package/dist/collections.js +25 -0
  16. package/dist/collections.js.map +1 -0
  17. package/dist/format/github.d.ts +13 -0
  18. package/dist/format/github.d.ts.map +1 -0
  19. package/dist/format/github.js +63 -0
  20. package/dist/format/github.js.map +1 -0
  21. package/dist/format/inline.d.ts +11 -0
  22. package/dist/format/inline.d.ts.map +1 -0
  23. package/dist/format/inline.js +15 -0
  24. package/dist/format/inline.js.map +1 -0
  25. package/dist/format/json.d.ts +7 -0
  26. package/dist/format/json.d.ts.map +1 -0
  27. package/dist/format/json.js +25 -0
  28. package/dist/format/json.js.map +1 -0
  29. package/dist/format/text.d.ts +3 -0
  30. package/dist/format/text.d.ts.map +1 -0
  31. package/dist/format/text.js +65 -0
  32. package/dist/format/text.js.map +1 -0
  33. package/dist/index.d.ts +18 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +17 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/key.d.ts +35 -0
  38. package/dist/key.d.ts.map +1 -0
  39. package/dist/key.js +76 -0
  40. package/dist/key.js.map +1 -0
  41. package/dist/lint.d.ts +22 -0
  42. package/dist/lint.d.ts.map +1 -0
  43. package/dist/lint.js +110 -0
  44. package/dist/lint.js.map +1 -0
  45. package/dist/parse.d.ts +11 -0
  46. package/dist/parse.d.ts.map +1 -0
  47. package/dist/parse.js +88 -0
  48. package/dist/parse.js.map +1 -0
  49. package/dist/rules/explicit-name-field.d.ts +13 -0
  50. package/dist/rules/explicit-name-field.d.ts.map +1 -0
  51. package/dist/rules/explicit-name-field.js +35 -0
  52. package/dist/rules/explicit-name-field.js.map +1 -0
  53. package/dist/rules/field-order-variant.d.ts +10 -0
  54. package/dist/rules/field-order-variant.d.ts.map +1 -0
  55. package/dist/rules/field-order-variant.js +60 -0
  56. package/dist/rules/field-order-variant.js.map +1 -0
  57. package/dist/rules/index.d.ts +13 -0
  58. package/dist/rules/index.d.ts.map +1 -0
  59. package/dist/rules/index.js +25 -0
  60. package/dist/rules/index.js.map +1 -0
  61. package/dist/rules/quota-headroom.d.ts +10 -0
  62. package/dist/rules/quota-headroom.d.ts.map +1 -0
  63. package/dist/rules/quota-headroom.js +40 -0
  64. package/dist/rules/quota-headroom.js.map +1 -0
  65. package/dist/rules/scope-mismatch.d.ts +10 -0
  66. package/dist/rules/scope-mismatch.d.ts.map +1 -0
  67. package/dist/rules/scope-mismatch.js +45 -0
  68. package/dist/rules/scope-mismatch.js.map +1 -0
  69. package/dist/types.d.ts +104 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +14 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/version.d.ts +2 -0
  74. package/dist/version.d.ts.map +1 -0
  75. package/dist/version.js +11 -0
  76. package/dist/version.js.map +1 -0
  77. package/package.json +54 -0
package/dist/cli.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { appendFileSync, realpathSync } from 'node:fs';
3
+ import process from 'node:process';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { parseArgs, usage, UsageError } from './args.js';
6
+ import { formatGithub } from './format/github.js';
7
+ import { formatJson } from './format/json.js';
8
+ import { formatText } from './format/text.js';
9
+ import { lintFiles } from './lint.js';
10
+ import { VERSION } from './version.js';
11
+ /**
12
+ * Exit codes (SPEC §4): 0 completed, 1 warnings over the budget, 2 usage error or unusable input.
13
+ * A file that could not be analysed is a 2 regardless of `--max-warnings`, because the run did not
14
+ * cover what it was asked to cover.
15
+ */
16
+ export function run(argv, streams) {
17
+ let command;
18
+ try {
19
+ command = parseArgs(argv);
20
+ }
21
+ catch (error) {
22
+ if (!(error instanceof UsageError))
23
+ throw error;
24
+ streams.err(`indexwright: ${error.message}\n\n${usage()}\n`);
25
+ return 2;
26
+ }
27
+ if (command.kind === 'help') {
28
+ streams.out(`${usage()}\n`);
29
+ return 0;
30
+ }
31
+ if (command.kind === 'version') {
32
+ streams.out(`${VERSION}\n`);
33
+ return 0;
34
+ }
35
+ const result = lintFiles(command.files, {
36
+ rules: command.rules,
37
+ quota: command.quota,
38
+ quotaThreshold: command.quotaThreshold,
39
+ });
40
+ switch (command.format) {
41
+ case 'json':
42
+ streams.out(formatJson(result));
43
+ break;
44
+ case 'github': {
45
+ const { commands, summary } = formatGithub(result);
46
+ streams.out(commands);
47
+ writeStepSummary(summary, streams);
48
+ break;
49
+ }
50
+ case 'text':
51
+ streams.out(formatText(result));
52
+ break;
53
+ }
54
+ if (result.summary.errors > 0)
55
+ return 2;
56
+ if (result.summary.warnings > command.maxWarnings)
57
+ return 1;
58
+ return 0;
59
+ }
60
+ function writeStepSummary(summary, streams) {
61
+ const target = process.env['GITHUB_STEP_SUMMARY'];
62
+ if (!target) {
63
+ streams.out(summary);
64
+ return;
65
+ }
66
+ try {
67
+ appendFileSync(target, summary);
68
+ }
69
+ catch (error) {
70
+ // A summary that cannot be written must not change the verdict of the lint.
71
+ streams.err(`indexwright: could not write GITHUB_STEP_SUMMARY: ${error.message}\n`);
72
+ streams.out(summary);
73
+ }
74
+ }
75
+ /**
76
+ * npm installs the bin as a symlink, so `process.argv[1]` is the link while `import.meta.url` is
77
+ * the real path. Comparing without resolving the link would leave the installed CLI doing nothing.
78
+ */
79
+ function invokedDirectly() {
80
+ const entry = process.argv[1];
81
+ if (entry === undefined)
82
+ return false;
83
+ try {
84
+ return import.meta.url === pathToFileURL(realpathSync(entry)).href;
85
+ }
86
+ catch {
87
+ return false;
88
+ }
89
+ }
90
+ if (invokedDirectly()) {
91
+ process.exitCode = run(process.argv.slice(2), {
92
+ out: (text) => process.stdout.write(text),
93
+ err: (text) => process.stderr.write(text),
94
+ });
95
+ }
96
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** Small ordering and grouping helpers. Determinism is a stated requirement (SPEC §7). */
2
+ export declare function compareStrings(a: string, b: string): number;
3
+ /** Group while remembering first-seen order, so callers can sort explicitly rather than by luck. */
4
+ export declare function groupBy<T>(items: Iterable<T>, keyOf: (item: T) => string): Map<string, T[]>;
5
+ export declare function uniqueSorted(values: Iterable<string>): string[];
6
+ //# sourceMappingURL=collections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../src/collections.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED,oGAAoG;AACpG,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAS3F;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAE/D"}
@@ -0,0 +1,25 @@
1
+ /** Small ordering and grouping helpers. Determinism is a stated requirement (SPEC §7). */
2
+ export function compareStrings(a, b) {
3
+ if (a < b)
4
+ return -1;
5
+ if (a > b)
6
+ return 1;
7
+ return 0;
8
+ }
9
+ /** Group while remembering first-seen order, so callers can sort explicitly rather than by luck. */
10
+ export function groupBy(items, keyOf) {
11
+ const groups = new Map();
12
+ for (const item of items) {
13
+ const key = keyOf(item);
14
+ const bucket = groups.get(key);
15
+ if (bucket)
16
+ bucket.push(item);
17
+ else
18
+ groups.set(key, [item]);
19
+ }
20
+ return groups;
21
+ }
22
+ export function uniqueSorted(values) {
23
+ return [...new Set(values)].sort(compareStrings);
24
+ }
25
+ //# sourceMappingURL=collections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.js","sourceRoot":"","sources":["../src/collections.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,MAAM,UAAU,cAAc,CAAC,CAAS,EAAE,CAAS;IACjD,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,OAAO,CAAI,KAAkB,EAAE,KAA0B;IACvE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAe,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QACxB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YACzB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAwB;IACnD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { LintResult } from '../types.js';
2
+ export interface GithubOutput {
3
+ /** Workflow commands, for stdout: GitHub reads them from the job log. */
4
+ commands: string;
5
+ /** Markdown, for `$GITHUB_STEP_SUMMARY`. */
6
+ summary: string;
7
+ }
8
+ /**
9
+ * Findings are annotations without a line number: locating a finding inside the JSON would need a
10
+ * position-tracking parser, which v0.1.0 does not carry (SPEC §4).
11
+ */
12
+ export declare function formatGithub(result: LintResult): GithubOutput;
13
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/format/github.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,UAAU,EAAE,MAAM,aAAa,CAAC;AAGvD,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAc7D"}
@@ -0,0 +1,63 @@
1
+ import { oneLine } from './inline.js';
2
+ /**
3
+ * Findings are annotations without a line number: locating a finding inside the JSON would need a
4
+ * position-tracking parser, which v0.1.0 does not carry (SPEC §4).
5
+ */
6
+ export function formatGithub(result) {
7
+ const commands = [
8
+ ...result.findings.map((finding) => `::warning file=${escapeProperty(finding.file)}::${escapeData(annotation(finding))}`),
9
+ ...result.errors.map((error) => `::error file=${escapeProperty(error.file)}::${escapeData(error.message)}`),
10
+ ];
11
+ return {
12
+ commands: commands.length > 0 ? `${commands.join('\n')}\n` : '',
13
+ summary: markdown(result),
14
+ };
15
+ }
16
+ function annotation(finding) {
17
+ const keys = [finding.key, ...finding.related].filter((key) => key !== null);
18
+ const suffix = keys.length > 0 ? ` [${keys.join(', ')}]` : '';
19
+ return `${finding.rule}: ${finding.message}${suffix}`;
20
+ }
21
+ function markdown(result) {
22
+ const lines = ['## indexwright', ''];
23
+ const { warnings, errors } = result.summary;
24
+ if (warnings === 0 && errors === 0) {
25
+ lines.push(`No findings across ${result.files.length} file(s).`, '');
26
+ return `${lines.join('\n')}\n`;
27
+ }
28
+ lines.push(`${warnings} warning(s)${errors > 0 ? ` and ${errors} error(s)` : ''} across ` +
29
+ `${result.files.length} file(s).`, '', '> Advisory only: no finding indicates that an index is unused or safe to delete.', '');
30
+ if (warnings > 0) {
31
+ lines.push('| Rule | File | Index | Detail |', '| --- | --- | --- | --- |');
32
+ for (const finding of result.findings) {
33
+ const keys = [finding.key, ...finding.related].filter((key) => key !== null);
34
+ const indexCell = keys.length === 0
35
+ ? '_whole file_'
36
+ : keys.map((key) => `\`${escapeCell(key)}\``).join('<br>');
37
+ lines.push(`| \`${finding.rule}\` | \`${escapeCell(finding.file)}\` | ${indexCell} | ${escapeCell(finding.message)} |`);
38
+ }
39
+ lines.push('');
40
+ }
41
+ if (errors > 0) {
42
+ lines.push('### Files that could not be analysed', '');
43
+ for (const error of result.errors) {
44
+ lines.push(`- \`${escapeCell(error.file)}\` — ${escapeCell(error.message)}`);
45
+ }
46
+ lines.push('');
47
+ }
48
+ return `${lines.join('\n')}\n`;
49
+ }
50
+ /**
51
+ * Canonical keys contain `|`, which would otherwise start a new table cell, and a value taken from
52
+ * a linted file may contain a newline, which would end the row entirely (see `oneLine`).
53
+ */
54
+ function escapeCell(value) {
55
+ return oneLine(value).replaceAll('|', '\\|');
56
+ }
57
+ function escapeData(value) {
58
+ return value.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A');
59
+ }
60
+ function escapeProperty(value) {
61
+ return escapeData(value).replaceAll(':', '%3A').replaceAll(',', '%2C');
62
+ }
63
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/format/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Values read from a linted file — a `collectionGroup`, a `fieldPath`, a direction — may hold any
3
+ * character JSON allows, including a newline, because §4 refuses only what cannot be read as an
4
+ * index declaration at all. `text` renders one finding per line and `github` renders one finding
5
+ * per table row, so a newline in a linted file would otherwise break out of the row and forge a
6
+ * heading or a table of its own in a report the reader is meant to trust.
7
+ *
8
+ * `json` deliberately does not use this: there the value is the contract, and JSON escapes it.
9
+ */
10
+ export declare function oneLine(value: string): string;
11
+ //# sourceMappingURL=inline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/format/inline.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C"}
@@ -0,0 +1,15 @@
1
+ /** Control characters: everything below the space, plus DEL. */
2
+ const CONTROL = /[\u0000-\u001f\u007f]+/gu;
3
+ /**
4
+ * Values read from a linted file — a `collectionGroup`, a `fieldPath`, a direction — may hold any
5
+ * character JSON allows, including a newline, because §4 refuses only what cannot be read as an
6
+ * index declaration at all. `text` renders one finding per line and `github` renders one finding
7
+ * per table row, so a newline in a linted file would otherwise break out of the row and forge a
8
+ * heading or a table of its own in a report the reader is meant to trust.
9
+ *
10
+ * `json` deliberately does not use this: there the value is the contract, and JSON escapes it.
11
+ */
12
+ export function oneLine(value) {
13
+ return value.replaceAll(CONTROL, ' ');
14
+ }
15
+ //# sourceMappingURL=inline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline.js","sourceRoot":"","sources":["../../src/format/inline.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,OAAO,GAAG,0BAA0B,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { LintResult } from '../types.js';
2
+ /**
3
+ * The stable machine-readable contract (SPEC §6). Field order here is the order in the spec, so a
4
+ * diff of two runs reads the same way the spec does.
5
+ */
6
+ export declare function formatJson(result: LintResult): string;
7
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/format/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAmBrD"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The stable machine-readable contract (SPEC §6). Field order here is the order in the spec, so a
3
+ * diff of two runs reads the same way the spec does.
4
+ */
5
+ export function formatJson(result) {
6
+ const ordered = {
7
+ version: result.version,
8
+ files: result.files,
9
+ summary: {
10
+ warnings: result.summary.warnings,
11
+ errors: result.summary.errors,
12
+ byRule: result.summary.byRule,
13
+ },
14
+ findings: result.findings.map((finding) => ({
15
+ rule: finding.rule,
16
+ file: finding.file,
17
+ key: finding.key,
18
+ message: finding.message,
19
+ related: finding.related,
20
+ })),
21
+ errors: result.errors.map((error) => ({ file: error.file, message: error.message })),
22
+ };
23
+ return `${JSON.stringify(ordered, null, 2)}\n`;
24
+ }
25
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/format/json.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,MAAM,OAAO,GAAG;QACd,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,OAAO,EAAE;YACP,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ;YACjC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;YAC7B,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM;SAC9B;QACD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC1C,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;KACrF,CAAC;IACF,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { LintResult } from '../types.js';
2
+ export declare function formatText(result: LintResult): string;
3
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/format/text.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,UAAU,EAAU,MAAM,aAAa,CAAC;AAU/D,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CA6BrD"}
@@ -0,0 +1,65 @@
1
+ import { getRule } from '../rules/index.js';
2
+ import { RULE_IDS } from '../types.js';
3
+ import { oneLine } from './inline.js';
4
+ /**
5
+ * No finding may read as authorisation to delete an index (SPEC §2), so the summary says so
6
+ * outright rather than leaving the reader to infer it.
7
+ */
8
+ const DISCLAIMER = 'Advisory only: no finding indicates that an index is unused or safe to delete.';
9
+ export function formatText(result) {
10
+ const lines = [];
11
+ for (const [rule, findings] of groupByRule(result.findings)) {
12
+ lines.push(`${rule} ${getRule(rule).description}`);
13
+ let currentFile = '';
14
+ for (const finding of findings) {
15
+ if (finding.file !== currentFile) {
16
+ currentFile = finding.file;
17
+ lines.push(` ${oneLine(currentFile)}`);
18
+ }
19
+ lines.push(` ${oneLine(finding.message)}`);
20
+ for (const key of [finding.key, ...finding.related]) {
21
+ if (key !== null)
22
+ lines.push(` ${oneLine(key)}`);
23
+ }
24
+ }
25
+ lines.push('');
26
+ }
27
+ if (result.errors.length > 0) {
28
+ lines.push('could not be analysed');
29
+ for (const error of result.errors) {
30
+ lines.push(` ${oneLine(error.file)}: ${oneLine(error.message)}`);
31
+ }
32
+ lines.push('');
33
+ }
34
+ lines.push(summarise(result));
35
+ return `${lines.join('\n')}\n`;
36
+ }
37
+ function summarise(result) {
38
+ const { warnings, errors } = result.summary;
39
+ const scope = `${count(result.files.length, 'file')}, ${count(Object.keys(result.summary.byRule).length, 'rule')}`;
40
+ if (warnings === 0 && errors === 0)
41
+ return `No findings (${scope}).`;
42
+ const parts = [count(warnings, 'warning')];
43
+ if (errors > 0)
44
+ parts.push(count(errors, 'error'));
45
+ return `${parts.join(', ')} (${scope}). ${DISCLAIMER}`;
46
+ }
47
+ function count(n, noun) {
48
+ return `${n} ${noun}${n === 1 ? '' : 's'}`;
49
+ }
50
+ function groupByRule(findings) {
51
+ const groups = new Map();
52
+ for (const finding of findings) {
53
+ const bucket = groups.get(finding.rule);
54
+ if (bucket)
55
+ bucket.push(finding);
56
+ else
57
+ groups.set(finding.rule, [finding]);
58
+ }
59
+ // Findings arrive sorted by file first, so first-seen order is not the rule order.
60
+ return RULE_IDS.flatMap((id) => {
61
+ const bucket = groups.get(id);
62
+ return bucket ? [[id, bucket]] : [];
63
+ });
64
+ }
65
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/format/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC;;;GAGG;AACH,MAAM,UAAU,GACd,gFAAgF,CAAC;AAEnF,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QACpD,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACjC,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC1C,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC9C,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpD,IAAI,GAAG,KAAK,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9B,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,SAAS,SAAS,CAAC,MAAkB;IACnC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;IAC5C,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAEnH,IAAI,QAAQ,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,gBAAgB,KAAK,IAAI,CAAC;IAErE,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;IAC3C,IAAI,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,MAAM,UAAU,EAAE,CAAC;AACzD,CAAC;AAED,SAAS,KAAK,CAAC,CAAS,EAAE,IAAY;IACpC,OAAO,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,WAAW,CAAC,QAA4B;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC5C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;YAC5B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,mFAAmF;IACnF,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAwB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The JavaScript API.
3
+ *
4
+ * Provisional before 1.0 (SPEC §9): only the `json` output shape carries the compatibility promise.
5
+ * This exists so the rules can be run without spawning a process — from a test, a codemod, or a
6
+ * bespoke reporter.
7
+ */
8
+ export { formatGithub, type GithubOutput } from './format/github.js';
9
+ export { formatJson } from './format/json.js';
10
+ export { formatText } from './format/text.js';
11
+ export { analyse, canonicalFields, fieldDirection, implicitNameDirection, indexKey, NAME_FIELD, } from './key.js';
12
+ export { DEFAULT_QUOTA, DEFAULT_QUOTA_THRESHOLD, lintDocuments, lintFiles, lintTexts, type DocumentInput, type LintOptions, type TextInput, } from './lint.js';
13
+ export { MalformedInputError, parseDocument, validateDocument } from './parse.js';
14
+ export { getRule, isRuleId, rules } from './rules/index.js';
15
+ export { RULE_IDS } from './types.js';
16
+ export type { AnalysedIndex, CanonicalField, CompositeIndex, Finding, IndexDocument, IndexField, LintError, LintResult, LintSummary, OutputFormat, Rule, RuleContext, RuleId, RuleOptions, } from './types.js';
17
+ export { VERSION } from './version.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,OAAO,EACP,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,QAAQ,EACR,UAAU,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,aAAa,EACb,SAAS,EACT,SAAS,EACT,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,SAAS,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EACV,aAAa,EACb,cAAc,EACd,cAAc,EACd,OAAO,EACP,aAAa,EACb,UAAU,EACV,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,EACZ,IAAI,EACJ,WAAW,EACX,MAAM,EACN,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The JavaScript API.
3
+ *
4
+ * Provisional before 1.0 (SPEC §9): only the `json` output shape carries the compatibility promise.
5
+ * This exists so the rules can be run without spawning a process — from a test, a codemod, or a
6
+ * bespoke reporter.
7
+ */
8
+ export { formatGithub } from './format/github.js';
9
+ export { formatJson } from './format/json.js';
10
+ export { formatText } from './format/text.js';
11
+ export { analyse, canonicalFields, fieldDirection, implicitNameDirection, indexKey, NAME_FIELD, } from './key.js';
12
+ export { DEFAULT_QUOTA, DEFAULT_QUOTA_THRESHOLD, lintDocuments, lintFiles, lintTexts, } from './lint.js';
13
+ export { MalformedInputError, parseDocument, validateDocument } from './parse.js';
14
+ export { getRule, isRuleId, rules } from './rules/index.js';
15
+ export { RULE_IDS } from './types.js';
16
+ export { VERSION } from './version.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,OAAO,EACP,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,QAAQ,EACR,UAAU,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,aAAa,EACb,SAAS,EACT,SAAS,GAIV,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAiBtC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/dist/key.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ import type { AnalysedIndex, CanonicalField, IndexDocument, IndexField } from './types.js';
2
+ export declare const NAME_FIELD = "__name__";
3
+ /**
4
+ * The direction a field contributes to the canonical key.
5
+ *
6
+ * `parse.ts` guarantees exactly one of `order`, `arrayConfig`, and `vectorConfig` is present, so
7
+ * the fallback is unreachable for validated input.
8
+ */
9
+ export declare function fieldDirection(field: IndexField): string;
10
+ /**
11
+ * The direction Firestore would give the document key it appends to this field list.
12
+ *
13
+ * See SPEC §5, *The implicit `__name__` direction*: the last preceding `order`, or `ASCENDING` when
14
+ * no preceding field carries one. `fields` must already exclude the trailing `__name__` entry.
15
+ */
16
+ export declare function implicitNameDirection(fields: readonly IndexField[]): string;
17
+ export interface CanonicalFields {
18
+ fields: CanonicalField[];
19
+ /** Direction of the trailing `__name__` that was removed, or `null` if nothing was removed. */
20
+ redundantNameDirection: string | null;
21
+ }
22
+ /**
23
+ * Reduce a declaration's fields to canonical form, dropping a trailing `__name__` that merely
24
+ * restates what Firestore appends anyway.
25
+ *
26
+ * The drop is one-sided by design: a `__name__` whose direction differs from the implicit default
27
+ * is meaningful and is kept, so this can fail to merge two spellings of one index but can never
28
+ * merge two distinct indexes.
29
+ */
30
+ export declare function canonicalFields(fields: readonly IndexField[]): CanonicalFields;
31
+ export declare function formatField(field: CanonicalField): string;
32
+ export declare function indexKey(collectionGroup: string, queryScope: string, fields: readonly CanonicalField[]): string;
33
+ /** Precompute the canonical form once so every rule shares one interpretation of the document. */
34
+ export declare function analyse(document: IndexDocument): AnalysedIndex[];
35
+ //# sourceMappingURL=key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../src/key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAkB,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3G,eAAO,MAAM,UAAU,aAAa,CAAC;AAErC;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAQxD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAM3E;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,+FAA+F;IAC/F,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,eAAe,CAS9E;AAMD,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEzD;AAED,wBAAgB,QAAQ,CACtB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC,MAAM,CAER;AAED,kGAAkG;AAClG,wBAAgB,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa,EAAE,CAEhE"}
package/dist/key.js ADDED
@@ -0,0 +1,76 @@
1
+ export const NAME_FIELD = '__name__';
2
+ /**
3
+ * The direction a field contributes to the canonical key.
4
+ *
5
+ * `parse.ts` guarantees exactly one of `order`, `arrayConfig`, and `vectorConfig` is present, so
6
+ * the fallback is unreachable for validated input.
7
+ */
8
+ export function fieldDirection(field) {
9
+ if (typeof field.order === 'string')
10
+ return field.order;
11
+ if (typeof field.arrayConfig === 'string')
12
+ return field.arrayConfig;
13
+ if (field.vectorConfig) {
14
+ const dimension = field.vectorConfig['dimension'];
15
+ return `VECTOR(${typeof dimension === 'number' ? dimension : '?'})`;
16
+ }
17
+ return 'UNKNOWN';
18
+ }
19
+ /**
20
+ * The direction Firestore would give the document key it appends to this field list.
21
+ *
22
+ * See SPEC §5, *The implicit `__name__` direction*: the last preceding `order`, or `ASCENDING` when
23
+ * no preceding field carries one. `fields` must already exclude the trailing `__name__` entry.
24
+ */
25
+ export function implicitNameDirection(fields) {
26
+ for (let i = fields.length - 1; i >= 0; i -= 1) {
27
+ const order = fields[i]?.order;
28
+ if (typeof order === 'string')
29
+ return order;
30
+ }
31
+ return 'ASCENDING';
32
+ }
33
+ /**
34
+ * Reduce a declaration's fields to canonical form, dropping a trailing `__name__` that merely
35
+ * restates what Firestore appends anyway.
36
+ *
37
+ * The drop is one-sided by design: a `__name__` whose direction differs from the implicit default
38
+ * is meaningful and is kept, so this can fail to merge two spellings of one index but can never
39
+ * merge two distinct indexes.
40
+ */
41
+ export function canonicalFields(fields) {
42
+ const last = fields[fields.length - 1];
43
+ if (last && last.fieldPath === NAME_FIELD && typeof last.order === 'string') {
44
+ const head = fields.slice(0, -1);
45
+ if (last.order === implicitNameDirection(head)) {
46
+ return { fields: head.map(toCanonicalField), redundantNameDirection: last.order };
47
+ }
48
+ }
49
+ return { fields: fields.map(toCanonicalField), redundantNameDirection: null };
50
+ }
51
+ function toCanonicalField(field) {
52
+ return { fieldPath: field.fieldPath, direction: fieldDirection(field) };
53
+ }
54
+ export function formatField(field) {
55
+ return `${field.fieldPath}:${field.direction}`;
56
+ }
57
+ export function indexKey(collectionGroup, queryScope, fields) {
58
+ return `${collectionGroup}::${queryScope}::${fields.map(formatField).join('|')}`;
59
+ }
60
+ /** Precompute the canonical form once so every rule shares one interpretation of the document. */
61
+ export function analyse(document) {
62
+ return document.indexes.map((source, position) => analyseIndex(source, position));
63
+ }
64
+ function analyseIndex(source, position) {
65
+ const { fields, redundantNameDirection } = canonicalFields(source.fields);
66
+ return {
67
+ source,
68
+ position,
69
+ collectionGroup: source.collectionGroup,
70
+ queryScope: source.queryScope,
71
+ fields,
72
+ key: indexKey(source.collectionGroup, source.queryScope, fields),
73
+ redundantNameDirection,
74
+ };
75
+ }
76
+ //# sourceMappingURL=key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key.js","sourceRoot":"","sources":["../src/key.ts"],"names":[],"mappings":"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"}
package/dist/lint.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { IndexDocument, LintResult, RuleId, RuleOptions } from './types.js';
2
+ export declare const DEFAULT_QUOTA = 1000;
3
+ export declare const DEFAULT_QUOTA_THRESHOLD = 0.8;
4
+ export interface LintOptions extends Partial<RuleOptions> {
5
+ /** Rules to run, in any order. Defaults to all of them. */
6
+ rules?: readonly RuleId[];
7
+ }
8
+ export interface DocumentInput {
9
+ file: string;
10
+ document: IndexDocument;
11
+ }
12
+ export interface TextInput {
13
+ file: string;
14
+ text: string;
15
+ }
16
+ /** Lint documents that are already parsed. The lowest layer; does no I/O. */
17
+ export declare function lintDocuments(inputs: readonly DocumentInput[], options?: LintOptions): LintResult;
18
+ /** Lint raw file contents. A file that does not parse becomes an error, not a thrown exception. */
19
+ export declare function lintTexts(inputs: readonly TextInput[], options?: LintOptions): LintResult;
20
+ /** Lint files on disk. Unreadable files are reported the same way malformed ones are. */
21
+ export declare function lintFiles(paths: readonly string[], options?: LintOptions): LintResult;
22
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,aAAa,EAEb,UAAU,EACV,MAAM,EACN,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,MAAM,WAAW,WAAY,SAAQ,OAAO,CAAC,WAAW,CAAC;IACvD,2DAA2D;IAC3D,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAKrG;AAED,mGAAmG;AACnG,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAW7F;AAED,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAWzF"}