@clearlydev/nx-archweave 0.3.2 → 0.3.3

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 (46) hide show
  1. package/dist/bin/review-verdict.js +5 -248
  2. package/dist/eslint/index.js +28 -321
  3. package/dist/generators/_chunks/chunk-2WSX5YNB.js +274 -0
  4. package/dist/generators/_chunks/chunk-34V6HAJT.js +55 -0
  5. package/dist/generators/_chunks/chunk-3A6RZUCZ.js +114 -0
  6. package/dist/generators/_chunks/chunk-47T7NOPP.js +30 -0
  7. package/dist/generators/_chunks/chunk-5MI3O4VZ.js +99 -0
  8. package/dist/generators/_chunks/chunk-67INEVWL.js +1068 -0
  9. package/dist/generators/_chunks/chunk-6ZTIUBB2.js +139 -0
  10. package/dist/generators/_chunks/chunk-AIJCJ7JK.js +203 -0
  11. package/dist/generators/_chunks/chunk-AQHV7DJS.js +656 -0
  12. package/dist/generators/_chunks/chunk-C2VVUWBA.js +386 -0
  13. package/dist/generators/_chunks/chunk-EXYZLL7A.js +234 -0
  14. package/dist/generators/_chunks/chunk-FQHFT5IH.js +50 -0
  15. package/dist/generators/_chunks/chunk-H222FPLK.js +80 -0
  16. package/dist/generators/_chunks/chunk-IIDOBI42.js +616 -0
  17. package/dist/generators/_chunks/chunk-JHRTVURZ.js +189 -0
  18. package/dist/generators/_chunks/chunk-L2RZ2LBO.js +132 -0
  19. package/dist/generators/_chunks/chunk-MQNQJOWA.js +217 -0
  20. package/dist/generators/_chunks/chunk-QQNDYJTR.js +243 -0
  21. package/dist/generators/_chunks/chunk-SFXU6IUS.js +0 -0
  22. package/dist/generators/_chunks/chunk-SNBIVIMI.js +89 -0
  23. package/dist/generators/_chunks/chunk-TUN34LAQ.js +8 -0
  24. package/dist/generators/_chunks/chunk-TXAQ2GJC.js +20 -0
  25. package/dist/generators/_chunks/chunk-U6LAFHSF.js +49 -0
  26. package/dist/generators/_chunks/chunk-URV6FLUH.js +73 -0
  27. package/dist/generators/_chunks/chunk-YNMLB4DL.js +25 -0
  28. package/dist/generators/_chunks/chunk-YY2UESLM.js +282 -0
  29. package/dist/generators/add-agent-rules/generator.js +11 -842
  30. package/dist/generators/add-backend/generator.js +11 -906
  31. package/dist/generators/add-fsd-lite/generator.js +12 -801
  32. package/dist/generators/add-github-templates/generator.js +13 -116
  33. package/dist/generators/add-integration/generator.js +15 -743
  34. package/dist/generators/add-review/generator.js +12 -1231
  35. package/dist/generators/allow-builds/generator.js +7 -71
  36. package/dist/generators/doctor/generator.js +74 -2042
  37. package/dist/generators/entity/generator.js +15 -604
  38. package/dist/generators/feature/generator.js +15 -604
  39. package/dist/generators/init/generator.js +68 -3376
  40. package/dist/generators/install-integration/generator.js +35 -871
  41. package/dist/generators/page/generator.js +15 -604
  42. package/dist/generators/record-integration/generator.js +12 -778
  43. package/dist/generators/shared/generator.js +15 -604
  44. package/dist/generators/widget/generator.js +15 -604
  45. package/dist/index.js +5 -663
  46. package/package.json +1 -1
@@ -1,257 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import "../generators/_chunks/chunk-SFXU6IUS.js";
3
+ import {
4
+ VERDICT_MARKER,
5
+ runVerdictGate
6
+ } from "../generators/_chunks/chunk-67INEVWL.js";
2
7
 
3
8
  // src/bin/review-verdict.ts
4
9
  import { spawnSync } from "node:child_process";
5
10
  import { randomBytes } from "node:crypto";
6
11
  import { appendFileSync, realpathSync } from "node:fs";
