@ploaness/cli 1.0.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +161 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/checks/assets.d.ts +30 -0
  8. package/dist/checks/assets.d.ts.map +1 -0
  9. package/dist/checks/assets.js +169 -0
  10. package/dist/checks/assets.js.map +1 -0
  11. package/dist/checks/containers.d.ts +9 -0
  12. package/dist/checks/containers.d.ts.map +1 -0
  13. package/dist/checks/containers.js +194 -0
  14. package/dist/checks/containers.js.map +1 -0
  15. package/dist/checks/conventions.d.ts +8 -0
  16. package/dist/checks/conventions.d.ts.map +1 -0
  17. package/dist/checks/conventions.js +77 -0
  18. package/dist/checks/conventions.js.map +1 -0
  19. package/dist/checks/dependencies.d.ts +11 -0
  20. package/dist/checks/dependencies.d.ts.map +1 -0
  21. package/dist/checks/dependencies.js +389 -0
  22. package/dist/checks/dependencies.js.map +1 -0
  23. package/dist/checks/editorconfig.d.ts +5 -0
  24. package/dist/checks/editorconfig.d.ts.map +1 -0
  25. package/dist/checks/editorconfig.js +38 -0
  26. package/dist/checks/editorconfig.js.map +1 -0
  27. package/dist/checks/generated.d.ts +10 -0
  28. package/dist/checks/generated.d.ts.map +1 -0
  29. package/dist/checks/generated.js +65 -0
  30. package/dist/checks/generated.js.map +1 -0
  31. package/dist/checks/history.d.ts +19 -0
  32. package/dist/checks/history.d.ts.map +1 -0
  33. package/dist/checks/history.js +71 -0
  34. package/dist/checks/history.js.map +1 -0
  35. package/dist/checks/images.d.ts +14 -0
  36. package/dist/checks/images.d.ts.map +1 -0
  37. package/dist/checks/images.js +100 -0
  38. package/dist/checks/images.js.map +1 -0
  39. package/dist/checks/install.d.ts +5 -0
  40. package/dist/checks/install.d.ts.map +1 -0
  41. package/dist/checks/install.js +21 -0
  42. package/dist/checks/install.js.map +1 -0
  43. package/dist/checks/integrity.d.ts +11 -0
  44. package/dist/checks/integrity.d.ts.map +1 -0
  45. package/dist/checks/integrity.js +66 -0
  46. package/dist/checks/integrity.js.map +1 -0
  47. package/dist/checks/payload.d.ts +7 -0
  48. package/dist/checks/payload.d.ts.map +1 -0
  49. package/dist/checks/payload.js +81 -0
  50. package/dist/checks/payload.js.map +1 -0
  51. package/dist/checks/preflight.d.ts +5 -0
  52. package/dist/checks/preflight.d.ts.map +1 -0
  53. package/dist/checks/preflight.js +48 -0
  54. package/dist/checks/preflight.js.map +1 -0
  55. package/dist/checks/references.d.ts +18 -0
  56. package/dist/checks/references.d.ts.map +1 -0
  57. package/dist/checks/references.js +100 -0
  58. package/dist/checks/references.js.map +1 -0
  59. package/dist/checks/suppressions.d.ts +5 -0
  60. package/dist/checks/suppressions.d.ts.map +1 -0
  61. package/dist/checks/suppressions.js +50 -0
  62. package/dist/checks/suppressions.js.map +1 -0
  63. package/dist/checks/tailwind.d.ts +5 -0
  64. package/dist/checks/tailwind.d.ts.map +1 -0
  65. package/dist/checks/tailwind.js +29 -0
  66. package/dist/checks/tailwind.js.map +1 -0
  67. package/dist/checks/tests.d.ts +8 -0
  68. package/dist/checks/tests.d.ts.map +1 -0
  69. package/dist/checks/tests.js +123 -0
  70. package/dist/checks/tests.js.map +1 -0
  71. package/dist/checks/toolchain.d.ts +25 -0
  72. package/dist/checks/toolchain.d.ts.map +1 -0
  73. package/dist/checks/toolchain.js +165 -0
  74. package/dist/checks/toolchain.js.map +1 -0
  75. package/dist/checks/tree.d.ts +6 -0
  76. package/dist/checks/tree.d.ts.map +1 -0
  77. package/dist/checks/tree.js +52 -0
  78. package/dist/checks/tree.js.map +1 -0
  79. package/dist/checks/wiring.d.ts +5 -0
  80. package/dist/checks/wiring.d.ts.map +1 -0
  81. package/dist/checks/wiring.js +163 -0
  82. package/dist/checks/wiring.js.map +1 -0
  83. package/dist/commands/commit-message.d.ts +11 -0
  84. package/dist/commands/commit-message.d.ts.map +1 -0
  85. package/dist/commands/commit-message.js +57 -0
  86. package/dist/commands/commit-message.js.map +1 -0
  87. package/dist/commands/format.d.ts +4 -0
  88. package/dist/commands/format.d.ts.map +1 -0
  89. package/dist/commands/format.js +35 -0
  90. package/dist/commands/format.js.map +1 -0
  91. package/dist/commands/hooks.d.ts +9 -0
  92. package/dist/commands/hooks.d.ts.map +1 -0
  93. package/dist/commands/hooks.js +57 -0
  94. package/dist/commands/hooks.js.map +1 -0
  95. package/dist/commands/init.d.ts +4 -0
  96. package/dist/commands/init.d.ts.map +1 -0
  97. package/dist/commands/init.js +92 -0
  98. package/dist/commands/init.js.map +1 -0
  99. package/dist/commands/sync.d.ts +4 -0
  100. package/dist/commands/sync.d.ts.map +1 -0
  101. package/dist/commands/sync.js +30 -0
  102. package/dist/commands/sync.js.map +1 -0
  103. package/dist/commands/verify.d.ts +18 -0
  104. package/dist/commands/verify.d.ts.map +1 -0
  105. package/dist/commands/verify.js +96 -0
  106. package/dist/commands/verify.js.map +1 -0
  107. package/dist/context.d.ts +157 -0
  108. package/dist/context.d.ts.map +1 -0
  109. package/dist/context.js +345 -0
  110. package/dist/context.js.map +1 -0
  111. package/dist/exec.d.ts +80 -0
  112. package/dist/exec.d.ts.map +1 -0
  113. package/dist/exec.js +117 -0
  114. package/dist/exec.js.map +1 -0
  115. package/dist/gates.d.ts +61 -0
  116. package/dist/gates.d.ts.map +1 -0
  117. package/dist/gates.js +298 -0
  118. package/dist/gates.js.map +1 -0
  119. package/dist/index.d.ts +10 -0
  120. package/dist/index.d.ts.map +1 -0
  121. package/dist/index.js +12 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/report.d.ts +40 -0
  124. package/dist/report.d.ts.map +1 -0
  125. package/dist/report.js +224 -0
  126. package/dist/report.js.map +1 -0
  127. package/package.json +60 -0
