@alveolus/arch 0.2.0 → 0.4.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 (60) hide show
  1. package/README.md +11 -0
  2. package/dist/bin.mjs +4 -2
  3. package/dist/bin.mjs.map +1 -1
  4. package/dist/{cli-P5PwH9OE.mjs → docs-DcFgskuN.mjs} +214 -43
  5. package/dist/docs-DcFgskuN.mjs.map +1 -0
  6. package/dist/index.d.mts +53 -12
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +2 -2
  9. package/docs/core/application/command-handlers.md +617 -0
  10. package/docs/core/application/event-publishers.md +234 -0
  11. package/docs/core/application/event-translators.md +329 -0
  12. package/docs/core/application/index.md +99 -0
  13. package/docs/core/application/integration-events.md +277 -0
  14. package/docs/core/application/outbox.md +416 -0
  15. package/docs/core/application/query-handlers.md +292 -0
  16. package/docs/core/application/unit-of-work.md +352 -0
  17. package/docs/core/domain/aggregates.md +822 -0
  18. package/docs/core/domain/domain-errors.md +251 -0
  19. package/docs/core/domain/domain-events.md +292 -0
  20. package/docs/core/domain/domain-services.md +249 -0
  21. package/docs/core/domain/entities.md +431 -0
  22. package/docs/core/domain/index.md +93 -0
  23. package/docs/core/domain/ports.md +284 -0
  24. package/docs/core/domain/repositories.md +335 -0
  25. package/docs/core/domain/value-objects.md +425 -0
  26. package/docs/core/domain/views.md +265 -0
  27. package/docs/core/index.md +108 -0
  28. package/docs/core/strategic/anti-corruption-layers.md +349 -0
  29. package/docs/core/strategic/index.md +83 -0
  30. package/docs/core/strategic/open-host-services.md +287 -0
  31. package/docs/core/strategic/published-language.md +265 -0
  32. package/docs/core/utilities/result.md +413 -0
  33. package/docs/guide/agents.md +68 -0
  34. package/docs/guide/existing-project.md +108 -0
  35. package/docs/guide/getting-started.md +286 -0
  36. package/docs/guide/learning-path.md +123 -0
  37. package/docs/guide/project-layout.md +324 -0
  38. package/docs/guide/versioning.md +42 -0
  39. package/docs/integrations/index.md +112 -0
  40. package/docs/integrations/nestjs.md +169 -0
  41. package/docs/rules/index.md +185 -0
  42. package/docs/rules/layers/no-driving-shortcut.md +119 -0
  43. package/docs/rules/layers/no-impure-domain.md +191 -0
  44. package/docs/rules/layers/no-outward-import.md +186 -0
  45. package/docs/rules/layers/no-portless-adapter.md +123 -0
  46. package/docs/rules/strategic/no-cross-context-import.md +140 -0
  47. package/docs/rules/strategic/no-fat-shared-kernel.md +81 -0
  48. package/docs/rules/strategic/no-leaky-host-service.md +107 -0
  49. package/docs/rules/strategic/no-unmapped-context.md +114 -0
  50. package/docs/rules/tactical/no-aggregate-reference.md +139 -0
  51. package/docs/rules/tactical/no-foreign-command-dependency.md +119 -0
  52. package/docs/rules/tactical/no-foreign-query-dependency.md +201 -0
  53. package/docs/rules/tactical/no-loose-code.md +171 -0
  54. package/docs/rules/tactical/no-misplaced-class.md +146 -0
  55. package/docs/rules/tactical/no-public-field.md +113 -0
  56. package/docs/rules/tactical/no-stateful-service.md +102 -0
  57. package/docs/rules/tactical/no-thrown-failure.md +162 -0
  58. package/docs/rules/tooling/no-loose-disable.md +98 -0
  59. package/package.json +4 -3
  60. package/dist/cli-P5PwH9OE.mjs.map +0 -1
package/README.md CHANGED
@@ -14,6 +14,11 @@ import { defineConfig } from "@alveolus/arch";
14
14
 