7
-
8
- // ../review-policy/dist/schema/review-finding.js
9
- function computeVerdict(findings) {
10
- if (findings.some((f) => f.priority === "P0" || f.priority === "P1"))
11
- return "BLOCK";
12
- if (findings.some((f) => f.priority === "P2"))
13
- return "CHANGES_RECOMMENDED";
14
- if (findings.length > 0)
15
- return "APPROVE_WITH_NOTES";
16
- return "APPROVE";
17
- }
18
-
19
- // ../review-policy/dist/verdict-gate/findings-block.js
20
- var FINDINGS_MARKER = "<!-- archweave-findings-v1 -->";
21
- var FINDINGS_SCHEMA_ID = "archweave/findings@1";
22
- var PRIORITIES = ["P0", "P1", "P2", "P3", "Nit"];
23
- function findingKey(f) {
24
- return `${f.file}:${f.lineRange.start}:${f.category}`;
25
- }
26
- function extractFindingsBlock(commentBody) {
27
- const markerIndex = commentBody.lastIndexOf(FINDINGS_MARKER);
28
- if (markerIndex === -1)
29
- return { status: "missing" };
30
- const after = commentBody.slice(markerIndex + FINDINGS_MARKER.length);
31
- const open = /```json[ \t]*\r?\n/.exec(after);
32
- if (!open)
33
- return { status: "invalid" };
34
- const contentStart = open.index + open[0].length;
35
- let parsed;
36
- let found = false;
37
- for (let close = after.indexOf("```", contentStart); close !== -1; close = after.indexOf("```", close + 3)) {
38
- try {
39
- parsed = JSON.parse(after.slice(contentStart, close));
40
- found = true;
41
- break;
42
- } catch {
43
- }
44
- }
45
- if (!found)
46
- return { status: "invalid" };
47
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
48
- return { status: "invalid" };
49
- const obj = parsed;
50
- if (obj.schema !== FINDINGS_SCHEMA_ID || !Array.isArray(obj.findings))
51
- return { status: "invalid" };
52
- const findings = [];
53
- for (const raw of obj.findings) {
54
- const finding = normalizeFinding(raw);
55
- if (finding === null)
56
- return { status: "invalid" };
57
- findings.push(finding);
58
- }
59
- return { status: "ok", findings };
60
- }
61
- function normalizeFinding(raw) {
62
- if (raw === null || typeof raw !== "object")
63
- return null;
64
- const r = raw;
65
- const lineRange = r["lineRange"];
66
- if (!PRIORITIES.includes(r["priority"]) || typeof r["category"] !== "string" || typeof r["title"] !== "string" || typeof r["file"] !== "string" || lineRange === null || lineRange === void 0 || typeof lineRange !== "object" || typeof lineRange.start !== "number" || !Number.isInteger(lineRange.start) || lineRange.start < 1) {
67
- return null;
68
- }
69
- const str = (value) => typeof value === "string" ? value : "";
70
- return {
71
- priority: r["priority"],
72
- category: r["category"],
73
- title: r["title"],
74
- file: r["file"],
75
- lineRange: { start: lineRange.start, ...typeof lineRange.end === "number" ? { end: lineRange.end } : {} },
76
- evidence: str(r["evidence"]),
77
- impact: str(r["impact"]),
78
- reproductionCondition: str(r["reproductionCondition"]),
79
- recommendation: str(r["recommendation"]),
80
- confidence: r["confidence"] === "plausible" ? "plausible" : "confirmed"
81
- };
82
- }
83
-
84
- // ../review-policy/dist/renderers/render-review-comment.js
85
- var COPY = {
86
- en: {
87
- terminationBanner: (maxRounds) => `> \u26A0\uFE0F **Automated review ended.** The maximum of ${maxRounds} review rounds has been reached with blocking findings (P0/P1) still open. Later pushes aren't reviewed by AI, so review them yourself before merging.`,
88
- verdictLines: {
89
- BLOCK: "**Verdict: BLOCK** \u2014 P0/P1 findings must be resolved before this change can merge.",
90
- CHANGES_RECOMMENDED: "**Verdict: CHANGES_RECOMMENDED** \u2014 P2 findings should be addressed.",
91
- APPROVE_WITH_NOTES: "**Verdict: APPROVE_WITH_NOTES** \u2014 only advisory notes below; safe to merge.",
92
- APPROVE: "**Verdict: APPROVE** \u2014 no findings."
93
- },
94
- mainSection: "## Correctness & Security",
95
- overEngineeringSection: "## Over-engineering",
96
- nitSection: "## Nits",
97
- roundHistoryHeading: "**Round history:**",
98
- roundLine: (round, fixed, remaining) => `- round ${round}: ${fixed} fixed, ${remaining} remaining`,
99
- fieldLabels: {
100
- file: "File",
101
- evidence: "Evidence",
102
- impact: "Impact",
103
- reproduction: "Reproduction",
104
- recommendation: "Recommendation",
105
- confidence: "Confidence"
106
- }
107
- },
108
- ko: {
109
- terminationBanner: (maxRounds) => `> \u26A0\uFE0F **\uC790\uB3D9 \uB9AC\uBDF0 \uC885\uB8CC.** \uCD5C\uB300 ${maxRounds}\uD68C\uC758 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC\uC5D0 \uB3C4\uB2EC\uD588\uC9C0\uB9CC \uBCD1\uD569\uC744 \uB9C9\uB294 finding(P0/P1)\uC774 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uC774\uD6C4 push\uB294 AI\uAC00 \uB9AC\uBDF0\uD558\uC9C0 \uC54A\uC73C\uB2C8, \uBCD1\uD569 \uC804\uC5D0 \uC9C1\uC811 \uB9AC\uBDF0\uD558\uC138\uC694.`,
110
- verdictLines: {
111
- BLOCK: "**\uD310\uC815: BLOCK** \u2014 \uBCD1\uD569 \uC804\uC5D0 P0/P1 finding\uC744 \uD574\uACB0\uD574\uC57C \uD569\uB2C8\uB2E4.",
112
- CHANGES_RECOMMENDED: "**\uD310\uC815: CHANGES_RECOMMENDED** \u2014 P2 finding\uC744 \uD574\uACB0\uD558\uB294 \uAC83\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
113
- APPROVE_WITH_NOTES: "**\uD310\uC815: APPROVE_WITH_NOTES** \u2014 \uCC38\uACE0\uC6A9 \uB178\uD2B8\uB9CC \uC788\uC2B5\uB2C8\uB2E4. \uBCD1\uD569\uD574\uB3C4 \uC548\uC804\uD569\uB2C8\uB2E4.",
114
- APPROVE: "**\uD310\uC815: APPROVE** \u2014 finding\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."
115
- },
116
- mainSection: "## \uC815\uD655\uC131 & \uBCF4\uC548",
117
- overEngineeringSection: "## \uACFC\uC789 \uC124\uACC4",
118
- nitSection: "## \uC0AC\uC18C\uD55C \uC9C0\uC801 (Nit)",
119
- roundHistoryHeading: "**\uB77C\uC6B4\uB4DC \uC774\uB825:**",
120
- roundLine: (round, fixed, remaining) => `- ${round}\uB77C\uC6B4\uB4DC: \uD574\uACB0 ${fixed}\uAC74, \uBBF8\uD574\uACB0 ${remaining}\uAC74`,
121
- fieldLabels: {
122
- file: "\uD30C\uC77C",
123
- evidence: "\uADFC\uAC70",
124
- impact: "\uC601\uD5A5",
125
- reproduction: "\uC7AC\uD604 \uC870\uAC74",
126
- recommendation: "\uAD8C\uC7A5 \uC870\uCE58",
127
- confidence: "\uD655\uC2E0\uB3C4"
128
- }
129
- }
130
- };
131
- function verdictLine(verdict, locale) {
132
- return COPY[locale].verdictLines[verdict];
133
- }
134
- function terminationBannerLine(maxRounds, locale) {
135
- return COPY[locale].terminationBanner(maxRounds);
136
- }
137
-
138
- // ../review-policy/dist/verdict-gate/verdict-comment.js
139
- var VERDICT_MARKER = "<!-- archweave-verdict -->";
140
- var HISTORY_HEADING = { en: "**Round history:**", ko: "**\uB77C\uC6B4\uB4DC \uC774\uB825:**" };
141
- var ROUND_LINE = {
142
- en: (round, fixed, remaining) => `- round ${round}: ${fixed} fixed, ${remaining} remaining`,
143
- ko: (round, fixed, remaining) => `- ${round}\uCC28: ${fixed}\uAC74 \uD574\uACB0, ${remaining}\uAC74 \uC794\uC874`
144
- };
145
- var UNPARSEABLE_NOTICE = {
146
- en: "\u26A0\uFE0F The review comment could not be machine-read (missing or invalid findings block). The verdict gate stays green; please review this PR manually.",
147
- ko: "\u26A0\uFE0F \uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uAE30\uACC4 \uD310\uB3C5\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(findings \uBE14\uB85D \uB204\uB77D \uB610\uB294 \uC190\uC0C1). verdict \uAC8C\uC774\uD2B8\uB294 \uB179\uC0C9\uC73C\uB85C \uB450\uB2C8 \uC774 PR\uC740 \uC0AC\uB78C\uC774 \uC9C1\uC811 \uB9AC\uBDF0\uD574 \uC8FC\uC138\uC694."
148
- };
149
- var NO_ROUND_NOTICE = {
150
- en: "\u26A0\uFE0F This run of the review posted no summary comment for the current commit, so there is no verdict for it (an earlier round is never reused). The verdict gate stays green; please review this PR manually.",
151
- ko: "\u26A0\uFE0F \uC774\uBC88 \uB9AC\uBDF0 \uC2E4\uD589\uC774 \uD604\uC7AC \uCEE4\uBC0B\uC5D0 \uB300\uD55C \uC694\uC57D \uCF54\uBA58\uD2B8\uB97C \uAC8C\uC2DC\uD558\uC9C0 \uC54A\uC544 verdict\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4(\uC774\uC804 \uB77C\uC6B4\uB4DC\uB294 \uC7AC\uC0AC\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). verdict \uAC8C\uC774\uD2B8\uB294 \uB179\uC0C9\uC73C\uB85C \uB450\uB2C8 \uC774 PR\uC740 \uC0AC\uB78C\uC774 \uC9C1\uC811 \uB9AC\uBDF0\uD574 \uC8FC\uC138\uC694."
152
- };
153
- var NO_SUMMARY_NOTICE = {
154
- en: "\u26D4 AI review posted no summary for this commit, so there is no verdict for it and the review check fails. Review this PR manually, or re-run all jobs to review it again.",
155
- ko: "\u26D4 AI \uB9AC\uBDF0\uAC00 \uC774 \uCEE4\uBC0B\uC5D0 \uB300\uD55C \uC694\uC57D\uC744 \uAC8C\uC2DC\uD558\uC9C0 \uC54A\uC544 verdict\uAC00 \uC5C6\uACE0, review \uCCB4\uD06C\uB294 \uC2E4\uD328\uD569\uB2C8\uB2E4. \uC774 PR\uC744 \uC9C1\uC811 \uB9AC\uBDF0\uD558\uAC70\uB098, \uBAA8\uB4E0 \uC7A1\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uB2E4\uC2DC \uB9AC\uBDF0\uBC1B\uC73C\uC138\uC694."
156
- };
157
- var UNREADABLE_SUMMARY_NOTICE = {
158
- en: "\u26D4 AI review posted a summary whose findings block could not be machine-read (missing or invalid), so there is no verdict for this commit and the review check fails. Review this PR manually, or re-run all jobs to review it again.",
159
- ko: "\u26D4 AI \uB9AC\uBDF0 \uC694\uC57D\uC758 findings \uBE14\uB85D\uC744 \uAE30\uACC4 \uD310\uB3C5\uD558\uC9C0 \uBABB\uD574(\uB204\uB77D \uB610\uB294 \uC190\uC0C1) \uC774 \uCEE4\uBC0B\uC758 verdict\uAC00 \uC5C6\uACE0, review \uCCB4\uD06C\uB294 \uC2E4\uD328\uD569\uB2C8\uB2E4. \uC774 PR\uC744 \uC9C1\uC811 \uB9AC\uBDF0\uD558\uAC70\uB098, \uBAA8\uB4E0 \uC7A1\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uB2E4\uC2DC \uB9AC\uBDF0\uBC1B\uC73C\uC138\uC694."
160
- };
161
- function renderCommitLine(locale, shortSha) {
162
- return locale === "en" ? `<sub>Commit \`${shortSha}\` \xB7 this comment is updated on every review round.</sub>` : `<sub>\uCEE4\uBC0B \`${shortSha}\` \xB7 \uC774 \uCF54\uBA58\uD2B8\uB294 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC\uB9C8\uB2E4 \uAC31\uC2E0\uB429\uB2C8\uB2E4.</sub>`;
163
- }
164
- var HEAD_LINE = {
165
- en: (sha) => renderCommitLine("en", sha.slice(0, 7)),
166
- ko: (sha) => renderCommitLine("ko", sha.slice(0, 7))
167
- };
168
- function withHead(lines, locale, headSha) {
169
- return (headSha === void 0 ? lines : [...lines, "", HEAD_LINE[locale](headSha)]).join("\n");
170
- }
171
- function renderUnparseableNotice(locale, headSha, failClosed = false) {
172
- return withHead([VERDICT_MARKER, (failClosed ? UNREADABLE_SUMMARY_NOTICE : UNPARSEABLE_NOTICE)[locale]], locale, headSha);
173
- }
174
- function renderNoSummaryNotice(locale, headSha) {
175
- return withHead([VERDICT_MARKER, NO_SUMMARY_NOTICE[locale]], locale, headSha);
176
- }
177
- function renderNoRoundNotice(locale, headSha) {
178
- return withHead([VERDICT_MARKER, NO_ROUND_NOTICE[locale]], locale, headSha);
179
- }
180
- function renderVerdictComment(input) {
181
- const lines = [VERDICT_MARKER];
182
- if (input.isFinalRound && input.verdict === "BLOCK") {
183
- lines.push(terminationBannerLine(input.maxRounds, input.locale), "");
184
- }
185
- lines.push(verdictLine(input.verdict, input.locale));
186
- if (input.history.length > 1) {
187
- lines.push("", HISTORY_HEADING[input.locale]);
188
- for (let i = 0; i < input.history.length; i++) {
189
- if (i === 0)
190
- continue;
191
- const entry = input.history[i];
192
- lines.push(ROUND_LINE[input.locale](entry.round, entry.fixedFindingKeys.length, entry.remainingFindingKeys.length));
193
- }
194
- }
195
- return withHead(lines, input.locale, input.headSha);
196
- }
197
-
198
- // ../review-policy/dist/verdict-gate/verdict-gate.js
199
- var ROUND_MARKER = "<!-- archweave-review-round -->";
200
- function collectRounds(comments) {
201
- return comments.filter((c) => c.body.includes(ROUND_MARKER) && !c.body.includes(VERDICT_MARKER)).map((c, index) => ({ round: index + 1, id: c.id, extraction: extractFindingsBlock(c.body) }));
202
- }
203
- var LINE_DRIFT = 3;
204
- function sameDefect(a, b) {
205
- if (a.file !== b.file)
206
- return false;
207
- const aEnd = a.lineRange.end ?? a.lineRange.start;
208
- const bEnd = b.lineRange.end ?? b.lineRange.start;
209
- return a.lineRange.start <= bEnd + LINE_DRIFT && b.lineRange.start <= aEnd + LINE_DRIFT;
210
- }
211
- function computeRoundHistory(rounds) {
212
- const parsed = rounds.filter((r) => r.extraction.status === "ok");
213
- const history = [];
214
- for (let i = 0; i < parsed.length; i++) {
215
- const keys = new Set(parsed[i].extraction.findings.map(findingKey));
216
- if (i === 0) {
217
- history.push({ round: parsed[i].round, fixedFindingKeys: [], remainingFindingKeys: [...keys] });
218
- continue;
219
- }
220
- const current = parsed[i].extraction.findings;
221
- history.push({
222
- round: parsed[i].round,
223
- fixedFindingKeys: parsed[i - 1].extraction.findings.filter((finding) => !current.some((now) => sameDefect(finding, now))).map(findingKey),
224
- remainingFindingKeys: [...keys]
225
- });
226
- }
227
- return history;
228
- }
229
- function runVerdictGate(input) {
230
- const rounds = collectRounds(input.comments);
231
- const reviewRan = input.reviewRan === true;
232
- if (rounds.length === 0) {
233
- return reviewRan ? { kind: "no-summary", commentBody: renderNoSummaryNotice(input.locale, input.headSha) } : { kind: "no-review" };
234
- }
235
- const latest = rounds[rounds.length - 1];
236
- if (input.roundAfterId !== void 0 && (latest.id === void 0 || latest.id <= input.roundAfterId)) {
237
- return reviewRan ? { kind: "no-summary", commentBody: renderNoSummaryNotice(input.locale, input.headSha) } : { kind: "no-round", commentBody: renderNoRoundNotice(input.locale, input.headSha) };
238
- }
239
- if (latest.extraction.status !== "ok") {
240
- return { kind: "unparseable", commentBody: renderUnparseableNotice(input.locale, input.headSha, reviewRan), failClosed: reviewRan };
241
- }
242
- const verdict = computeVerdict(latest.extraction.findings);
243
- const commentBody = renderVerdictComment({
244
- verdict,
245
- history: computeRoundHistory(rounds),
246
- maxRounds: input.maxRounds,
247
- locale: input.locale,
248
- isFinalRound: rounds.length >= input.maxRounds,
249
- headSha: input.headSha
250
- });
251
- return { kind: "verdict", verdict, commentBody, exitCode: verdict === "BLOCK" ? 1 : 0 };
252
- }
253
-
254
- // src/bin/review-verdict.ts
255
12
  var realExec = (command, args, stdin) => {
256
13
  const result = spawnSync(command, args, { encoding: "utf-8", input: stdin });
257
14
  return { status: result.status ?? null, stdout: result.stdout ?? "" };
@@ -1,315 +1,22 @@
1
+ import {
2
+ fsdBoundariesRule
3
+ } from "../generators/_chunks/chunk-MQNQJOWA.js";
4
+ import {
5
+ readRecordedFsd,
6
+ resolveFsdPolicy
7
+ } from "../generators/_chunks/chunk-5MI3O4VZ.js";
8
+
1
9
  // src/eslint/index.ts
2
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
3
- import { dirname as dirname3, join as join3, posix as posix2, resolve as resolve3, sep as sep3 } from "node:path";
10
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
11
+ import { dirname as dirname2, join, posix, resolve as resolve2, sep as sep2 } from "node:path";
4
12
  import { fileURLToPath } from "node:url";
5
13
 
6
- // src/eslint/fsd-boundaries.ts
7
- import { posix, relative, resolve, sep } from "node:path";
8
-
9
- // src/eslint/fsd-policy.ts
10
- import { existsSync, readFileSync } from "node:fs";
11
- import { dirname, join } from "node:path";
12
- var PRESETS = {
13
- basic: {
14
- sliceGroups: { auto: true, include: [], exclude: [] },
15
- sharedPublicApi: "segment",
16
- testFiles: false,
17
- segments: [],
18
- platformSuffixes: [],
19
- routerDirs: []
20
- },
21
- practical: {
22
- sliceGroups: { auto: true, include: [], exclude: [] },
23
- sharedPublicApi: "file",
24
- testFiles: ["**/*.{spec,test}.*", "**/__tests__/**"],
25
- segments: [],
26
- platformSuffixes: [],
27
- routerDirs: []
28
- }
29
- };
30
- var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
31
- function resolveFsdPolicy(recorded, overrides = {}) {
32
- const fsd = typeof recorded === "object" && recorded !== null ? recorded : {};
33
- const preset = fsd["preset"] === "practical" ? "practical" : "basic";
34
- const policy = { ...PRESETS[preset] };
35
- const groups = fsd["sliceGroups"];
36
- if (groups === false) policy.sliceGroups = false;
37
- else if (groups === "auto") policy.sliceGroups = { auto: true, include: [], exclude: [] };
38
- else if (typeof groups === "object" && groups !== null) {
39
- const value = groups;
40
- policy.sliceGroups = {
41
- auto: value["auto"] !== false,
42
- include: isStringArray(value["include"]) ? value["include"] : [],
43
- exclude: isStringArray(value["exclude"]) ? value["exclude"] : []
44
- };
45
- }
46
- const shared = fsd["sharedPublicApi"];
47
- if (shared === "segment" || shared === "component" || shared === "file") policy.sharedPublicApi = shared;
48
- const tests = fsd["testFiles"];
49
- if (tests === false || isStringArray(tests)) policy.testFiles = tests;
50
- if (isStringArray(fsd["segments"])) policy.segments = fsd["segments"];
51
- if (isStringArray(fsd["platformSuffixes"])) policy.platformSuffixes = fsd["platformSuffixes"];
52
- if (isStringArray(fsd["routerDirs"])) policy.routerDirs = fsd["routerDirs"];
53
- for (const [key, value] of Object.entries(overrides)) {
54
- if (value !== void 0) policy[key] = value;
55
- }
56
- return policy;
57
- }
58
- function readRecordedFsd(from) {
59
- for (let dir = from; ; dir = dirname(dir)) {
60
- const file = join(dir, "toolkit.config.json");
61
- if (existsSync(file)) {
62
- try {
63
- return JSON.parse(readFileSync(file, "utf-8"))["fsd"];
64
- } catch {
65
- return void 0;
66
- }
67
- }
68
- if (dirname(dir) === dir) return void 0;
69
- }
70
- }
71
- var escapeLiteral = (text) => text.replace(/[.+^$()|[\]\\{}]/g, "\\$&");
72
- function wildcardsToRegExp(glob) {
73
- let source = "";
74
- for (let i = 0; i < glob.length; i++) {
75
- const ch = glob[i];
76
- if (ch === "*" && glob[i + 1] === "*") {
77
- const slash = glob[i + 2] === "/";
78
- source += slash ? "(?:.*/)?" : ".*";
79
- i += slash ? 2 : 1;
80
- } else if (ch === "*") source += "[^/]*";
81
- else if (ch === "?") source += "[^/]";
82
- else source += escapeLiteral(ch);
83
- }
84
- return source;
85
- }
86
- function globToRegExp(glob) {
87
- let source = "";
88
- let rest = glob;
89
- for (; ; ) {
90
- const open = rest.indexOf("{");
91
- const close = open === -1 ? -1 : rest.indexOf("}", open);
92
- if (open === -1 || close === -1) {
93
- source += wildcardsToRegExp(rest);
94
- break;
95
- }
96
- source += wildcardsToRegExp(rest.slice(0, open));
97
- source += `(?:${rest.slice(open + 1, close).split(",").map(wildcardsToRegExp).join("|")})`;
98
- rest = rest.slice(close + 1);
99
- }
100
- return new RegExp(`^${source}$`);
101
- }
102
-
103
- // src/eslint/slice-groups.ts
104
- import { readdirSync, statSync } from "node:fs";
105
- import { join as join2 } from "node:path";
106
- var SEGMENTS = [
107
- "ui",
108
- "model",
109
- "api",
110
- "lib",
111
- "config",
112
- "components",
113
- "hooks",
114
- "types",
115
- "utils",
116
- "helpers",
117
- "constants",
118
- "consts",
119
- "store",
120
- "stores",
121
- "styles",
122
- "assets",
123
- "services",
124
- "context"
125
- ];
126
- var IGNORED = /^(\.|readme(\.|$))/i;
127
- var listings = /* @__PURE__ */ new Map();
128
- function list(dir) {
129
- let mtimeMs;
130
- try {
131
- mtimeMs = statSync(dir).mtimeMs;
132
- } catch {
133
- listings.delete(dir);
134
- return null;
135
- }
136
- const cached = listings.get(dir);
137
- if (cached !== void 0 && cached.mtimeMs === mtimeMs) return cached.entries;
138
- try {
139
- const entries = readdirSync(dir, { withFileTypes: true }).map((entry) => ({ name: entry.name, directory: entry.isDirectory() }));
140
- listings.set(dir, { mtimeMs, entries });
141
- return entries;
142
- } catch {
143
- return null;
144
- }
145
- }
146
- function createGroupDetector(rootAbsolute, policy, isIndexFile, listDirectory = list) {
147
- const groups = policy.sliceGroups;
148
- if (groups === false) return () => false;
149
- const segments = /* @__PURE__ */ new Set([...SEGMENTS, ...policy.segments]);
150
- return (layerDir, name) => {
151
- const key = `${layerDir}/${name}`;
152
- if (groups.include.includes(key)) return true;
153
- if (groups.exclude.includes(key) || !groups.auto) return false;
154
- const entries = listDirectory(join2(rootAbsolute, layerDir, name))?.filter((entry) => !IGNORED.test(entry.name)) ?? null;
155
- if (entries === null || entries.length === 0) return false;
156
- if (entries.some((entry) => !entry.directory || segments.has(entry.name))) return false;
157
- return entries.some((entry) => listDirectory(join2(rootAbsolute, layerDir, name, entry.name))?.some((child) => !child.directory && isIndexFile(child.name)) ?? false);
158
- };
159
- }
160
-
161
- // src/eslint/fsd-boundaries.ts
162
- var LAYER_ORDER = ["app", "pages", "widgets", "features", "entities", "shared"];
163
- var LAYER_BY_DIR = {
164
- app: "app",
165
- _app: "app",
166
- pages: "pages",
167
- _pages: "pages",
168
- widgets: "widgets",
169
- features: "features",
170
- entities: "entities",
171
- shared: "shared"
172
- };
173
- var SLICED_LAYERS = /* @__PURE__ */ new Set(["pages", "widgets", "features", "entities"]);
174
- function indexFilePattern(platformSuffixes) {
175
- const platforms = platformSuffixes.length === 0 ? "" : `(\\.(${platformSuffixes.map((suffix) => suffix.replace(/[^a-z0-9-]/gi, "")).join("|")}))?`;
176
- return new RegExp(`^index${platforms}(\\.[cm]?[jt]sx?)?$`);
177
- }
178
- var DEFAULT_POLICY = resolveFsdPolicy(void 0);
179
- var NO_GROUPS = () => false;
180
- function locate(rootRelativePath, policy = DEFAULT_POLICY, isGroup = NO_GROUPS) {
181
- const segments = rootRelativePath.split("/").filter((s) => s.length > 0 && s !== ".");
182
- if (segments.length === 0) return null;
183
- const layer = policy.routerDirs.includes(segments[0]) ? "app" : LAYER_BY_DIR[segments[0]];
184
- if (layer === void 0) return null;
185
- let unit;
186
- let rest;
187
- if (layer === "app") {
188
- unit = null;
189
- rest = segments.slice(1);
190
- } else if (layer === "shared") {
191
- const component = policy.sharedPublicApi === "component" && segments.length > 2;
192
- unit = segments[1] === void 0 ? null : component ? `${segments[1]}/${segments[2]}` : segments[1];
193
- rest = policy.sharedPublicApi === "file" ? [] : segments.slice(component ? 3 : 2);
194
- } else if (segments[1] !== void 0 && isGroup(segments[0], segments[1])) {
195
- if (segments[2] === void 0) return { layer, unit: segments[1], rest: [], group: true };
196
- unit = `${segments[1]}/${segments[2]}`;
197
- rest = segments.slice(3);
198
- } else {
199
- unit = segments[1] ?? null;
200
- rest = segments.slice(2);
201
- }
202
- if (rest.length > 0 && indexFilePattern(policy.platformSuffixes).test(rest[rest.length - 1])) rest.pop();
203
- return { layer, unit, rest };
204
- }
205
- function judge(from, to) {
206
- if (LAYER_ORDER.indexOf(to.layer) < LAYER_ORDER.indexOf(from.layer)) return { kind: "upward", from, to };
207
- if (to.unit === null) return null;
208
- if (to.group) return { kind: "group", from, to };
209
- const sameUnit = from.layer === to.layer && from.unit === to.unit;
210
- if (sameUnit) return null;
211
- const importerSlice = from.unit?.split("/").pop();
212
- const viaCrossImportApi = to.rest[0] === "@x" && to.rest[1] === importerSlice && from.layer === to.layer;
213
- if (viaCrossImportApi) return null;
214
- if (from.layer === to.layer && SLICED_LAYERS.has(to.layer)) return { kind: "cross-slice", from, to };
215
- if (to.rest.length > 0) return { kind: "sidestep", from, to };
216
- return null;
217
- }
218
- function describeLocation(location) {
219
- return location.unit === null ? location.layer : `${location.layer}/${location.unit}`;
220
- }
221
- function resolveSpecifier(specifier, importerRootRelative, options) {
222
- let rootRelative = null;
223
- if (specifier.startsWith("./") || specifier.startsWith("../") || specifier === "." || specifier === "..") {
224
- rootRelative = posix.normalize(posix.join(posix.dirname(importerRootRelative), specifier));
225
- } else {
226
- for (const [prefix, target] of Object.entries(options.aliases)) {
227
- if (!specifier.startsWith(prefix)) continue;
228
- const absolute = resolve(resolve(options.cwd, target), specifier.slice(prefix.length));
229
- rootRelative = toPosix(relative(options.rootAbsolute, absolute)) || ".";
230
- break;
231
- }
232
- }
233
- if (rootRelative === null) return null;
234
- if (rootRelative === ".." || rootRelative.startsWith("../")) return null;
235
- return rootRelative;
236
- }
237
- function toPosix(path) {
238
- return sep === "/" ? path : path.split(sep).join("/");
239
- }
240
- var fsdBoundariesRule = {
241
- meta: {
242
- type: "problem",
243
- docs: {
244
- description: "Enforce canonical Feature-Sliced Design import rules inside a frontend app",
245
- url: "https://github.com/clearlydev/nx-archweave#fsd-lite"
246
- },
247
- schema: [
248
- {
249
- type: "object",
250
- properties: {
251
- root: { type: "string" },
252
- aliases: { type: "object", additionalProperties: { type: "string" } },
253
- policy: { type: "object" }
254
- },
255
- additionalProperties: false
256
- }
257
- ],
258
- messages: {
259
- upward: 'FSD: "{{from}}" may not import from "{{to}}" (a higher layer).',
260
- crossSlice: 'FSD: "{{from}}" may not import a sibling slice "{{to}}" \u2014 move the shared part down a layer, or expose it through "{{to}}/@x/{{fromUnit}}".',
261
- sidestep: 'FSD: import "{{to}}" through its public API (index.ts), not its internal file "{{path}}".',
262
- group: 'FSD: "{{to}}" is a group of slices, not a slice \u2014 import one of its slices through its index.ts.'
263
- }
264
- },
265
- create(context) {
266
- const options = context.options[0] ?? {};
267
- const root = options.root ?? "src";
268
- const aliases = options.aliases ?? { "@/": root };
269
- const cwd = context.cwd;
270
- const rootAbsolute = resolve(cwd, root);
271
- const importerRootRelative = toPosix(relative(rootAbsolute, context.filename));
272
- if (importerRootRelative === "" || importerRootRelative.startsWith("..")) return {};
273
- const policy = resolveFsdPolicy(void 0, options.policy ?? {});
274
- const isIndexFile = (name) => indexFilePattern(policy.platformSuffixes).test(name);
275
- const isGroup = createGroupDetector(rootAbsolute, policy, isIndexFile);
276
- const from = locate(importerRootRelative, policy, isGroup);
277
- if (from === null) return {};
278
- const isTestFile = policy.testFiles !== false && policy.testFiles.some((glob) => globToRegExp(glob).test(importerRootRelative));
279
- const check = (node, specifier) => {
280
- if (typeof specifier !== "string") return;
281
- const target = resolveSpecifier(specifier, importerRootRelative, { rootAbsolute, cwd, aliases });
282
- if (target === null) return;
283
- const to = locate(target, policy, isGroup);
284
- if (to === null) return;
285
- const violation = judge(from, to);
286
- if (violation === null || isTestFile && violation.kind !== "upward") return;
287
- const data = { from: describeLocation(from), to: describeLocation(to), fromUnit: from.unit?.split("/").pop() ?? "", path: target };
288
- context.report({
289
- node,
290
- messageId: violation.kind === "cross-slice" ? "crossSlice" : violation.kind,
291
- data
292
- });
293
- };
294
- const fromSource = (node) => check(node, node.source?.value);
295
- return {
296
- ImportDeclaration: fromSource,
297
- ExportNamedDeclaration: fromSource,
298
- ExportAllDeclaration: fromSource,
299
- ImportExpression: (node) => {
300
- const argument = node.source;
301
- if (argument && argument.type === "Literal") check(node, argument.value);
302
- }
303
- };
304
- }
305
- };
306
-
307
14
  // src/eslint/tsconfig-aliases.ts
308
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
309
- import { dirname as dirname2, isAbsolute, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
310
17
  function aliasesFromTsconfig({ cwd, tsconfig = "tsconfig.json", maxDepth = 8 }) {
311
18
  const aliases = {};
312
- let current = resolve2(cwd, tsconfig);
19
+ let current = resolve(cwd, tsconfig);
313
20
  const seen = /* @__PURE__ */ new Set();
314
21
  for (let depth = 0; current !== null && depth < maxDepth && !seen.has(current); depth++) {
315
22
  seen.add(current);
@@ -318,37 +25,37 @@ function aliasesFromTsconfig({ cwd, tsconfig = "tsconfig.json", maxDepth = 8 })
318
25
  const compilerOptions = asRecord(config["compilerOptions"]);
319
26
  const paths = asRecord(compilerOptions?.["paths"]);
320
27
  if (paths) {
321
- const baseUrl = typeof compilerOptions?.["baseUrl"] === "string" ? resolve2(dirname2(current), compilerOptions["baseUrl"]) : dirname2(current);
28
+ const baseUrl = typeof compilerOptions?.["baseUrl"] === "string" ? resolve(dirname(current), compilerOptions["baseUrl"]) : dirname(current);
322
29
  for (const [pattern, targets] of Object.entries(paths)) {
323
30
  if (!pattern.endsWith("*")) continue;
324
31
  const prefix = pattern.slice(0, -1);
325
32
  if (prefix in aliases) continue;
326
33
  const target = Array.isArray(targets) ? targets.find((t) => typeof t === "string") : void 0;
327
34
  if (target === void 0 || !target.endsWith("/*")) continue;
328
- const dir = toPosix2(relative2(cwd, resolve2(baseUrl, target.slice(0, -"/*".length))));
35
+ const dir = toPosix(relative(cwd, resolve(baseUrl, target.slice(0, -"/*".length))));
329
36
  aliases[prefix] = dir === "" ? "." : dir;
330
37
  }
331
38
  }
332
39
  const parent = config["extends"];
333
40
  const parentPath = typeof parent === "string" ? parent : Array.isArray(parent) ? parent.find((p) => typeof p === "string") : void 0;
334
- current = parentPath !== void 0 && (parentPath.startsWith(".") || isAbsolute(parentPath)) ? resolveExtends(dirname2(current), parentPath) : null;
41
+ current = parentPath !== void 0 && (parentPath.startsWith(".") || isAbsolute(parentPath)) ? resolveExtends(dirname(current), parentPath) : null;
335
42
  }
336
43
  return aliases;
337
44
  }
338
45
  function resolveExtends(fromDir, target) {
339
- const candidates = [resolve2(fromDir, target), resolve2(fromDir, `${target}.json`)];
340
- return candidates.find((candidate) => existsSync2(candidate)) ?? null;
46
+ const candidates = [resolve(fromDir, target), resolve(fromDir, `${target}.json`)];
47
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
341
48
  }
342
49
  function asRecord(value) {
343
50
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
344
51
  }
345
- function toPosix2(path) {
346
- return sep2 === "/" ? path : path.split(sep2).join("/");
52
+ function toPosix(path) {
53
+ return sep === "/" ? path : path.split(sep).join("/");
347
54
  }
348
55
  function readJsonc(path) {
349
56
  let text;
350
57
  try {
351
- text = readFileSync2(path, "utf-8");
58
+ text = readFileSync(path, "utf-8");
352
59
  } catch {
353
60
  return null;
354
61
  }
@@ -398,14 +105,14 @@ function readJsonc(path) {
398
105
 
399
106
  // src/eslint/index.ts
400
107
  function ownVersion() {
401
- let dir = dirname3(fileURLToPath(import.meta.url));
108
+ let dir = dirname2(fileURLToPath(import.meta.url));
402
109
  for (; ; ) {
403
- const manifest = join3(dir, "package.json");
404
- if (existsSync3(manifest)) {
405
- const pkg = JSON.parse(readFileSync3(manifest, "utf-8"));
110
+ const manifest = join(dir, "package.json");
111
+ if (existsSync2(manifest)) {
112
+ const pkg = JSON.parse(readFileSync2(manifest, "utf-8"));
406
113
  if (pkg.name === "@clearlydev/nx-archweave" && pkg.version) return pkg.version;
407
114
  }
408
- const parent = dirname3(dir);
115
+ const parent = dirname2(dir);
409
116
  if (parent === dir) throw new Error("@clearlydev/nx-archweave: its own package.json was not found");
410
117
  dir = parent;
411
118
  }
@@ -420,7 +127,7 @@ function fsdBoundaries(options = {}) {
420
127
  const cwd = options.cwd ?? process.cwd();
421
128
  const fromTsconfig = options.aliases === void 0 && options.tsconfig !== false ? aliasesFromTsconfig({ cwd, tsconfig: options.tsconfig }) : {};
422
129
  const aliases = options.aliases ?? { "@/": root, ...fromTsconfig };
423
- const anchor = (dir) => options.cwd === void 0 ? dir : resolve3(options.cwd, dir);
130
+ const anchor = (dir) => options.cwd === void 0 ? dir : resolve2(options.cwd, dir);
424
131
  const { sliceGroups, sharedPublicApi, testFiles, segments, platformSuffixes, routerDirs } = options;
425
132
  const policy = resolveFsdPolicy(readRecordedFsd(cwd), { sliceGroups, sharedPublicApi, testFiles, segments, platformSuffixes, routerDirs });
426
133
  const ruleOptions = {
@@ -428,7 +135,7 @@ function fsdBoundaries(options = {}) {
428
135
  aliases: Object.fromEntries(Object.entries(aliases).map(([prefix, dir]) => [prefix, anchor(dir)])),
429
136
  policy
430
137
  };
431
- const rootGlob = posix2.normalize(root.split(sep3).join("/"));
138
+ const rootGlob = posix.normalize(root.split(sep2).join("/"));
432
139
  const atAppRoot = rootGlob === "." || rootGlob === "./";
433
140
  return [
434
141
  {