@clearlydev/nx-archweave 0.3.2 → 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 (47) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/review-verdict.js +8 -249
  3. package/dist/eslint/index.js +28 -321
  4. package/dist/generators/_chunks/chunk-2WSX5YNB.js +274 -0
  5. package/dist/generators/_chunks/chunk-34V6HAJT.js +55 -0
  6. package/dist/generators/_chunks/chunk-3A6RZUCZ.js +114 -0
  7. package/dist/generators/_chunks/chunk-47T7NOPP.js +30 -0
  8. package/dist/generators/_chunks/chunk-4FJ7CLEU.js +189 -0
  9. package/dist/generators/_chunks/chunk-5MI3O4VZ.js +99 -0
  10. package/dist/generators/_chunks/chunk-6ZTIUBB2.js +139 -0
  11. package/dist/generators/_chunks/chunk-AIJCJ7JK.js +203 -0
  12. package/dist/generators/_chunks/chunk-ALSXKTLM.js +1098 -0
  13. package/dist/generators/_chunks/chunk-AQHV7DJS.js +656 -0
  14. package/dist/generators/_chunks/chunk-C2VVUWBA.js +386 -0
  15. package/dist/generators/_chunks/chunk-EXYZLL7A.js +234 -0
  16. package/dist/generators/_chunks/chunk-FQHFT5IH.js +50 -0
  17. package/dist/generators/_chunks/chunk-H222FPLK.js +80 -0
  18. package/dist/generators/_chunks/chunk-IIDOBI42.js +616 -0
  19. package/dist/generators/_chunks/chunk-L2RZ2LBO.js +132 -0
  20. package/dist/generators/_chunks/chunk-MQNQJOWA.js +217 -0
  21. package/dist/generators/_chunks/chunk-QQNDYJTR.js +243 -0
  22. package/dist/generators/_chunks/chunk-SFXU6IUS.js +0 -0
  23. package/dist/generators/_chunks/chunk-SNBIVIMI.js +89 -0
  24. package/dist/generators/_chunks/chunk-TUN34LAQ.js +8 -0
  25. package/dist/generators/_chunks/chunk-TXAQ2GJC.js +20 -0
  26. package/dist/generators/_chunks/chunk-U6LAFHSF.js +49 -0
  27. package/dist/generators/_chunks/chunk-URV6FLUH.js +73 -0
  28. package/dist/generators/_chunks/chunk-YNMLB4DL.js +25 -0
  29. package/dist/generators/_chunks/chunk-YY2UESLM.js +282 -0
  30. package/dist/generators/add-agent-rules/generator.js +11 -842
  31. package/dist/generators/add-backend/generator.js +11 -906
  32. package/dist/generators/add-fsd-lite/generator.js +12 -801
  33. package/dist/generators/add-github-templates/generator.js +13 -116
  34. package/dist/generators/add-integration/generator.js +15 -743
  35. package/dist/generators/add-review/generator.js +12 -1231
  36. package/dist/generators/allow-builds/generator.js +7 -71
  37. package/dist/generators/doctor/generator.js +106 -2044
  38. package/dist/generators/entity/generator.js +15 -604
  39. package/dist/generators/feature/generator.js +15 -604
  40. package/dist/generators/init/generator.js +68 -3376
  41. package/dist/generators/install-integration/generator.js +35 -871
  42. package/dist/generators/page/generator.js +15 -604
  43. package/dist/generators/record-integration/generator.js +12 -778
  44. package/dist/generators/shared/generator.js +15 -604
  45. package/dist/generators/widget/generator.js +15 -604
  46. package/dist/index.js +5 -663
  47. package/package.json +1 -1