15
15
  export default defineConfig({
16
16
  boundedContexts: { catalog: "catalog", notifications: "notifications", ordering: "ordering" },
17
+ contextMap: {
18
+ catalog: { consumes: [] },
19
+ notifications: { consumes: ["ordering"] },
20
+ ordering: { consumes: ["catalog"] },
21
+ },
17
22
  root: "src",
18
23
  subdomains: { core: ["catalog", "ordering"], generic: ["notifications"] },
19
24
  });
@@ -35,8 +40,14 @@ Seventeen rules, each with a page that says what it reports, why, how to fix it
35
40
  cannot see. A baseline for existing projects, `error` / `warn` / `info` levels, disable comments
36
41
  with a reason, JSON and SARIF output for the pull request.
37
42
 
43
+ The documentation is installed with the package, for you and for a coding agent:
44
+ `npx alveolus explain layers/no-impure-domain` prints a rule, `npx alveolus explain aggregates` a
45
+ building block, and `npx alveolus init` writes the configuration and the instructions that tell
46
+ Claude Code, Cursor or Codex to read them there.
47
+
38
48
  - [Rules](https://alveolus.dev/rules/)
39
49
  - [Getting started](https://alveolus.dev/guide/getting-started)
40
50
  - [Adopt it on an existing project](https://alveolus.dev/guide/existing-project)
51
+ - [Coding agents](https://alveolus.dev/guide/agents)
41
52
 
42
53
  Node.js 24 or later. MIT.
package/dist/bin.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { t as Cli } from "./cli-P5PwH9OE.mjs";
2
+ import { r as Cli, t as Docs } from "./docs-DcFgskuN.mjs";
3
+ import { fileURLToPath } from "node:url";
3
4
  //#region src/bin.ts
4
- const cli = new Cli(process.stdout, process.stderr, process.cwd(), process.stdout.isTTY);
5
+ const docs = new Docs(fileURLToPath(new URL("../docs/", import.meta.url)));
6
+ const cli = new Cli(process.stdout, process.stderr, process.cwd(), docs, process.stdout.isTTY);
5
7
  process.exitCode = await cli.run(process.argv.slice(2));
6
8
  //#endregion
7
9
  export {};
package/dist/bin.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Cli } from \"./cli/index.ts\";\n\nconst cli = new Cli(process.stdout, process.stderr, process.cwd(), process.stdout.isTTY);\nprocess.exitCode = await cli.run(process.argv.slice(2));\n"],"mappings":";;;AAGA,MAAM,MAAM,IAAI,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,IAAI,GAAG,QAAQ,OAAO,KAAK;AACvF,QAAQ,WAAW,MAAM,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { fileURLToPath } from \"node:url\";\n\nimport { Cli } from \"./cli/index.ts\";\nimport { Docs } from \"./docs/index.ts\";\n\nconst docs = new Docs(fileURLToPath(new URL(\"../docs/\", import.meta.url)));\nconst cli = new Cli(process.stdout, process.stderr, process.cwd(), docs, process.stdout.isTTY);\nprocess.exitCode = await cli.run(process.argv.slice(2));\n"],"mappings":";;;;AAMA,MAAM,OAAO,IAAI,KAAK,cAAc,IAAI,IAAI,YAAY,YAAY,GAAG,CAAC,CAAC;AACzE,MAAM,MAAM,IAAI,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,IAAI,GAAG,MAAM,QAAQ,OAAO,KAAK;AAC7F,QAAQ,WAAW,MAAM,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Command, CommanderError, Option } from "commander";
2
- import { existsSync } from "node:fs";
2
+ import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, isAbsolute, join, matchesGlob, normalize, relative, resolve, sep } from "node:path";
4
4
  import { readFile, writeFile } from "node:fs/promises";
5
5
  import { createHash } from "node:crypto";
@@ -839,24 +839,22 @@ var Architecture = class {
839
839
  };
840
840
  //#endregion
841
841
  //#region src/architecture/context-map.ts
