@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.
- package/README.md +1 -1
- package/dist/bin/review-verdict.js +8 -249
- package/dist/eslint/index.js +28 -321
- package/dist/generators/_chunks/chunk-2WSX5YNB.js +274 -0
- package/dist/generators/_chunks/chunk-34V6HAJT.js +55 -0
- package/dist/generators/_chunks/chunk-3A6RZUCZ.js +114 -0
- package/dist/generators/_chunks/chunk-47T7NOPP.js +30 -0
- package/dist/generators/_chunks/chunk-4FJ7CLEU.js +189 -0
- package/dist/generators/_chunks/chunk-5MI3O4VZ.js +99 -0
- package/dist/generators/_chunks/chunk-6ZTIUBB2.js +139 -0
- package/dist/generators/_chunks/chunk-AIJCJ7JK.js +203 -0
- package/dist/generators/_chunks/chunk-ALSXKTLM.js +1098 -0
- package/dist/generators/_chunks/chunk-AQHV7DJS.js +656 -0
- package/dist/generators/_chunks/chunk-C2VVUWBA.js +386 -0
- package/dist/generators/_chunks/chunk-EXYZLL7A.js +234 -0
- package/dist/generators/_chunks/chunk-FQHFT5IH.js +50 -0
- package/dist/generators/_chunks/chunk-H222FPLK.js +80 -0
- package/dist/generators/_chunks/chunk-IIDOBI42.js +616 -0
- package/dist/generators/_chunks/chunk-L2RZ2LBO.js +132 -0
- package/dist/generators/_chunks/chunk-MQNQJOWA.js +217 -0
- package/dist/generators/_chunks/chunk-QQNDYJTR.js +243 -0
- package/dist/generators/_chunks/chunk-SFXU6IUS.js +0 -0
- package/dist/generators/_chunks/chunk-SNBIVIMI.js +89 -0
- package/dist/generators/_chunks/chunk-TUN34LAQ.js +8 -0
- package/dist/generators/_chunks/chunk-TXAQ2GJC.js +20 -0
- package/dist/generators/_chunks/chunk-U6LAFHSF.js +49 -0
- package/dist/generators/_chunks/chunk-URV6FLUH.js +73 -0
- package/dist/generators/_chunks/chunk-YNMLB4DL.js +25 -0
- package/dist/generators/_chunks/chunk-YY2UESLM.js +282 -0
- package/dist/generators/add-agent-rules/generator.js +11 -842
- package/dist/generators/add-backend/generator.js +11 -906
- package/dist/generators/add-fsd-lite/generator.js +12 -801
- package/dist/generators/add-github-templates/generator.js +13 -116
- package/dist/generators/add-integration/generator.js +15 -743
- package/dist/generators/add-review/generator.js +12 -1231
- package/dist/generators/allow-builds/generator.js +7 -71
- package/dist/generators/doctor/generator.js +106 -2044
- package/dist/generators/entity/generator.js +15 -604
- package/dist/generators/feature/generator.js +15 -604
- package/dist/generators/init/generator.js +68 -3376
- package/dist/generators/install-integration/generator.js +35 -871
- package/dist/generators/page/generator.js +15 -604
- package/dist/generators/record-integration/generator.js +12 -778
- package/dist/generators/shared/generator.js +15 -604
- package/dist/generators/widget/generator.js +15 -604
- package/dist/index.js +5 -663
- package/package.json +1 -1
|
@@ -1,607 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return [
|
|
17
|
-
{ sourceTag: tags.frontend, notDependOnLibsWithTags: [tags.backend] },
|
|
18
|
-
{ sourceTag: tags.backend, notDependOnLibsWithTags: [tags.frontend] },
|
|
19
|
-
{ sourceTag: tags.shared, notDependOnLibsWithTags: [tags.frontend, tags.backend] }
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
var SCOPE_CONSTRAINTS = scopeConstraints();
|
|
23
|
-
|
|
24
|
-
// ../template-engine/dist/frameworks/profiles.js
|
|
25
|
-
var ALL_LAYERS = ["app", "pages", "features", "entities", "shared"];
|
|
26
|
-
var NEXT = {
|
|
27
|
-
id: "next",
|
|
28
|
-
recognised: true,
|
|
29
|
-
// Next.js's own app/ (App Router) directory is the composition root; FSD's Next.js guide
|
|
30
|
-
// names the FSD app and pages layers _app and _pages, since Next.js reads src/app and
|
|
31
|
-
// src/pages as its routers.
|
|
32
|
-
layers: ["pages", "features", "entities", "shared"],
|
|
33
|
-
layerDirs: { app: "_app", pages: "_pages" },
|
|
34
|
-
buildOutputDirs: [".next", "out"],
|
|
35
|
-
platformSuffixes: [],
|
|
36
|
-
routerDirs: ["pages"]
|
|
37
|
-
};
|
|
38
|
-
var REACT_VITE = {
|
|
39
|
-
id: "react-vite",
|
|
40
|
-
recognised: true,
|
|
41
|
-
layers: ALL_LAYERS,
|
|
42
|
-
layerDirs: {},
|
|
43
|
-
buildOutputDirs: ["dist"],
|
|
44
|
-
platformSuffixes: [],
|
|
45
|
-
routerDirs: []
|
|
46
|
-
};
|
|
47
|
-
var EXPO_ROUTER = {
|
|
48
|
-
id: "expo",
|
|
49
|
-
recognised: true,
|
|
50
|
-
layers: ALL_LAYERS,
|
|
51
|
-
layerDirs: { app: "_app" },
|
|
52
|
-
buildOutputDirs: [".expo", "dist"],
|
|
53
|
-
platformSuffixes: ["ios", "android", "native", "web"],
|
|
54
|
-
routerDirs: []
|
|
55
|
-
};
|
|
56
|
-
var EXPO = { ...EXPO_ROUTER, layerDirs: {} };
|
|
57
|
-
var GENERIC = {
|
|
58
|
-
id: "generic",
|
|
59
|
-
recognised: false,
|
|
60
|
-
layers: ALL_LAYERS,
|
|
61
|
-
layerDirs: {},
|
|
62
|
-
buildOutputDirs: ["dist", "build"],
|
|
63
|
-
platformSuffixes: [],
|
|
64
|
-
routerDirs: []
|
|
65
|
-
};
|
|
66
|
-
var PROFILES = { next: NEXT, "react-vite": REACT_VITE, expo: EXPO_ROUTER, generic: GENERIC };
|
|
67
|
-
function profileById(id) {
|
|
68
|
-
return PROFILES[id];
|
|
69
|
-
}
|
|
70
|
-
var NEXT_CONFIGS = ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"];
|
|
71
|
-
var VITE_CONFIGS = ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"];
|
|
72
|
-
var EXPO_CONFIGS = ["app.config.ts", "app.config.js", "app.config.mjs"];
|
|
73
|
-
function hasExpoKey(input, file) {
|
|
74
|
-
const text = input.read(file);
|
|
75
|
-
if (typeof text !== "string")
|
|
76
|
-
return false;
|
|
77
|
-
try {
|
|
78
|
-
return typeof JSON.parse(text)["expo"] === "object";
|
|
79
|
-
} catch {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function dependsOn(input, manifest, name) {
|
|
84
|
-
const text = input.read(manifest);
|
|
85
|
-
if (typeof text !== "string")
|
|
86
|
-
return false;
|
|
87
|
-
try {
|
|
88
|
-
const parsed = JSON.parse(text);
|
|
89
|
-
return ["dependencies", "devDependencies", "peerDependencies"].some((field) => parsed[field]?.[name] !== void 0);
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function detectFramework(input) {
|
|
95
|
-
const prefix = input.projectRoot === "" || input.projectRoot === "." ? "" : `${input.projectRoot}/`;
|
|
96
|
-
const executors = input.executors ?? [];
|
|
97
|
-
if (NEXT_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || executors.some((executor) => executor.startsWith("@nx/next:"))) {
|
|
98
|
-
return NEXT;
|
|
99
|
-
}
|
|
100
|
-
const dependsAnywhere = (name) => dependsOn(input, `${prefix}package.json`, name) || dependsOn(input, "package.json", name);
|
|
101
|
-
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");
|
|
102
|
-
if (usesExpo)
|
|
103
|
-
return dependsAnywhere("expo-router") ? EXPO_ROUTER : EXPO;
|
|
104
|
-
const usesReact = dependsAnywhere("react");
|
|
105
|
-
const usesVite = VITE_CONFIGS.some((file) => input.exists(`${prefix}${file}`)) || executors.some((executor) => executor.startsWith("@nx/vite:"));
|
|
106
|
-
if (usesVite && usesReact)
|
|
107
|
-
return REACT_VITE;
|
|
108
|
-
return GENERIC;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ../template-engine/dist/fsd/slice-rules.js
|
|
112
|
-
function toCamelCase(kebabName) {
|
|
113
|
-
return kebabName.split("-").filter((part) => part.length > 0).map((part, index) => index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
114
|
-
}
|
|
115
|
-
function resolveSlicePublicApiName(name, publicApiName) {
|
|
116
|
-
return publicApiName ?? toCamelCase(name);
|
|
117
|
-
}
|
|
118
|
-
var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
119
|
-
var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
120
|
-
"await",
|
|
121
|
-
"break",
|
|
122
|
-
"case",
|
|
123
|
-
"catch",
|
|
124
|
-
"class",
|
|
125
|
-
"const",
|
|
126
|
-
"continue",
|
|
127
|
-
"debugger",
|
|
128
|
-
"default",
|
|
129
|
-
"delete",
|
|
130
|
-
"do",
|
|
131
|
-
"else",
|
|
132
|
-
"enum",
|
|
133
|
-
"export",
|
|
134
|
-
"extends",
|
|
135
|
-
"false",
|
|
136
|
-
"finally",
|
|
137
|
-
"for",
|
|
138
|
-
"function",
|
|
139
|
-
"if",
|
|
140
|
-
"implements",
|
|
141
|
-
"import",
|
|
142
|
-
"in",
|
|
143
|
-
"instanceof",
|
|
144
|
-
"interface",
|
|
145
|
-
"let",
|
|
146
|
-
"new",
|
|
147
|
-
"null",
|
|
148
|
-
"package",
|
|
149
|
-
"private",
|
|
150
|
-
"protected",
|
|
151
|
-
"public",
|
|
152
|
-
"return",
|
|
153
|
-
"static",
|
|
154
|
-
"super",
|
|
155
|
-
"switch",
|
|
156
|
-
"this",
|
|
157
|
-
"throw",
|
|
158
|
-
"true",
|
|
159
|
-
"try",
|
|
160
|
-
"typeof",
|
|
161
|
-
"var",
|
|
162
|
-
"void",
|
|
163
|
-
"while",
|
|
164
|
-
"with",
|
|
165
|
-
"yield"
|
|
166
|
-
]);
|
|
167
|
-
function isUsableSliceApiName(candidate) {
|
|
168
|
-
return IDENTIFIER_PATTERN.test(candidate) && !RESERVED_WORDS.has(candidate);
|
|
169
|
-
}
|
|
170
|
-
var ESSENCE_SEGMENT_NAMES = [
|
|
171
|
-
"components",
|
|
172
|
-
"hooks",
|
|
173
|
-
"helpers",
|
|
174
|
-
"utils",
|
|
175
|
-
"types",
|
|
176
|
-
"constants",
|
|
177
|
-
"consts",
|
|
178
|
-
"interfaces",
|
|
179
|
-
"enums",
|
|
180
|
-
"modals",
|
|
181
|
-
"stores",
|
|
182
|
-
"services"
|
|
183
|
-
];
|
|
184
|
-
function isEssenceSegmentName(name) {
|
|
185
|
-
return ESSENCE_SEGMENT_NAMES.includes(name);
|
|
186
|
-
}
|
|
187
|
-
var SEGMENT_NAME_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
188
|
-
|
|
189
|
-
// src/generators/shared/toolkit-config-file.ts
|
|
190
|
-
var TOOLKIT_CONFIG_PATH = "toolkit.config.json";
|
|
191
|
-
function readToolkitConfigFile(tree) {
|
|
192
|
-
const raw = tree.read(TOOLKIT_CONFIG_PATH, "utf-8");
|
|
193
|
-
if (raw === null) return { raw: null, parsed: null };
|
|
194
|
-
try {
|
|
195
|
-
const parsed = JSON.parse(raw);
|
|
196
|
-
return {
|
|
197
|
-
raw,
|
|
198
|
-
parsed: typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null
|
|
199
|
-
};
|
|
200
|
-
} catch {
|
|
201
|
-
return { raw, parsed: null };
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function readToolkitConfig(tree) {
|
|
205
|
-
return readToolkitConfigFile(tree).parsed;
|
|
206
|
-
}
|
|
207
|
-
function resolveLocaleOption(option, config) {
|
|
208
|
-
if (option) return option;
|
|
209
|
-
const locale = config?.["locale"];
|
|
210
|
-
return locale === "ko" || locale === "en" ? locale : "en";
|
|
211
|
-
}
|
|
212
|
-
var RECORDED_FRONTENDS = ["react-vite", "next", "expo", "generic", "none"];
|
|
213
|
-
function recordedFrontends(config) {
|
|
214
|
-
const frontends = config?.["frontends"];
|
|
215
|
-
if (!Array.isArray(frontends)) return [];
|
|
216
|
-
return frontends.flatMap(
|
|
217
|
-
(entry) => typeof entry === "object" && entry !== null && typeof entry["project"] === "string" ? [entry.project] : []
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// src/generators/shared/frontend-app.ts
|
|
222
|
-
import { getProjects } from "@nx/devkit";
|
|
223
|
-
|
|
224
|
-
// src/generators/shared/project-type.ts
|
|
225
|
-
import { parseJson } from "@nx/devkit";
|
|
226
|
-
function resolveProjectType(tree, root, declared) {
|
|
227
|
-
if (declared) return declared === "library" ? "library" : "application";
|
|
228
|
-
const prefix = root === "" || root === "." ? "" : `${root}/`;
|
|
229
|
-
if (tree.exists(`${prefix}tsconfig.lib.json`)) return "library";
|
|
230
|
-
if (tree.exists(`${prefix}tsconfig.app.json`)) return "application";
|
|
231
|
-
const raw = tree.read(`${prefix}package.json`, "utf-8");
|
|
232
|
-
if (raw === null) return "library";
|
|
233
|
-
try {
|
|
234
|
-
const manifest = parseJson(raw);
|
|
235
|
-
if (manifest && !["exports", "main", "module", "bin"].some((key) => Boolean(manifest[key]))) return "application";
|
|
236
|
-
} catch {
|
|
237
|
-
}
|
|
238
|
-
return "library";
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// src/generators/shared/frontend-app.ts
|
|
242
|
-
var baseNameOf = (root) => root.split("/").pop() ?? root;
|
|
243
|
-
function isEndToEnd(root) {
|
|
244
|
-
const dir = baseNameOf(root);
|
|
245
|
-
return dir === "e2e" || dir.endsWith("-e2e");
|
|
246
|
-
}
|
|
247
|
-
function isBackend(root, config, backendTag) {
|
|
248
|
-
return baseNameOf(root) === "api" || (config.tags ?? []).includes(backendTag);
|
|
249
|
-
}
|
|
250
|
-
function prefixOf(root) {
|
|
251
|
-
return root === "" || root === "." ? "" : `${root}/`;
|
|
252
|
-
}
|
|
253
|
-
function sourceRootOf(tree, config) {
|
|
254
|
-
const prefix = prefixOf(config.root);
|
|
255
|
-
const declared = config.sourceRoot?.replace(/\/$/, "");
|
|
256
|
-
const atRoot = declared !== void 0 && (declared === config.root || prefix === "" && (declared === "." || declared === ""));
|
|
257
|
-
if (declared && !atRoot) return declared;
|
|
258
|
-
if (atRoot && !tree.exists(`${prefix}src`)) return prefix === "" ? "." : config.root;
|
|
259
|
-
return `${prefix}src`;
|
|
260
|
-
}
|
|
261
|
-
function frameworkOf(tree, config) {
|
|
262
|
-
return detectFramework({
|
|
263
|
-
exists: (path) => tree.exists(path),
|
|
264
|
-
read: (path) => tree.read(path, "utf-8"),
|
|
265
|
-
projectRoot: config.root,
|
|
266
|
-
executors: Object.values(config.targets ?? {}).flatMap((target) => target.executor ? [target.executor] : [])
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
function resolveFrontendApp(tree, explicitProject) {
|
|
270
|
-
let projects;
|
|
271
|
-
try {
|
|
272
|
-
projects = getProjects(tree);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
275
|
-
const pathMatch = /Cannot parse ([^:]+):/.exec(message);
|
|
276
|
-
return { kind: "invalid-project-json", detail: pathMatch ? pathMatch[1] : message };
|
|
277
|
-
}
|
|
278
|
-
const found = (project2, config2) => {
|
|
279
|
-
const profile = frameworkOf(tree, config2);
|
|
280
|
-
return { kind: "found", project: project2, root: config2.root, sourceRoot: sourceRootOf(tree, config2), framework: profile.id, profile };
|
|
281
|
-
};
|
|
282
|
-
if (explicitProject !== void 0) {
|
|
283
|
-
const config2 = projects.get(explicitProject);
|
|
284
|
-
if (config2) return found(explicitProject, config2);
|
|
285
|
-
const rootApp = inferredRootApp(tree, projects);
|
|
286
|
-
return rootApp?.kind === "found" && rootApp.project === explicitProject ? rootApp : { kind: "not-found", project: explicitProject };
|
|
287
|
-
}
|
|
288
|
-
const backendTag = readBoundaryTags(readToolkitConfig(tree)).backend;
|
|
289
|
-
const eligible = [...projects.entries()].filter(([, config2]) => !isEndToEnd(config2.root) && !isBackend(config2.root, config2, backendTag));
|
|
290
|
-
const declared = eligible.filter(([, config2]) => config2.projectType === "application");
|
|
291
|
-
const inferred = eligible.filter(([, config2]) => {
|
|
292
|
-
if (config2.projectType !== void 0) return false;
|
|
293
|
-
if (config2.root === "" || config2.root === ".") return frameworkOf(tree, config2).recognised;
|
|
294
|
-
return resolveProjectType(tree, config2.root, void 0) === "application";
|
|
295
|
-
});
|
|
296
|
-
const candidates = (declared.length > 0 ? declared : inferred).sort(([a], [b]) => a.localeCompare(b));
|
|
297
|
-
if (candidates.length === 0) {
|
|
298
|
-
const rootApp = inferredRootApp(tree, projects);
|
|
299
|
-
if (rootApp !== null) return rootApp;
|
|
300
|
-
const otherProjects = [...projects.keys()].filter((name) => !isEndToEnd(projects.get(name)?.root ?? "")).sort();
|
|
301
|
-
return { kind: "none", otherProjects };
|
|
302
|
-
}
|
|
303
|
-
if (candidates.length > 1) return { kind: "ambiguous", candidates: candidates.map(([name]) => name) };
|
|
304
|
-
const [project, config] = candidates[0];
|
|
305
|
-
return found(project, config);
|
|
306
|
-
}
|
|
307
|
-
function inferredRootApp(tree, projects) {
|
|
308
|
-
if ([...projects.values()].some((config2) => config2.root === "" || config2.root === ".")) return null;
|
|
309
|
-
const raw = tree.read("package.json", "utf-8");
|
|
310
|
-
if (raw === null) return null;
|
|
311
|
-
let name;
|
|
312
|
-
try {
|
|
313
|
-
name = JSON.parse(raw).name;
|
|
314
|
-
} catch {
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
if (typeof name !== "string" || name === "") return null;
|
|
318
|
-
const config = { name, root: "." };
|
|
319
|
-
const framework = frameworkOf(tree, config);
|
|
320
|
-
if (!framework.recognised) return null;
|
|
321
|
-
return { kind: "found", project: name, root: ".", sourceRoot: sourceRootOf(tree, config), framework: framework.id, profile: framework, inferredRoot: true };
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// src/generators/shared/template-files.ts
|
|
325
|
-
import { existsSync } from "node:fs";
|
|
326
|
-
import { fileURLToPath } from "node:url";
|
|
327
|
-
import { generateFiles } from "@nx/devkit";
|
|
328
|
-
import { createTree } from "@nx/devkit/testing";
|
|
329
|
-
function generatorFilesDir(generator, fromUrl) {
|
|
330
|
-
for (const relative of [`../${generator}/files`, `./generators/${generator}/files`]) {
|
|
331
|
-
const dir = fileURLToPath(new URL(relative, fromUrl));
|
|
332
|
-
if (existsSync(dir)) return dir;
|
|
333
|
-
}
|
|
334
|
-
throw new Error(`The "${generator}" generator's template files are missing next to ${fileURLToPath(fromUrl)}`);
|
|
335
|
-
}
|
|
336
|
-
function renderTemplateFiles(srcFolder, target, substitutions) {
|
|
337
|
-
const scratch = createTree();
|
|
338
|
-
const seeded = new Set(scratch.listChanges().map((change) => change.path));
|
|
339
|
-
generateFiles(scratch, srcFolder, target, substitutions);
|
|
340
|
-
return scratch.listChanges().flatMap(
|
|
341
|
-
(change) => change.type === "CREATE" && change.content !== null && !seeded.has(change.path) ? [{ filePath: change.path, content: change.content.toString("utf-8") }] : []
|
|
342
|
-
).sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// src/generators/slice/core.ts
|
|
346
|
-
function composeSlice(input) {
|
|
347
|
-
const files = generatorFilesDir("slice", import.meta.url);
|
|
348
|
-
const directory = `${input.sourceRoot}/${input.layerDirName}/${input.group === void 0 ? "" : `${input.group}/`}${input.name}`;
|
|
349
|
-
const shared = input.layer === "shared";
|
|
350
|
-
const values = {
|
|
351
|
-
name: input.name,
|
|
352
|
-
publicApiName: input.publicApiName,
|
|
353
|
-
layer: input.layer,
|
|
354
|
-
locale: input.locale,
|
|
355
|
-
modulePath: shared ? `./${input.name}` : `./model/${input.name}`
|
|
356
|
-
};
|
|
357
|
-
const module = renderTemplateFiles(join(files, "module"), shared ? directory : `${directory}/model`, values).filter((file) => input.withSpec !== false || !file.filePath.endsWith(".spec.ts")).sort((a, b) => Number(a.filePath.endsWith(".spec.ts")) - Number(b.filePath.endsWith(".spec.ts")));
|
|
358
|
-
const segments = shared ? [] : Array.from(new Set(input.segments)).filter((segment) => segment !== "model").flatMap((segment) => renderTemplateFiles(join(files, "segment"), `${directory}/${segment}`, { ...values, segment }));
|
|
359
|
-
return {
|
|
360
|
-
directory,
|
|
361
|
-
publicApiName: input.publicApiName,
|
|
362
|
-
files: [...renderTemplateFiles(join(files, "index"), directory, values), ...module, ...segments]
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
var KEBAB_CASE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
366
|
-
function layerDirNameFor(layer, frontend) {
|
|
367
|
-
return profileById(frontend).layerDirs[layer] ?? layer;
|
|
368
|
-
}
|
|
369
|
-
function layerDirFor(layer, sourceRoot, frontend = "react-vite") {
|
|
370
|
-
return `${sourceRoot}/${layerDirNameFor(layer, frontend)}`;
|
|
371
|
-
}
|
|
372
|
-
function resolveFrontendForApp(option, config, detected) {
|
|
373
|
-
if (option !== void 0) return option;
|
|
374
|
-
const recorded = config?.["frontend"];
|
|
375
|
-
if (typeof recorded === "string" && RECORDED_FRONTENDS.includes(recorded) && recorded !== "none") {
|
|
376
|
-
return recorded;
|
|
377
|
-
}
|
|
378
|
-
return detected;
|
|
379
|
-
}
|
|
380
|
-
function listFilesUnder(tree, dir, prefix = "") {
|
|
381
|
-
return tree.children(dir).flatMap((child) => {
|
|
382
|
-
const path = `${dir}/${child}`;
|
|
383
|
-
return tree.isFile(path) ? [`${prefix}${child}`] : listFilesUnder(tree, path, `${prefix}${child}/`);
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
function computeSliceAction(tree, plan) {
|
|
387
|
-
if (!tree.exists(plan.directory)) return "created";
|
|
388
|
-
const expectedNames = plan.files.map((f) => f.filePath.slice(plan.directory.length + 1)).sort();
|
|
389
|
-
const actualNames = listFilesUnder(tree, plan.directory).sort();
|
|
390
|
-
if (actualNames.length !== expectedNames.length || !actualNames.every((n, i) => n === expectedNames[i])) {
|
|
391
|
-
return "skipped";
|
|
392
|
-
}
|
|
393
|
-
const allMatch = plan.files.every((f) => tree.read(f.filePath, "utf-8") === f.content);
|
|
394
|
-
return allMatch ? "unchanged" : "skipped";
|
|
395
|
-
}
|
|
396
|
-
function refusal(reason, dryRun, layer, name, app, extra = {}) {
|
|
397
|
-
const layerDir = layerDirFor(layer, app.sourceRoot);
|
|
398
|
-
return {
|
|
399
|
-
dryRun,
|
|
400
|
-
layer,
|
|
401
|
-
name,
|
|
402
|
-
project: app.project,
|
|
403
|
-
layerDir,
|
|
404
|
-
directory: `${layerDir}/${name}`,
|
|
405
|
-
publicApiName: null,
|
|
406
|
-
refused: true,
|
|
407
|
-
refusalReason: reason,
|
|
408
|
-
action: null,
|
|
409
|
-
files: [],
|
|
410
|
-
...extra
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
function runFsdSlice(tree, layer, options) {
|
|
414
|
-
const dryRun = options.dryRun ?? false;
|
|
415
|
-
const unresolved = { project: null, sourceRoot: "<app>/src" };
|
|
416
|
-
if (!KEBAB_CASE.test(options.name)) {
|
|
417
|
-
return refusal("invalid-name", dryRun, layer, options.name, unresolved);
|
|
418
|
-
}
|
|
419
|
-
if (!isUsableSliceApiName(resolveSlicePublicApiName(options.name, options.publicApiName))) {
|
|
420
|
-
return refusal("invalid-public-api-name", dryRun, layer, options.name, unresolved);
|
|
421
|
-
}
|
|
422
|
-
if (layer === "shared" && isEssenceSegmentName(options.name)) {
|
|
423
|
-
return refusal("segment-by-essence", dryRun, layer, options.name, unresolved);
|
|
424
|
-
}
|
|
425
|
-
const segments = Array.from(new Set(options.segments ?? []));
|
|
426
|
-
if (segments.length > 0 && layer === "shared") {
|
|
427
|
-
return refusal("segments-on-shared", dryRun, layer, options.name, unresolved);
|
|
428
|
-
}
|
|
429
|
-
const badSegment = segments.find((segment) => !SEGMENT_NAME_PATTERN.test(segment));
|
|
430
|
-
if (badSegment !== void 0) return refusal("invalid-segment", dryRun, layer, options.name, unresolved, { detail: badSegment });
|
|
431
|
-
const essenceSegment = segments.find((segment) => isEssenceSegmentName(segment));
|
|
432
|
-
if (essenceSegment !== void 0) return refusal("segment-by-essence", dryRun, layer, options.name, unresolved, { detail: essenceSegment });
|
|
433
|
-
if (options.group !== void 0 && layer === "shared") return refusal("group-on-shared", dryRun, layer, options.name, unresolved);
|
|
434
|
-
if (options.group !== void 0 && (!KEBAB_CASE.test(options.group) || SEGMENT_FOLDERS.includes(options.group) || isEssenceSegmentName(options.group))) {
|
|
435
|
-
return refusal("invalid-group", dryRun, layer, options.name, unresolved, { detail: options.group });
|
|
436
|
-
}
|
|
437
|
-
const app = resolveFrontendApp(tree, options.project);
|
|
438
|
-
if (app.kind === "not-found") return refusal("project-not-found", dryRun, layer, options.name, { project: app.project, sourceRoot: unresolved.sourceRoot });
|
|
439
|
-
if (app.kind === "ambiguous") {
|
|
440
|
-
return refusal("ambiguous-frontend", dryRun, layer, options.name, unresolved, { candidates: app.candidates, recordedFrontends: recordedFrontends(readToolkitConfig(tree)) });
|
|
441
|
-
}
|
|
442
|
-
if (app.kind === "invalid-project-json") return refusal("invalid-project-json", dryRun, layer, options.name, unresolved, { detail: app.detail });
|
|
443
|
-
if (app.kind === "none") return refusal("no-frontend-app", dryRun, layer, options.name, unresolved, { candidates: app.otherProjects });
|
|
444
|
-
const config = readToolkitConfig(tree);
|
|
445
|
-
const frontend = resolveFrontendForApp(options.frontend, config, app.framework);
|
|
446
|
-
const layerDir = layerDirFor(layer, app.sourceRoot, frontend);
|
|
447
|
-
const locale = resolveLocaleOption(options.locale, config);
|
|
448
|
-
const requested = options.unitTestRunner !== void 0 && options.unitTestRunner !== "auto" ? options.unitTestRunner : recordedUnitTestRunner(config);
|
|
449
|
-
const runner = requested ?? detectUnitTestRunner(tree, app.root);
|
|
450
|
-
const plan = composeSlice({
|
|
451
|
-
layer,
|
|
452
|
-
name: options.name,
|
|
453
|
-
publicApiName: resolveSlicePublicApiName(options.name, options.publicApiName),
|
|
454
|
-
locale,
|
|
455
|
-
sourceRoot: app.sourceRoot,
|
|
456
|
-
layerDirName: layerDirNameFor(layer, frontend),
|
|
457
|
-
segments,
|
|
458
|
-
group: options.group,
|
|
459
|
-
withSpec: runner !== "none"
|
|
460
|
-
});
|
|
461
|
-
const action = computeSliceAction(tree, plan);
|
|
462
|
-
if (action === "created" && !dryRun) {
|
|
463
|
-
for (const file of plan.files) tree.write(file.filePath, file.content);
|
|
464
|
-
}
|
|
465
|
-
return {
|
|
466
|
-
dryRun,
|
|
467
|
-
layer,
|
|
468
|
-
name: options.name,
|
|
469
|
-
project: app.project,
|
|
470
|
-
layerDir,
|
|
471
|
-
directory: plan.directory,
|
|
472
|
-
publicApiName: plan.publicApiName,
|
|
473
|
-
refused: false,
|
|
474
|
-
refusalReason: null,
|
|
475
|
-
action,
|
|
476
|
-
files: plan.files,
|
|
477
|
-
...runner === "none" ? { specSkipped: requested === "none" ? "none-requested" : "no-test-runner" } : {}
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
var SEGMENT_FOLDERS = ["ui", "model", "api", "lib", "config"];
|
|
481
|
-
function recordedUnitTestRunner(config) {
|
|
482
|
-
const fsd = config?.["fsd"];
|
|
483
|
-
const value = typeof fsd === "object" && fsd !== null ? fsd["unitTestRunner"] : void 0;
|
|
484
|
-
return value === "jest" || value === "vitest" || value === "none" ? value : void 0;
|
|
485
|
-
}
|
|
486
|
-
function hasDependency(tree, manifest, name) {
|
|
487
|
-
const raw = tree.read(manifest, "utf-8");
|
|
488
|
-
if (raw === null) return false;
|
|
489
|
-
try {
|
|
490
|
-
const parsed = JSON.parse(raw);
|
|
491
|
-
return parsed["dependencies"]?.[name] !== void 0 || parsed["devDependencies"]?.[name] !== void 0;
|
|
492
|
-
} catch {
|
|
493
|
-
return false;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
function detectUnitTestRunner(tree, projectRoot) {
|
|
497
|
-
const prefix = projectRoot === "" || projectRoot === "." ? "" : `${projectRoot}/`;
|
|
498
|
-
const project = tree.read(`${prefix}project.json`, "utf-8");
|
|
499
|
-
if (project !== null) {
|
|
500
|
-
try {
|
|
501
|
-
const targets = JSON.parse(project).targets ?? {};
|
|
502
|
-
for (const target of Object.values(targets)) {
|
|
503
|
-
if (target?.executor?.startsWith("@nx/jest:")) return "jest";
|
|
504
|
-
if (target?.executor?.startsWith("@nx/vite:test") || target?.executor?.startsWith("@nx/vitest:")) return "vitest";
|
|
505
|
-
}
|
|
506
|
-
} catch {
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
for (const dir of prefix === "" ? [""] : [prefix, ""]) {
|
|
510
|
-
if (["ts", "mts", "cts", "js", "mjs", "cjs"].some((ext) => tree.exists(`${dir}jest.config.${ext}`))) return "jest";
|
|
511
|
-
if (["ts", "mts", "js", "mjs"].some((ext) => tree.exists(`${dir}vitest.config.${ext}`) || tree.exists(`${dir}vitest.workspace.${ext}`))) return "vitest";
|
|
512
|
-
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
513
|
-
const vite = tree.read(`${dir}vite.config.${ext}`, "utf-8");
|
|
514
|
-
if (vite !== null && /\btest\s*:/.test(vite)) return "vitest";
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
for (const manifest of prefix === "" ? ["package.json"] : [`${prefix}package.json`, "package.json"]) {
|
|
518
|
-
if (hasDependency(tree, manifest, "vitest")) return "vitest";
|
|
519
|
-
if (hasDependency(tree, manifest, "jest")) return "jest";
|
|
520
|
-
}
|
|
521
|
-
return "none";
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// src/generators/slice/render.ts
|
|
525
|
-
var ACTION_LINES = {
|
|
526
|
-
created: "created",
|
|
527
|
-
unchanged: "unchanged (already matches the generated content)",
|
|
528
|
-
skipped: "skipped \u2014 a slice with this name already exists with different content"
|
|
529
|
-
};
|
|
530
|
-
function renderSliceReport(report) {
|
|
531
|
-
const lines = [];
|
|
532
|
-
const label = `${report.layer} "${report.name}"`;
|
|
533
|
-
if (report.refused) {
|
|
534
|
-
lines.push(`${label}: refused \u2014 nothing was written.`);
|
|
535
|
-
lines.push("");
|
|
536
|
-
if (report.refusalReason === "invalid-name") {
|
|
537
|
-
lines.push(` "${report.name}" is not a valid kebab-case slice name.`);
|
|
538
|
-
lines.push(' Use lowercase letters, digits, and single hyphens, starting with a letter (e.g. "add-to-cart").');
|
|
539
|
-
} else if (report.refusalReason === "invalid-public-api-name") {
|
|
540
|
-
lines.push(" The public API name is not a usable JavaScript identifier (or is a reserved word).");
|
|
541
|
-
lines.push(' Pass --publicApiName with a valid identifier, e.g. "addToCart" (this also happens when');
|
|
542
|
-
lines.push(' the slice name camelCases into a reserved word, like "class" or "delete").');
|
|
543
|
-
} else if (report.refusalReason === "project-not-found") {
|
|
544
|
-
lines.push(` --project=${report.project} names no project in this workspace.`);
|
|
545
|
-
} else if (report.refusalReason === "ambiguous-frontend") {
|
|
546
|
-
lines.push(" More than one frontend app was found \u2014 rerun with --project=<one of them>:");
|
|
547
|
-
for (const name of report.candidates ?? []) lines.push(` - ${name}`);
|
|
548
|
-
if ((report.recordedFrontends ?? []).length > 0) lines.push(` (toolkit.config.json records: ${(report.recordedFrontends ?? []).join(", ")})`);
|
|
549
|
-
} else if (report.refusalReason === "invalid-project-json") {
|
|
550
|
-
lines.push(` A project.json could not be parsed (${report.detail ?? "unknown"}) \u2014 fix it, then rerun.`);
|
|
551
|
-
} else if (report.refusalReason === "no-frontend-app" && (report.candidates ?? []).length > 0) {
|
|
552
|
-
lines.push(" No project was recognised as a frontend app. If one of these is it, rerun with --project=<name>:");
|
|
553
|
-
for (const name of report.candidates ?? []) lines.push(` - ${name}`);
|
|
554
|
-
} else if (report.refusalReason === "no-frontend-app") {
|
|
555
|
-
lines.push(" The workspace has no projects to hold FSD layers \u2014 create a frontend app first.");
|
|
556
|
-
} else if (report.refusalReason === "invalid-segment") {
|
|
557
|
-
lines.push(` --segments: "${report.detail ?? ""}" is not a valid segment name.`);
|
|
558
|
-
lines.push(" Use lowercase letters, digits, and single hyphens, starting with a letter (e.g. ui, api, lib).");
|
|
559
|
-
} else if (report.refusalReason === "group-on-shared") {
|
|
560
|
-
lines.push(" --group does not apply to shared: shared holds segments, not slices.");
|
|
561
|
-
} else if (report.refusalReason === "invalid-group") {
|
|
562
|
-
lines.push(` --group: "${report.detail ?? ""}" is not a usable group name.`);
|
|
563
|
-
lines.push(' Use kebab-case (e.g. "mypage"), and not a segment name (ui, model, api, lib, config).');
|
|
564
|
-
} else if (report.refusalReason === "segments-on-shared") {
|
|
565
|
-
lines.push(" --segments does not apply to shared: a shared segment holds no segments of its own.");
|
|
566
|
-
lines.push(" Run the generator once per segment instead (e.g. --name=ui, then --name=api).");
|
|
567
|
-
} else {
|
|
568
|
-
const offender = report.detail ?? report.name;
|
|
569
|
-
lines.push(` "${offender}" names what a segment contains, not what it is for.`);
|
|
570
|
-
lines.push(" Canonical FSD names segments by purpose \u2014 ui, api, model, lib, config \u2014 so the code stays");
|
|
571
|
-
lines.push(" findable by what it does (e.g. a date helper lives in shared/lib, a Button in shared/ui).");
|
|
572
|
-
}
|
|
573
|
-
return lines.join("\n");
|
|
574
|
-
}
|
|
575
|
-
if (report.dryRun) {
|
|
576
|
-
lines.push("dry-run \u2014 no files were written.");
|
|
577
|
-
lines.push("");
|
|
578
|
-
}
|
|
579
|
-
lines.push(`${label}: ${ACTION_LINES[report.action]}`);
|
|
580
|
-
if (report.action === "skipped") {
|
|
581
|
-
lines.push(` ${report.directory} already exists and does not exactly match the generated content.`);
|
|
582
|
-
lines.push(" Pick a new slice name (a name collision is a signal to rename, not to merge)");
|
|
583
|
-
lines.push(" \u2014 or remove the existing directory manually first if you intended to replace it.");
|
|
584
|
-
return lines.join("\n");
|
|
585
|
-
}
|
|
586
|
-
if (report.specSkipped === "no-test-runner") {
|
|
587
|
-
lines.push(" no test runner found (jest or vitest) \u2014 no .spec.ts was written; pass --unitTestRunner=jest|vitest to add one.");
|
|
588
|
-
} else if (report.specSkipped === "none-requested") {
|
|
589
|
-
lines.push(" no .spec.ts was written (unitTestRunner: none).");
|
|
590
|
-
}
|
|
591
|
-
if (report.dryRun) {
|
|
592
|
-
for (const file of report.files) {
|
|
593
|
-
lines.push("");
|
|
594
|
-
lines.push(`${file.filePath}:`);
|
|
595
|
-
lines.push(file.content);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
return lines.join("\n");
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// src/generators/shared/dry-run.ts
|
|
602
|
-
function resolveDryRun(options) {
|
|
603
|
-
return process.env["NX_DRY_RUN"] === "true" || (options.dryRun ?? false);
|
|
604
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
renderSliceReport
|
|
3
|
+
} from "../_chunks/chunk-H222FPLK.js";
|
|
4
|
+
import {
|
|
5
|
+
runFsdSlice
|
|
6
|
+
} from "../_chunks/chunk-AIJCJ7JK.js";
|
|
7
|
+
import "../_chunks/chunk-2WSX5YNB.js";
|
|
8
|
+
import "../_chunks/chunk-TXAQ2GJC.js";
|
|
9
|
+
import "../_chunks/chunk-6ZTIUBB2.js";
|
|
10
|
+
import "../_chunks/chunk-YNMLB4DL.js";
|
|
11
|
+
import {
|
|
12
|
+
resolveDryRun
|
|
13
|
+
} from "../_chunks/chunk-TUN34LAQ.js";
|
|
14
|
+
import "../_chunks/chunk-EXYZLL7A.js";
|
|
15
|
+
import "../_chunks/chunk-FQHFT5IH.js";
|
|
605
16
|
|
|
606
17
|
// src/generators/entity/generator.ts
|
|
607
18
|
async function entityGenerator(tree, options) {
|