@clearlydev/nx-archweave 0.3.1 → 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.
- package/dist/bin/review-verdict.js +5 -248
- 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-5MI3O4VZ.js +99 -0
- package/dist/generators/_chunks/chunk-67INEVWL.js +1068 -0
- package/dist/generators/_chunks/chunk-6ZTIUBB2.js +139 -0
- package/dist/generators/_chunks/chunk-AIJCJ7JK.js +203 -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-JHRTVURZ.js +189 -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 +74 -2042
- 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 +2 -2
|
@@ -1,909 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return "pnpm";
|
|
13
|
-
const found = LOCKFILES.filter(([, files]) => files.some((file) => exists(file))).map(([pm]) => pm);
|
|
14
|
-
if (found.length <= 1)
|
|
15
|
-
return found[0] ?? null;
|
|
16
|
-
const declared = /"packageManager"\s*:\s*"(npm|yarn|bun)@/.exec(read("package.json") ?? "")?.[1];
|
|
17
|
-
const named = found.find((pm) => pm === declared);
|
|
18
|
-
if (named)
|
|
19
|
-
return named;
|
|
20
|
-
return found.includes("npm") ? "npm" : found[0];
|
|
21
|
-
}
|
|
22
|
-
function detectYarnFlavor(exists, read) {
|
|
23
|
-
if (exists(".yarnrc.yml"))
|
|
24
|
-
return "berry";
|
|
25
|
-
if (/^__metadata:/m.test(read("yarn.lock") ?? ""))
|
|
26
|
-
return "berry";
|
|
27
|
-
const major = /"packageManager"\s*:\s*"yarn@(\d+)\./.exec(read("package.json") ?? "")?.[1];
|
|
28
|
-
return major !== void 0 && Number(major) >= 2 ? "berry" : "classic";
|
|
29
|
-
}
|
|
30
|
-
function packageManagerCommands(pm, flavor = "classic") {
|
|
31
|
-
switch (pm) {
|
|
32
|
-
case "pnpm":
|
|
33
|
-
return { addDevAtRoot: (spec) => `pnpm add -D -w ${spec}`, addAtRoot: (spec) => `pnpm add -w ${spec}`, runNx: "pnpm nx", install: "pnpm install", workspaceRegistration: "pnpm-workspace.yaml" };
|
|
34
|
-
case "yarn":
|
|
35
|
-
return {
|
|
36
|
-
addDevAtRoot: (spec) => flavor === "berry" ? `yarn add -D ${spec}` : `yarn add -D -W ${spec}`,
|
|
37
|
-
addAtRoot: (spec) => flavor === "berry" ? `yarn add ${spec}` : `yarn add -W ${spec}`,
|
|
38
|
-
runNx: "yarn nx",
|
|
39
|
-
install: "yarn install",
|
|
40
|
-
workspaceRegistration: "package.json workspaces"
|
|
41
|
-
};
|
|
42
|
-
case "bun":
|
|
43
|
-
return { addDevAtRoot: (spec) => `bun add -d ${spec}`, addAtRoot: (spec) => `bun add ${spec}`, runNx: "bun nx", install: "bun install", workspaceRegistration: "package.json workspaces" };
|
|
44
|
-
case "npm":
|
|
45
|
-
return { addDevAtRoot: (spec) => `npm install -D ${spec}`, addAtRoot: (spec) => `npm install ${spec}`, runNx: "npm exec nx --", install: "npm install", workspaceRegistration: "package.json workspaces" };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ../template-engine/dist/backend/eslint-boundaries.js
|
|
50
|
-
var DEFAULT_BOUNDARY_TAGS = { frontend: "scope:frontend", backend: "scope:backend", shared: "scope:shared" };
|
|
51
|
-
function readBoundaryTags(config) {
|
|
52
|
-
const boundaries = config?.["boundaries"];
|
|
53
|
-
const tags = typeof boundaries === "object" && boundaries !== null ? boundaries["tags"] : void 0;
|
|
54
|
-
const pick = (role) => {
|
|
55
|
-
const value = typeof tags === "object" && tags !== null ? tags[role] : void 0;
|
|
56
|
-
return typeof value === "string" && value.trim() !== "" ? value : DEFAULT_BOUNDARY_TAGS[role];
|
|
57
|
-
};
|
|
58
|
-
return { frontend: pick("frontend"), backend: pick("backend"), shared: pick("shared") };
|
|
59
|
-
}
|
|
60
|
-
function scopeConstraints(tags = DEFAULT_BOUNDARY_TAGS) {
|
|
61
|
-
return [
|
|
62
|
-
{ sourceTag: tags.frontend, notDependOnLibsWithTags: [tags.backend] },
|
|
63
|
-
{ sourceTag: tags.backend, notDependOnLibsWithTags: [tags.frontend] },
|
|
64
|
-
{ sourceTag: tags.shared, notDependOnLibsWithTags: [tags.frontend, tags.backend] }
|
|
65
|
-
];
|
|
66
|
-
}
|
|
67
|
-
var SCOPE_CONSTRAINTS = scopeConstraints();
|
|
68
|
-
var ESLINT_CONFIG_CANDIDATES = ["eslint.config.mjs", "eslint.config.js", "eslint.config.cjs"];
|
|
69
|
-
var appliedMarker = (tags) => `sourceTag: '${tags.frontend}'`;
|
|
70
|
-
var ALLOW_ALL_BLOCK = /depConstraints:\s*\[\s*\{\s*sourceTag:\s*'\*',\s*onlyDependOnLibsWithTags:\s*\[\s*'\*'\s*\],?\s*\},?\s*\]/g;
|
|
71
|
-
function maskComments(text) {
|
|
72
|
-
const out = text.split("");
|
|
73
|
-
let mode = "code";
|
|
74
|
-
let quote = "";
|
|
75
|
-
for (let i = 0; i < text.length; i++) {
|
|
76
|
-
const c = text[i];
|
|
77
|
-
const d = text[i + 1];
|
|
78
|
-
if (mode === "code") {
|
|
79
|
-
if (c === "'" || c === '"' || c === "`") {
|
|
80
|
-
mode = "string";
|
|
81
|
-
quote = c;
|
|
82
|
-
} else if (c === "/" && d === "/") {
|
|
83
|
-
mode = "line";
|
|
84
|
-
out[i] = " ";
|
|
85
|
-
} else if (c === "/" && d === "*") {
|
|
86
|
-
mode = "block";
|
|
87
|
-
out[i] = " ";
|
|
88
|
-
}
|
|
89
|
-
} else if (mode === "string") {
|
|
90
|
-
if (c === "\\") {
|
|
91
|
-
i++;
|
|
92
|
-
} else if (c === quote) {
|
|
93
|
-
mode = "code";
|
|
94
|
-
}
|
|
95
|
-
} else if (mode === "line") {
|
|
96
|
-
if (c === "\n") {
|
|
97
|
-
mode = "code";
|
|
98
|
-
} else {
|
|
99
|
-
out[i] = " ";
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
if (c === "*" && d === "/") {
|
|
103
|
-
out[i] = " ";
|
|
104
|
-
out[i + 1] = " ";
|
|
105
|
-
i++;
|
|
106
|
-
mode = "code";
|
|
107
|
-
} else if (c !== "\n") {
|
|
108
|
-
out[i] = " ";
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return out.join("");
|
|
113
|
-
}
|
|
114
|
-
function findAllowAllMatches(masked) {
|
|
115
|
-
const matches = [];
|
|
116
|
-
ALLOW_ALL_BLOCK.lastIndex = 0;
|
|
117
|
-
for (let m = ALLOW_ALL_BLOCK.exec(masked); m !== null; m = ALLOW_ALL_BLOCK.exec(masked)) {
|
|
118
|
-
matches.push({ index: m.index, length: m[0].length });
|
|
119
|
-
}
|
|
120
|
-
return matches;
|
|
121
|
-
}
|
|
122
|
-
function classifyBoundaryConstraints(configText, tags = DEFAULT_BOUNDARY_TAGS) {
|
|
123
|
-
const masked = maskComments(configText);
|
|
124
|
-
if (masked.includes(appliedMarker(tags)))
|
|
125
|
-
return "enforced";
|
|
126
|
-
if (findAllowAllMatches(masked).length > 0)
|
|
127
|
-
return "allow-all";
|
|
128
|
-
return "unknown";
|
|
129
|
-
}
|
|
130
|
-
function renderConstraintsBlock(indent, eol, tags) {
|
|
131
|
-
const renderList = (list) => list.map((t) => `'${t}'`).join(", ");
|
|
132
|
-
const inner = scopeConstraints(tags).map((c) => {
|
|
133
|
-
const field = c.notDependOnLibsWithTags ? `notDependOnLibsWithTags: [${renderList(c.notDependOnLibsWithTags)}]` : `onlyDependOnLibsWithTags: [${renderList(c.onlyDependOnLibsWithTags ?? [])}]`;
|
|
134
|
-
return [`${indent} {`, `${indent} sourceTag: '${c.sourceTag}',`, `${indent} ${field},`, `${indent} },`].join(eol);
|
|
135
|
-
}).join(eol);
|
|
136
|
-
return [
|
|
137
|
-
`// nx-archweave boundaries \u2014 the frontend and the backend may never import each other`,
|
|
138
|
-
`${indent}depConstraints: [`,
|
|
139
|
-
inner,
|
|
140
|
-
[
|
|
141
|
-
`${indent} // baseline: everything else stays allowed (constraints apply conjunctively)`,
|
|
142
|
-
`${indent} {`,
|
|
143
|
-
`${indent} sourceTag: '*',`,
|
|
144
|
-
`${indent} onlyDependOnLibsWithTags: ['*'],`,
|
|
145
|
-
`${indent} },`
|
|
146
|
-
].join(eol),
|
|
147
|
-
`${indent}]`
|
|
148
|
-
].join(eol);
|
|
149
|
-
}
|
|
150
|
-
function applyBoundaryConstraints(configText, tags = DEFAULT_BOUNDARY_TAGS) {
|
|
151
|
-
const classification = classifyBoundaryConstraints(configText, tags);
|
|
152
|
-
if (classification === "enforced")
|
|
153
|
-
return { status: "already-applied" };
|
|
154
|
-
if (classification === "unknown")
|
|
155
|
-
return { status: "unrecognized" };
|
|
156
|
-
const masked = maskComments(configText);
|
|
157
|
-
const matches = findAllowAllMatches(masked);
|
|
158
|
-
if (matches.length !== 1) {
|
|
159
|
-
return { status: "unrecognized" };
|
|
160
|
-
}
|
|
161
|
-
const [match] = matches;
|
|
162
|
-
const eol = configText.includes("\r\n") ? "\r\n" : "\n";
|
|
163
|
-
const lineStart = configText.lastIndexOf("\n", match.index) + 1;
|
|
164
|
-
const indent = /^[ \t]*/.exec(configText.slice(lineStart, match.index))?.[0] ?? "";
|
|
165
|
-
const replacement = renderConstraintsBlock(indent, eol, tags);
|
|
166
|
-
const text = configText.slice(0, match.index) + replacement + configText.slice(match.index + match.length);
|
|
167
|
-
return { status: "applied", text };
|
|
168
|
-
}
|
|
169
|
-
function buildBoundarySnippet(tags = DEFAULT_BOUNDARY_TAGS) {
|
|
170
|
-
return [
|
|
171
|
-
"// nx-archweave project boundaries \u2014 merge these entries into the depConstraints",
|
|
172
|
-
"// array of the '@nx/enforce-module-boundaries' rule in your root ESLint config.",
|
|
173
|
-
"// They ride on top of your existing entries: constraints apply conjunctively,",
|
|
174
|
-
"// so the allow-all baseline can stay.",
|
|
175
|
-
renderConstraintsBlock("", "\n", tags)
|
|
176
|
-
].join("\n");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ../templates/dist/backend/shared-types.js
|
|
180
|
-
function sharedTypesIndexContent() {
|
|
181
|
-
return `export * from './lib/api-types.js';
|
|
182
|
-
`;
|
|
183
|
-
}
|
|
184
|
-
function sharedTypesApiTypesContent() {
|
|
185
|
-
return [
|
|
186
|
-
"// Types shared between the frontend and the backend live here.",
|
|
187
|
-
"// Example: the api app returns this from GET /api/health, and the",
|
|
188
|
-
"// frontend consumes the same shape \u2014 one definition, no drift.",
|
|
189
|
-
"export interface ApiHealthResponse {",
|
|
190
|
-
" status: 'ok';",
|
|
191
|
-
"}",
|
|
192
|
-
""
|
|
193
|
-
].join("\n");
|
|
194
|
-
}
|
|
195
|
-
function sharedTypesPackageJsonContent(packageName, sharedTag = "scope:shared") {
|
|
196
|
-
return `${JSON.stringify({
|
|
197
|
-
name: packageName,
|
|
198
|
-
version: "0.0.1",
|
|
199
|
-
private: true,
|
|
200
|
-
type: "module",
|
|
201
|
-
main: "./src/index.ts",
|
|
202
|
-
types: "./src/index.ts",
|
|
203
|
-
exports: {
|
|
204
|
-
".": {
|
|
205
|
-
types: "./src/index.ts",
|
|
206
|
-
import: "./src/index.ts",
|
|
207
|
-
default: "./src/index.ts"
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
nx: {
|
|
211
|
-
projectType: "library",
|
|
212
|
-
tags: [sharedTag],
|
|
213
|
-
targets: {}
|
|
214
|
-
}
|
|
215
|
-
}, null, 2)}
|
|
216
|
-
`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ../template-engine/dist/backend/compose-backend-wiring.js
|
|
220
|
-
var DEFAULT_SHARED_TYPES_ROOT = "libs/shared-types";
|
|
221
|
-
function readSharedTypesConfig(config) {
|
|
222
|
-
const value = config?.["sharedTypes"];
|
|
223
|
-
if (value === false)
|
|
224
|
-
return false;
|
|
225
|
-
if (typeof value !== "object" || value === null)
|
|
226
|
-
return { path: DEFAULT_SHARED_TYPES_ROOT };
|
|
227
|
-
const record = value;
|
|
228
|
-
const path = typeof record["path"] === "string" && record["path"].trim() !== "" ? record["path"].replace(/\/+$/, "") : DEFAULT_SHARED_TYPES_ROOT;
|
|
229
|
-
const importPath = typeof record["importPath"] === "string" && record["importPath"].trim() !== "" ? record["importPath"] : void 0;
|
|
230
|
-
return importPath === void 0 ? { path } : { path, importPath };
|
|
231
|
-
}
|
|
232
|
-
function composeBackendWiring(input) {
|
|
233
|
-
const scoped = /^@([^/]+)\//.exec(input.workspaceName);
|
|
234
|
-
const scope = scoped ? `@${scoped[1]}` : `@${input.workspaceName.split("/").pop() || "workspace"}`;
|
|
235
|
-
const sharedTypesRoot = input.sharedTypes?.path ?? DEFAULT_SHARED_TYPES_ROOT;
|
|
236
|
-
const sharedTypesPackageName = input.sharedTypes?.importPath ?? `${scope}/shared-types`;
|
|
237
|
-
const tagByRole = input.tags ?? DEFAULT_BOUNDARY_TAGS;
|
|
238
|
-
return {
|
|
239
|
-
sharedTypesFiles: [
|
|
240
|
-
{ filePath: `${sharedTypesRoot}/package.json`, content: sharedTypesPackageJsonContent(sharedTypesPackageName, tagByRole.shared) },
|
|
241
|
-
{ filePath: `${sharedTypesRoot}/src/index.ts`, content: sharedTypesIndexContent() },
|
|
242
|
-
{ filePath: `${sharedTypesRoot}/src/lib/api-types.ts`, content: sharedTypesApiTypesContent() }
|
|
243
|
-
],
|
|
244
|
-
sharedTypesPackageName,
|
|
245
|
-
sharedTypesRoot,
|
|
246
|
-
tagByRole
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
function addSharedTypesNpmWorkspace(packageJsonText, entry = DEFAULT_SHARED_TYPES_ROOT) {
|
|
250
|
-
let parsed;
|
|
251
|
-
try {
|
|
252
|
-
parsed = JSON.parse(packageJsonText);
|
|
253
|
-
} catch {
|
|
254
|
-
return { status: "unrecognized" };
|
|
255
|
-
}
|
|
256
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
257
|
-
return { status: "unrecognized" };
|
|
258
|
-
const pkg = parsed;
|
|
259
|
-
const field = pkg["workspaces"];
|
|
260
|
-
let target;
|
|
261
|
-
if (field === void 0) {
|
|
262
|
-
target = [];
|
|
263
|
-
} else if (Array.isArray(field)) {
|
|
264
|
-
target = field;
|
|
265
|
-
} else if (field !== null && typeof field === "object" && Array.isArray(field.packages)) {
|
|
266
|
-
target = field.packages;
|
|
267
|
-
} else {
|
|
268
|
-
return { status: "unrecognized" };
|
|
269
|
-
}
|
|
270
|
-
const parentGlob = `${entry.split("/").slice(0, -1).join("/")}/*`;
|
|
271
|
-
if (target.some((existing) => existing === entry || existing === parentGlob))
|
|
272
|
-
return { status: "present" };
|
|
273
|
-
const surgical = insertSharedTypesWorkspace(packageJsonText, field === void 0 ? "absent" : Array.isArray(field) ? "array" : "object", JSON.stringify(entry));
|
|
274
|
-
if (surgical !== null)
|
|
275
|
-
return { status: "added", text: surgical };
|
|
276
|
-
if (field === void 0)
|
|
277
|
-
pkg["workspaces"] = target;
|
|
278
|
-
target.push(entry);
|
|
279
|
-
const indent = /\n([ \t]+)"/.exec(packageJsonText)?.[1] ?? " ";
|
|
280
|
-
const trailing = packageJsonText.endsWith("\n") ? "\n" : "";
|
|
281
|
-
return { status: "added", text: `${JSON.stringify(pkg, null, indent)}${trailing}` };
|
|
282
|
-
}
|
|
283
|
-
function skipJsonValue(text, start) {
|
|
284
|
-
if (text[start] === '"') {
|
|
285
|
-
let i = start + 1;
|
|
286
|
-
while (i < text.length && text[i] !== '"')
|
|
287
|
-
i += text[i] === "\\" ? 2 : 1;
|
|
288
|
-
return i + 1;
|
|
289
|
-
}
|
|
290
|
-
if (text[start] === "[" || text[start] === "{") {
|
|
291
|
-
let depth = 0;
|
|
292
|
-
let i = start;
|
|
293
|
-
while (i < text.length) {
|
|
294
|
-
const c = text[i];
|
|
295
|
-
if (c === '"') {
|
|
296
|
-
i = skipJsonValue(text, i);
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (c === "[" || c === "{")
|
|
300
|
-
depth++;
|
|
301
|
-
if (c === "]" || c === "}") {
|
|
302
|
-
depth--;
|
|
303
|
-
if (depth === 0)
|
|
304
|
-
return i + 1;
|
|
305
|
-
}
|
|
306
|
-
i++;
|
|
307
|
-
}
|
|
308
|
-
return -1;
|
|
309
|
-
}
|
|
310
|
-
const literal = /^[^,}\]\s]+/.exec(text.slice(start));
|
|
311
|
-
return literal ? start + literal[0].length : -1;
|
|
312
|
-
}
|
|
313
|
-
function objectMembers(text, open) {
|
|
314
|
-
const members = [];
|
|
315
|
-
let i = open + 1;
|
|
316
|
-
for (; ; ) {
|
|
317
|
-
while (/[\s,]/.test(text[i] ?? ""))
|
|
318
|
-
i++;
|
|
319
|
-
if (text[i] === "}")
|
|
320
|
-
return members;
|
|
321
|
-
if (text[i] !== '"')
|
|
322
|
-
return null;
|
|
323
|
-
const keyEnd = skipJsonValue(text, i);
|
|
324
|
-
const key = JSON.parse(text.slice(i, keyEnd));
|
|
325
|
-
i = keyEnd;
|
|
326
|
-
while (/\s/.test(text[i] ?? ""))
|
|
327
|
-
i++;
|
|
328
|
-
if (text[i] !== ":")
|
|
329
|
-
return null;
|
|
330
|
-
i++;
|
|
331
|
-
while (/\s/.test(text[i] ?? ""))
|
|
332
|
-
i++;
|
|
333
|
-
const valueEnd = skipJsonValue(text, i);
|
|
334
|
-
if (valueEnd === -1)
|
|
335
|
-
return null;
|
|
336
|
-
members.push({ key, valueStart: i, valueEnd });
|
|
337
|
-
i = valueEnd;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function appendToArray(text, open, close, eol, quoted) {
|
|
341
|
-
const inner = text.slice(open + 1, close - 1);
|
|
342
|
-
if (inner.trim() === "")
|
|
343
|
-
return `${text.slice(0, open + 1)}${quoted}${text.slice(close - 1)}`;
|
|
344
|
-
if (!inner.includes("\n")) {
|
|
345
|
-
const body = inner.replace(/\s+$/, "");
|
|
346
|
-
return `${text.slice(0, open + 1)}${body}, ${quoted}${inner.slice(body.length)}${text.slice(close - 1)}`;
|
|
347
|
-
}
|
|
348
|
-
const lastEntryLineStart = text.lastIndexOf("\n", open + 1 + inner.trimEnd().length - 1) + 1;
|
|
349
|
-
const itemIndent = /^[ \t]*/.exec(text.slice(lastEntryLineStart))?.[0] ?? " ";
|
|
350
|
-
const bodyEnd = open + 1 + inner.trimEnd().length;
|
|
351
|
-
return `${text.slice(0, bodyEnd)},${eol}${itemIndent}${quoted}${text.slice(bodyEnd)}`;
|
|
352
|
-
}
|
|
353
|
-
function insertSharedTypesWorkspace(text, form, quoted) {
|
|
354
|
-
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
355
|
-
const rootOpen = text.indexOf("{");
|
|
356
|
-
if (rootOpen === -1)
|
|
357
|
-
return null;
|
|
358
|
-
const members = objectMembers(text, rootOpen);
|
|
359
|
-
if (members === null)
|
|
360
|
-
return null;
|
|
361
|
-
const workspaces = members.find((member) => member.key === "workspaces");
|
|
362
|
-
if (form === "absent") {
|
|
363
|
-
const last = members[members.length - 1];
|
|
364
|
-
const indent = /\n([ \t]+)"/.exec(text)?.[1] ?? " ";
|
|
365
|
-
if (last === void 0) {
|
|
366
|
-
const close = skipJsonValue(text, rootOpen) - 1;
|
|
367
|
-
return `${text.slice(0, rootOpen + 1)}${eol}${indent}"workspaces": [${quoted}]${eol}${text.slice(close)}`;
|
|
368
|
-
}
|
|
369
|
-
return `${text.slice(0, last.valueEnd)},${eol}${indent}"workspaces": [${quoted}]${text.slice(last.valueEnd)}`;
|
|
370
|
-
}
|
|
371
|
-
if (workspaces === void 0)
|
|
372
|
-
return null;
|
|
373
|
-
if (form === "array") {
|
|
374
|
-
if (text[workspaces.valueStart] !== "[")
|
|
375
|
-
return null;
|
|
376
|
-
return appendToArray(text, workspaces.valueStart, workspaces.valueEnd, eol, quoted);
|
|
377
|
-
}
|
|
378
|
-
if (text[workspaces.valueStart] !== "{")
|
|
379
|
-
return null;
|
|
380
|
-
const packages = objectMembers(text, workspaces.valueStart)?.find((member) => member.key === "packages");
|
|
381
|
-
if (packages === void 0 || text[packages.valueStart] !== "[")
|
|
382
|
-
return null;
|
|
383
|
-
return appendToArray(text, packages.valueStart, packages.valueEnd, eol, quoted);
|
|
384
|
-
}
|
|
385
|
-
function addSharedTypesWorkspaceGlob(yamlText, entry = DEFAULT_SHARED_TYPES_ROOT) {
|
|
386
|
-
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
387
|
-
const parentGlob = `${entry.split("/").slice(0, -1).join("/")}/*`;
|
|
388
|
-
if (new RegExp(`^\\s*-\\s*['"]?(${escape(entry)}|${escape(parentGlob)})['"]?\\s*$`, "m").test(yamlText)) {
|
|
389
|
-
return { status: "present" };
|
|
390
|
-
}
|
|
391
|
-
const lines = yamlText.split("\n");
|
|
392
|
-
const packagesIndex = lines.findIndex((line) => /^packages:\s*$/.test(line));
|
|
393
|
-
if (packagesIndex === -1)
|
|
394
|
-
return { status: "unrecognized" };
|
|
395
|
-
let lastEntry = packagesIndex;
|
|
396
|
-
for (let i = packagesIndex + 1; i < lines.length; i++) {
|
|
397
|
-
if (/^\s+-\s+\S/.test(lines[i])) {
|
|
398
|
-
lastEntry = i;
|
|
399
|
-
} else if (lines[i].trim() === "") {
|
|
400
|
-
continue;
|
|
401
|
-
} else {
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (lastEntry === packagesIndex)
|
|
406
|
-
return { status: "unrecognized" };
|
|
407
|
-
const indent = /^(\s+)-/.exec(lines[lastEntry])?.[1] ?? " ";
|
|
408
|
-
lines.splice(lastEntry + 1, 0, `${indent}- '${entry}'`);
|
|
409
|
-
return { status: "added", text: lines.join("\n") };
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/generators/shared/dry-run.ts
|
|
413
|
-
function resolveDryRun(options) {
|
|
414
|
-
return process.env["NX_DRY_RUN"] === "true" || (options.dryRun ?? false);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// src/generators/shared/json-edit.ts
|
|
418
|
-
import { findNodeAtLocation, parseTree } from "jsonc-parser";
|
|
419
|
-
function detectIndent(raw) {
|
|
420
|
-
for (const line of raw.split("\n")) {
|
|
421
|
-
const match = /^(\t+| +)/.exec(line);
|
|
422
|
-
if (match) return match[1];
|
|
423
|
-
}
|
|
424
|
-
return " ";
|
|
425
|
-
}
|
|
426
|
-
function styleOf(raw) {
|
|
427
|
-
return { eol: raw.includes("\r\n") ? "\r\n" : "\n", unit: detectIndent(raw) };
|
|
428
|
-
}
|
|
429
|
-
function lineIndentAt(raw, offset) {
|
|
430
|
-
const lineStart = raw.lastIndexOf("\n", offset - 1) + 1;
|
|
431
|
-
return /^[ \t]*/.exec(raw.slice(lineStart))?.[0] ?? "";
|
|
432
|
-
}
|
|
433
|
-
function isMultiline(raw, node) {
|
|
434
|
-
return raw.slice(node.offset, node.offset + node.length).includes("\n");
|
|
435
|
-
}
|
|
436
|
-
function isPrimitive(value) {
|
|
437
|
-
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
438
|
-
}
|
|
439
|
-
function renderValue(value, indent, style) {
|
|
440
|
-
if (isPrimitive(value)) return JSON.stringify(value);
|
|
441
|
-
if (Array.isArray(value)) {
|
|
442
|
-
if (value.every(isPrimitive)) return `[${value.map((item) => JSON.stringify(item)).join(", ")}]`;
|
|
443
|
-
const inner2 = `${indent}${style.unit}`;
|
|
444
|
-
return `[${style.eol}${value.map((item) => `${inner2}${renderValue(item, inner2, style)}`).join(`,${style.eol}`)}${style.eol}${indent}]`;
|
|
445
|
-
}
|
|
446
|
-
const entries = Object.entries(value);
|
|
447
|
-
if (entries.length === 0) return "{}";
|
|
448
|
-
const inner = `${indent}${style.unit}`;
|
|
449
|
-
const body = entries.map(([key, item]) => `${inner}${JSON.stringify(key)}: ${renderValue(item, inner, style)}`).join(`,${style.eol}`);
|
|
450
|
-
return `{${style.eol}${body}${style.eol}${indent}}`;
|
|
451
|
-
}
|
|
452
|
-
function splice(raw, start, end, text) {
|
|
453
|
-
return raw.slice(0, start) + text + raw.slice(end);
|
|
454
|
-
}
|
|
455
|
-
function nest(keys, value) {
|
|
456
|
-
return keys.reduceRight((inner, key) => ({ [key]: inner }), value);
|
|
457
|
-
}
|
|
458
|
-
function insertProperty(raw, object, key, value, style) {
|
|
459
|
-
const properties = object.children ?? [];
|
|
460
|
-
const objectIndent = lineIndentAt(raw, object.offset);
|
|
461
|
-
if (properties.length === 0) {
|
|
462
|
-
const inner = `${objectIndent}${style.unit}`;
|
|
463
|
-
const text = `{${style.eol}${inner}${JSON.stringify(String(key))}: ${renderValue(value, inner, style)}${style.eol}${objectIndent}}`;
|
|
464
|
-
return splice(raw, object.offset, object.offset + object.length, text);
|
|
465
|
-
}
|
|
466
|
-
const last = properties[properties.length - 1];
|
|
467
|
-
const end = last.offset + last.length;
|
|
468
|
-
if (!isMultiline(raw, object)) {
|
|
469
|
-
return splice(raw, end, end, `, ${JSON.stringify(String(key))}: ${renderValue(value, "", style)}`);
|
|
470
|
-
}
|
|
471
|
-
const indent = lineIndentAt(raw, last.offset);
|
|
472
|
-
return splice(raw, end, end, `,${style.eol}${indent}${JSON.stringify(String(key))}: ${renderValue(value, indent, style)}`);
|
|
473
|
-
}
|
|
474
|
-
function setJsonValue(raw, path, value) {
|
|
475
|
-
const style = styleOf(raw);
|
|
476
|
-
const root = parseTree(raw);
|
|
477
|
-
if (root === void 0) throw new Error("setJsonValue: not a JSON document");
|
|
478
|
-
const existing = findNodeAtLocation(root, path);
|
|
479
|
-
if (existing !== void 0) {
|
|
480
|
-
return splice(raw, existing.offset, existing.offset + existing.length, renderValue(value, lineIndentAt(raw, existing.offset), style));
|
|
481
|
-
}
|
|
482
|
-
for (let depth = path.length - 1; depth >= 0; depth--) {
|
|
483
|
-
const parent = depth === 0 ? root : findNodeAtLocation(root, path.slice(0, depth));
|
|
484
|
-
if (parent === void 0) continue;
|
|
485
|
-
if (parent.type !== "object") throw new Error(`setJsonValue: ${path.slice(0, depth).join(".") || "the root"} is not an object`);
|
|
486
|
-
return insertProperty(raw, parent, path[depth], nest(path.slice(depth + 1), value), style);
|
|
487
|
-
}
|
|
488
|
-
throw new Error("setJsonValue: unreachable");
|
|
489
|
-
}
|
|
490
|
-
function appendJsonArrayItem(raw, path, item) {
|
|
491
|
-
const style = styleOf(raw);
|
|
492
|
-
const root = parseTree(raw);
|
|
493
|
-
const array = root === void 0 ? void 0 : findNodeAtLocation(root, path);
|
|
494
|
-
if (array === void 0) return setJsonValue(raw, path, [item]);
|
|
495
|
-
if (array.type !== "array") throw new Error(`appendJsonArrayItem: ${path.join(".")} is not an array`);
|
|
496
|
-
const items = array.children ?? [];
|
|
497
|
-
if (items.length === 0) return splice(raw, array.offset, array.offset + array.length, renderValue([item], "", style));
|
|
498
|
-
const last = items[items.length - 1];
|
|
499
|
-
const end = last.offset + last.length;
|
|
500
|
-
if (!isMultiline(raw, array)) {
|
|
501
|
-
const separator = items.length >= 2 ? raw.slice(items[0].offset + items[0].length, items[1].offset) : ", ";
|
|
502
|
-
return splice(raw, end, end, `${separator}${renderValue(item, "", style)}`);
|
|
503
|
-
}
|
|
504
|
-
const indent = lineIndentAt(raw, last.offset);
|
|
505
|
-
return splice(raw, end, end, `,${style.eol}${indent}${renderValue(item, indent, style)}`);
|
|
506
|
-
}
|
|
507
|
-
function applyJsonEdits(raw, edits) {
|
|
508
|
-
if (raw === null) {
|
|
509
|
-
let text = "{}\n";
|
|
510
|
-
for (const [path, value] of edits) text = setJsonValue(text, path, value);
|
|
511
|
-
return text;
|
|
512
|
-
}
|
|
513
|
-
return edits.reduce((text, [path, value]) => setJsonValue(text, path, value), raw);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// src/generators/shared/toolkit-config-file.ts
|
|
517
|
-
var TOOLKIT_CONFIG_PATH = "toolkit.config.json";
|
|
518
|
-
function readToolkitConfigFile(tree) {
|
|
519
|
-
const raw = tree.read(TOOLKIT_CONFIG_PATH, "utf-8");
|
|
520
|
-
if (raw === null) return { raw: null, parsed: null };
|
|
521
|
-
try {
|
|
522
|
-
const parsed = JSON.parse(raw);
|
|
523
|
-
return {
|
|
524
|
-
raw,
|
|
525
|
-
parsed: typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null
|
|
526
|
-
};
|
|
527
|
-
} catch {
|
|
528
|
-
return { raw, parsed: null };
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
function readToolkitConfig(tree) {
|
|
532
|
-
return readToolkitConfigFile(tree).parsed;
|
|
533
|
-
}
|
|
534
|
-
function recordedChoice(config, key, allowed) {
|
|
535
|
-
const value = config?.[key];
|
|
536
|
-
return typeof value === "string" && allowed.includes(value) ? value : void 0;
|
|
537
|
-
}
|
|
538
|
-
var RECORDED_PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
|
|
539
|
-
function workspacePackageManager(exists, read, config) {
|
|
540
|
-
return detectWorkspacePackageManager(exists, read) ?? recordedChoice(config, "packageManager", RECORDED_PACKAGE_MANAGERS) ?? null;
|
|
541
|
-
}
|
|
542
|
-
function treePackageManager(tree, config) {
|
|
543
|
-
return workspacePackageManager((p) => tree.exists(p), (p) => tree.read(p, "utf-8"), config);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// src/generators/shared/project-type.ts
|
|
547
|
-
import { parseJson } from "@nx/devkit";
|
|
548
|
-
function resolveProjectType(tree, root, declared) {
|
|
549
|
-
if (declared) return declared === "library" ? "library" : "application";
|
|
550
|
-
const prefix = root === "" || root === "." ? "" : `${root}/`;
|
|
551
|
-
if (tree.exists(`${prefix}tsconfig.lib.json`)) return "library";
|
|
552
|
-
if (tree.exists(`${prefix}tsconfig.app.json`)) return "application";
|
|
553
|
-
const raw = tree.read(`${prefix}package.json`, "utf-8");
|
|
554
|
-
if (raw === null) return "library";
|
|
555
|
-
try {
|
|
556
|
-
const manifest = parseJson(raw);
|
|
557
|
-
if (manifest && !["exports", "main", "module", "bin"].some((key) => Boolean(manifest[key]))) return "application";
|
|
558
|
-
} catch {
|
|
559
|
-
}
|
|
560
|
-
return "library";
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// src/generators/add-backend/render.ts
|
|
564
|
-
function renderAddBackendBody(report) {
|
|
565
|
-
const lines = [];
|
|
566
|
-
if (report.refused === "no-frontend") {
|
|
567
|
-
lines.push(`Refused: no frontend application and no "${report.apiName}" app found in this workspace.`);
|
|
568
|
-
lines.push(" add-backend wires a backend app \u2014 beside a frontend, or on its own in a backend-only");
|
|
569
|
-
lines.push(` workspace (create one with: npx create-nx-workspace --preset=${report.backend} --appName=${report.apiName}).`);
|
|
570
|
-
lines.push(" If the backend app has another name, pass --apiName=<its directory name>.");
|
|
571
|
-
return lines.join("\n");
|
|
572
|
-
}
|
|
573
|
-
if (report.refused === "frontend-not-found") {
|
|
574
|
-
lines.push(`Refused: --frontendProject "${report.frontendProject}" does not match any project.`);
|
|
575
|
-
lines.push(" Nothing was written. Check the name with `nx show projects`.");
|
|
576
|
-
return lines.join("\n");
|
|
577
|
-
}
|
|
578
|
-
if (report.refused === "ambiguous-frontend") {
|
|
579
|
-
lines.push("Refused: multiple frontend applications found \u2014 add-backend will not guess:");
|
|
580
|
-
for (const name of report.frontendCandidates ?? []) lines.push(` - ${name}`);
|
|
581
|
-
lines.push(" Nothing was written. Re-run with --frontendProject=<name> to pick one.");
|
|
582
|
-
return lines.join("\n");
|
|
583
|
-
}
|
|
584
|
-
if (report.refused === "invalid-project-json") {
|
|
585
|
-
lines.push("Refused: a project.json this run must update could not be parsed:");
|
|
586
|
-
for (const path of report.invalidProjects ?? []) lines.push(` - ${path}`);
|
|
587
|
-
lines.push(" Nothing was written \u2014 fix the JSON, then re-run add-backend.");
|
|
588
|
-
return lines.join("\n");
|
|
589
|
-
}
|
|
590
|
-
if (report.refused === "missing-api-project") {
|
|
591
|
-
lines.push(`Refused: no "${report.apiName}" application project found.`);
|
|
592
|
-
lines.push(" Create it with the official commands first (copy-paste, names already filled in),");
|
|
593
|
-
lines.push(" then re-run add-backend to wire it up:");
|
|
594
|
-
lines.push("");
|
|
595
|
-
for (const command of report.commands ?? []) lines.push(` ${command}`);
|
|
596
|
-
if (report.expressRange !== void 0) {
|
|
597
|
-
lines.push("");
|
|
598
|
-
lines.push(` The express line declares express@${report.expressRange}, the range this workspace's @nx/react supports \u2014`);
|
|
599
|
-
lines.push(" the official generator then keeps it instead of adding express 5, which npm rejects as a peer conflict.");
|
|
600
|
-
}
|
|
601
|
-
return lines.join("\n");
|
|
602
|
-
}
|
|
603
|
-
if (report.packageManagerNote !== void 0) lines.push(` note: ${report.packageManagerNote}`);
|
|
604
|
-
lines.push(` frontend project: ${report.frontendProject ?? "none (backend-only workspace)"}`);
|
|
605
|
-
const libPath = report.sharedTypesRoot ?? "libs/shared-types";
|
|
606
|
-
lines.push(
|
|
607
|
-
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`
|
|
608
|
-
);
|
|
609
|
-
if (report.config !== void 0 && report.config !== "unchanged") {
|
|
610
|
-
lines.push(` toolkit.config.json: backend "${report.backend}" recorded (${report.config}) \u2014 doctor's backend checks now run`);
|
|
611
|
-
}
|
|
612
|
-
if (report.workspaceGlob !== void 0) {
|
|
613
|
-
const pmName = report.packageManager ?? "pnpm";
|
|
614
|
-
const registry = packageManagerCommands(pmName).workspaceRegistration;
|
|
615
|
-
lines.push(
|
|
616
|
-
{
|
|
617
|
-
added: ` ${registry}: '${libPath}' registered \u2014 run \`${pmName} install\` once to link it`,
|
|
618
|
-
present: ` ${registry}: already covers ${libPath}`,
|
|
619
|
-
unrecognized: (
|
|
620
|
-
// an integrated layout lists no workspace packages: the paths alias below links it
|
|
621
|
-
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`
|
|
622
|
-
)
|
|
623
|
-
}[report.workspaceGlob]
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
for (const tag of report.tags) {
|
|
627
|
-
lines.push(` tag ${tag.project}: ${tag.action === "added" ? "added" : "already present"}`);
|
|
628
|
-
}
|
|
629
|
-
lines.push(
|
|
630
|
-
{
|
|
631
|
-
added: ` tsconfig.base.json paths: ${report.sharedTypesPackage} added (a classic or integrated setup \u2014 libs resolve through paths)`,
|
|
632
|
-
present: " tsconfig.base.json paths: already wired",
|
|
633
|
-
"workspace-link": " tsconfig paths: not needed \u2014 the workspace package link is the wiring",
|
|
634
|
-
unrecognized: " tsconfig.base.json: could not be parsed \u2014 left untouched",
|
|
635
|
-
none: " tsconfig paths: nothing to wire (no shared-types lib)"
|
|
636
|
-
}[report.tsconfigPaths]
|
|
637
|
-
);
|
|
638
|
-
switch (report.eslint) {
|
|
639
|
-
case "applied":
|
|
640
|
-
lines.push(" ESLint boundaries: cross-side deny rules written (existing imports stay allowed)");
|
|
641
|
-
break;
|
|
642
|
-
case "already-applied":
|
|
643
|
-
lines.push(" ESLint boundaries: already enforced \u2014 no change");
|
|
644
|
-
break;
|
|
645
|
-
case "degraded-unrecognized":
|
|
646
|
-
lines.push(" ESLint boundaries: your config was NOT recognized \u2014 left untouched.");
|
|
647
|
-
lines.push(" Merge this yourself (then boundaries are actually enforced):");
|
|
648
|
-
break;
|
|
649
|
-
case "snippet-only":
|
|
650
|
-
lines.push(" ESLint boundaries: report-only (pass --applyEslint to write them).");
|
|
651
|
-
lines.push(" Paste-ready snippet:");
|
|
652
|
-
break;
|
|
653
|
-
}
|
|
654
|
-
if (report.snippet) {
|
|
655
|
-
lines.push("");
|
|
656
|
-
lines.push(report.snippet);
|
|
657
|
-
}
|
|
658
|
-
return lines.join("\n");
|
|
659
|
-
}
|
|
660
|
-
function renderAddBackendReport(report, dryRun) {
|
|
661
|
-
return [`add-backend (${report.backend})${dryRun ? " \u2014 dry run" : ""}`, "", renderAddBackendBody(report)].join("\n");
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// src/generators/add-backend/generator.ts
|
|
665
|
-
function runAddBackend(tree, options) {
|
|
666
|
-
const backend = options.backend;
|
|
667
|
-
const apiName = options.apiName ?? "api";
|
|
668
|
-
const applyEslint = options.applyEslint ?? false;
|
|
669
|
-
const dryRun = options.dryRun ?? false;
|
|
670
|
-
const write = !dryRun;
|
|
671
|
-
const base = { backend, apiName };
|
|
672
|
-
const refusalShape = {
|
|
673
|
-
sharedTypes: "skipped",
|
|
674
|
-
tags: [],
|
|
675
|
-
tsconfigPaths: "unrecognized",
|
|
676
|
-
eslint: "snippet-only"
|
|
677
|
-
};
|
|
678
|
-
let projects;
|
|
679
|
-
try {
|
|
680
|
-
projects = getProjects(tree);
|
|
681
|
-
} catch (error) {
|
|
682
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
683
|
-
const pathMatch = /Cannot parse ([^:]+):/.exec(message);
|
|
684
|
-
return {
|
|
685
|
-
...base,
|
|
686
|
-
refused: "invalid-project-json",
|
|
687
|
-
invalidProjects: [pathMatch ? pathMatch[1] : message],
|
|
688
|
-
...refusalShape
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
const detectedPm = treePackageManager(tree, readToolkitConfig(tree));
|
|
692
|
-
const packageManager = detectedPm ?? "pnpm";
|
|
693
|
-
const pmCommands = packageManagerCommands(
|
|
694
|
-
packageManager,
|
|
695
|
-
packageManager === "yarn" ? detectYarnFlavor((p) => tree.exists(p), (p) => tree.read(p, "utf-8")) : void 0
|
|
696
|
-
);
|
|
697
|
-
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;
|
|
698
|
-
const baseNameOf = (root) => root.split("/").pop() ?? root;
|
|
699
|
-
const entries = [...projects.entries()];
|
|
700
|
-
const declaredApp = ([, config]) => config.projectType === "application";
|
|
701
|
-
const inferredApp = ([, config]) => config.projectType === void 0 && config.root !== "" && config.root !== "." && resolveProjectType(tree, config.root, void 0) === "application";
|
|
702
|
-
const apiNamed = entries.filter(([, config]) => baseNameOf(config.root) === apiName);
|
|
703
|
-
const apiEntry = apiNamed.find(declaredApp) ?? apiNamed.find((entry) => inferredApp(entry) && tree.exists(`${entry[1].root}/tsconfig.app.json`));
|
|
704
|
-
let frontendProject;
|
|
705
|
-
if (options.frontendProject !== void 0) {
|
|
706
|
-
if (!projects.has(options.frontendProject)) {
|
|
707
|
-
return { ...base, refused: "frontend-not-found", frontendProject: options.frontendProject, ...refusalShape };
|
|
708
|
-
}
|
|
709
|
-
frontendProject = options.frontendProject;
|
|
710
|
-
} else {
|
|
711
|
-
const eligible = entries.filter(([, config]) => {
|
|
712
|
-
const dir = baseNameOf(config.root);
|
|
713
|
-
return dir !== apiName && dir !== "e2e" && !dir.endsWith("-e2e");
|
|
714
|
-
});
|
|
715
|
-
const declared = eligible.filter(declaredApp);
|
|
716
|
-
const candidates = (declared.length > 0 ? declared : eligible.filter(inferredApp)).map(([name]) => name).sort();
|
|
717
|
-
if (candidates.length === 0 && !apiEntry) {
|
|
718
|
-
return { ...base, refused: "no-frontend", ...refusalShape };
|
|
719
|
-
}
|
|
720
|
-
if (candidates.length > 1) {
|
|
721
|
-
return { ...base, refused: "ambiguous-frontend", frontendCandidates: candidates, ...refusalShape };
|
|
722
|
-
}
|
|
723
|
-
frontendProject = candidates[0];
|
|
724
|
-
}
|
|
725
|
-
if (!apiEntry) {
|
|
726
|
-
if (frontendProject === void 0) return { ...base, refused: "no-frontend", ...refusalShape };
|
|
727
|
-
let nxPin = "";
|
|
728
|
-
let expressDeclared = false;
|
|
729
|
-
const rootPkgRaw = tree.read("package.json", "utf-8");
|
|
730
|
-
if (rootPkgRaw !== null) {
|
|
731
|
-
try {
|
|
732
|
-
const pkg = JSON.parse(rootPkgRaw);
|
|
733
|
-
const nxVersion = pkg.devDependencies?.["nx"] ?? pkg.dependencies?.["nx"];
|
|
734
|
-
if (nxVersion && /^[~^]?\d/.test(nxVersion)) nxPin = `@${nxVersion}`;
|
|
735
|
-
expressDeclared = pkg.dependencies?.["express"] !== void 0 || pkg.devDependencies?.["express"] !== void 0;
|
|
736
|
-
} catch {
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
const frontendRoot = projects.get(frontendProject)?.root ?? "";
|
|
740
|
-
const frontendParent = frontendRoot.split("/").slice(0, -1).join("/");
|
|
741
|
-
const apiDirectory = frontendRoot === "" || frontendRoot === "." ? `apps/${apiName}` : `${frontendParent === "" ? "" : `${frontendParent}/`}${apiName}`;
|
|
742
|
-
const expressAlignment = backend === "express" && !expressDeclared ? readExpressPeerRange(tree) : void 0;
|
|
743
|
-
const quotedExpress = expressAlignment ? `"express@${expressAlignment}"` : void 0;
|
|
744
|
-
return {
|
|
745
|
-
...base,
|
|
746
|
-
refused: "missing-api-project",
|
|
747
|
-
frontendProject,
|
|
748
|
-
...expressAlignment !== void 0 ? { expressRange: expressAlignment } : {},
|
|
749
|
-
commands: [
|
|
750
|
-
pmCommands.addDevAtRoot(`@nx/${backend}${nxPin}`),
|
|
751
|
-
...quotedExpress ? [pmCommands.addAtRoot(quotedExpress)] : [],
|
|
752
|
-
`${pmCommands.runNx} g @nx/${backend}:application ${apiDirectory} --frontendProject=${frontendProject}`
|
|
753
|
-
],
|
|
754
|
-
...refusalShape
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
const recordedConfig = readToolkitConfigFile(tree).parsed;
|
|
758
|
-
const boundaryTags = readBoundaryTags(recordedConfig);
|
|
759
|
-
const sharedTypesConfig = readSharedTypesConfig(recordedConfig);
|
|
760
|
-
const withSharedTypes = sharedTypesConfig !== false;
|
|
761
|
-
const sharedTypesRoot = sharedTypesConfig === false ? "" : sharedTypesConfig.path;
|
|
762
|
-
const sharedTypesExists = withSharedTypes && (tree.exists(`${sharedTypesRoot}/package.json`) || tree.exists(`${sharedTypesRoot}/project.json`));
|
|
763
|
-
const rootPackageJson = tree.read("package.json", "utf-8");
|
|
764
|
-
const workspaceName = rootPackageJson ? JSON.parse(rootPackageJson).name ?? "workspace" : "workspace";
|
|
765
|
-
const wiring = composeBackendWiring({ workspaceName, tags: boundaryTags, ...sharedTypesConfig === false ? {} : { sharedTypes: sharedTypesConfig } });
|
|
766
|
-
const [apiProjectName, apiConfig] = apiEntry;
|
|
767
|
-
const tagTargets = [
|
|
768
|
-
...frontendProject === void 0 ? [] : [{ project: frontendProject, root: projects.get(frontendProject)?.root ?? `apps/${frontendProject}` }],
|
|
769
|
-
{ project: apiProjectName, root: apiConfig.root }
|
|
770
|
-
];
|
|
771
|
-
if (sharedTypesExists) tagTargets.push({ project: wiring.sharedTypesPackageName, root: sharedTypesRoot });
|
|
772
|
-
const parsedTargets = /* @__PURE__ */ new Map();
|
|
773
|
-
const invalidProjects = [];
|
|
774
|
-
for (const target of tagTargets) {
|
|
775
|
-
const projectJsonPath = `${target.root}/project.json`;
|
|
776
|
-
const packageJsonPath = `${target.root}/package.json`;
|
|
777
|
-
const kind = tree.exists(projectJsonPath) ? "project" : "package";
|
|
778
|
-
const path = kind === "project" ? projectJsonPath : packageJsonPath;
|
|
779
|
-
const raw = tree.read(path, "utf-8");
|
|
780
|
-
if (raw === null) {
|
|
781
|
-
invalidProjects.push(path);
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
try {
|
|
785
|
-
parsedTargets.set(target.project, { path, raw, kind, parsed: JSON.parse(raw) });
|
|
786
|
-
} catch {
|
|
787
|
-
invalidProjects.push(path);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
if (invalidProjects.length > 0) {
|
|
791
|
-
return { ...base, refused: "invalid-project-json", frontendProject, invalidProjects, ...refusalShape };
|
|
792
|
-
}
|
|
793
|
-
const sharedTypes = !withSharedTypes ? "disabled" : sharedTypesExists ? "skipped" : "created";
|
|
794
|
-
if (sharedTypes === "created" && write) {
|
|
795
|
-
for (const file of wiring.sharedTypesFiles) tree.write(file.filePath, file.content);
|
|
796
|
-
}
|
|
797
|
-
let configOutcome = "unchanged";
|
|
798
|
-
{
|
|
799
|
-
const { raw: configRaw, parsed: configParsed } = readToolkitConfigFile(tree);
|
|
800
|
-
const parseOk = configRaw === null || configParsed !== null;
|
|
801
|
-
const existing = configParsed ?? {};
|
|
802
|
-
if (parseOk && existing["backend"] !== backend) {
|
|
803
|
-
configOutcome = configRaw === null ? "created" : "updated";
|
|
804
|
-
if (write) tree.write(TOOLKIT_CONFIG_PATH, applyJsonEdits(configRaw, [[["backend"], backend]]));
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
let workspaceGlob = withSharedTypes ? "unrecognized" : void 0;
|
|
808
|
-
const workspaceYaml = withSharedTypes ? tree.read("pnpm-workspace.yaml", "utf-8") : null;
|
|
809
|
-
if (!withSharedTypes) {
|
|
810
|
-
} else if (workspaceYaml !== null) {
|
|
811
|
-
const globResult = addSharedTypesWorkspaceGlob(workspaceYaml, sharedTypesRoot);
|
|
812
|
-
workspaceGlob = globResult.status;
|
|
813
|
-
if (globResult.status === "added" && write) tree.write("pnpm-workspace.yaml", globResult.text);
|
|
814
|
-
} else if ((detectedPm === "npm" || detectedPm === "yarn" || detectedPm === "bun") && rootPackageJson !== null) {
|
|
815
|
-
const yarnNeedsPrivate = detectedPm === "yarn" && JSON.parse(rootPackageJson).private !== true;
|
|
816
|
-
const npmResult = yarnNeedsPrivate ? { status: "unrecognized" } : addSharedTypesNpmWorkspace(rootPackageJson, sharedTypesRoot);
|
|
817
|
-
workspaceGlob = npmResult.status;
|
|
818
|
-
if (npmResult.status === "added" && write) tree.write("package.json", npmResult.text);
|
|
819
|
-
}
|
|
820
|
-
const mergeTag = (project, tag) => {
|
|
821
|
-
const target = parsedTargets.get(project);
|
|
822
|
-
if (!target) return { project, action: "added" };
|
|
823
|
-
const holder = target.kind === "project" ? target.parsed : target.parsed.nx ??= {};
|
|
824
|
-
const tags2 = holder.tags ?? [];
|
|
825
|
-
if (tags2.includes(tag)) return { project, action: "present" };
|
|
826
|
-
holder.tags = [...tags2, tag];
|
|
827
|
-
target.raw = appendJsonArrayItem(target.raw, target.kind === "project" ? ["tags"] : ["nx", "tags"], tag);
|
|
828
|
-
if (write) tree.write(target.path, target.raw);
|
|
829
|
-
return { project, action: "added" };
|
|
830
|
-
};
|
|
831
|
-
const tags = [
|
|
832
|
-
...frontendProject === void 0 ? [] : [mergeTag(frontendProject, wiring.tagByRole.frontend)],
|
|
833
|
-
mergeTag(apiProjectName, wiring.tagByRole.backend),
|
|
834
|
-
...withSharedTypes ? [mergeTag(wiring.sharedTypesPackageName, wiring.tagByRole.shared)] : []
|
|
835
|
-
];
|
|
836
|
-
let tsconfigPaths = withSharedTypes ? "workspace-link" : "none";
|
|
837
|
-
const tsconfigRaw = withSharedTypes ? tree.read("tsconfig.base.json", "utf-8") : null;
|
|
838
|
-
if (tsconfigRaw !== null) {
|
|
839
|
-
try {
|
|
840
|
-
const tsconfig = JSON.parse(tsconfigRaw);
|
|
841
|
-
const baseUrl = tsconfig.compilerOptions?.baseUrl;
|
|
842
|
-
if (tsconfig.compilerOptions && (baseUrl || tsconfig.compilerOptions.paths !== void 0)) {
|
|
843
|
-
const paths = tsconfig.compilerOptions.paths ?? {};
|
|
844
|
-
if (paths[wiring.sharedTypesPackageName]) {
|
|
845
|
-
tsconfigPaths = "present";
|
|
846
|
-
} else {
|
|
847
|
-
tsconfigPaths = "added";
|
|
848
|
-
const entry = [`${baseUrl ? "" : "./"}${sharedTypesRoot}/src/index.ts`];
|
|
849
|
-
if (write) tree.write("tsconfig.base.json", setJsonValue(tsconfigRaw, ["compilerOptions", "paths", wiring.sharedTypesPackageName], entry));
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
} catch {
|
|
853
|
-
tsconfigPaths = "unrecognized";
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
let eslint;
|
|
857
|
-
let snippet;
|
|
858
|
-
if (!applyEslint) {
|
|
859
|
-
eslint = "snippet-only";
|
|
860
|
-
snippet = buildBoundarySnippet(boundaryTags);
|
|
861
|
-
} else {
|
|
862
|
-
const configPath = ESLINT_CONFIG_CANDIDATES.find((candidate) => tree.exists(candidate));
|
|
863
|
-
const configText = configPath ? tree.read(configPath, "utf-8") : null;
|
|
864
|
-
const result = configText !== null ? applyBoundaryConstraints(configText, boundaryTags) : { status: "unrecognized" };
|
|
865
|
-
if (result.status === "applied") {
|
|
866
|
-
eslint = "applied";
|
|
867
|
-
if (write && configPath) tree.write(configPath, result.text);
|
|
868
|
-
} else if (result.status === "already-applied") {
|
|
869
|
-
eslint = "already-applied";
|
|
870
|
-
} else {
|
|
871
|
-
eslint = "degraded-unrecognized";
|
|
872
|
-
snippet = buildBoundarySnippet(boundaryTags);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
return {
|
|
876
|
-
...base,
|
|
877
|
-
frontendProject,
|
|
878
|
-
sharedTypes,
|
|
879
|
-
tags,
|
|
880
|
-
workspaceGlob,
|
|
881
|
-
tsconfigPaths,
|
|
882
|
-
eslint,
|
|
883
|
-
snippet,
|
|
884
|
-
sharedTypesPackage: withSharedTypes ? wiring.sharedTypesPackageName : void 0,
|
|
885
|
-
sharedTypesRoot: withSharedTypes ? sharedTypesRoot : void 0,
|
|
886
|
-
config: configOutcome,
|
|
887
|
-
packageManager,
|
|
888
|
-
...packageManagerNote !== void 0 ? { packageManagerNote } : {}
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
function readExpressPeerRange(tree) {
|
|
892
|
-
const raw = tree.read("node_modules/@nx/react/package.json", "utf-8");
|
|
893
|
-
if (raw === null) return void 0;
|
|
894
|
-
try {
|
|
895
|
-
const range = JSON.parse(raw).peerDependencies?.["express"];
|
|
896
|
-
return typeof range === "string" && range.trim().length > 0 ? range.trim() : void 0;
|
|
897
|
-
} catch {
|
|
898
|
-
return void 0;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
async function addBackendGenerator(tree, options) {
|
|
902
|
-
const dryRun = resolveDryRun(options);
|
|
903
|
-
const report = runAddBackend(tree, { ...options, dryRun });
|
|
904
|
-
console.log(renderAddBackendReport(report, dryRun));
|
|
905
|
-
}
|
|
906
|
-
var generator_default = addBackendGenerator;
|
|
1
|
+
import {
|
|
2
|
+
addBackendGenerator,
|
|
3
|
+
generator_default,
|
|
4
|
+
runAddBackend
|
|
5
|
+
} from "../_chunks/chunk-C2VVUWBA.js";
|
|
6
|
+
import "../_chunks/chunk-QQNDYJTR.js";
|
|
7
|
+
import "../_chunks/chunk-TXAQ2GJC.js";
|
|
8
|
+
import "../_chunks/chunk-6ZTIUBB2.js";
|
|
9
|
+
import "../_chunks/chunk-TUN34LAQ.js";
|
|
10
|
+
import "../_chunks/chunk-EXYZLL7A.js";
|
|
11
|
+
import "../_chunks/chunk-FQHFT5IH.js";
|
|
907
12
|
export {
|
|
908
13
|
addBackendGenerator,
|
|
909
14
|
generator_default as default,
|