842
- var ContextMap = class ContextMap {
842
+ var ContextMap = class {
843
843
  upstreams;
844
844
  constructor(upstreams) {
845
845
  this.upstreams = upstreams;
846
846
  }
847
- static ofEdges(edges) {
848
- const upstreams = {};
849
- for (const { from, to } of edges) {
850
- const known = upstreams[from] ?? [];
851
- if (!known.includes(to)) upstreams[from] = [...known, to];
852
- }
853
- return new ContextMap(upstreams);
854
- }
855
847
  get contexts() {
856
- const names = new Set(Object.keys(this.upstreams));
848
+ return Object.keys(this.upstreams);
849
+ }
850
+ get consumed() {
851
+ const names = /* @__PURE__ */ new Set();
857
852
  for (const targets of Object.values(this.upstreams)) for (const target of targets) names.add(target);
858
853
  return [...names];
859
854
  }
855
+ selfConsumers() {
856
+ return this.contexts.filter((context) => this.allows(context, context));
857
+ }
860
858
  allows(from, to) {
861
859
  return (this.upstreams[from] ?? []).includes(to);
862
860
  }
@@ -868,9 +866,6 @@ var ContextMap = class ContextMap {
868
866
  if (found !== void 0) return found;
869
867
  }
870
868
  }
871
- cycleThrough(from, to) {
872
- return this.reaches(to, from, /* @__PURE__ */ new Set());
873
- }
874
869
  cycleFrom(context, path, visiting, done) {
875
870
  if (done.has(context)) return;
876
871
  if (visiting.has(context)) return [...path.slice(path.indexOf(context)), context];
@@ -882,12 +877,6 @@ var ContextMap = class ContextMap {
882
877
  visiting.delete(context);
883
878
  done.add(context);
884
879
  }
885
- reaches(from, target, seen) {
886
- if (from === target) return true;
887
- if (seen.has(from)) return false;
888
- seen.add(from);
889
- return (this.upstreams[from] ?? []).some((upstream) => this.reaches(upstream, target, seen));
890
- }
891
880
  };
892
881
  //#endregion
893
882
  //#region src/rules/framework/wording.ts
@@ -1374,23 +1363,13 @@ var NoLeakyHostServiceRule = class extends ClassRule {
1374
1363
  var NoUnmappedContextRule = class extends Rule {
1375
1364
  meta = {
1376
1365
  contexts: "every",
1377
- description: "A bounded context consuming one the context map does not allow, or two contexts that depend on each other.",
1366
+ description: "A bounded context consuming one the context map does not allow.",
1378
1367
  id: "strategic/no-unmapped-context",
1379
- messages: {
1380
- cycle: "{from} consumes {to}, which consumes {from} back: two contexts that depend on each other can no longer change alone; declare a contextMap and reverse one dependency.",
1381
- unmapped: "{from} consumes {to}, which the context map does not allow: add {to} to contextMap.{from}, or reverse the dependency."
1382
- }
1368
+ messages: { unmapped: "{from} consumes {to}, which the context map does not allow: reverse the dependency, or if {from} really is downstream of {to}, add {to} to contextMap.{from}.consumes." }
1383
1369
  };
1384
1370
  check(architecture) {
1385
- const consumptions = this.consumptionsIn(architecture);
1386
- const declared = architecture.contextMap;
1387
1371
  const findings = [];
1388
- if (declared !== void 0) {
1389
- for (const consumption of consumptions) if (!declared.allows(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption, "unmapped"));
1390
- return findings;
1391
- }
1392
- const observed = ContextMap.ofEdges(consumptions);
1393
- for (const consumption of consumptions) if (observed.cycleThrough(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption, "cycle"));
1372
+ for (const consumption of this.consumptionsIn(architecture)) if (!architecture.contextMap.allows(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption));
1394
1373
  return findings;
1395
1374
  }
1396
1375
  consumptionsIn(architecture) {
@@ -1411,9 +1390,9 @@ var NoUnmappedContextRule = class extends Rule {
1411
1390
  }
1412
1391
  return consumptions;
1413
1392
  }
1414
- consumptionFinding(consumption, messageId) {
1393
+ consumptionFinding(consumption) {
1415
1394
  const { dependency, file, from, to } = consumption;
1416
- return this.finding(file, dependency.line, dependency.label, messageId, {
1395
+ return this.finding(file, dependency.line, dependency.label, "unmapped", {
1417
1396
  from,
1418
1397
  to
1419
1398
  });
@@ -1997,7 +1976,9 @@ var Report = class {
1997
1976
  text() {
1998
1977
  const blocks = [];
1999
1978
  for (const [file, violations] of this.byFile()) blocks.push(this.block(file, violations));
2000
- return `${[...blocks, this.summary()].join("\n\n")}\n`;
1979
+ const parts = [...blocks, this.summary()];
1980
+ if (blocks.length > 0) parts.push(this.colors.dim("Why, and how to fix it: npx alveolus explain <rule>"));
1981
+ return `${parts.join("\n\n")}\n`;
2001
1982
  }
2002
1983
  json() {
2003
1984
  const { baselined, files, stale, suppressed, violations } = this.input;
@@ -2116,7 +2097,7 @@ var Config = class {
2116
2097
  this.applicationDependencies = this.domainDependencies.with(new AllowedPackages(config.applicationDependencies));
2117
2098
  this.ignored = [...testFiles, ...config.ignore ?? []];
2118
2099
  this.extraFolders = config.layout?.extraFolders ?? {};
2119
- this.contextMap = config.contextMap === void 0 ? void 0 : this.validContextMap(config.contextMap, Object.keys(config.boundedContexts));
2100
+ this.contextMap = this.validContextMap(config.contextMap, Object.keys(config.boundedContexts));
2120
2101
  this.rules = config.rules;
2121
2102
  const sharedKernel = config.sharedKernel ?? "shared-kernel";
2122
2103
  this.contextFolders = [...this.classifiedContexts(config.subdomains ?? {}, config.boundedContexts), {
@@ -2150,10 +2131,16 @@ var Config = class {
2150
2131
  if (unclassified.length > 0) throw new Error(`boundedContexts declares ${unclassified.join(", ")}, which subdomains does not classify: list each context under subdomains.core, subdomains.supporting or subdomains.generic.`);
2151
2132
  return folders;
2152
2133
  }
2153
- validContextMap(upstreams, contexts) {
2134
+ validContextMap(relations, contexts) {
2135
+ const upstreams = {};
2136
+ for (const [name, { consumes }] of Object.entries(relations)) upstreams[name] = consumes;
2154
2137
  const map = new ContextMap(upstreams);
2155
- const unknown = map.contexts.filter((name) => !contexts.includes(name));
2138
+ const unknown = [.../* @__PURE__ */ new Set([...map.contexts, ...map.consumed])].filter((name) => !contexts.includes(name));
2156
2139
  if (unknown.length > 0) throw new Error(`contextMap names ${unknown.join(", ")}, which boundedContexts does not declare.`);
2140
+ const unlisted = contexts.filter((name) => !map.contexts.includes(name));
2141
+ if (unlisted.length > 0) throw new Error(`contextMap does not list ${unlisted.join(", ")}: every bounded context lists the contexts it consumes, consumes: [] when none.`);
2142
+ const selfConsumers = map.selfConsumers();
2143
+ if (selfConsumers.length > 0) throw new Error(`contextMap lists ${selfConsumers.join(", ")} as consuming itself: a context consumes other contexts only.`);
2157
2144
  const cycle = map.cycle();
2158
2145
  if (cycle !== void 0) throw new Error(`contextMap has a cycle: ${cycle.join(" → ")}. Two contexts that depend on each other can no longer change alone: reverse one dependency.`);
2159
2146
  return map;
@@ -2180,7 +2167,7 @@ const schema = z.strictObject({
2180
2167
  applicationDependencies: packageDependencies.exactOptional(),
2181
2168
  boundedContexts: z.record(z.string(), z.string()),
2182
2169
  compositionRoot: z.string().exactOptional(),
2183
- contextMap: z.record(z.string(), z.array(z.string())).exactOptional(),
2170
+ contextMap: z.record(z.string(), z.strictObject({ consumes: z.array(z.string()) })),
2184
2171
  domainDependencies: packageDependencies.exactOptional(),
2185
2172
  ignore: z.array(z.string()).exactOptional(),
2186
2173
  layout: z.strictObject({ extraFolders: z.strictObject({
@@ -2773,18 +2760,92 @@ var TsMorphImporter = class TsMorphImporter extends Importer {
2773
2760
  return offset !== "" && !offset.startsWith("..") && !isAbsolute(offset) && !offset.split(/[\\/]/).includes("node_modules");
2774
2761
  }
2775
2762
  };
2763
+ const scaffolds = [
2764
+ {
2765
+ content: `import { defineConfig } from "@alveolus/arch";
2766
+
2767
+ export default defineConfig({
2768
+ boundedContexts: {},
2769
+ contextMap: {},
2770
+ root: "src",
2771
+ });
2772
+ `,
2773
+ path: "alveolus.config.ts"
2774
+ },
2775
+ {
2776
+ content: `---
2777
+ name: alveolus
2778
+ description: Domain-Driven Design with @alveolus/core and @alveolus/arch. Use when writing or changing a class of the domain, the application or an adapter, when a class extends an Alveolus building block, or when alveolus arch check reports a violation.
2779
+ ---
2780
+
2781
+ # Alveolus
2782
+
2783
+ The documentation is installed with the package: read it with \`npx alveolus explain <topic>\`, not on the web. \`npx alveolus explain\` lists every topic.
2784
+
2785
+ 1. Before writing a class, read its building block: \`npx alveolus explain aggregates\`, \`entities\`, \`value-objects\`, \`domain-events\`, \`domain-errors\`, \`ports\`, \`repositories\`, \`command-handlers\`, \`query-handlers\`, \`result\`.
2786
+ 2. Where a file goes and what each layer may import: \`npx alveolus explain project-layout\`.
2787
+ 3. After each change, run \`npx alveolus arch check\`.
2788
+ 4. On a violation, read the rule before changing the code: \`npx alveolus explain <rule>\`, with the rule id of the report, such as \`layers/no-impure-domain\`. Fix the cause: never turn a rule off, add a disable comment, add a context to \`contextMap\` or edit \`alveolus.baseline.json\` by hand without asking.
2789
+ `,
2790
+ path: ".claude/skills/alveolus/SKILL.md"
2791
+ },
2792
+ {
2793
+ content: `## Alveolus
2794
+
2795
+ This project uses Alveolus for Domain-Driven Design: the building blocks of \`@alveolus/core\` and the architecture checks of \`@alveolus/arch\`. The documentation is installed with the package: \`npx alveolus explain\` lists the topics and \`npx alveolus explain <topic>\` prints one, so do not look for it on the web. Run \`npx alveolus arch check\` after each change, and read the rule reported with \`npx alveolus explain <rule>\` before fixing. See \`.claude/skills/alveolus/SKILL.md\`.
2796
+ `,
2797
+ marker: "npx alveolus explain",
2798
+ path: "AGENTS.md"
2799
+ }
2800
+ ];
2801
+ //#endregion
2802
+ //#region src/init/init.ts
2803
+ var Init = class {
2804
+ projectDir;
2805
+ constructor(projectDir) {
2806
+ this.projectDir = projectDir;
2807
+ }
2808
+ run() {
2809
+ const written = [];
2810
+ for (const scaffold of scaffolds) written.push({
2811
+ outcome: this.write(scaffold),
2812
+ path: scaffold.path
2813
+ });
2814
+ return written;
2815
+ }
2816
+ get hint() {
2817
+ const claude = join(this.projectDir, "CLAUDE.md");
2818
+ if (!existsSync(claude)) return;
2819
+ return "CLAUDE.md exists: Claude Code reads it instead of AGENTS.md, so add a line with @AGENTS.md to it.";
2820
+ }
2821
+ write(scaffold) {
2822
+ const path = join(this.projectDir, scaffold.path);
2823
+ if (!existsSync(path)) {
2824
+ mkdirSync(dirname(path), { recursive: true });
2825
+ writeFileSync(path, scaffold.content);
2826
+ return "created";
2827
+ }
2828
+ if (scaffold.marker === void 0) return "kept";
2829
+ const existing = readFileSync(path, "utf8");
2830
+ if (existing.includes(scaffold.marker)) return "kept";
2831
+ writeFileSync(path, `${existing.trimEnd()}\n\n${scaffold.content}`);
2832
+ return "appended";
2833
+ }
2834
+ };
2776
2835
  //#endregion
2777
2836
  //#region src/cli/cli.ts
2778
2837
  var Cli = class {
2779
2838
  stdout;
2780
2839
  stderr;
2781
2840
  cwd;
2841
+ docs;
2782
2842
  colored;
2783
2843
  exitCode = 0;
2784
- constructor(stdout, stderr, cwd, colored = false) {
2844
+ constructor(stdout, stderr, cwd, docs, colored = false) {
2785
2845
  this.stdout = stdout;
2786
2846
  this.stderr = stderr;
2787
2847
  this.cwd = cwd;
2848
+ this.docs = docs;
2788
2849
  this.colored = colored;
2789
2850
  }
2790
2851
  async run(args) {
@@ -2804,8 +2865,30 @@ var Cli = class {
2804
2865
  const arch = program.command("arch").description("Check the architecture of a Domain-Driven Design project");
2805
2866
  this.withOptions(arch.command("check").description("Report the violations that are not in the baseline")).action((options) => this.check(options));
2806
2867
  this.withOptions(arch.command("baseline").description(`Write the current violations to ${Baseline.fileName}`)).option("--allow-growth", "write the baseline even when it holds more entries than before").action((options) => this.baseline(options));
2868
+ program.command("explain").description("Print a page of the documentation: a rule, a building block or a guide").argument("[topic]", "a rule id, a building block or a guide; without it, the list of topics").action((topic) => this.explain(topic));
2869
+ program.command("init").description(`Write ${ConfigLoader.fileName}, and the instructions that tell a coding agent to read the documentation installed with the package`).option("--project <dir>", "project directory", ".").action((options) => this.init(options));
2807
2870
  return program;
2808
2871
  }
2872
+ explain(topic) {
2873
+ if (topic === void 0) {
2874
+ this.stdout.write(`${this.docs.topics().join("\n")}\n`);
2875
+ return;
2876
+ }
2877
+ const match = this.docs.find(topic);
2878
+ if ("page" in match) {
2879
+ this.stdout.write(match.page.text());
2880
+ return;
2881
+ }
2882
+ const list = match.candidates.length === 0 ? "alveolus explain lists the topics." : `Did you mean ${match.candidates.join(", ")}?`;
2883
+ this.stderr.write(`No page for ${topic}: ${list}\n`);
2884
+ this.exitCode = 1;
2885
+ }
2886
+ init(options) {
2887
+ const init = new Init(resolve(this.cwd, options.project));
2888
+ for (const { outcome, path } of init.run()) this.stdout.write(`${outcome} ${path}\n`);
2889
+ this.stdout.write(`Name your bounded contexts in ${ConfigLoader.fileName}, then run alveolus arch check.\n`);
2890
+ if (init.hint !== void 0) this.stderr.write(`${init.hint}\n`);
2891
+ }
2809
2892
  withOptions(command) {
2810
2893
  return command.option("--project <dir>", "project directory", ".").option("--config <file>", "configuration file", ConfigLoader.fileName).option("--tsconfig <file>", "TypeScript configuration, tsconfig.json or the one set in the configuration file").addOption(new Option("--format <format>", "how violations are printed").choices([
2811
2894
  "text",
@@ -2869,6 +2952,94 @@ var Cli = class {
2869
2952
  }
2870
2953
  };
2871
2954
  //#endregion
2872
- export { Config as a, RuleRegistry as c, AllowedPackages as d, Baseline as f, ConfigLoader as i, Rule as l, TsMorphImporter as n, Report as o, Importer as r, Checker as s, Cli as t, Architecture as u };
2955
+ //#region src/docs/page.ts
2956
+ const html = [
2957
+ {
2958
+ pattern: /<dt>/g,
2959
+ text: "- "
2960
+ },
2961
+ {
2962
+ pattern: /<\/dt>\s*<dd>/g,
2963
+ text: ": "
2964
+ },
2965
+ {
2966
+ pattern: /<[^>\n]+>/g,
2967
+ text: ""
2968
+ },
2969
+ {
2970
+ pattern: /^\t+- /gm,
2971
+ text: "- "
2972
+ },
2973
+ {
2974
+ pattern: /&lt;/g,
2975
+ text: "<"
2976
+ },
2977
+ {
2978
+ pattern: /&gt;/g,
2979
+ text: ">"
2980
+ },
2981
+ {
2982
+ pattern: /&amp;/g,
2983
+ text: "&"
2984
+ }
2985
+ ];
2986
+ const frontmatter = /^---\n([\s\S]*?)\n---\n/;
2987
+ var Page = class {
2988
+ topic;
2989
+ source;
2990
+ constructor(topic, source) {
2991
+ this.topic = topic;
2992
+ this.source = source;
2993
+ }
2994
+ get description() {
2995
+ const header = frontmatter.exec(this.source)?.[1] ?? "";
2996
+ return /^description: "?(.*?)"?$/m.exec(header)?.[1] ?? "";
2997
+ }
2998
+ text() {
2999
+ let text = this.source.replace(frontmatter, "");
3000
+ for (const { pattern, text: replacement } of html) text = text.replace(pattern, replacement);
3001
+ return `${text.replace(/\n{3,}/g, "\n\n").trim()}\n`;
3002
+ }
3003
+ };
3004
+ //#endregion
3005
+ //#region src/docs/docs.ts
3006
+ var Docs = class Docs {
3007
+ dir;
3008
+ static sections = [
3009
+ "guide",
3010
+ "integrations",
3011
+ "core",
3012
+ "rules"
3013
+ ];
3014
+ constructor(dir) {
3015
+ this.dir = dir;
3016
+ }
3017
+ topics() {
3018
+ const topics = [];
3019
+ for (const section of Docs.sections) for (const file of globSync("**/*.md", { cwd: join(this.dir, section) }).sort()) topics.push(this.topicOf(join(section, file)));
3020
+ return topics.sort();
3021
+ }
3022
+ find(name) {
3023
+ const wanted = name.replace(/\.md$/, "").replace(/\/$/, "").replace(/\\/g, "/");
3024
+ const candidates = this.topics().filter((topic) => topic === wanted || topic.endsWith(`/${wanted}`));
3025
+ const exact = candidates.find((topic) => topic === wanted);
3026
+ if (exact !== void 0) return { page: this.read(exact) };
3027
+ if (candidates.length === 1 && candidates[0] !== void 0) return { page: this.read(candidates[0]) };
3028
+ return { candidates };
3029
+ }
3030
+ read(topic) {
3031
+ const path = join(this.dir, ...topic.split("/"));
3032
+ try {
3033
+ return new Page(topic, readFileSync(`${path}.md`, "utf8"));
3034
+ } catch {
3035
+ return new Page(topic, readFileSync(join(path, "index.md"), "utf8"));
3036
+ }
3037
+ }
3038
+ topicOf(file) {
3039
+ return file.split(sep).join("/").replace(/\.md$/, "").replace(/\/index$/, "");
3040
+ }
3041
+ };
3042
+ //#endregion
3043
+ export { TsMorphImporter as a, Config as c, RuleRegistry as d, Rule as f, Baseline as h, Init as i, Report as l, AllowedPackages as m, Page as n, Importer as o, Architecture as p, Cli as r, ConfigLoader as s, Docs as t, Checker as u };
2873
3044
 
2874
- //# sourceMappingURL=cli-P5PwH9OE.mjs.map
3045
+ //# sourceMappingURL=docs-DcFgskuN.mjs.map