@@ -0,0 +1,100 @@
1
+ // Rot gates: a reference that no longer resolves. Documentation drifts away from the repository, a tool
2
+ // config keeps carving out a file that was renamed, and a skill manifest loses the frontmatter an agent
3
+ // needs to find it. None of these break a build on their own, which is exactly why they accumulate.
4
+ import { existsSync, globSync, readFileSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { asRecord, declaredDependencies, extractLiteralSourcePaths, findAgentDocuments, findDocumentReferenceViolations, findMissingConfigReferences, findSkillManifestViolations, findUnreachedExclusionsBySetting, requiredBiomeFiles, } from '@ploaness/governance';
7
+ import { shippedDirectory, trackedFiles, } from '../context.js';
8
+ import { failed, passed } from '../exec.js';
9
+ const declaredScripts = (context) => asRecord(asRecord(context.packageJson)['scripts']);
10
+ const biomeFilesJson = (context) => JSON.stringify(requiredBiomeFiles(context.settings.sourceRoots));
11
+ // A doc is read against its OWN member and against the repository, never against one alone. A member
12
+ // doc legitimately names a command the root declares - "run `verify:unmanaged` at the root" - and the
13
+ // root doc names files that live inside a member, so resolving either against a single package.json
14
+ // reports rot where there is none. What the union still catches is the only thing this gate is for: a
15
+ // name that resolves NOWHERE.
16
+ const ownerOf = (repository, directory) => repository.members.find((candidate) => candidate.path === directory);
17
+ const namesFrom = (repository, owner, read) => new Set([...read(repository), ...(owner === undefined ? [] : read(owner))]);
18
+ const scriptNames = (context) => Object.keys(declaredScripts(context));
19
+ const dependencyNames = (context) => Object.keys(declaredDependencies(context.packageJson));
20
+ /**
21
+ * Every npm script and full-path file the agent docs name must still exist.
22
+ * @param repository the repository being judged, and the members whose docs it holds.
23
+ * @param reservedWords words that look like a script but name something else, such as a gate. The
24
+ * registry supplies them, so this module does not have to import it back and create a cycle.
25
+ */
26
+ export const documentation = (repository, reservedWords = new Set()) => {
27
+ const existsAt = (relativePath) => existsSync(path.join(repository.root, relativePath));
28
+ const documents = findAgentDocuments(repository.members.map((member) => member.path), existsAt);
29
+ const findings = documents.flatMap((document) => {
30
+ const owner = ownerOf(repository, document.directory);
31
+ return findDocumentReferenceViolations({
32
+ markdown: readFileSync(path.join(repository.root, document.file), 'utf8'),
33
+ scriptNames: namesFrom(repository, owner, scriptNames),
34
+ packageNames: namesFrom(repository, owner, dependencyNames),
35
+ // A path is tried inside the owning member first, then at the repository root, for the same
36
+ // reason the script names are unioned.
37
+ isExistingFile: (relativePath) => existsAt(path.join(document.directory, relativePath)) || existsAt(relativePath),
38
+ reservedWords,
39
+ }).map((violation) => `${document.file}: ${violation.reference} (${violation.kind}) ${violation.reason}`);
40
+ });
41
+ return findings.length > 0
42
+ ? failed(`${String(findings.length)} stale reference(s) in the agent docs`, findings)
43
+ : passed(`references in ${String(documents.length)} agent doc(s) resolve`);
44
+ };
45
+ // Both the ploaness-owned configs and any the project still holds are scanned. A carve-out in the
46
+ // harness config names a consumer path, so it rots when the consumer renames that file.
47
+ const configTargets = (context) => {
48
+ const owned = shippedDirectory('@ploaness/config');
49
+ return [
50
+ path.join(owned, 'biome.json'),
51
+ path.join(owned, 'dependency-cruiser.json'),
52
+ path.join(owned, 'knip.json'),
53
+ path.join(context.root, 'biome.json'),
54
+ path.join(context.root, 'vitest.config.mts'),
55
+ ].filter((file) => existsSync(file));
56
+ };
57
+ // A carve-out ploaness itself dictates is not the project's to repair. The wiring gate requires the
58
+ // biome file-selection block byte for byte, so a path inside it cannot be stale in the sense this gate
59
+ // means: the project is forbidden to remove it. Reporting it anyway would fail a project for obeying
60
+ // another gate, which is what a Payload project that has not generated its types yet would hit on its
61
+ // first run. Whether those generated artefacts are correct is the payload-generated gate's question.
62
+ const mandatedReferences = (context) => new Set(extractLiteralSourcePaths(biomeFilesJson(context)));
63
+ // A declared exclusion is judged against the paths the gate it narrows would otherwise have read: a
64
+ // coverage exclusion against what the coverage report measures, a typography or JavaScript exclusion
65
+ // against the tracked tree the conventions gate walks. Judging either against the other's set would
66
+ // report a finding about the wrong thing.
67
+ // Which file set an exclusion has to reach is decided by the SETTING it came from, not by how its
68
+ // pattern is written - the partition itself lives in `governance`, where it is spec'd against file lists
69
+ // no repository has to be built to produce. What is left here is the one read it needs.
70
+ const deadExclusions = (context) => findUnreachedExclusionsBySetting(context.settings.declaredExclusions, trackedFiles(context.root));
71
+ /**
72
+ * A concrete source file carved out of a tool config must still exist on disk, and a declared exclusion
73
+ * must exclude something. Both are the same defect at different scales: a carve-out that reaches
74
+ * nothing leaves the report reading exactly as it would have read without it.
75
+ */
76
+ export const configReferences = (context) => {
77
+ const isExistingFile = (relativePath) => existsSync(path.join(context.root, relativePath));
78
+ const mandated = mandatedReferences(context);
79
+ const dangling = configTargets(context).flatMap((configPath) => findMissingConfigReferences(extractLiteralSourcePaths(readFileSync(configPath, 'utf8')), isExistingFile)
80
+ .filter((violation) => !mandated.has(violation.path))
81
+ .map((violation) => `${path.basename(configPath)}: ${violation.path} (${violation.reason})`));
82
+ const findings = [...dangling, ...deadExclusions(context)];
83
+ return findings.length > 0
84
+ ? failed(`${String(findings.length)} carve-out(s) that reach nothing`, findings)
85
+ : passed('every carve-out of a tool config or setting reaches a file that exists');
86
+ };
87
+ /** The frontmatter an agent uses to discover and invoke a skill must be well formed. */
88
+ export const skills = (context) => {
89
+ const found = globSync('.claude/skills/**/SKILL.md', { cwd: context.root });
90
+ const findings = [...found]
91
+ .sort((left, right) => left.localeCompare(right))
92
+ .flatMap((relativePath) => findSkillManifestViolations({
93
+ content: readFileSync(path.join(context.root, relativePath), 'utf8'),
94
+ directoryName: path.basename(path.dirname(relativePath)),
95
+ }).map((violation) => `${relativePath} [${violation.rule}] ${violation.reason}`));
96
+ return findings.length > 0
97
+ ? failed(`${String(findings.length)} skill frontmatter violation(s)`, findings)
98
+ : passed(`${String(found.length)} skill manifest(s) are well formed`);
99
+ };
100
+ //# sourceMappingURL=references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"references.js","sourceRoot":"","sources":["../../src/checks/references.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { type Context } from '../context.js';
2
+ import { type GateResult } from '../exec.js';
3
+ /** Count suppressions and source lines across the project's own code. */
4
+ export declare const suppressions: (context: Context) => GateResult;
5
+ //# sourceMappingURL=suppressions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppressions.d.ts","sourceRoot":"","sources":["../../src/checks/suppressions.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,KAAK,OAAO,EAAgB,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAU,KAAK,UAAU,EAAU,MAAM,YAAY,CAAA;AAW5D,yEAAyE;AACzE,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,UAoD/C,CAAA"}
@@ -0,0 +1,50 @@
1
+ // The suppression ceiling gate. The decision lives in governance; this reads the tree.
2
+ //
3
+ // Its summary is what satisfies the reporting rule. `reportGate` prints a gate's summary on every run,
4
+ // pass or fail, so putting the count and the headroom there makes the trend readable before the ceiling
5
+ // is reached without adding a reporting path of its own. Findings stay empty while the project is
6
+ // within budget because the summary already carries the whole report; passing findings are reserved for
7
+ // a separate informational report such as dependency freshness.
8
+ import { existsSync, readFileSync, statSync } from 'node:fs';
9
+ import path from 'node:path';
10
+ import { countSourceLines, findSuppressions, isGovernedCode, judgeSuppressions, } from '@ploaness/governance';
11
+ import { trackedFiles } from '../context.js';
12
+ import { failed, passed } from '../exec.js';
13
+ import { managedPaths } from './assets.js';
14
+ const isUnderSourceRoots = (file, sourceRoots) => sourceRoots.some((sourceRoot) => file.startsWith(`${sourceRoot}/`));
15
+ /** Count suppressions and source lines across the project's own code. */
16
+ export const suppressions = (context) => {
17
+ const excluded = context.settings.typographyExclusions;
18
+ // A managed file is ploaness's, byte for byte, and the project can neither remove a suppression
19
+ // inside it nor be asked to justify one. Its lines are left out of the denominator for the same
20
+ // reason: a ceiling earned by code the project did not write would be an allowance, not a measure.
21
+ const managed = managedPaths(context);
22
+ // A tracked path is not always a regular file: a symlink and a submodule gitlink both appear here.
23
+ const files = trackedFiles(context.root).filter((file) => {
24
+ const full = path.join(context.root, file);
25
+ return (isUnderSourceRoots(file, context.settings.sourceRoots) &&
26
+ !managed.has(file) &&
27
+ isGovernedCode(file, excluded) &&
28
+ existsSync(full) &&
29
+ statSync(full).isFile());
30
+ });
31
+ // Read once, then derive both figures from the same contents rather than accumulating as we go.
32
+ const contents = files.map((file) => ({
33
+ file,
34
+ content: readFileSync(path.join(context.root, file), 'utf8'),
35
+ }));
36
+ const sites = contents.flatMap((scanned) => findSuppressions(scanned.file, scanned.content));
37
+ const sourceLines = contents.reduce((total, scanned) => total + countSourceLines(scanned.content), 0);
38
+ const report = judgeSuppressions(sites, sourceLines, context.settings.maxSuppressions);
39
+ const summary = `${String(report.count)} of ${String(report.ceiling)} permitted, ` +
40
+ `${String(report.remaining)} remaining (${String(report.sourceLines)} source lines)`;
41
+ if (report.withinCeiling) {
42
+ return passed(summary);
43
+ }
44
+ return failed(summary, [
45
+ `the suppression ceiling is ${String(report.ceiling)} for ` +
46
+ `${String(report.sourceLines)} source lines; remove one before adding another`,
47
+ ...report.sites.map((site) => `${site.file}:${String(site.line)} ${site.token}`),
48
+ ]);
49
+ };
50
+ //# sourceMappingURL=suppressions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppressions.js","sourceRoot":"","sources":["../../src/checks/suppressions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { type Context } from '../context.js';
2
+ import { type GateResult } from '../exec.js';
3
+ /** Verify every visual value comes from the Tailwind theme rather than an arbitrary literal. */
4
+ export declare const tailwindTokens: (context: Context) => GateResult;
5
+ //# sourceMappingURL=tailwind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind.d.ts","sourceRoot":"","sources":["../../src/checks/tailwind.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,OAAO,EAAgB,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAU,KAAK,UAAU,EAAU,MAAM,YAAY,CAAA;AAc5D,gGAAgG;AAChG,eAAO,MAAM,cAAc,GAAI,SAAS,OAAO,KAAG,UAejD,CAAA"}
@@ -0,0 +1,29 @@
1
+ // The token-bound gate. The rule is pure and lives in @ploaness/governance; this file supplies the
2
+ // I/O: which tracked files carry Tailwind classes, and what a finding reads like.
3
+ import { readFileSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { findArbitraryValues, hasExtension, matchesRole, } from '@ploaness/governance';
6
+ import { trackedFiles } from '../context.js';
7
+ import { failed, passed } from '../exec.js';
8
+ // Scoped by role rather than by a list of folders. A Tailwind class is written in JSX, so `.tsx` is
9
+ // where the rule can find one - and it is also the scope that keeps the rule from misreading a regex
10
+ // or an index expression in ordinary TypeScript, which is what a whole-of-`src` scan would do.
11
+ const MARKUP_EXTENSIONS = ['.tsx'];
12
+ // A file under a source root, so a project that declares another one is covered without saying so
13
+ // twice. `matchesRole` is reused rather than a second glob matcher.
14
+ const isScanned = (context, file) => hasExtension(file, MARKUP_EXTENSIONS) &&
15
+ context.settings.sourceRoots.some((root) => file.startsWith(`${root}/`)) &&
16
+ !matchesRole(file, context.settings.typographyExclusions);
17
+ /** Verify every visual value comes from the Tailwind theme rather than an arbitrary literal. */
18
+ export const tailwindTokens = (context) => {
19
+ const scanned = trackedFiles(context.root).filter((file) => isScanned(context, file));
20
+ const findings = scanned.flatMap((file) => {
21
+ const content = readFileSync(path.join(context.root, file), 'utf8');
22
+ return findArbitraryValues(content).map((violation) => `${file}:${String(violation.line)}:${String(violation.column)} ` +
23
+ `arbitrary Tailwind value \`${violation.value}\`; apply a theme token through a utility class`);
24
+ });
25
+ return findings.length > 0
26
+ ? failed(`${String(findings.length)} arbitrary Tailwind value(s)`, findings)
27
+ : passed(`${String(scanned.length)} markup file(s) use only token-bound Tailwind utilities`);
28
+ };
29
+ //# sourceMappingURL=tailwind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind.js","sourceRoot":"","sources":["../../src/checks/tailwind.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,kFAAkF;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAEL,mBAAmB,EACnB,YAAY,EACZ,WAAW,GACZ,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAgB,YAAY,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM,YAAY,CAAA;AAE5D,oGAAoG;AACpG,qGAAqG;AACrG,+FAA+F;AAC/F,MAAM,iBAAiB,GAAsB,CAAC,MAAM,CAAC,CAAA;AAErD,kGAAkG;AAClG,oEAAoE;AACpE,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAE,IAAY,EAAW,EAAE,CAC5D,YAAY,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;IACzF,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAA;AAE3D,gGAAgG;AAChG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAc,EAAE;IAC7D,MAAM,OAAO,GAAsB,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAC7F,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CACzB,CAAA;IACD,MAAM,QAAQ,GAAsB,OAAO,CAAC,OAAO,CAAC,CAAC,IAAY,EAAqB,EAAE;QACtF,MAAM,OAAO,GAAW,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;QAC3E,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC,GAAG,CACrC,CAAC,SAAkC,EAAU,EAAE,CAC7C,GAAG,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG;YAChE,8BAA8B,SAAS,CAAC,KAAK,iDAAiD,CACjG,CAAA;IACH,CAAC,CAAC,CAAA;IACF,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC;QACxB,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,8BAA8B,EAAE,QAAQ,CAAC;QAC5E,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,yDAAyD,CAAC,CAAA;AAChG,CAAC,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { type Context, type Member } from '../context.js';
2
+ import { type GateResult } from '../exec.js';
3
+ export declare const tests: (context: Member) => GateResult;
4
+ /** Run the Playwright end-to-end suite. */
5
+ export declare const endToEnd: (context: Context) => GateResult;
6
+ /** Produce the production build, which the bundle gate then measures. */
7
+ export declare const build: (context: Context) => GateResult;
8
+ //# sourceMappingURL=tests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tests.d.ts","sourceRoot":"","sources":["../../src/checks/tests.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,MAAM,EAGZ,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,UAAU,EAIhB,MAAM,YAAY,CAAA;AA4DnB,eAAO,MAAM,KAAK,GAAI,SAAS,MAAM,KAAG,UAoBpC,CAAA;AAEJ,2CAA2C;AAC3C,eAAO,MAAM,QAAQ,GAAI,SAAS,OAAO,KAAG,UAwC3C,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,KAAK,GAAI,SAAS,OAAO,KAAG,UAwBxC,CAAA"}
@@ -0,0 +1,123 @@
1
+ // Test execution and the production build. The runner is the project's own Vitest instance, never the
2
+ // harness copy: the project's specs import vitest directly, and a second copy would load a different
3
+ // module registry and fail to match its own matchers.
4
+ import { existsSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { CODE_EXTENSIONS, carriesSourceCode, hasExtension } from '@ploaness/governance';
7
+ import { hasOwnRuntime, resolveProjectTool, trackedFiles, } from '../context.js';
8
+ import { asFindings, failed, fromRun, passed, run, } from '../exec.js';
9
+ // A Payload suite needs a database before it can boot, and how the project obtains one is a fact ploaness
10
+ // cannot know. The project declares it; the thresholds and the gate itself stay ploaness's.
11
+ const runPretest = (context) => {
12
+ const [command, ...rest] = context.settings.pretest;
13
+ if (command === undefined) {
14
+ return undefined;
15
+ }
16
+ return run(command, rest, { cwd: context.root });
17
+ };
18
+ const wrapped = (context, interpreterArguments) => {
19
+ const [wrapper, ...wrapperRest] = context.settings.testWrapper;
20
+ if (wrapper === undefined) {
21
+ return { command: process.execPath, commandArguments: interpreterArguments };
22
+ }
23
+ return {
24
+ command: wrapper,
25
+ commandArguments: [...wrapperRest, process.execPath, ...interpreterArguments],
26
+ };
27
+ };
28
+ const withPretest = (context, gate) => {
29
+ const pretest = runPretest(context);
30
+ if (pretest !== undefined && pretest.code !== 0) {
31
+ return failed('the declared pretest command failed', asFindings(pretest.output));
32
+ }
33
+ return gate();
34
+ };
35
+ // Resolution failure is an answer, not an exception to catch three times over.
36
+ const resolveProjectToolOrUndefined = (context, tool, binary) => {
37
+ try {
38
+ return binary === undefined
39
+ ? resolveProjectTool(context, tool)
40
+ : resolveProjectTool(context, tool, binary);
41
+ }
42
+ catch {
43
+ return undefined;
44
+ }
45
+ };
46
+ /** Run the unit and integration suite with coverage, at the ploaness thresholds. */
47
+ // A member that holds no first-party source has no suite to run, and a runner started there fails on an
48
+ // empty include - reporting a package with nothing to test as one that failed to test it. A member
49
+ // holding code and no suite is untouched by this and still fails.
50
+ const hasSourceToTest = (context) => carriesSourceCode(trackedFiles(context.root), context.settings.sourceRoots, (filePath) => hasExtension(filePath, CODE_EXTENSIONS));
51
+ export const tests = (context) => withPretest(context, () => {
52
+ if (!hasSourceToTest(context)) {
53
+ return passed('no first-party source in this package, so there is no suite to run');
54
+ }
55
+ const vitest = resolveProjectToolOrUndefined(context, 'vitest');
56
+ if (vitest === undefined) {
57
+ return failed('vitest could not be resolved from the project', [
58
+ 'the project must declare vitest itself, because its specs import it directly',
59
+ ]);
60
+ }
61
+ const invocation = wrapped(context, [vitest, 'run', '--coverage']);
62
+ return fromRun(run(invocation.command, invocation.commandArguments, {
63
+ cwd: context.root,
64
+ env: { NODE_OPTIONS: '--no-deprecation' },
65
+ }), 'the suite passes and meets the per-file coverage thresholds', 'the suite failed, or a file is below a coverage threshold');
66
+ });
67
+ /** Run the Playwright end-to-end suite. */
68
+ export const endToEnd = (context) => {
69
+ // A library has no browser to drive, and `assets` already withholds the managed specs from one on the
70
+ // same test. Without this the two gates contradicted each other: the catalogue correctly gave a
71
+ // library no `playwright.config.ts`, and this gate then failed it for the file's absence.
72
+ if (!hasOwnRuntime(context)) {
73
+ return passed('this package has no runtime of its own, so there is no end-to-end suite');
74
+ }
75
+ // Not optional, and no longer treated as such. ploaness ships the accessibility sweep as a managed
76
+ // spec, so a project with no Playwright config is a project whose managed files are missing rather
77
+ // than one that opted out; reporting a pass here would hide that behind a green gate.
78
+ if (!existsSync(path.join(context.root, 'playwright.config.ts'))) {
79
+ return failed('playwright.config.ts is missing, so the end-to-end suite cannot run', [
80
+ 'run `ploaness init` to write it, then `ploaness sync` to materialise the managed specs',
81
+ ]);
82
+ }
83
+ return withPretest(context, () => {
84
+ const playwright = resolveProjectToolOrUndefined(context, '@playwright/test', 'playwright');
85
+ if (playwright === undefined) {
86
+ return failed('@playwright/test could not be resolved from the project', [
87
+ 'the project must declare @playwright/test itself, because its specs import it directly',
88
+ ]);
89
+ }
90
+ const invocation = wrapped(context, [
91
+ playwright,
92
+ 'test',
93
+ '--config=playwright.config.ts',
94
+ ]);
95
+ return fromRun(run(invocation.command, invocation.commandArguments, {
96
+ cwd: context.root,
97
+ env: { NODE_OPTIONS: '--no-deprecation --import=tsx/esm' },
98
+ }), 'the end-to-end suite passes', 'the end-to-end suite failed');
99
+ });
100
+ };
101
+ /** Produce the production build, which the bundle gate then measures. */
102
+ export const build = (context) => {
103
+ if (!hasOwnRuntime(context)) {
104
+ return passed('this package has no runtime of its own, so there is nothing to build');
105
+ }
106
+ return withPretest(context, () => {
107
+ const next = resolveProjectToolOrUndefined(context, 'next');
108
+ if (next === undefined) {
109
+ return failed('next could not be resolved from the project', [
110
+ 'a Payload application builds through Next, which the project must declare',
111
+ ]);
112
+ }
113
+ const invocation = wrapped(context, [next, 'build']);
114
+ return fromRun(run(invocation.command, invocation.commandArguments, {
115
+ cwd: context.root,
116
+ env: {
117
+ NEXT_TELEMETRY_DISABLED: '1',
118
+ NODE_OPTIONS: '--no-deprecation --max-old-space-size=8000',
119
+ },
120
+ }), 'the production build succeeds', 'the production build failed');
121
+ });
122
+ };
123
+ //# sourceMappingURL=tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tests.js","sourceRoot":"","sources":["../../src/checks/tests.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type Context, type Member } from '../context.js';
2
+ import { type GateResult, type RunResult } from '../exec.js';
3
+ /** Strict type checking against the project's tsconfig, which extends the ploaness one. */
4
+ export declare const types: (context: Context) => GateResult;
5
+ /** Formatting and fast lint. */
6
+ export declare const biome: (context: Context) => GateResult;
7
+ /** Apply Biome's own fixes, used by `ploaness format`. */
8
+ export declare const biomeWrite: (context: Context) => RunResult;
9
+ export declare const biomeSchema: () => GateResult;
10
+ /** The type-aware lint layer, which catches what a syntax-only linter cannot. */
11
+ export declare const eslint: (context: Member) => GateResult;
12
+ /**
13
+ * The same run with its fixable rules applied, used by `ploaness format`.
14
+ *
15
+ * It lives beside the gate that judges what it writes, for the reason `biomeWrite` does: the two must
16
+ * not disagree about which files the tool touches, and the arguments they share say so in one place.
17
+ */
18
+ export declare const eslintWrite: (context: Member) => RunResult;
19
+ /** Style sheets. Biome remains the formatter, so Stylelint is lint-only. */
20
+ export declare const css: (context: Context) => GateResult;
21
+ export declare const architecture: (context: Member) => GateResult;
22
+ /** Full type coverage on hand-written logic: no implicit or explicit `any` survives. */
23
+ export declare const typeCoverage: (context: Context) => GateResult;
24
+ export declare const knip: (context: Member) => GateResult;
25
+ //# sourceMappingURL=toolchain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolchain.d.ts","sourceRoot":"","sources":["../../src/checks/toolchain.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAA2C,MAAM,eAAe,CAAA;AAClG,OAAO,EAAmB,KAAK,UAAU,EAAU,KAAK,SAAS,EAAW,MAAM,YAAY,CAAA;AAI9F,2FAA2F;AAC3F,eAAO,MAAM,KAAK,GAAI,SAAS,OAAO,KAAG,UAOtC,CAAA;AAIH,gCAAgC;AAChC,eAAO,MAAM,KAAK,GAAI,SAAS,OAAO,KAAG,UAOtC,CAAA;AAEH,0DAA0D;AAC1D,eAAO,MAAM,UAAU,GAAI,SAAS,OAAO,KAAG,SACqD,CAAA;AASnG,eAAO,MAAM,WAAW,QAAO,UAiB9B,CAAA;AAOD,iFAAiF;AACjF,eAAO,MAAM,MAAM,GAAI,SAAS,MAAM,KAAG,UAOtC,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,SAG1C,CAAA;AAQJ,4EAA4E;AAC5E,eAAO,MAAM,GAAG,GAAI,SAAS,OAAO,KAAG,UAepC,CAAA;AAoCH,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,UAe9C,CAAA;AA+BD,wFAAwF;AACxF,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,UAmB7C,CAAA;AAqBH,eAAO,MAAM,IAAI,GAAI,SAAS,MAAM,KAAG,UAUpC,CAAA"}
@@ -0,0 +1,165 @@
1
+ // The gates that delegate to a tool. Each one points the tool at the ploaness-owned configuration rather
2
+ // than trusting a file in the consumer tree, because most of those files are FORBIDDEN paths: the
3
+ // project has no copy to drift from.
4
+ import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import path from 'node:path';
7
+ import { analysisBoundaries, asRecord, eslintArguments, memberKindOf, pureLogicRule, } from '@ploaness/governance';
8
+ import { readJson, resolveTool, shippedDirectory } from '../context.js';
9
+ import { failed, fromRun, passed, runNode } from '../exec.js';
10
+ const configFile = (name) => path.join(shippedDirectory('@ploaness/config'), name);
11
+ /** Strict type checking against the project's tsconfig, which extends the ploaness one. */
12
+ export const types = (context) => fromRun(runNode(resolveTool('typescript', 'tsc'), ['--noEmit', '-p', 'tsconfig.json'], {
13
+ cwd: context.root,
14
+ }), 'the project type-checks under the ploaness compiler options', 'the project does not type-check under the ploaness compiler options');
15
+ // `--error-on-warnings` for the reason the Stylelint invocation below carries `--max-warnings=0`: a
16
+ // check has two verdicts, and a diagnostic Biome ranks as a warning would otherwise print and exit 0.
17
+ /** Formatting and fast lint. */
18
+ export const biome = (context) => fromRun(runNode(resolveTool('@biomejs/biome', 'biome'), ['check', '--error-on-warnings', '.'], {
19
+ cwd: context.root,
20
+ }), 'Biome reports no formatting or lint defect', 'Biome reports a formatting or lint defect');
21
+ /** Apply Biome's own fixes, used by `ploaness format`. */
22
+ export const biomeWrite = (context) => runNode(resolveTool('@biomejs/biome', 'biome'), ['check', '--write', '.'], { cwd: context.root });
23
+ const versionOf = (manifest) => typeof manifest['version'] === 'string' ? manifest['version'] : '';
24
+ const SCHEMA_PATTERN = /biomejs\.dev\/schemas\/(\d+\.\d+\.\d+)\/schema\.json/;
25
+ // Biome reports a schema mismatch only as a non-failing information notice, so without this gate a Biome
26
+ // bump would leave the pinned `$schema` stale and silently out of date.
27
+ export const biomeSchema = () => {
28
+ const manifest = readJson(path.join(shippedDirectory('@biomejs/biome'), 'package.json'));
29
+ const installed = versionOf(asRecord(manifest));
30
+ const declared = SCHEMA_PATTERN.exec(readFileSync(configFile('biome.json'), 'utf8'))?.[1];
31
+ if (declared === undefined) {
32
+ return failed('the ploaness Biome config declares no $schema URL', [
33
+ 'this is a ploaness packaging defect; report it',
34
+ ]);
35
+ }
36
+ return declared === installed
37
+ ? passed(`Biome $schema matches the installed CLI (${installed})`)
38
+ : failed('the ploaness Biome $schema drifted from the installed CLI', [
39
+ `$schema is ${declared} but @biomejs/biome is ${installed}`,
40
+ 'this is a ploaness packaging defect; report it',
41
+ ]);
42
+ };
43
+ // Which files the run covers is `eslintArguments`' to decide, so that the writing run below cannot
44
+ // answer it differently. What stays here is why this run renders a verdict at all: `--max-warnings=0` is
45
+ // the backstop behind the severity escalation in `eslint-core.js`. That escalation raises what the
46
+ // current presets declare; the flag fails the build on a warning whatever its source, including a preset
47
+ // added later whose severities nobody re-read.
48
+ /** The type-aware lint layer, which catches what a syntax-only linter cannot. */
49
+ export const eslint = (context) => fromRun(runNode(resolveTool('eslint'), [...eslintArguments(context.siblingPaths, 'report')], {
50
+ cwd: context.root,
51
+ }), 'ESLint reports no defect', 'ESLint reports at least one defect');
52
+ /**
53
+ * The same run with its fixable rules applied, used by `ploaness format`.
54
+ *
55
+ * It lives beside the gate that judges what it writes, for the reason `biomeWrite` does: the two must
56
+ * not disagree about which files the tool touches, and the arguments they share say so in one place.
57
+ */
58
+ export const eslintWrite = (context) => runNode(resolveTool('eslint'), [...eslintArguments(context.siblingPaths, 'fix')], {
59
+ cwd: context.root,
60
+ });
61
+ // `--max-warnings=0` because the shipped config asks Stylelint to report a descriptionless, needless, or
62
+ // wrongly scoped disable, and Stylelint reports those at warning severity. A warning severity does not
63
+ // exist here: any finding is a failure.
64
+ const stylesheetGlobs = (context) => context.settings.sourceRoots.map((sourceRoot) => `${sourceRoot}/**/*.css`);
65
+ /** Style sheets. Biome remains the formatter, so Stylelint is lint-only. */
66
+ export const css = (context) => fromRun(runNode(resolveTool('stylelint'), [
67
+ '--config',
68
+ configFile('stylelint.json'),
69
+ ...stylesheetGlobs(context),
70
+ '--allow-empty-input',
71
+ '--max-warnings=0',
72
+ ], { cwd: context.root }), 'style sheets pass Stylelint', 'a style sheet does not pass Stylelint');
73
+ /** Module architecture: layer boundaries, cycles, and import hygiene. */
74
+ // The layer map is rendered rather than shipped, because the pure-logic floor is the one genuinely
75
+ // project-shaped part of the contract and a governed project may not own the analyzer's config file to
76
+ // state it. Written outside the working tree, so a verification run still writes nothing a fingerprint
77
+ // would notice, and pointed at the shipped rules by absolute path so the relative extends still resolves.
78
+ // Matches the indent every JSON file ploaness writes.
79
+ const JSON_INDENT = 2;
80
+ const architectureConfig = (context, kind) => {
81
+ const shipped = configFile(kind === 'library' ? 'dependency-cruiser-core.json' : 'dependency-cruiser.json');
82
+ const floor = pureLogicRule(context.settings.pureLogicRoots);
83
+ if (floor === undefined) {
84
+ return shipped;
85
+ }
86
+ const directory = mkdtempSync(path.join(tmpdir(), 'ploaness-arch-'));
87
+ const rendered = path.join(directory, 'dependency-cruiser.json');
88
+ writeFileSync(rendered, `${JSON.stringify({ extends: shipped, forbidden: [floor] }, null, JSON_INDENT)}\n`);
89
+ return rendered;
90
+ };
91
+ // Every declared source root, not `src` alone: the acyclic-dependency rule is about the package's
92
+ // dependency units, and a cycle through `scripts` or `tests` is still a cycle. Only the roots that
93
+ // EXIST, though - the analyzer refuses a directory it cannot open, so a package holding shared scripts
94
+ // and no `src` failed on the shape of its own layout rather than on anything about its architecture.
95
+ const presentSourceRoots = (context) => context.settings.sourceRoots.filter((root) => existsSync(path.join(context.root, root)));
96
+ export const architecture = (context) => {
97
+ const roots = presentSourceRoots(context);
98
+ if (roots.length === 0) {
99
+ return passed('this package holds no source directory to cruise');
100
+ }
101
+ const kind = memberKindOf(context.packageJson);
102
+ return fromRun(runNode(resolveTool('dependency-cruiser', 'depcruise'), [...roots, '--config', architectureConfig(context, kind)], { cwd: context.root }), 'module architecture holds', 'the module architecture contract is broken');
103
+ };
104
+ // Roles with no hand-written type surface to measure: framework glue, generated files, views, and
105
+ // configuration. Measuring them would dilute the score rather than sharpen it.
106
+ //
107
+ // This list must never become empty, for a reason beyond what it names. type-coverage also reads a
108
+ // `typeCoverage` key from the consumer's package.json, which ploaness does not police - and does not
109
+ // need to, because a CLI `--ignore-files` REPLACES that key's `ignoreFiles` rather than merging with
110
+ // it. Measured: with these flags the score is identical whether or not a consumer declares
111
+ // `typeCoverage.ignoreFiles: ["src/**", "scripts/**"]`. Passing no `--ignore-files` would hand the
112
+ // consumer that key back, which is a scope narrowing the harness is supposed to refuse.
113
+ const TYPE_COVERAGE_IGNORE = [
114
+ '.next/**',
115
+ 'src/app/**',
116
+ 'src/**/*.tsx',
117
+ 'src/payload.config.ts',
118
+ 'src/payload-types.ts',
119
+ 'src/seed/**',
120
+ // Test code is held to tsc at full strictness, to every ESLint rule, and to the coverage floors -
121
+ // the static-analysis checks the standard means. Type coverage is a different measurement: with
122
+ // `--strict` it counts every type assertion as uncovered, and a test's job is to construct inputs
123
+ // the production types cannot express. A Payload access predicate takes a full PayloadRequest, so
124
+ // asserting a partial one is the correct way to test the predicate, and the only way to reach 100%
125
+ // here would be to build framework objects nobody reads. This is a file role, not a convenience.
126
+ 'tests/**',
127
+ '*.config.ts',
128
+ '*.config.mts',
129
+ '*.config.mjs',
130
+ 'vitest.setup.ts',
131
+ ];
132
+ /** Full type coverage on hand-written logic: no implicit or explicit `any` survives. */
133
+ export const typeCoverage = (context) => fromRun(runNode(resolveTool('type-coverage'), [
134
+ '--at-least',
135
+ '100',
136
+ '--strict',
137
+ '--cache',
138
+ 'false',
139
+ ...TYPE_COVERAGE_IGNORE.flatMap((pattern) => [
140
+ '--ignore-files',
141
+ pattern,
142
+ ]),
143
+ ], { cwd: context.root }), 'hand-written logic is fully typed', 'some hand-written logic is not fully typed');
144
+ /** Dead code, unused files, and unused dependencies. */
145
+ // The member boundary `analysisBoundaries` states, reaching knip. It takes the patterns as configuration
146
+ // rather than as a flag, and its own config is a shipped file a project may not edit - so the boundary
147
+ // has to be rendered into a copy for the run rather than passed on the command line as ESLint's is.
148
+ const knipConfig = (context) => {
149
+ const boundaries = analysisBoundaries(context.siblingPaths);
150
+ if (boundaries.length === 0) {
151
+ return configFile('knip.json');
152
+ }
153
+ const shipped = asRecord(readJson(configFile('knip.json')));
154
+ const directory = mkdtempSync(path.join(tmpdir(), 'ploaness-knip-'));
155
+ const rendered = path.join(directory, 'knip.json');
156
+ writeFileSync(rendered, `${JSON.stringify({ ...shipped, ignore: boundaries }, null, JSON_INDENT)}\n`);
157
+ return rendered;
158
+ };
159
+ export const knip = (context) => fromRun(runNode(resolveTool('knip'), ['--config', knipConfig(context)], {
160
+ cwd: context.root,
161
+ // knip traces the graph by importing the project, and a Payload config validates `process.env`
162
+ // at module scope. Without placeholders the analysis dies on configuration rather than on code.
163
+ env: context.settings.analysisEnv,
164
+ }), 'no dead code or unused dependency', 'dead code or an unused dependency was found');
165
+ //# sourceMappingURL=toolchain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolchain.js","sourceRoot":"","sources":["../../src/checks/toolchain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type Context } from '../context.js';
2
+ import { type GateResult } from '../exec.js';
3
+ export declare const treeSnapshot: (context: Context) => GateResult;
4
+ /** Verify no gate modified a tracked file during verification. */
5
+ export declare const treeVerify: (context: Context) => GateResult;
6
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/checks/tree.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,OAAO,EAAgB,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAU,KAAK,UAAU,EAAU,MAAM,YAAY,CAAA;AA0B5D,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,UAK/C,CAAA;AAED,kEAAkE;AAClE,eAAO,MAAM,UAAU,GAAI,SAAS,OAAO,KAAG,UAY7C,CAAA"}