@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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// ../template-engine/dist/backend/eslint-boundaries.js
|
|
2
|
+
var DEFAULT_BOUNDARY_TAGS = { frontend: "scope:frontend", backend: "scope:backend", shared: "scope:shared" };
|
|
3
|
+
function readBoundaryTags(config) {
|
|
4
|
+
const boundaries = config?.["boundaries"];
|
|
5
|
+
const tags = typeof boundaries === "object" && boundaries !== null ? boundaries["tags"] : void 0;
|
|
6
|
+
const pick = (role) => {
|
|
7
|
+
const value = typeof tags === "object" && tags !== null ? tags[role] : void 0;
|
|
8
|
+
return typeof value === "string" && value.trim() !== "" ? value : DEFAULT_BOUNDARY_TAGS[role];
|
|
9
|
+
};
|
|
10
|
+
return { frontend: pick("frontend"), backend: pick("backend"), shared: pick("shared") };
|
|
11
|
+
}
|
|
12
|
+
function scopeConstraints(tags = DEFAULT_BOUNDARY_TAGS) {
|
|
13
|
+
return [
|
|
14
|
+
{ sourceTag: tags.frontend, notDependOnLibsWithTags: [tags.backend] },
|
|
15
|
+
{ sourceTag: tags.backend, notDependOnLibsWithTags: [tags.frontend] },
|
|
16
|
+
{ sourceTag: tags.shared, notDependOnLibsWithTags: [tags.frontend, tags.backend] }
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
var SCOPE_CONSTRAINTS = scopeConstraints();
|
|
20
|
+
var ESLINT_CONFIG_CANDIDATES = ["eslint.config.mjs", "eslint.config.js", "eslint.config.cjs"];
|
|
21
|
+
var appliedMarker = (tags) => `sourceTag: '${tags.frontend}'`;
|
|
22
|
+
var ALLOW_ALL_BLOCK = /depConstraints:\s*\[\s*\{\s*sourceTag:\s*'\*',\s*onlyDependOnLibsWithTags:\s*\[\s*'\*'\s*\],?\s*\},?\s*\]/g;
|
|
23
|
+
function maskComments(text) {
|
|
24
|
+
const out = text.split("");
|
|
25
|
+
let mode = "code";
|
|
26
|
+
let quote = "";
|
|
27
|
+
for (let i = 0; i < text.length; i++) {
|
|
28
|
+
const c = text[i];
|
|
29
|
+
const d = text[i + 1];
|
|
30
|
+
if (mode === "code") {
|
|
31
|
+
if (c === "'" || c === '"' || c === "`") {
|
|
32
|
+
mode = "string";
|
|
33
|
+
quote = c;
|
|
34
|
+
} else if (c === "/" && d === "/") {
|
|
35
|
+
mode = "line";
|
|
36
|
+
out[i] = " ";
|
|
37
|
+
} else if (c === "/" && d === "*") {
|
|
38
|
+
mode = "block";
|
|
39
|
+
out[i] = " ";
|
|
40
|
+
}
|
|
41
|
+
} else if (mode === "string") {
|
|
42
|
+
if (c === "\\") {
|
|
43
|
+
i++;
|
|
44
|
+
} else if (c === quote) {
|
|
45
|
+
mode = "code";
|
|
46
|
+
}
|
|
47
|
+
} else if (mode === "line") {
|
|
48
|
+
if (c === "\n") {
|
|
49
|
+
mode = "code";
|
|
50
|
+
} else {
|
|
51
|
+
out[i] = " ";
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
if (c === "*" && d === "/") {
|
|
55
|
+
out[i] = " ";
|
|
56
|
+
out[i + 1] = " ";
|
|
57
|
+
i++;
|
|
58
|
+
mode = "code";
|
|
59
|
+
} else if (c !== "\n") {
|
|
60
|
+
out[i] = " ";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out.join("");
|
|
65
|
+
}
|
|
66
|
+
function findAllowAllMatches(masked) {
|
|
67
|
+
const matches = [];
|
|
68
|
+
ALLOW_ALL_BLOCK.lastIndex = 0;
|
|
69
|
+
for (let m = ALLOW_ALL_BLOCK.exec(masked); m !== null; m = ALLOW_ALL_BLOCK.exec(masked)) {
|
|
70
|
+
matches.push({ index: m.index, length: m[0].length });
|
|
71
|
+
}
|
|
72
|
+
return matches;
|
|
73
|
+
}
|
|
74
|
+
function classifyBoundaryConstraints(configText, tags = DEFAULT_BOUNDARY_TAGS) {
|
|
75
|
+
const masked = maskComments(configText);
|
|
76
|
+
if (masked.includes(appliedMarker(tags)))
|
|
77
|
+
return "enforced";
|
|
78
|
+
if (findAllowAllMatches(masked).length > 0)
|
|
79
|
+
return "allow-all";
|
|
80
|
+
return "unknown";
|
|
81
|
+
}
|
|
82
|
+
function renderConstraintsBlock(indent, eol, tags) {
|
|
83
|
+
const renderList = (list) => list.map((t) => `'${t}'`).join(", ");
|
|
84
|
+
const inner = scopeConstraints(tags).map((c) => {
|
|
85
|
+
const field = c.notDependOnLibsWithTags ? `notDependOnLibsWithTags: [${renderList(c.notDependOnLibsWithTags)}]` : `onlyDependOnLibsWithTags: [${renderList(c.onlyDependOnLibsWithTags ?? [])}]`;
|
|
86
|
+
return [`${indent} {`, `${indent} sourceTag: '${c.sourceTag}',`, `${indent} ${field},`, `${indent} },`].join(eol);
|
|
87
|
+
}).join(eol);
|
|
88
|
+
return [
|
|
89
|
+
`// nx-archweave boundaries \u2014 the frontend and the backend may never import each other`,
|
|
90
|
+
`${indent}depConstraints: [`,
|
|
91
|
+
inner,
|
|
92
|
+
[
|
|
93
|
+
`${indent} // baseline: everything else stays allowed (constraints apply conjunctively)`,
|
|
94
|
+
`${indent} {`,
|
|
95
|
+
`${indent} sourceTag: '*',`,
|
|
96
|
+
`${indent} onlyDependOnLibsWithTags: ['*'],`,
|
|
97
|
+
`${indent} },`
|
|
98
|
+
].join(eol),
|
|
99
|
+
`${indent}]`
|
|
100
|
+
].join(eol);
|
|
101
|
+
}
|
|
102
|
+
function applyBoundaryConstraints(configText, tags = DEFAULT_BOUNDARY_TAGS) {
|
|
103
|
+
const classification = classifyBoundaryConstraints(configText, tags);
|
|
104
|
+
if (classification === "enforced")
|
|
105
|
+
return { status: "already-applied" };
|
|
106
|
+
if (classification === "unknown")
|
|
107
|
+
return { status: "unrecognized" };
|
|
108
|
+
const masked = maskComments(configText);
|
|
109
|
+
const matches = findAllowAllMatches(masked);
|
|
110
|
+
if (matches.length !== 1) {
|
|
111
|
+
return { status: "unrecognized" };
|
|
112
|
+
}
|
|
113
|
+
const [match] = matches;
|
|
114
|
+
const eol = configText.includes("\r\n") ? "\r\n" : "\n";
|
|
115
|
+
const lineStart = configText.lastIndexOf("\n", match.index) + 1;
|
|
116
|
+
const indent = /^[ \t]*/.exec(configText.slice(lineStart, match.index))?.[0] ?? "";
|
|
117
|
+
const replacement = renderConstraintsBlock(indent, eol, tags);
|
|
118
|
+
const text = configText.slice(0, match.index) + replacement + configText.slice(match.index + match.length);
|
|
119
|
+
return { status: "applied", text };
|
|
120
|
+
}
|
|
121
|
+
function buildBoundarySnippet(tags = DEFAULT_BOUNDARY_TAGS) {
|
|
122
|
+
return [
|
|
123
|
+
"// nx-archweave project boundaries \u2014 merge these entries into the depConstraints",
|
|
124
|
+
"// array of the '@nx/enforce-module-boundaries' rule in your root ESLint config.",
|
|
125
|
+
"// They ride on top of your existing entries: constraints apply conjunctively,",
|
|
126
|
+
"// so the allow-all baseline can stay.",
|
|
127
|
+
renderConstraintsBlock("", "\n", tags)
|
|
128
|
+
].join("\n");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export {
|
|
132
|
+
DEFAULT_BOUNDARY_TAGS,
|
|
133
|
+
readBoundaryTags,
|
|
134
|
+
ESLINT_CONFIG_CANDIDATES,
|
|
135
|
+
maskComments,
|
|
136
|
+
classifyBoundaryConstraints,
|
|
137
|
+
applyBoundaryConstraints,
|
|
138
|
+
buildBoundarySnippet
|
|
139
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SEGMENT_NAME_PATTERN,
|
|
3
|
+
isEssenceSegmentName,
|
|
4
|
+
isUsableSliceApiName,
|
|
5
|
+
profileById,
|
|
6
|
+
resolveFrontendApp,
|
|
7
|
+
resolveSlicePublicApiName
|
|
8
|
+
} from "./chunk-2WSX5YNB.js";
|
|
9
|
+
import {
|
|
10
|
+
generatorFilesDir,
|
|
11
|
+
renderTemplateFiles
|
|
12
|
+
} from "./chunk-YNMLB4DL.js";
|
|
13
|
+
import {
|
|
14
|
+
RECORDED_FRONTENDS,
|
|
15
|
+
readToolkitConfig,
|
|
16
|
+
recordedFrontends,
|
|
17
|
+
resolveLocaleOption
|
|
18
|
+
} from "./chunk-EXYZLL7A.js";
|
|
19
|
+
|
|
20
|
+
// src/generators/slice/core.ts
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
function composeSlice(input) {
|
|
23
|
+
const files = generatorFilesDir("slice", import.meta.url);
|
|
24
|
+
const directory = `${input.sourceRoot}/${input.layerDirName}/${input.group === void 0 ? "" : `${input.group}/`}${input.name}`;
|
|
25
|
+
const shared = input.layer === "shared";
|
|
26
|
+
const values = {
|
|
27
|
+
name: input.name,
|
|
28
|
+
publicApiName: input.publicApiName,
|
|
29
|
+
layer: input.layer,
|
|
30
|
+
locale: input.locale,
|
|
31
|
+
modulePath: shared ? `./${input.name}` : `./model/${input.name}`
|
|
32
|
+
};
|
|
33
|
+
const module = renderTemplateFiles(join(files, "module"), shared ? directory : `${directory}/model`, values).filter((file) => input.withSpec !== false || !file.filePath.endsWith(".spec.ts")).sort((a, b) => Number(a.filePath.endsWith(".spec.ts")) - Number(b.filePath.endsWith(".spec.ts")));
|
|
34
|
+
const segments = shared ? [] : Array.from(new Set(input.segments)).filter((segment) => segment !== "model").flatMap((segment) => renderTemplateFiles(join(files, "segment"), `${directory}/${segment}`, { ...values, segment }));
|
|
35
|
+
return {
|
|
36
|
+
directory,
|
|
37
|
+
publicApiName: input.publicApiName,
|
|
38
|
+
files: [...renderTemplateFiles(join(files, "index"), directory, values), ...module, ...segments]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
var KEBAB_CASE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
42
|
+
function layerDirNameFor(layer, frontend) {
|
|
43
|
+
return profileById(frontend).layerDirs[layer] ?? layer;
|
|
44
|
+
}
|
|
45
|
+
function layerDirFor(layer, sourceRoot, frontend = "react-vite") {
|
|
46
|
+
return `${sourceRoot}/${layerDirNameFor(layer, frontend)}`;
|
|
47
|
+
}
|
|
48
|
+
function resolveFrontendForApp(option, config, detected) {
|
|
49
|
+
if (option !== void 0) return option;
|
|
50
|
+
const recorded = config?.["frontend"];
|
|
51
|
+
if (typeof recorded === "string" && RECORDED_FRONTENDS.includes(recorded) && recorded !== "none") {
|
|
52
|
+
return recorded;
|
|
53
|
+
}
|
|
54
|
+
return detected;
|
|
55
|
+
}
|
|
56
|
+
function listFilesUnder(tree, dir, prefix = "") {
|
|
57
|
+
return tree.children(dir).flatMap((child) => {
|
|
58
|
+
const path = `${dir}/${child}`;
|
|
59
|
+
return tree.isFile(path) ? [`${prefix}${child}`] : listFilesUnder(tree, path, `${prefix}${child}/`);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function computeSliceAction(tree, plan) {
|
|
63
|
+
if (!tree.exists(plan.directory)) return "created";
|
|
64
|
+
const expectedNames = plan.files.map((f) => f.filePath.slice(plan.directory.length + 1)).sort();
|
|
65
|
+
const actualNames = listFilesUnder(tree, plan.directory).sort();
|
|
66
|
+
if (actualNames.length !== expectedNames.length || !actualNames.every((n, i) => n === expectedNames[i])) {
|
|
67
|
+
return "skipped";
|
|
68
|
+
}
|
|
69
|
+
const allMatch = plan.files.every((f) => tree.read(f.filePath, "utf-8") === f.content);
|
|
70
|
+
return allMatch ? "unchanged" : "skipped";
|
|
71
|
+
}
|
|
72
|
+
function refusal(reason, dryRun, layer, name, app, extra = {}) {
|
|
73
|
+
const layerDir = layerDirFor(layer, app.sourceRoot);
|
|
74
|
+
return {
|
|
75
|
+
dryRun,
|
|
76
|
+
layer,
|
|
77
|
+
name,
|
|
78
|
+
project: app.project,
|
|
79
|
+
layerDir,
|
|
80
|
+
directory: `${layerDir}/${name}`,
|
|
81
|
+
publicApiName: null,
|
|
82
|
+
refused: true,
|
|
83
|
+
refusalReason: reason,
|
|
84
|
+
action: null,
|
|
85
|
+
files: [],
|
|
86
|
+
...extra
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function runFsdSlice(tree, layer, options) {
|
|
90
|
+
const dryRun = options.dryRun ?? false;
|
|
91
|
+
const unresolved = { project: null, sourceRoot: "<app>/src" };
|
|
92
|
+
if (!KEBAB_CASE.test(options.name)) {
|
|
93
|
+
return refusal("invalid-name", dryRun, layer, options.name, unresolved);
|
|
94
|
+
}
|
|
95
|
+
if (!isUsableSliceApiName(resolveSlicePublicApiName(options.name, options.publicApiName))) {
|
|
96
|
+
return refusal("invalid-public-api-name", dryRun, layer, options.name, unresolved);
|
|
97
|
+
}
|
|
98
|
+
if (layer === "shared" && isEssenceSegmentName(options.name)) {
|
|
99
|
+
return refusal("segment-by-essence", dryRun, layer, options.name, unresolved);
|
|
100
|
+
}
|
|
101
|
+
const segments = Array.from(new Set(options.segments ?? []));
|
|
102
|
+
if (segments.length > 0 && layer === "shared") {
|
|
103
|
+
return refusal("segments-on-shared", dryRun, layer, options.name, unresolved);
|
|
104
|
+
}
|
|
105
|
+
const badSegment = segments.find((segment) => !SEGMENT_NAME_PATTERN.test(segment));
|
|
106
|
+
if (badSegment !== void 0) return refusal("invalid-segment", dryRun, layer, options.name, unresolved, { detail: badSegment });
|
|
107
|
+
const essenceSegment = segments.find((segment) => isEssenceSegmentName(segment));
|
|
108
|
+
if (essenceSegment !== void 0) return refusal("segment-by-essence", dryRun, layer, options.name, unresolved, { detail: essenceSegment });
|
|
109
|
+
if (options.group !== void 0 && layer === "shared") return refusal("group-on-shared", dryRun, layer, options.name, unresolved);
|
|
110
|
+
if (options.group !== void 0 && (!KEBAB_CASE.test(options.group) || SEGMENT_FOLDERS.includes(options.group) || isEssenceSegmentName(options.group))) {
|
|
111
|
+
return refusal("invalid-group", dryRun, layer, options.name, unresolved, { detail: options.group });
|
|
112
|
+
}
|
|
113
|
+
const app = resolveFrontendApp(tree, options.project);
|
|
114
|
+
if (app.kind === "not-found") return refusal("project-not-found", dryRun, layer, options.name, { project: app.project, sourceRoot: unresolved.sourceRoot });
|
|
115
|
+
if (app.kind === "ambiguous") {
|
|
116
|
+
return refusal("ambiguous-frontend", dryRun, layer, options.name, unresolved, { candidates: app.candidates, recordedFrontends: recordedFrontends(readToolkitConfig(tree)) });
|
|
117
|
+
}
|
|
118
|
+
if (app.kind === "invalid-project-json") return refusal("invalid-project-json", dryRun, layer, options.name, unresolved, { detail: app.detail });
|
|
119
|
+
if (app.kind === "none") return refusal("no-frontend-app", dryRun, layer, options.name, unresolved, { candidates: app.otherProjects });
|
|
120
|
+
const config = readToolkitConfig(tree);
|
|
121
|
+
const frontend = resolveFrontendForApp(options.frontend, config, app.framework);
|
|
122
|
+
const layerDir = layerDirFor(layer, app.sourceRoot, frontend);
|
|
123
|
+
const locale = resolveLocaleOption(options.locale, config);
|
|
124
|
+
const requested = options.unitTestRunner !== void 0 && options.unitTestRunner !== "auto" ? options.unitTestRunner : recordedUnitTestRunner(config);
|
|
125
|
+
const runner = requested ?? detectUnitTestRunner(tree, app.root);
|
|
126
|
+
const plan = composeSlice({
|
|
127
|
+
layer,
|
|
128
|
+
name: options.name,
|
|
129
|
+
publicApiName: resolveSlicePublicApiName(options.name, options.publicApiName),
|
|
130
|
+
locale,
|
|
131
|
+
sourceRoot: app.sourceRoot,
|
|
132
|
+
layerDirName: layerDirNameFor(layer, frontend),
|
|
133
|
+
segments,
|
|
134
|
+
group: options.group,
|
|
135
|
+
withSpec: runner !== "none"
|
|
136
|
+
});
|
|
137
|
+
const action = computeSliceAction(tree, plan);
|
|
138
|
+
if (action === "created" && !dryRun) {
|
|
139
|
+
for (const file of plan.files) tree.write(file.filePath, file.content);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
dryRun,
|
|
143
|
+
layer,
|
|
144
|
+
name: options.name,
|
|
145
|
+
project: app.project,
|
|
146
|
+
layerDir,
|
|
147
|
+
directory: plan.directory,
|
|
148
|
+
publicApiName: plan.publicApiName,
|
|
149
|
+
refused: false,
|
|
150
|
+
refusalReason: null,
|
|
151
|
+
action,
|
|
152
|
+
files: plan.files,
|
|
153
|
+
...runner === "none" ? { specSkipped: requested === "none" ? "none-requested" : "no-test-runner" } : {}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
var SEGMENT_FOLDERS = ["ui", "model", "api", "lib", "config"];
|
|
157
|
+
function recordedUnitTestRunner(config) {
|
|
158
|
+
const fsd = config?.["fsd"];
|
|
159
|
+
const value = typeof fsd === "object" && fsd !== null ? fsd["unitTestRunner"] : void 0;
|
|
160
|
+
return value === "jest" || value === "vitest" || value === "none" ? value : void 0;
|
|
161
|
+
}
|
|
162
|
+
function hasDependency(tree, manifest, name) {
|
|
163
|
+
const raw = tree.read(manifest, "utf-8");
|
|
164
|
+
if (raw === null) return false;
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(raw);
|
|
167
|
+
return parsed["dependencies"]?.[name] !== void 0 || parsed["devDependencies"]?.[name] !== void 0;
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function detectUnitTestRunner(tree, projectRoot) {
|
|
173
|
+
const prefix = projectRoot === "" || projectRoot === "." ? "" : `${projectRoot}/`;
|
|
174
|
+
const project = tree.read(`${prefix}project.json`, "utf-8");
|
|
175
|
+
if (project !== null) {
|
|
176
|
+
try {
|
|
177
|
+
const targets = JSON.parse(project).targets ?? {};
|
|
178
|
+
for (const target of Object.values(targets)) {
|
|
179
|
+
if (target?.executor?.startsWith("@nx/jest:")) return "jest";
|
|
180
|
+
if (target?.executor?.startsWith("@nx/vite:test") || target?.executor?.startsWith("@nx/vitest:")) return "vitest";
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const dir of prefix === "" ? [""] : [prefix, ""]) {
|
|
186
|
+
if (["ts", "mts", "cts", "js", "mjs", "cjs"].some((ext) => tree.exists(`${dir}jest.config.${ext}`))) return "jest";
|
|
187
|
+
if (["ts", "mts", "js", "mjs"].some((ext) => tree.exists(`${dir}vitest.config.${ext}`) || tree.exists(`${dir}vitest.workspace.${ext}`))) return "vitest";
|
|
188
|
+
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
189
|
+
const vite = tree.read(`${dir}vite.config.${ext}`, "utf-8");
|
|
190
|
+
if (vite !== null && /\btest\s*:/.test(vite)) return "vitest";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const manifest of prefix === "" ? ["package.json"] : [`${prefix}package.json`, "package.json"]) {
|
|
194
|
+
if (hasDependency(tree, manifest, "vitest")) return "vitest";
|
|
195
|
+
if (hasDependency(tree, manifest, "jest")) return "jest";
|
|
196
|
+
}
|
|
197
|
+
return "none";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
runFsdSlice,
|
|
202
|
+
detectUnitTestRunner
|
|
203
|
+
};
|