@@ -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
  {
@@ -0,0 +1,274 @@
1
+ import {
2
+ resolveProjectType
3
+ } from "./chunk-TXAQ2GJC.js";
4
+ import {
5
+ readBoundaryTags
6
+ } from "./chunk-6ZTIUBB2.js";
7
+ import {
8
+ readToolkitConfig
9
+ } from "./chunk-EXYZLL7A.js";
10
+
11
+ // ../template-engine/dist/frameworks/profiles.js
12
+ var ALL_LAYERS = ["app", "pages", "features", "entities", "shared"];
13
+ var NEXT = {
14
+ id: "next",
15
+ recognised: true,
16
+ // Next.js's own app/ (App Router) directory is the composition root; FSD's Next.js guide
17
+ // names the FSD app and pages layers _app and _pages, since Next.js reads src/app and
18
+ // src/pages as its routers.
19
+ layers: ["pages", "features", "entities", "shared"],
20
+ layerDirs: { app: "_app", pages: "_pages" },
21
+ buildOutputDirs: [".next", "out"],
22
+ platformSuffixes: [],
23
+ routerDirs: ["pages"]
24
+ };
25
+ var REACT_VITE = {
26
+ id: "react-vite",
27
+ recognised: true,
28
+ layers: ALL_LAYERS,
29
+ layerDirs: {},
30
+ buildOutputDirs: ["dist"],
31
+ platformSuffixes: [],
32
+ routerDirs: []
33
+ };
34
+ var EXPO_ROUTER = {
35
+ id: "expo",
36
+ recognised: true,
37
+ layers: ALL_LAYERS,
38
+ layerDirs: { app: "_app" },
39
+ buildOutputDirs: [".expo", "dist"],
40
+ platformSuffixes: ["ios", "android", "native", "web"],
41
+ routerDirs: []
42
+ };
43
+ var EXPO = { ...EXPO_ROUTER, layerDirs: {} };
44
+ var GENERIC = {
45
+ id: "generic",
46
+ recognised: false,
47
+ layers: ALL_LAYERS,
48
+ layerDirs: {},
49
+ buildOutputDirs: ["dist", "build"],
50
+ platformSuffixes: [],
51
+ routerDirs: []
52
+ };
53
+ var PROFILES = { next: NEXT, "react-vite": REACT_VITE, expo: EXPO_ROUTER, generic: GENERIC };
54
+ function profileById(id) {
55
+ return PROFILES[id];
56
+ }
57
+ function allFrameworkProfiles() {
58
+ return Object.values(PROFILES);
59
+ }
60
+ var NEXT_CONFIGS = ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"];
61
+ var VITE_CONFIGS = ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"];
62
+ var EXPO_CONFIGS = ["app.config.ts", "app.config.js", "app.config.mjs"];
63
+ function hasExpoKey(input, file) {
64
+ const text = input.read(file);
65
+ if (typeof text !== "string")
66
+ return false;
67
+ try {
68
+ return typeof JSON.parse(text)["expo"] === "object";
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+ function dependsOn(input, manifest, name) {
74
+ const text = input.read(manifest);
75
+ if (typeof text !== "string")
76
+ return false;
77
+ try {
78
+ const parsed = JSON.parse(text);
79
+ return ["dependencies", "devDependencies", "peerDependencies"].some((field) => parsed[field]?.[name] !== void 0);
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+ function detectFramework(input) {
85
+ const prefix = input.projectRoot === "" || input.projectRoot === "." ? "" : `${input.projectRoot}/`;
86
+ const executors = input.executors ?? [];
87
+ if (NEXT_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || executors.some((executor) => executor.startsWith("@nx/next:"))) {
88
+ return NEXT;
89
+ }
90
+ const dependsAnywhere = (name) => dependsOn(input, `${prefix}package.json`, name) || dependsOn(input, "package.json", name);
91
+ const usesExpo = executors.some((executor) => executor.startsWith("@nx/expo:")) || hasExpoKey(input, `${prefix}app.json`) || EXPO_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || dependsOn(input, `${prefix}package.json`, "expo");
92
+ if (usesExpo)
93
+ return dependsAnywhere("expo-router") ? EXPO_ROUTER : EXPO;
94
+ const usesReact = dependsAnywhere("react");
95
+ const usesVite = VITE_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || executors.some((executor) => executor.startsWith("@nx/vite:"));
96
+ if (usesVite && usesReact)
97
+ return REACT_VITE;
98
+ return GENERIC;
99
+ }
100
+
101
+ // src/generators/shared/frontend-app.ts
102
+ import { getProjects } from "@nx/devkit";
103
+
104
+ // ../template-engine/dist/fsd/slice-rules.js
105
+ function toCamelCase(kebabName) {
106
+ return kebabName.split("-").filter((part) => part.length > 0).map((part, index) => index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
107
+ }
108
+ function resolveSlicePublicApiName(name, publicApiName) {
109
+ return publicApiName ?? toCamelCase(name);
110
+ }
111
+ var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
112
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
113
+ "await",
114
+ "break",
115
+ "case",
116
+ "catch",
117
+ "class",
118
+ "const",
119
+ "continue",
120
+ "debugger",
121
+ "default",
122
+ "delete",
123
+ "do",
124
+ "else",
125
+ "enum",
126
+ "export",
127
+ "extends",
128
+ "false",
129
+ "finally",
130
+ "for",
131
+ "function",
132
+ "if",
133
+ "implements",
134
+ "import",
135
+ "in",
136
+ "instanceof",
137
+ "interface",
138
+ "let",
139
+ "new",
140
+ "null",
141
+ "package",
142
+ "private",
143
+ "protected",
144
+ "public",
145
+ "return",
146
+ "static",
147
+ "super",
148
+ "switch",
149
+ "this",
150
+ "throw",
151
+ "true",
152
+ "try",
153
+ "typeof",
154
+ "var",
155
+ "void",
156
+ "while",
157
+ "with",
158
+ "yield"
159
+ ]);
160
+ function isUsableSliceApiName(candidate) {
161
+ return IDENTIFIER_PATTERN.test(candidate) && !RESERVED_WORDS.has(candidate);
162
+ }
163
+ var ESSENCE_SEGMENT_NAMES = [
164
+ "components",
165
+ "hooks",
166
+ "helpers",
167
+ "utils",
168
+ "types",
169
+ "constants",
170
+ "consts",
171
+ "interfaces",
172
+ "enums",
173
+ "modals",
174
+ "stores",
175
+ "services"
176
+ ];
177
+ function isEssenceSegmentName(name) {
178
+ return ESSENCE_SEGMENT_NAMES.includes(name);
179
+ }
180
+ var SEGMENT_NAME_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
181
+
182
+ // src/generators/shared/frontend-app.ts
183
+ var baseNameOf = (root) => root.split("/").pop() ?? root;
184
+ function isEndToEnd(root) {
185
+ const dir = baseNameOf(root);
186
+ return dir === "e2e" || dir.endsWith("-e2e");
187
+ }
188
+ function isBackend(root, config, backendTag) {
189
+ return baseNameOf(root) === "api" || (config.tags ?? []).includes(backendTag);
190
+ }
191
+ function prefixOf(root) {
192
+ return root === "" || root === "." ? "" : `${root}/`;
193
+ }
194
+ function sourceRootOf(tree, config) {
195
+ const prefix = prefixOf(config.root);
196
+ const declared = config.sourceRoot?.replace(/\/$/, "");
197
+ const atRoot = declared !== void 0 && (declared === config.root || prefix === "" && (declared === "." || declared === ""));
198
+ if (declared && !atRoot) return declared;
199
+ if (atRoot && !tree.exists(`${prefix}src`)) return prefix === "" ? "." : config.root;
200
+ return `${prefix}src`;
201
+ }
202
+ function frameworkOf(tree, config) {
203
+ return detectFramework({
204
+ exists: (path) => tree.exists(path),
205
+ read: (path) => tree.read(path, "utf-8"),
206
+ projectRoot: config.root,
207
+ executors: Object.values(config.targets ?? {}).flatMap((target) => target.executor ? [target.executor] : [])
208
+ });
209
+ }
210
+ function resolveFrontendApp(tree, explicitProject) {
211
+ let projects;
212
+ try {
213
+ projects = getProjects(tree);
214
+ } catch (error) {
215
+ const message = error instanceof Error ? error.message : String(error);
216
+ const pathMatch = /Cannot parse ([^:]+):/.exec(message);
217
+ return { kind: "invalid-project-json", detail: pathMatch ? pathMatch[1] : message };
218
+ }
219
+ const found = (project2, config2) => {
220
+ const profile = frameworkOf(tree, config2);
221
+ return { kind: "found", project: project2, root: config2.root, sourceRoot: sourceRootOf(tree, config2), framework: profile.id, profile };
222
+ };
223
+ if (explicitProject !== void 0) {
224
+ const config2 = projects.get(explicitProject);
225
+ if (config2) return found(explicitProject, config2);
226
+ const rootApp = inferredRootApp(tree, projects);
227
+ return rootApp?.kind === "found" && rootApp.project === explicitProject ? rootApp : { kind: "not-found", project: explicitProject };
228
+ }
229
+ const backendTag = readBoundaryTags(readToolkitConfig(tree)).backend;
230
+ const eligible = [...projects.entries()].filter(([, config2]) => !isEndToEnd(config2.root) && !isBackend(config2.root, config2, backendTag));
231
+ const declared = eligible.filter(([, config2]) => config2.projectType === "application");
232
+ const inferred = eligible.filter(([, config2]) => {
233
+ if (config2.projectType !== void 0) return false;
234
+ if (config2.root === "" || config2.root === ".") return frameworkOf(tree, config2).recognised;
235
+ return resolveProjectType(tree, config2.root, void 0) === "application";
236
+ });
237
+ const candidates = (declared.length > 0 ? declared : inferred).sort(([a], [b]) => a.localeCompare(b));
238
+ if (candidates.length === 0) {
239
+ const rootApp = inferredRootApp(tree, projects);
240
+ if (rootApp !== null) return rootApp;
241
+ const otherProjects = [...projects.keys()].filter((name) => !isEndToEnd(projects.get(name)?.root ?? "")).sort();
242
+ return { kind: "none", otherProjects };
243
+ }
244
+ if (candidates.length > 1) return { kind: "ambiguous", candidates: candidates.map(([name]) => name) };
245
+ const [project, config] = candidates[0];
246
+ return found(project, config);
247
+ }
248
+ function inferredRootApp(tree, projects) {
249
+ if ([...projects.values()].some((config2) => config2.root === "" || config2.root === ".")) return null;
250
+ const raw = tree.read("package.json", "utf-8");
251
+ if (raw === null) return null;
252
+ let name;
253
+ try {
254
+ name = JSON.parse(raw).name;
255
+ } catch {
256
+ return null;
257
+ }
258
+ if (typeof name !== "string" || name === "") return null;
259
+ const config = { name, root: "." };
260
+ const framework = frameworkOf(tree, config);
261
+ if (!framework.recognised) return null;
262
+ return { kind: "found", project: name, root: ".", sourceRoot: sourceRootOf(tree, config), framework: framework.id, profile: framework, inferredRoot: true };
263
+ }
264
+
265
+ export {
266
+ profileById,
267
+ allFrameworkProfiles,
268
+ resolveSlicePublicApiName,
269
+ isUsableSliceApiName,
270
+ isEssenceSegmentName,
271
+ SEGMENT_NAME_PATTERN,
272
+ sourceRootOf,
273
+ resolveFrontendApp
274
+ };