@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
@@ -0,0 +1,386 @@
1
+ import {
2
+ addSharedTypesNpmWorkspace,
3
+ addSharedTypesWorkspaceGlob,
4
+ composeBackendWiring,
5
+ readSharedTypesConfig
6
+ } from "./chunk-QQNDYJTR.js";
7
+ import {
8
+ resolveProjectType
9
+ } from "./chunk-TXAQ2GJC.js";
10
+ import {
11
+ ESLINT_CONFIG_CANDIDATES,
12
+ applyBoundaryConstraints,
13
+ buildBoundarySnippet,
14
+ readBoundaryTags
15
+ } from "./chunk-6ZTIUBB2.js";
16
+ import {
17
+ resolveDryRun
18
+ } from "./chunk-TUN34LAQ.js";
19
+ import {
20
+ TOOLKIT_CONFIG_PATH,
21
+ appendJsonArrayItem,
22
+ applyJsonEdits,
23
+ readToolkitConfig,
24
+ readToolkitConfigFile,
25
+ setJsonValue,
26
+ treePackageManager
27
+ } from "./chunk-EXYZLL7A.js";
28
+ import {
29
+ detectYarnFlavor,
30
+ packageManagerCommands
31
+ } from "./chunk-FQHFT5IH.js";
32
+
33
+ // src/generators/add-backend/generator.ts
34
+ import { getProjects } from "@nx/devkit";
35
+
36
+ // src/generators/add-backend/render.ts
37
+ function renderAddBackendBody(report) {
38
+ const lines = [];
39
+ if (report.refused === "no-frontend") {
40
+ lines.push(`Refused: no frontend application and no "${report.apiName}" app found in this workspace.`);
41
+ lines.push(" add-backend wires a backend app \u2014 beside a frontend, or on its own in a backend-only");
42
+ lines.push(` workspace (create one with: npx create-nx-workspace --preset=${report.backend} --appName=${report.apiName}).`);
43
+ lines.push(" If the backend app has another name, pass --apiName=<its directory name>.");
44
+ return lines.join("\n");
45
+ }
46
+ if (report.refused === "frontend-not-found") {
47
+ lines.push(`Refused: --frontendProject "${report.frontendProject}" does not match any project.`);
48
+ lines.push(" Nothing was written. Check the name with `nx show projects`.");
49
+ return lines.join("\n");
50
+ }
51
+ if (report.refused === "ambiguous-frontend") {
52
+ lines.push("Refused: multiple frontend applications found \u2014 add-backend will not guess:");
53
+ for (const name of report.frontendCandidates ?? []) lines.push(` - ${name}`);
54
+ lines.push(" Nothing was written. Re-run with --frontendProject=<name> to pick one.");
55
+ return lines.join("\n");
56
+ }
57
+ if (report.refused === "invalid-project-json") {
58
+ lines.push("Refused: a project.json this run must update could not be parsed:");
59
+ for (const path of report.invalidProjects ?? []) lines.push(` - ${path}`);
60
+ lines.push(" Nothing was written \u2014 fix the JSON, then re-run add-backend.");
61
+ return lines.join("\n");
62
+ }
63
+ if (report.refused === "missing-api-project") {
64
+ lines.push(`Refused: no "${report.apiName}" application project found.`);
65
+ lines.push(" Create it with the official commands first (copy-paste, names already filled in),");
66
+ lines.push(" then re-run add-backend to wire it up:");
67
+ lines.push("");
68
+ for (const command of report.commands ?? []) lines.push(` ${command}`);
69
+ if (report.expressRange !== void 0) {
70
+ lines.push("");
71
+ lines.push(` The express line declares express@${report.expressRange}, the range this workspace's @nx/react supports \u2014`);
72
+ lines.push(" the official generator then keeps it instead of adding express 5, which npm rejects as a peer conflict.");
73
+ }
74
+ return lines.join("\n");
75
+ }
76
+ if (report.packageManagerNote !== void 0) lines.push(` note: ${report.packageManagerNote}`);
77
+ lines.push(` frontend project: ${report.frontendProject ?? "none (backend-only workspace)"}`);
78
+ const libPath = report.sharedTypesRoot ?? "libs/shared-types";
79
+ lines.push(
80
+ report.sharedTypes === "created" ? ` ${libPath}: created (import it as ${report.sharedTypesPackage})` : report.sharedTypes === "disabled" ? " shared-types lib: none (sharedTypes: false in toolkit.config.json)" : ` ${libPath}: already exists \u2014 skipped`
81
+ );
82
+ if (report.config !== void 0 && report.config !== "unchanged") {
83
+ lines.push(` toolkit.config.json: backend "${report.backend}" recorded (${report.config}) \u2014 doctor's backend checks now run`);
84
+ }
85
+ if (report.workspaceGlob !== void 0) {
86
+ const pmName = report.packageManager ?? "pnpm";
87
+ const registry = packageManagerCommands(pmName).workspaceRegistration;
88
+ lines.push(
89
+ {
90
+ added: ` ${registry}: '${libPath}' registered \u2014 run \`${pmName} install\` once to link it`,
91
+ present: ` ${registry}: already covers ${libPath}`,
92
+ unrecognized: (
93
+ // an integrated layout lists no workspace packages: the paths alias below links it
94
+ report.tsconfigPaths === "added" || report.tsconfigPaths === "present" ? ` ${registry}: no workspace package list (integrated layout) \u2014 the tsconfig paths alias links ${libPath}` : ` ${registry}: shape not recognized \u2014 add '${libPath}' to its workspace list yourself`
95
+ )
96
+ }[report.workspaceGlob]
97
+ );
98
+ }
99
+ for (const tag of report.tags) {
100
+ lines.push(` tag ${tag.project}: ${tag.action === "added" ? "added" : "already present"}`);
101
+ }
102
+ lines.push(
103
+ {
104
+ added: ` tsconfig.base.json paths: ${report.sharedTypesPackage} added (a classic or integrated setup \u2014 libs resolve through paths)`,
105
+ present: " tsconfig.base.json paths: already wired",
106
+ "workspace-link": " tsconfig paths: not needed \u2014 the workspace package link is the wiring",
107
+ unrecognized: " tsconfig.base.json: could not be parsed \u2014 left untouched",
108
+ none: " tsconfig paths: nothing to wire (no shared-types lib)"
109
+ }[report.tsconfigPaths]
110
+ );
111
+ switch (report.eslint) {
112
+ case "applied":
113
+ lines.push(" ESLint boundaries: cross-side deny rules written (existing imports stay allowed)");
114
+ break;
115
+ case "already-applied":
116
+ lines.push(" ESLint boundaries: already enforced \u2014 no change");
117
+ break;
118
+ case "degraded-unrecognized":
119
+ lines.push(" ESLint boundaries: your config was NOT recognized \u2014 left untouched.");
120
+ lines.push(" Merge this yourself (then boundaries are actually enforced):");
121
+ break;
122
+ case "snippet-only":
123
+ lines.push(" ESLint boundaries: report-only (pass --applyEslint to write them).");
124
+ lines.push(" Paste-ready snippet:");
125
+ break;
126
+ }
127
+ if (report.snippet) {
128
+ lines.push("");
129
+ lines.push(report.snippet);
130
+ }
131
+ return lines.join("\n");
132
+ }
133
+ function renderAddBackendReport(report, dryRun) {
134
+ return [`add-backend (${report.backend})${dryRun ? " \u2014 dry run" : ""}`, "", renderAddBackendBody(report)].join("\n");
135
+ }
136
+
137
+ // src/generators/add-backend/generator.ts
138
+ function runAddBackend(tree, options) {
139
+ const backend = options.backend;
140
+ const apiName = options.apiName ?? "api";
141
+ const applyEslint = options.applyEslint ?? false;
142
+ const dryRun = options.dryRun ?? false;
143
+ const write = !dryRun;
144
+ const base = { backend, apiName };
145
+ const refusalShape = {
146
+ sharedTypes: "skipped",
147
+ tags: [],
148
+ tsconfigPaths: "unrecognized",
149
+ eslint: "snippet-only"
150
+ };
151
+ let projects;
152
+ try {
153
+ projects = getProjects(tree);
154
+ } catch (error) {
155
+ const message = error instanceof Error ? error.message : String(error);
156
+ const pathMatch = /Cannot parse ([^:]+):/.exec(message);
157
+ return {
158
+ ...base,
159
+ refused: "invalid-project-json",
160
+ invalidProjects: [pathMatch ? pathMatch[1] : message],
161
+ ...refusalShape
162
+ };
163
+ }
164
+ const detectedPm = treePackageManager(tree, readToolkitConfig(tree));
165
+ const packageManager = detectedPm ?? "pnpm";
166
+ const pmCommands = packageManagerCommands(
167
+ packageManager,
168
+ packageManager === "yarn" ? detectYarnFlavor((p) => tree.exists(p), (p) => tree.read(p, "utf-8")) : void 0
169
+ );
170
+ const packageManagerNote = detectedPm === null ? "the package manager could not be detected (no pnpm, yarn, bun or npm markers) \u2014 commands below assume pnpm." : void 0;
171
+ const baseNameOf = (root) => root.split("/").pop() ?? root;
172
+ const entries = [...projects.entries()];
173
+ const declaredApp = ([, config]) => config.projectType === "application";
174
+ const inferredApp = ([, config]) => config.projectType === void 0 && config.root !== "" && config.root !== "." && resolveProjectType(tree, config.root, void 0) === "application";
175
+ const apiNamed = entries.filter(([, config]) => baseNameOf(config.root) === apiName);
176
+ const apiEntry = apiNamed.find(declaredApp) ?? apiNamed.find((entry) => inferredApp(entry) && tree.exists(`${entry[1].root}/tsconfig.app.json`));
177
+ let frontendProject;
178
+ if (options.frontendProject !== void 0) {
179
+ if (!projects.has(options.frontendProject)) {
180
+ return { ...base, refused: "frontend-not-found", frontendProject: options.frontendProject, ...refusalShape };
181
+ }
182
+ frontendProject = options.frontendProject;
183
+ } else {
184
+ const eligible = entries.filter(([, config]) => {
185
+ const dir = baseNameOf(config.root);
186
+ return dir !== apiName && dir !== "e2e" && !dir.endsWith("-e2e");
187
+ });
188
+ const declared = eligible.filter(declaredApp);
189
+ const candidates = (declared.length > 0 ? declared : eligible.filter(inferredApp)).map(([name]) => name).sort();
190
+ if (candidates.length === 0 && !apiEntry) {
191
+ return { ...base, refused: "no-frontend", ...refusalShape };
192
+ }
193
+ if (candidates.length > 1) {
194
+ return { ...base, refused: "ambiguous-frontend", frontendCandidates: candidates, ...refusalShape };
195
+ }
196
+ frontendProject = candidates[0];
197
+ }
198
+ if (!apiEntry) {
199
+ if (frontendProject === void 0) return { ...base, refused: "no-frontend", ...refusalShape };
200
+ let nxPin = "";
201
+ let expressDeclared = false;
202
+ const rootPkgRaw = tree.read("package.json", "utf-8");
203
+ if (rootPkgRaw !== null) {
204
+ try {
205
+ const pkg = JSON.parse(rootPkgRaw);
206
+ const nxVersion = pkg.devDependencies?.["nx"] ?? pkg.dependencies?.["nx"];
207
+ if (nxVersion && /^[~^]?\d/.test(nxVersion)) nxPin = `@${nxVersion}`;
208
+ expressDeclared = pkg.dependencies?.["express"] !== void 0 || pkg.devDependencies?.["express"] !== void 0;
209
+ } catch {
210
+ }
211
+ }
212
+ const frontendRoot = projects.get(frontendProject)?.root ?? "";
213
+ const frontendParent = frontendRoot.split("/").slice(0, -1).join("/");
214
+ const apiDirectory = frontendRoot === "" || frontendRoot === "." ? `apps/${apiName}` : `${frontendParent === "" ? "" : `${frontendParent}/`}${apiName}`;
215
+ const expressAlignment = backend === "express" && !expressDeclared ? readExpressPeerRange(tree) : void 0;
216
+ const quotedExpress = expressAlignment ? `"express@${expressAlignment}"` : void 0;
217
+ return {
218
+ ...base,
219
+ refused: "missing-api-project",
220
+ frontendProject,
221
+ ...expressAlignment !== void 0 ? { expressRange: expressAlignment } : {},
222
+ commands: [
223
+ pmCommands.addDevAtRoot(`@nx/${backend}${nxPin}`),
224
+ ...quotedExpress ? [pmCommands.addAtRoot(quotedExpress)] : [],
225
+ `${pmCommands.runNx} g @nx/${backend}:application ${apiDirectory} --frontendProject=${frontendProject}`
226
+ ],
227
+ ...refusalShape
228
+ };
229
+ }
230
+ const recordedConfig = readToolkitConfigFile(tree).parsed;
231
+ const boundaryTags = readBoundaryTags(recordedConfig);
232
+ const sharedTypesConfig = readSharedTypesConfig(recordedConfig);
233
+ const withSharedTypes = sharedTypesConfig !== false;
234
+ const sharedTypesRoot = sharedTypesConfig === false ? "" : sharedTypesConfig.path;
235
+ const sharedTypesExists = withSharedTypes && (tree.exists(`${sharedTypesRoot}/package.json`) || tree.exists(`${sharedTypesRoot}/project.json`));
236
+ const rootPackageJson = tree.read("package.json", "utf-8");
237
+ const workspaceName = rootPackageJson ? JSON.parse(rootPackageJson).name ?? "workspace" : "workspace";
238
+ const wiring = composeBackendWiring({ workspaceName, tags: boundaryTags, ...sharedTypesConfig === false ? {} : { sharedTypes: sharedTypesConfig } });
239
+ const [apiProjectName, apiConfig] = apiEntry;
240
+ const tagTargets = [
241
+ ...frontendProject === void 0 ? [] : [{ project: frontendProject, root: projects.get(frontendProject)?.root ?? `apps/${frontendProject}` }],
242
+ { project: apiProjectName, root: apiConfig.root }
243
+ ];
244
+ if (sharedTypesExists) tagTargets.push({ project: wiring.sharedTypesPackageName, root: sharedTypesRoot });
245
+ const parsedTargets = /* @__PURE__ */ new Map();
246
+ const invalidProjects = [];
247
+ for (const target of tagTargets) {
248
+ const projectJsonPath = `${target.root}/project.json`;
249
+ const packageJsonPath = `${target.root}/package.json`;
250
+ const kind = tree.exists(projectJsonPath) ? "project" : "package";
251
+ const path = kind === "project" ? projectJsonPath : packageJsonPath;
252
+ const raw = tree.read(path, "utf-8");
253
+ if (raw === null) {
254
+ invalidProjects.push(path);
255
+ continue;
256
+ }
257
+ try {
258
+ parsedTargets.set(target.project, { path, raw, kind, parsed: JSON.parse(raw) });
259
+ } catch {
260
+ invalidProjects.push(path);
261
+ }
262
+ }
263
+ if (invalidProjects.length > 0) {
264
+ return { ...base, refused: "invalid-project-json", frontendProject, invalidProjects, ...refusalShape };
265
+ }
266
+ const sharedTypes = !withSharedTypes ? "disabled" : sharedTypesExists ? "skipped" : "created";
267
+ if (sharedTypes === "created" && write) {
268
+ for (const file of wiring.sharedTypesFiles) tree.write(file.filePath, file.content);
269
+ }
270
+ let configOutcome = "unchanged";
271
+ {
272
+ const { raw: configRaw, parsed: configParsed } = readToolkitConfigFile(tree);
273
+ const parseOk = configRaw === null || configParsed !== null;
274
+ const existing = configParsed ?? {};
275
+ if (parseOk && existing["backend"] !== backend) {
276
+ configOutcome = configRaw === null ? "created" : "updated";
277
+ if (write) tree.write(TOOLKIT_CONFIG_PATH, applyJsonEdits(configRaw, [[["backend"], backend]]));
278
+ }
279
+ }
280
+ let workspaceGlob = withSharedTypes ? "unrecognized" : void 0;
281
+ const workspaceYaml = withSharedTypes ? tree.read("pnpm-workspace.yaml", "utf-8") : null;
282
+ if (!withSharedTypes) {
283
+ } else if (workspaceYaml !== null) {
284
+ const globResult = addSharedTypesWorkspaceGlob(workspaceYaml, sharedTypesRoot);
285
+ workspaceGlob = globResult.status;
286
+ if (globResult.status === "added" && write) tree.write("pnpm-workspace.yaml", globResult.text);
287
+ } else if ((detectedPm === "npm" || detectedPm === "yarn" || detectedPm === "bun") && rootPackageJson !== null) {
288
+ const yarnNeedsPrivate = detectedPm === "yarn" && JSON.parse(rootPackageJson).private !== true;
289
+ const npmResult = yarnNeedsPrivate ? { status: "unrecognized" } : addSharedTypesNpmWorkspace(rootPackageJson, sharedTypesRoot);
290
+ workspaceGlob = npmResult.status;
291
+ if (npmResult.status === "added" && write) tree.write("package.json", npmResult.text);
292
+ }
293
+ const mergeTag = (project, tag) => {
294
+ const target = parsedTargets.get(project);
295
+ if (!target) return { project, action: "added" };
296
+ const holder = target.kind === "project" ? target.parsed : target.parsed.nx ??= {};
297
+ const tags2 = holder.tags ?? [];
298
+ if (tags2.includes(tag)) return { project, action: "present" };
299
+ holder.tags = [...tags2, tag];
300
+ target.raw = appendJsonArrayItem(target.raw, target.kind === "project" ? ["tags"] : ["nx", "tags"], tag);
301
+ if (write) tree.write(target.path, target.raw);
302
+ return { project, action: "added" };
303
+ };
304
+ const tags = [
305
+ ...frontendProject === void 0 ? [] : [mergeTag(frontendProject, wiring.tagByRole.frontend)],
306
+ mergeTag(apiProjectName, wiring.tagByRole.backend),
307
+ ...withSharedTypes ? [mergeTag(wiring.sharedTypesPackageName, wiring.tagByRole.shared)] : []
308
+ ];
309
+ let tsconfigPaths = withSharedTypes ? "workspace-link" : "none";
310
+ const tsconfigRaw = withSharedTypes ? tree.read("tsconfig.base.json", "utf-8") : null;
311
+ if (tsconfigRaw !== null) {
312
+ try {
313
+ const tsconfig = JSON.parse(tsconfigRaw);
314
+ const baseUrl = tsconfig.compilerOptions?.baseUrl;
315
+ if (tsconfig.compilerOptions && (baseUrl || tsconfig.compilerOptions.paths !== void 0)) {
316
+ const paths = tsconfig.compilerOptions.paths ?? {};
317
+ if (paths[wiring.sharedTypesPackageName]) {
318
+ tsconfigPaths = "present";
319
+ } else {
320
+ tsconfigPaths = "added";
321
+ const entry = [`${baseUrl ? "" : "./"}${sharedTypesRoot}/src/index.ts`];
322
+ if (write) tree.write("tsconfig.base.json", setJsonValue(tsconfigRaw, ["compilerOptions", "paths", wiring.sharedTypesPackageName], entry));
323
+ }
324
+ }
325
+ } catch {
326
+ tsconfigPaths = "unrecognized";
327
+ }
328
+ }
329
+ let eslint;
330
+ let snippet;
331
+ if (!applyEslint) {
332
+ eslint = "snippet-only";
333
+ snippet = buildBoundarySnippet(boundaryTags);
334
+ } else {
335
+ const configPath = ESLINT_CONFIG_CANDIDATES.find((candidate) => tree.exists(candidate));
336
+ const configText = configPath ? tree.read(configPath, "utf-8") : null;
337
+ const result = configText !== null ? applyBoundaryConstraints(configText, boundaryTags) : { status: "unrecognized" };
338
+ if (result.status === "applied") {
339
+ eslint = "applied";
340
+ if (write && configPath) tree.write(configPath, result.text);
341
+ } else if (result.status === "already-applied") {
342
+ eslint = "already-applied";
343
+ } else {
344
+ eslint = "degraded-unrecognized";
345
+ snippet = buildBoundarySnippet(boundaryTags);
346
+ }
347
+ }
348
+ return {
349
+ ...base,
350
+ frontendProject,
351
+ sharedTypes,
352
+ tags,
353
+ workspaceGlob,
354
+ tsconfigPaths,
355
+ eslint,
356
+ snippet,
357
+ sharedTypesPackage: withSharedTypes ? wiring.sharedTypesPackageName : void 0,
358
+ sharedTypesRoot: withSharedTypes ? sharedTypesRoot : void 0,
359
+ config: configOutcome,
360
+ packageManager,
361
+ ...packageManagerNote !== void 0 ? { packageManagerNote } : {}
362
+ };
363
+ }
364
+ function readExpressPeerRange(tree) {
365
+ const raw = tree.read("node_modules/@nx/react/package.json", "utf-8");
366
+ if (raw === null) return void 0;
367
+ try {
368
+ const range = JSON.parse(raw).peerDependencies?.["express"];
369
+ return typeof range === "string" && range.trim().length > 0 ? range.trim() : void 0;
370
+ } catch {
371
+ return void 0;
372
+ }
373
+ }
374
+ async function addBackendGenerator(tree, options) {
375
+ const dryRun = resolveDryRun(options);
376
+ const report = runAddBackend(tree, { ...options, dryRun });
377
+ console.log(renderAddBackendReport(report, dryRun));
378
+ }
379
+ var generator_default = addBackendGenerator;
380
+
381
+ export {
382
+ renderAddBackendBody,
383
+ runAddBackend,
384
+ addBackendGenerator,
385
+ generator_default
386
+ };
@@ -0,0 +1,234 @@
1
+ import {
2
+ detectWorkspacePackageManager
3
+ } from "./chunk-FQHFT5IH.js";
4
+
5
+ // ../template-engine/dist/ai-hosts/ai-hosts.js
6
+ var AI_HOSTS = [
7
+ { id: "claude-code", label: "Claude Code", nxAgent: "claude", rulesFile: "CLAUDE.md", reviewProvider: "claude" },
8
+ { id: "codex", label: "Codex", nxAgent: "codex", rulesFile: "AGENTS.md", reviewProvider: "gpt" },
9
+ { id: "cursor", label: "Cursor", nxAgent: "cursor", rulesFile: "AGENTS.md", reviewProvider: "cursor" },
10
+ { id: "gemini-cli", label: "Gemini CLI", nxAgent: "gemini", rulesFile: "AGENTS.md", reviewProvider: "gemini" }
11
+ ];
12
+ var AI_HOST_IDS = AI_HOSTS.map((host) => host.id);
13
+ var DEFAULT_AI_HOSTS = ["claude-code"];
14
+ function isAiHost(value) {
15
+ return typeof value === "string" && AI_HOST_IDS.includes(value);
16
+ }
17
+ function definitionOf(id) {
18
+ const host = AI_HOSTS.find((candidate) => candidate.id === id);
19
+ if (!host)
20
+ throw new Error(`Unknown AI host "${id}"`);
21
+ return host;
22
+ }
23
+ function rulesFilesFor(hosts) {
24
+ const picked = hosts.length > 0 ? hosts : DEFAULT_AI_HOSTS;
25
+ const files = new Set(picked.map((id) => definitionOf(id).rulesFile));
26
+ return ["CLAUDE.md", "AGENTS.md"].filter((file) => files.has(file));
27
+ }
28
+ function reviewProviderFor(hosts) {
29
+ return definitionOf(hosts[0] ?? DEFAULT_AI_HOSTS[0]).reviewProvider;
30
+ }
31
+
32
+ // src/generators/shared/json-edit.ts
33
+ import { findNodeAtLocation, parseTree } from "jsonc-parser";
34
+ function detectIndent(raw) {
35
+ for (const line of raw.split("\n")) {
36
+ const match = /^(\t+| +)/.exec(line);
37
+ if (match) return match[1];
38
+ }
39
+ return " ";
40
+ }
41
+ function styleOf(raw) {
42
+ return { eol: raw.includes("\r\n") ? "\r\n" : "\n", unit: detectIndent(raw) };
43
+ }
44
+ function lineIndentAt(raw, offset) {
45
+ const lineStart = raw.lastIndexOf("\n", offset - 1) + 1;
46
+ return /^[ \t]*/.exec(raw.slice(lineStart))?.[0] ?? "";
47
+ }
48
+ function isMultiline(raw, node) {
49
+ return raw.slice(node.offset, node.offset + node.length).includes("\n");
50
+ }
51
+ function isPrimitive(value) {
52
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
53
+ }
54
+ function renderValue(value, indent, style) {
55
+ if (isPrimitive(value)) return JSON.stringify(value);
56
+ if (Array.isArray(value)) {
57
+ if (value.every(isPrimitive)) return `[${value.map((item) => JSON.stringify(item)).join(", ")}]`;
58
+ const inner2 = `${indent}${style.unit}`;
59
+ return `[${style.eol}${value.map((item) => `${inner2}${renderValue(item, inner2, style)}`).join(`,${style.eol}`)}${style.eol}${indent}]`;
60
+ }
61
+ const entries = Object.entries(value);
62
+ if (entries.length === 0) return "{}";
63
+ const inner = `${indent}${style.unit}`;
64
+ const body = entries.map(([key, item]) => `${inner}${JSON.stringify(key)}: ${renderValue(item, inner, style)}`).join(`,${style.eol}`);
65
+ return `{${style.eol}${body}${style.eol}${indent}}`;
66
+ }
67
+ function splice(raw, start, end, text) {
68
+ return raw.slice(0, start) + text + raw.slice(end);
69
+ }
70
+ function nest(keys, value) {
71
+ return keys.reduceRight((inner, key) => ({ [key]: inner }), value);
72
+ }
73
+ function insertProperty(raw, object, key, value, style) {
74
+ const properties = object.children ?? [];
75
+ const objectIndent = lineIndentAt(raw, object.offset);
76
+ if (properties.length === 0) {
77
+ const inner = `${objectIndent}${style.unit}`;
78
+ const text = `{${style.eol}${inner}${JSON.stringify(String(key))}: ${renderValue(value, inner, style)}${style.eol}${objectIndent}}`;
79
+ return splice(raw, object.offset, object.offset + object.length, text);
80
+ }
81
+ const last = properties[properties.length - 1];
82
+ const end = last.offset + last.length;
83
+ if (!isMultiline(raw, object)) {
84
+ return splice(raw, end, end, `, ${JSON.stringify(String(key))}: ${renderValue(value, "", style)}`);
85
+ }
86
+ const indent = lineIndentAt(raw, last.offset);
87
+ return splice(raw, end, end, `,${style.eol}${indent}${JSON.stringify(String(key))}: ${renderValue(value, indent, style)}`);
88
+ }
89
+ function setJsonValue(raw, path, value) {
90
+ const style = styleOf(raw);
91
+ const root = parseTree(raw);
92
+ if (root === void 0) throw new Error("setJsonValue: not a JSON document");
93
+ const existing = findNodeAtLocation(root, path);
94
+ if (existing !== void 0) {
95
+ return splice(raw, existing.offset, existing.offset + existing.length, renderValue(value, lineIndentAt(raw, existing.offset), style));
96
+ }
97
+ for (let depth = path.length - 1; depth >= 0; depth--) {
98
+ const parent = depth === 0 ? root : findNodeAtLocation(root, path.slice(0, depth));
99
+ if (parent === void 0) continue;
100
+ if (parent.type !== "object") throw new Error(`setJsonValue: ${path.slice(0, depth).join(".") || "the root"} is not an object`);
101
+ return insertProperty(raw, parent, path[depth], nest(path.slice(depth + 1), value), style);
102
+ }
103
+ throw new Error("setJsonValue: unreachable");
104
+ }
105
+ function appendJsonArrayItem(raw, path, item) {
106
+ const style = styleOf(raw);
107
+ const root = parseTree(raw);
108
+ const array = root === void 0 ? void 0 : findNodeAtLocation(root, path);
109
+ if (array === void 0) return setJsonValue(raw, path, [item]);
110
+ if (array.type !== "array") throw new Error(`appendJsonArrayItem: ${path.join(".")} is not an array`);
111
+ const items = array.children ?? [];
112
+ if (items.length === 0) return splice(raw, array.offset, array.offset + array.length, renderValue([item], "", style));
113
+ const last = items[items.length - 1];
114
+ const end = last.offset + last.length;
115
+ if (!isMultiline(raw, array)) {
116
+ const separator = items.length >= 2 ? raw.slice(items[0].offset + items[0].length, items[1].offset) : ", ";
117
+ return splice(raw, end, end, `${separator}${renderValue(item, "", style)}`);
118
+ }
119
+ const indent = lineIndentAt(raw, last.offset);
120
+ return splice(raw, end, end, `,${style.eol}${indent}${renderValue(item, indent, style)}`);
121
+ }
122
+ function applyJsonEdits(raw, edits) {
123
+ if (raw === null) {
124
+ let text = "{}\n";
125
+ for (const [path, value] of edits) text = setJsonValue(text, path, value);
126
+ return text;
127
+ }
128
+ return edits.reduce((text, [path, value]) => setJsonValue(text, path, value), raw);
129
+ }
130
+
131
+ // src/generators/shared/toolkit-config-file.ts
132
+ var TOOLKIT_CONFIG_PATH = "toolkit.config.json";
133
+ function readToolkitConfigFile(tree) {
134
+ const raw = tree.read(TOOLKIT_CONFIG_PATH, "utf-8");
135
+ if (raw === null) return { raw: null, parsed: null };
136
+ try {
137
+ const parsed = JSON.parse(raw);
138
+ return {
139
+ raw,
140
+ parsed: typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null
141
+ };
142
+ } catch {
143
+ return { raw, parsed: null };
144
+ }
145
+ }
146
+ function readToolkitConfig(tree) {
147
+ return readToolkitConfigFile(tree).parsed;
148
+ }
149
+ function resolveLocaleOption(option, config) {
150
+ if (option) return option;
151
+ const locale = config?.["locale"];
152
+ return locale === "ko" || locale === "en" ? locale : "en";
153
+ }
154
+ function recordedChoice(config, key, allowed) {
155
+ const value = config?.[key];
156
+ return typeof value === "string" && allowed.includes(value) ? value : void 0;
157
+ }
158
+ var RECORDED_FRONTENDS = ["react-vite", "next", "expo", "generic", "none"];
159
+ var RECORDED_BACKENDS = ["express", "nest", "none"];
160
+ var RECORDED_PRESETS = ["quick-start", "solo", "team"];
161
+ var RECORDED_PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
162
+ function resolveFrontendOption(option, config, generator) {
163
+ const frontend = option ?? recordedChoice(config, "frontend", RECORDED_FRONTENDS);
164
+ if (frontend === void 0) {
165
+ throw new Error(`${generator}: pass --frontend=react-vite|next|expo|generic (toolkit.config.json records none \u2014 init writes it).`);
166
+ }
167
+ if (frontend === "none") {
168
+ throw new Error(
169
+ `${generator}: this is a backend-only workspace (toolkit.config.json frontend: none) \u2014 FSD structures a frontend app. Pass --frontend=react-vite|next|expo|generic if you added one.`
170
+ );
171
+ }
172
+ return frontend;
173
+ }
174
+ function resolveInitFrontend(option, config) {
175
+ const frontend = option ?? recordedChoice(config, "frontend", RECORDED_FRONTENDS);
176
+ if (frontend === void 0) {
177
+ throw new Error("init: pass --frontend=react-vite|next|expo|generic|none (toolkit.config.json records none \u2014 init writes it).");
178
+ }
179
+ return frontend;
180
+ }
181
+ function workspacePackageManager(exists, read, config) {
182
+ return detectWorkspacePackageManager(exists, read) ?? recordedChoice(config, "packageManager", RECORDED_PACKAGE_MANAGERS) ?? null;
183
+ }
184
+ function treePackageManager(tree, config) {
185
+ return workspacePackageManager((p) => tree.exists(p), (p) => tree.read(p, "utf-8"), config);
186
+ }
187
+ function resolveAiHostsOption(option, config) {
188
+ if (option !== void 0 && option.length > 0) return [...new Set(option)];
189
+ const recorded = config?.["aiHosts"];
190
+ const fromConfig = Array.isArray(recorded) ? [...new Set(recorded.filter(isAiHost))] : [];
191
+ return fromConfig.length > 0 ? fromConfig : [...DEFAULT_AI_HOSTS];
192
+ }
193
+ function recordAiHosts(tree, hosts, write) {
194
+ const { raw, parsed } = readToolkitConfigFile(tree);
195
+ if (raw !== null && parsed === null) return "unrecognized";
196
+ const existing = parsed ?? {};
197
+ const recorded = existing["aiHosts"];
198
+ if (Array.isArray(recorded) && recorded.length === hosts.length && recorded.every((id, index) => id === hosts[index])) {
199
+ return "unchanged";
200
+ }
201
+ if (write) tree.write(TOOLKIT_CONFIG_PATH, applyJsonEdits(raw, [[["aiHosts"], [...hosts]]]));
202
+ return raw === null ? "created" : "updated";
203
+ }
204
+ function recordedFrontends(config) {
205
+ const frontends = config?.["frontends"];
206
+ if (!Array.isArray(frontends)) return [];
207
+ return frontends.flatMap(
208
+ (entry) => typeof entry === "object" && entry !== null && typeof entry["project"] === "string" ? [entry.project] : []
209
+ );
210
+ }
211
+
212
+ export {
213
+ DEFAULT_AI_HOSTS,
214
+ rulesFilesFor,
215
+ reviewProviderFor,
216
+ setJsonValue,
217
+ appendJsonArrayItem,
218
+ applyJsonEdits,
219
+ TOOLKIT_CONFIG_PATH,
220
+ readToolkitConfigFile,
221
+ readToolkitConfig,
222
+ resolveLocaleOption,
223
+ recordedChoice,
224
+ RECORDED_FRONTENDS,
225
+ RECORDED_BACKENDS,
226
+ RECORDED_PRESETS,
227
+ resolveFrontendOption,
228
+ resolveInitFrontend,
229
+ workspacePackageManager,
230
+ treePackageManager,
231
+ resolveAiHostsOption,
232
+ recordAiHosts,
233
+ recordedFrontends
234
+ };
@@ -0,0 +1,50 @@
1
+ // ../template-engine/dist/environment/package-manager.js
2
+ var LOCKFILES = [
3
+ ["yarn", ["yarn.lock"]],
4
+ ["bun", ["bun.lock", "bun.lockb"]],
5
+ ["npm", ["package-lock.json"]]
6
+ ];
7
+ function detectWorkspacePackageManager(exists, read = () => void 0) {
8
+ if (exists("pnpm-lock.yaml") || exists("pnpm-workspace.yaml"))
9
+ return "pnpm";
10
+ const found = LOCKFILES.filter(([, files]) => files.some((file) => exists(file))).map(([pm]) => pm);
11
+ if (found.length <= 1)
12
+ return found[0] ?? null;
13
+ const declared = /"packageManager"\s*:\s*"(npm|yarn|bun)@/.exec(read("package.json") ?? "")?.[1];
14
+ const named = found.find((pm) => pm === declared);
15
+ if (named)
16
+ return named;
17
+ return found.includes("npm") ? "npm" : found[0];
18
+ }
19
+ function detectYarnFlavor(exists, read) {
20
+ if (exists(".yarnrc.yml"))
21
+ return "berry";
22
+ if (/^__metadata:/m.test(read("yarn.lock") ?? ""))
23
+ return "berry";
24
+ const major = /"packageManager"\s*:\s*"yarn@(\d+)\./.exec(read("package.json") ?? "")?.[1];
25
+ return major !== void 0 && Number(major) >= 2 ? "berry" : "classic";
26
+ }
27
+ function packageManagerCommands(pm, flavor = "classic") {
28
+ switch (pm) {
29
+ case "pnpm":
30
+ return { addDevAtRoot: (spec) => `pnpm add -D -w ${spec}`, addAtRoot: (spec) => `pnpm add -w ${spec}`, runNx: "pnpm nx", install: "pnpm install", workspaceRegistration: "pnpm-workspace.yaml" };
31
+ case "yarn":
32
+ return {
33
+ addDevAtRoot: (spec) => flavor === "berry" ? `yarn add -D ${spec}` : `yarn add -D -W ${spec}`,
34
+ addAtRoot: (spec) => flavor === "berry" ? `yarn add ${spec}` : `yarn add -W ${spec}`,
35
+ runNx: "yarn nx",
36
+ install: "yarn install",
37
+ workspaceRegistration: "package.json workspaces"
38
+ };
39
+ case "bun":
40
+ return { addDevAtRoot: (spec) => `bun add -d ${spec}`, addAtRoot: (spec) => `bun add ${spec}`, runNx: "bun nx", install: "bun install", workspaceRegistration: "package.json workspaces" };
41
+ case "npm":
42
+ return { addDevAtRoot: (spec) => `npm install -D ${spec}`, addAtRoot: (spec) => `npm install ${spec}`, runNx: "npm exec nx --", install: "npm install", workspaceRegistration: "package.json workspaces" };
43
+ }
44
+ }
45
+
46
+ export {
47
+ detectWorkspacePackageManager,
48
+ detectYarnFlavor,
49
+ packageManagerCommands
50
+ };