@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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/generators/shared/project-type.ts
|
|
2
|
+
import { parseJson } from "@nx/devkit";
|
|
3
|
+
function resolveProjectType(tree, root, declared) {
|
|
4
|
+
if (declared) return declared === "library" ? "library" : "application";
|
|
5
|
+
const prefix = root === "" || root === "." ? "" : `${root}/`;
|
|
6
|
+
if (tree.exists(`${prefix}tsconfig.lib.json`)) return "library";
|
|
7
|
+
if (tree.exists(`${prefix}tsconfig.app.json`)) return "application";
|
|
8
|
+
const raw = tree.read(`${prefix}package.json`, "utf-8");
|
|
9
|
+
if (raw === null) return "library";
|
|
10
|
+
try {
|
|
11
|
+
const manifest = parseJson(raw);
|
|
12
|
+
if (manifest && !["exports", "main", "module", "bin"].some((key) => Boolean(manifest[key]))) return "application";
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
return "library";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
resolveProjectType
|
|
20
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ../integrations/dist/manifest/manifest.js
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
var MANIFEST_FILENAME = "archweave-integrations.json";
|
|
6
|
+
function createEmptyManifest() {
|
|
7
|
+
return { integrations: {} };
|
|
8
|
+
}
|
|
9
|
+
function loadManifest(root) {
|
|
10
|
+
const manifestPath = join(root, MANIFEST_FILENAME);
|
|
11
|
+
if (!existsSync(manifestPath)) {
|
|
12
|
+
return createEmptyManifest();
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
16
|
+
if (parsed && typeof parsed === "object" && typeof parsed.integrations === "object") {
|
|
17
|
+
return { integrations: parsed.integrations ?? {} };
|
|
18
|
+
}
|
|
19
|
+
return createEmptyManifest();
|
|
20
|
+
} catch {
|
|
21
|
+
return createEmptyManifest();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function serializeManifest(manifest) {
|
|
25
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
function writeManifestAtomic(root, manifest) {
|
|
29
|
+
const finalPath = join(root, MANIFEST_FILENAME);
|
|
30
|
+
if (existsSync(finalPath) && lstatSync(finalPath).isSymbolicLink()) {
|
|
31
|
+
throw new Error(`Refusing to write ${MANIFEST_FILENAME}: the path is a symlink. Remove or replace it with a regular file first.`);
|
|
32
|
+
}
|
|
33
|
+
const suffix = randomBytes(6).toString("hex");
|
|
34
|
+
const tempPath = join(root, `.${MANIFEST_FILENAME}.tmp-${suffix}`);
|
|
35
|
+
try {
|
|
36
|
+
writeFileSync(tempPath, serializeManifest(manifest));
|
|
37
|
+
renameSync(tempPath, finalPath);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (existsSync(tempPath)) {
|
|
40
|
+
unlinkSync(tempPath);
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
loadManifest,
|
|
48
|
+
writeManifestAtomic
|
|
49
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadManifest,
|
|
3
|
+
writeManifestAtomic
|
|
4
|
+
} from "./chunk-U6LAFHSF.js";
|
|
5
|
+
import {
|
|
6
|
+
getCatalogEntry,
|
|
7
|
+
recordMetadataFor,
|
|
8
|
+
verifyClaudeMarketplacePlugin
|
|
9
|
+
} from "./chunk-AQHV7DJS.js";
|
|
10
|
+
|
|
11
|
+
// ../integrations/dist/manifest/record-plan.js
|
|
12
|
+
function entriesEqual(a, b) {
|
|
13
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
14
|
+
}
|
|
15
|
+
function buildRecordPlan(input) {
|
|
16
|
+
const isRecordable = input.verification.verified && input.verificationSource === "claude-cli";
|
|
17
|
+
const proposedEntry = isRecordable ? {
|
|
18
|
+
source: input.metadata.source,
|
|
19
|
+
repository: input.metadata.repository,
|
|
20
|
+
version: input.verification.resolvedVersion,
|
|
21
|
+
method: input.metadata.method,
|
|
22
|
+
managedBy: "nx-archweave",
|
|
23
|
+
verification: "passed",
|
|
24
|
+
target: input.target,
|
|
25
|
+
evidenceKind: "claude-cli"
|
|
26
|
+
} : void 0;
|
|
27
|
+
const hasConflict = input.existingEntry !== void 0 && proposedEntry !== void 0 && !entriesEqual(input.existingEntry, proposedEntry);
|
|
28
|
+
return {
|
|
29
|
+
id: input.id,
|
|
30
|
+
target: input.target,
|
|
31
|
+
verificationSource: input.verificationSource,
|
|
32
|
+
currentVerification: input.verification,
|
|
33
|
+
existingEntry: input.existingEntry,
|
|
34
|
+
proposedEntry,
|
|
35
|
+
hasConflict
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/generators/shared/record-pipeline.ts
|
|
40
|
+
async function runRecordPipeline(input) {
|
|
41
|
+
const entry = getCatalogEntry(input.id);
|
|
42
|
+
const metadata = entry === void 0 ? void 0 : recordMetadataFor(entry);
|
|
43
|
+
if (!entry || !metadata) {
|
|
44
|
+
throw new Error(`No manifest metadata is defined for "${input.id}".`);
|
|
45
|
+
}
|
|
46
|
+
const { result: verification, source: verificationSource } = await verifyClaudeMarketplacePlugin(
|
|
47
|
+
entry,
|
|
48
|
+
{ runner: input.runner },
|
|
49
|
+
{ workspaceRoot: input.workspaceRoot, targetEnvironment: input.target }
|
|
50
|
+
);
|
|
51
|
+
const manifest = loadManifest(input.workspaceRoot);
|
|
52
|
+
const existingEntry = manifest.integrations[input.id];
|
|
53
|
+
const plan = buildRecordPlan({ id: input.id, target: input.target, verificationSource, verification, existingEntry, metadata });
|
|
54
|
+
let wasWritten = false;
|
|
55
|
+
if (input.write && plan.proposedEntry) {
|
|
56
|
+
writeManifestAtomic(input.workspaceRoot, {
|
|
57
|
+
integrations: { ...manifest.integrations, [input.id]: plan.proposedEntry }
|
|
58
|
+
});
|
|
59
|
+
wasWritten = true;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
verificationSource,
|
|
63
|
+
verification,
|
|
64
|
+
existingEntry,
|
|
65
|
+
proposedEntry: plan.proposedEntry,
|
|
66
|
+
hasConflict: plan.hasConflict,
|
|
67
|
+
wasWritten
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
runRecordPipeline
|
|
73
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/generators/shared/template-files.ts
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { generateFiles } from "@nx/devkit";
|
|
5
|
+
import { createTree } from "@nx/devkit/testing";
|
|
6
|
+
function generatorFilesDir(generator, fromUrl) {
|
|
7
|
+
for (const relative of [`../${generator}/files`, `./generators/${generator}/files`]) {
|
|
8
|
+
const dir = fileURLToPath(new URL(relative, fromUrl));
|
|
9
|
+
if (existsSync(dir)) return dir;
|
|
10
|
+
}
|
|
11
|
+
throw new Error(`The "${generator}" generator's template files are missing next to ${fileURLToPath(fromUrl)}`);
|
|
12
|
+
}
|
|
13
|
+
function renderTemplateFiles(srcFolder, target, substitutions) {
|
|
14
|
+
const scratch = createTree();
|
|
15
|
+
const seeded = new Set(scratch.listChanges().map((change) => change.path));
|
|
16
|
+
generateFiles(scratch, srcFolder, target, substitutions);
|
|
17
|
+
return scratch.listChanges().flatMap(
|
|
18
|
+
(change) => change.type === "CREATE" && change.content !== null && !seeded.has(change.path) ? [{ filePath: change.path, content: change.content.toString("utf-8") }] : []
|
|
19
|
+
).sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
generatorFilesDir,
|
|
24
|
+
renderTemplateFiles
|
|
25
|
+
};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyFsdEslintConfig,
|
|
3
|
+
buildFsdEslintPasteSnippet,
|
|
4
|
+
composeFsdLayerGuide
|
|
5
|
+
} from "./chunk-L2RZ2LBO.js";
|
|
6
|
+
import {
|
|
7
|
+
profileById,
|
|
8
|
+
resolveFrontendApp
|
|
9
|
+
} from "./chunk-2WSX5YNB.js";
|
|
10
|
+
import {
|
|
11
|
+
ESLINT_CONFIG_CANDIDATES
|
|
12
|
+
} from "./chunk-6ZTIUBB2.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveDryRun
|
|
15
|
+
} from "./chunk-TUN34LAQ.js";
|
|
16
|
+
import {
|
|
17
|
+
applyJsonEdits,
|
|
18
|
+
readToolkitConfig,
|
|
19
|
+
resolveFrontendOption,
|
|
20
|
+
resolveLocaleOption,
|
|
21
|
+
setJsonValue,
|
|
22
|
+
treePackageManager
|
|
23
|
+
} from "./chunk-EXYZLL7A.js";
|
|
24
|
+
import {
|
|
25
|
+
detectYarnFlavor,
|
|
26
|
+
packageManagerCommands
|
|
27
|
+
} from "./chunk-FQHFT5IH.js";
|
|
28
|
+
|
|
29
|
+
// src/generators/add-fsd-lite/generator.ts
|
|
30
|
+
import { posix } from "node:path";
|
|
31
|
+
|
|
32
|
+
// ../shared/dist/lib/toolkit-config.js
|
|
33
|
+
function readFsdEnforcement(fsd) {
|
|
34
|
+
if (typeof fsd !== "object" || fsd === null)
|
|
35
|
+
return void 0;
|
|
36
|
+
const enforcement = fsd["enforcement"];
|
|
37
|
+
return enforcement === "error" || enforcement === "warn" ? enforcement : void 0;
|
|
38
|
+
}
|
|
39
|
+
var DEFAULT_CONFLICT_POLICY = "merge-proposal";
|
|
40
|
+
|
|
41
|
+
// ../templates/dist/fsd/fsd-steiger.js
|
|
42
|
+
var STEIGER_DEPENDENCIES = {
|
|
43
|
+
steiger: "^0.7.0",
|
|
44
|
+
"@feature-sliced/steiger-plugin": "^0.8.0"
|
|
45
|
+
};
|
|
46
|
+
var STEIGER_CONFIG_FILE = "steiger.config.mjs";
|
|
47
|
+
var FSD_CHECK_TARGET = "fsd-check";
|
|
48
|
+
function buildFsdCheckTarget(sourceRootName = "src") {
|
|
49
|
+
return { command: `steiger ${sourceRootName}`, options: { cwd: "{projectRoot}" } };
|
|
50
|
+
}
|
|
51
|
+
function buildSteigerConfig({ renamedLayerDirs, locale }) {
|
|
52
|
+
const ko = locale === "ko";
|
|
53
|
+
const lines = [
|
|
54
|
+
"import { defineConfig } from 'steiger';",
|
|
55
|
+
"import fsd from '@feature-sliced/steiger-plugin';",
|
|
56
|
+
"",
|
|
57
|
+
ko ? "// nx-archweave: \uC815\uC2DD FSD \uADDC\uCE59 \uC804\uCCB4(recommended). \uAD6C\uC870\uB294 generator\uAC00 \uC815\uC2DD\uB300\uB85C \uB9CC\uB4E4\uBBC0\uB85C \uADF8\uB300\uB85C \uB9DE\uBB3C\uB9BD\uB2C8\uB2E4." : "// nx-archweave: the canonical rule set (recommended). The generators scaffold canonical FSD, so it fits as is.",
|
|
58
|
+
"export default defineConfig([",
|
|
59
|
+
" ...fsd.configs.recommended,",
|
|
60
|
+
" {",
|
|
61
|
+
" rules: {",
|
|
62
|
+
ko ? " // \uC544\uBB34\uB3C4 \uCC38\uC870\uD558\uC9C0 \uC54A\uB294 \uC2AC\uB77C\uC774\uC2A4 = \uBC29\uAE08 \uC0DD\uC131\uD55C \uC2AC\uB77C\uC774\uC2A4. \uACBD\uACE0\uB85C \uBCF4\uC774\uB418 \uAC80\uC0AC\uB97C \uB9C9\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : " // a slice nothing references yet is every freshly generated slice: visible, not blocking",
|
|
63
|
+
" 'fsd/insignificant-slice': 'warn',",
|
|
64
|
+
...renamedLayerDirs.length > 0 ? [
|
|
65
|
+
ko ? ` // ${renamedLayerDirs.join(", ")}: \uD504\uB808\uC784\uC6CC\uD06C \uB77C\uC6B0\uD130\uC640 \uACB9\uCE58\uC9C0 \uC54A\uAC8C \uC774\uB984\uC744 \uBC14\uAFBC \uB808\uC774\uC5B4 (FSD \uACF5\uC2DD \uBA85\uBA85) \u2014 steiger 0.7\uC740 \uC544\uC9C1 \uC624\uD0C0\uB85C \uBD05\uB2C8\uB2E4.` : ` // ${renamedLayerDirs.join(", ")}: layers renamed out of the framework router's way (FSD's own naming) \u2014 steiger 0.7 still reads them as typos`,
|
|
66
|
+
" 'fsd/typo-in-layer-name': 'off',"
|
|
67
|
+
] : [],
|
|
68
|
+
" },",
|
|
69
|
+
" },",
|
|
70
|
+
"]);",
|
|
71
|
+
""
|
|
72
|
+
];
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/generators/add-fsd-lite/render.ts
|
|
77
|
+
function renderAddFsdLiteReport(report) {
|
|
78
|
+
const lines = [];
|
|
79
|
+
if (report.aborted) {
|
|
80
|
+
lines.push("add-fsd-lite aborted \u2014 nothing was written.");
|
|
81
|
+
const refusal = report.appRefusal;
|
|
82
|
+
if (refusal !== null) {
|
|
83
|
+
lines.push("");
|
|
84
|
+
lines.push(" FSD layers live inside the frontend app's source root (e.g. apps/web/src), and no app could be picked:");
|
|
85
|
+
if (refusal.reason === "project-not-found") {
|
|
86
|
+
lines.push(` --project=${refusal.project} names no project in this workspace.`);
|
|
87
|
+
} else if (refusal.reason === "ambiguous-frontend") {
|
|
88
|
+
lines.push(" more than one frontend app was found \u2014 rerun with --project=<one of them>:");
|
|
89
|
+
for (const name of refusal.candidates) lines.push(` - ${name}`);
|
|
90
|
+
} else if (refusal.reason === "invalid-project-json") {
|
|
91
|
+
lines.push(` a project.json could not be parsed (${refusal.detail}) \u2014 fix it, then rerun.`);
|
|
92
|
+
} else if (refusal.otherProjects.length > 0) {
|
|
93
|
+
lines.push(" no project was recognised as a frontend app. If one of these is it, rerun with --project=<name>:");
|
|
94
|
+
for (const name of refusal.otherProjects) lines.push(` - ${name}`);
|
|
95
|
+
} else {
|
|
96
|
+
lines.push(" the workspace has no projects \u2014 create one (e.g. nx g @nx/react:app or nx g @nx/next:app), then rerun.");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return lines.join("\n");
|
|
100
|
+
}
|
|
101
|
+
if (report.dryRun) {
|
|
102
|
+
lines.push("add-fsd-lite dry-run \u2014 no files were written.");
|
|
103
|
+
lines.push("");
|
|
104
|
+
}
|
|
105
|
+
lines.push(`FSD layers (in ${report.project}, ${report.sourceRoot}):`);
|
|
106
|
+
if (report.layerGuide !== null && report.frontend === "generic") {
|
|
107
|
+
lines.push("Frontend: framework not recognised \u2014 the common FSD rules apply, with no framework-specific layer names.");
|
|
108
|
+
}
|
|
109
|
+
if (report.layerGuide !== null) lines.push(report.layerGuide);
|
|
110
|
+
lines.push("");
|
|
111
|
+
const where = report.eslintPath ?? `${report.project}'s eslint.config.mjs`;
|
|
112
|
+
switch (report.eslint) {
|
|
113
|
+
case "applied":
|
|
114
|
+
lines.push(`ESLint: archweave/fsd-boundaries (${report.enforcement}) written to ${where}`);
|
|
115
|
+
break;
|
|
116
|
+
case "already-applied":
|
|
117
|
+
lines.push(`ESLint: archweave/fsd-boundaries already wired in ${where} \u2014 no change`);
|
|
118
|
+
break;
|
|
119
|
+
case "degraded-unrecognized":
|
|
120
|
+
lines.push(`ESLint: ${report.eslintPath === null ? `no ESLint config found for ${report.project}` : `${where} was NOT recognized \u2014 left untouched`}.`);
|
|
121
|
+
lines.push(" Add the rule yourself (then the layer rules are actually enforced):");
|
|
122
|
+
break;
|
|
123
|
+
case "snippet-only":
|
|
124
|
+
lines.push(`ESLint: report-only (pass --applyEslint to write the rule into ${where}).`);
|
|
125
|
+
break;
|
|
126
|
+
case "not-applied":
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (report.snippet !== null) lines.push(report.snippet);
|
|
130
|
+
if (report.steiger !== null) {
|
|
131
|
+
const st = report.steiger;
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("steiger (official FSD linter, opt-in):");
|
|
134
|
+
lines.push(
|
|
135
|
+
` ${st.configPath}: ${st.config === "created" ? "created" : st.config === "unchanged" ? "unchanged" : "kept \u2014 an existing config with other content is never replaced"}`
|
|
136
|
+
);
|
|
137
|
+
lines.push(
|
|
138
|
+
st.target === "added" ? ` target ${"fsd-check"}: added to ${st.targetPath}` : st.target === "present" ? ` target ${"fsd-check"}: already present in ${st.targetPath}` : " target fsd-check: NOT added \u2014 no parseable project.json / package.json for the app"
|
|
139
|
+
);
|
|
140
|
+
lines.push(
|
|
141
|
+
st.dependencies === "added" ? ` devDependencies: steiger + @feature-sliced/steiger-plugin added to package.json \u2014 run \`${st.installCommand}\`` : st.dependencies === "present" ? " devDependencies: already present" : " devDependencies: NOT added \u2014 the root package.json could not be parsed"
|
|
142
|
+
);
|
|
143
|
+
lines.push(` run it with: ${st.runCommand} (steiger needs Node >= 22.18)`);
|
|
144
|
+
}
|
|
145
|
+
return lines.join("\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/generators/add-fsd-lite/generator.ts
|
|
149
|
+
function resolveEnforcement(option, config) {
|
|
150
|
+
if (option === "error" || option === "warn") return option;
|
|
151
|
+
return readFsdEnforcement(config?.["fsd"]) ?? "warn";
|
|
152
|
+
}
|
|
153
|
+
function runAddFsdLite(tree, options) {
|
|
154
|
+
const config = readToolkitConfig(tree);
|
|
155
|
+
const locale = resolveLocaleOption(options.locale, config);
|
|
156
|
+
const enforcement = resolveEnforcement(options.enforcement, config);
|
|
157
|
+
const frontend = resolveFrontendOption(options.frontend, config, "add-fsd-lite");
|
|
158
|
+
const dryRun = options.dryRun ?? false;
|
|
159
|
+
const applyEslint = options.applyEslint ?? false;
|
|
160
|
+
const app = resolveFrontendApp(tree, options.project);
|
|
161
|
+
if (app.kind !== "found") {
|
|
162
|
+
const snippet2 = buildFsdEslintPasteSnippet(enforcement, locale);
|
|
163
|
+
const appRefusal = app.kind === "not-found" ? { reason: "project-not-found", project: app.project } : app.kind === "ambiguous" ? { reason: "ambiguous-frontend", candidates: app.candidates } : app.kind === "invalid-project-json" ? { reason: "invalid-project-json", detail: app.detail } : { reason: "no-frontend-app", otherProjects: app.otherProjects };
|
|
164
|
+
return { dryRun, aborted: true, appRefusal, project: null, sourceRoot: null, enforcement, frontend, eslint: "not-applied", eslintPath: null, snippet: snippet2, layerGuide: null, steiger: null };
|
|
165
|
+
}
|
|
166
|
+
const appDir = app.root === "" || app.root === "." ? "" : `${app.root}/`;
|
|
167
|
+
const eslintRoot = posix.relative(app.root === "" ? "." : app.root, app.sourceRoot) || ".";
|
|
168
|
+
const profile = app.framework === frontend ? app.profile : profileById(frontend);
|
|
169
|
+
const { platformSuffixes, routerDirs } = profile;
|
|
170
|
+
const framework = { platformSuffixes, routerDirs };
|
|
171
|
+
const snippet = buildFsdEslintPasteSnippet(enforcement, locale, eslintRoot, framework);
|
|
172
|
+
const eslintPath = ESLINT_CONFIG_CANDIDATES.map((name) => `${appDir}${name}`).find((path) => tree.exists(path)) ?? null;
|
|
173
|
+
const configText = eslintPath === null ? null : tree.read(eslintPath, "utf-8");
|
|
174
|
+
let eslint;
|
|
175
|
+
if (!applyEslint) {
|
|
176
|
+
eslint = configText !== null && applyFsdEslintConfig(configText, enforcement, eslintRoot, framework).status === "already-applied" ? "already-applied" : "snippet-only";
|
|
177
|
+
} else if (configText === null) {
|
|
178
|
+
eslint = "degraded-unrecognized";
|
|
179
|
+
} else {
|
|
180
|
+
const result = applyFsdEslintConfig(configText, enforcement, eslintRoot, framework);
|
|
181
|
+
eslint = result.status === "applied" ? "applied" : result.status === "already-applied" ? "already-applied" : "degraded-unrecognized";
|
|
182
|
+
if (result.status === "applied" && !dryRun && eslintPath !== null) tree.write(eslintPath, result.text);
|
|
183
|
+
}
|
|
184
|
+
const steiger = options.steiger ? addSteiger(tree, { appDir, appRoot: app.root, sourceRoot: app.sourceRoot, profile, locale, write: !dryRun, config }) : null;
|
|
185
|
+
return {
|
|
186
|
+
dryRun,
|
|
187
|
+
aborted: false,
|
|
188
|
+
appRefusal: null,
|
|
189
|
+
project: app.project,
|
|
190
|
+
sourceRoot: app.sourceRoot,
|
|
191
|
+
steiger,
|
|
192
|
+
enforcement,
|
|
193
|
+
frontend,
|
|
194
|
+
eslint,
|
|
195
|
+
eslintPath,
|
|
196
|
+
snippet: eslint === "applied" || eslint === "already-applied" ? null : snippet,
|
|
197
|
+
layerGuide: composeFsdLayerGuide({ profile, sourceRoot: app.sourceRoot, locale })
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function readJsonObject(tree, path) {
|
|
201
|
+
const raw = tree.read(path, "utf-8");
|
|
202
|
+
if (raw === null) return null;
|
|
203
|
+
try {
|
|
204
|
+
const parsed = JSON.parse(raw);
|
|
205
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { raw, parsed } : null;
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function addSteiger(tree, input) {
|
|
211
|
+
const configPath = `${input.appDir}${STEIGER_CONFIG_FILE}`;
|
|
212
|
+
const configText = buildSteigerConfig({ renamedLayerDirs: Object.values(input.profile.layerDirs), locale: input.locale });
|
|
213
|
+
const existing = tree.read(configPath, "utf-8");
|
|
214
|
+
let configAction;
|
|
215
|
+
if (existing === null) {
|
|
216
|
+
configAction = "created";
|
|
217
|
+
if (input.write) tree.write(configPath, configText);
|
|
218
|
+
} else {
|
|
219
|
+
configAction = existing === configText ? "unchanged" : "kept";
|
|
220
|
+
}
|
|
221
|
+
const rootPrefix = input.appRoot === "" || input.appRoot === "." ? "" : `${input.appRoot}/`;
|
|
222
|
+
const sourceRootName = input.sourceRoot.startsWith(rootPrefix) ? input.sourceRoot.slice(rootPrefix.length) : "src";
|
|
223
|
+
const target = buildFsdCheckTarget(sourceRootName);
|
|
224
|
+
let targetAction = "unrecognized";
|
|
225
|
+
let targetPath = null;
|
|
226
|
+
for (const candidate of [`${input.appDir}project.json`, `${input.appDir}package.json`]) {
|
|
227
|
+
const file = readJsonObject(tree, candidate);
|
|
228
|
+
if (file === null) continue;
|
|
229
|
+
targetPath = candidate;
|
|
230
|
+
const holder = candidate.endsWith("project.json") ? file.parsed : file.parsed["nx"] ??= {};
|
|
231
|
+
if (typeof holder !== "object" || holder === null) break;
|
|
232
|
+
const targets = holder["targets"] ??= {};
|
|
233
|
+
if (typeof targets !== "object" || targets === null) break;
|
|
234
|
+
if (targets[FSD_CHECK_TARGET] !== void 0) {
|
|
235
|
+
targetAction = "present";
|
|
236
|
+
} else {
|
|
237
|
+
targets[FSD_CHECK_TARGET] = target;
|
|
238
|
+
targetAction = "added";
|
|
239
|
+
const path = candidate.endsWith("project.json") ? ["targets", FSD_CHECK_TARGET] : ["nx", "targets", FSD_CHECK_TARGET];
|
|
240
|
+
if (input.write) tree.write(candidate, setJsonValue(file.raw, path, target));
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
let dependenciesAction = "unrecognized";
|
|
245
|
+
const rootPackage = readJsonObject(tree, "package.json");
|
|
246
|
+
if (rootPackage !== null) {
|
|
247
|
+
const devDependencies = rootPackage.parsed["devDependencies"] ??= {};
|
|
248
|
+
const dependencies = rootPackage.parsed["dependencies"] ?? {};
|
|
249
|
+
const missing = Object.entries(STEIGER_DEPENDENCIES).filter(([name]) => devDependencies[name] === void 0 && dependencies[name] === void 0);
|
|
250
|
+
if (missing.length === 0) {
|
|
251
|
+
dependenciesAction = "present";
|
|
252
|
+
} else {
|
|
253
|
+
for (const [name, range] of missing) devDependencies[name] = range;
|
|
254
|
+
dependenciesAction = "added";
|
|
255
|
+
const edited = applyJsonEdits(rootPackage.raw, missing.map(([name, range]) => [["devDependencies", name], range]));
|
|
256
|
+
if (input.write) tree.write("package.json", edited);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const pm = treePackageManager(tree, input.config) ?? "pnpm";
|
|
260
|
+
const commands = packageManagerCommands(pm, pm === "yarn" ? detectYarnFlavor((p) => tree.exists(p), (p) => tree.read(p, "utf-8")) : void 0);
|
|
261
|
+
return {
|
|
262
|
+
configPath,
|
|
263
|
+
config: configAction,
|
|
264
|
+
targetPath,
|
|
265
|
+
target: targetAction,
|
|
266
|
+
dependencies: dependenciesAction,
|
|
267
|
+
installCommand: commands.install,
|
|
268
|
+
runCommand: `${commands.runNx} run <project>:${FSD_CHECK_TARGET}`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
async function addFsdLiteGenerator(tree, options) {
|
|
272
|
+
console.log(renderAddFsdLiteReport(runAddFsdLite(tree, { ...options, dryRun: resolveDryRun(options) })));
|
|
273
|
+
}
|
|
274
|
+
var generator_default = addFsdLiteGenerator;
|
|
275
|
+
|
|
276
|
+
export {
|
|
277
|
+
DEFAULT_CONFLICT_POLICY,
|
|
278
|
+
renderAddFsdLiteReport,
|
|
279
|
+
runAddFsdLite,
|
|
280
|
+
addFsdLiteGenerator,
|
|
281
|
+
generator_default
|
|
282
|
+
};
|