@nikelyh/metamorph 2.1.0 → 2.1.2
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/LICENSE +674 -0
- package/README.md +58 -31
- package/bin/metamorph.js +0 -0
- package/dist/NextMigrationHints-BGO2W3K4.js +32 -0
- package/dist/ProjectDetector-WU3OD6WT.js +16 -0
- package/dist/{chunk-OXXEISZT.js → chunk-APRESSUR.js} +103 -44
- package/dist/chunk-H2LZEPNI.js +352 -0
- package/dist/{chunk-VEAYUIOZ.js → chunk-WLHSZWET.js} +39 -0
- package/dist/chunk-XLD7NXYU.js +528 -0
- package/dist/index.js +624 -520
- package/dist/public/assets/index-BX5N2qs-.js +63 -0
- package/dist/public/assets/index-C7odDrsP.css +1 -0
- package/dist/public/icons/angular.svg +1 -1
- package/dist/public/icons/expressjs.svg +1 -1
- package/dist/public/icons/fastify.svg +1 -1
- package/dist/public/icons/hapi.svg +1 -14
- package/dist/public/icons/nestjs.svg +1 -1
- package/dist/public/icons/nextjs.svg +1 -1
- package/dist/public/icons/react.svg +1 -1
- package/dist/public/icons/svelte.svg +1 -21
- package/dist/public/icons/vue.svg +1 -1
- package/dist/public/icons.svg +1 -24
- package/dist/public/index.html +2 -2
- package/dist/{src-2UXWWNPF.js → src-Q3DGKZEQ.js} +20 -4
- package/dist/{src-UC3SN46X.js → src-YPCIVBLZ.js} +3 -1
- package/package.json +18 -5
- package/dist/TechDetector-5YP44RVR.js +0 -6
- package/dist/chunk-3QN2Q75J.js +0 -101
- package/dist/public/assets/index-DDJRaEaa.css +0 -1
- package/dist/public/assets/index-PNJ1umlc.js +0 -63
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
// ../infrastructure/src/detector/WorkspaceResolver.ts
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
3
|
+
import { join, resolve, dirname } from "path";
|
|
4
|
+
function resolveProjectRoot(startPath) {
|
|
5
|
+
let current = resolve(startPath);
|
|
6
|
+
const root = resolve("/");
|
|
7
|
+
while (current && current !== root && current !== dirname(current)) {
|
|
8
|
+
if (existsSync(join(current, "package.json"))) {
|
|
9
|
+
return current;
|
|
10
|
+
}
|
|
11
|
+
if (existsSync(join(current, ".git"))) {
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
current = dirname(current);
|
|
15
|
+
}
|
|
16
|
+
return resolve(startPath);
|
|
17
|
+
}
|
|
18
|
+
function findWorkspacePackages(rootDir, patterns) {
|
|
19
|
+
const packages = [];
|
|
20
|
+
for (const pattern of patterns) {
|
|
21
|
+
const cleanPattern = pattern.replace(/[\\/]+$/, "");
|
|
22
|
+
if (cleanPattern.endsWith("/*")) {
|
|
23
|
+
const parentDir = join(rootDir, cleanPattern.slice(0, -2));
|
|
24
|
+
if (existsSync(parentDir)) {
|
|
25
|
+
try {
|
|
26
|
+
const entries = readdirSync(parentDir);
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const subDir = join(parentDir, entry);
|
|
29
|
+
const pkgJsonPath = join(subDir, "package.json");
|
|
30
|
+
if (existsSync(pkgJsonPath) && statSync(subDir).isDirectory()) {
|
|
31
|
+
try {
|
|
32
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
33
|
+
packages.push({
|
|
34
|
+
name: pkg.name || entry,
|
|
35
|
+
relativePath: join(cleanPattern.slice(0, -2), entry).replace(/\\/g, "/"),
|
|
36
|
+
absolutePath: subDir
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
const subDir = join(rootDir, cleanPattern);
|
|
47
|
+
const pkgJsonPath = join(subDir, "package.json");
|
|
48
|
+
if (existsSync(pkgJsonPath) && statSync(subDir).isDirectory()) {
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
51
|
+
packages.push({
|
|
52
|
+
name: pkg.name || cleanPattern,
|
|
53
|
+
relativePath: cleanPattern.replace(/\\/g, "/"),
|
|
54
|
+
absolutePath: subDir
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return packages;
|
|
62
|
+
}
|
|
63
|
+
function detectMonorepo(projectPath) {
|
|
64
|
+
const rootPath = resolve(projectPath);
|
|
65
|
+
const pkgPath = join(rootPath, "package.json");
|
|
66
|
+
let tool;
|
|
67
|
+
let patterns = [];
|
|
68
|
+
const pnpmWorkspacePath = join(rootPath, "pnpm-workspace.yaml");
|
|
69
|
+
if (existsSync(pnpmWorkspacePath)) {
|
|
70
|
+
tool = "pnpm";
|
|
71
|
+
try {
|
|
72
|
+
const content = readFileSync(pnpmWorkspacePath, "utf-8");
|
|
73
|
+
const lines = content.split("\n");
|
|
74
|
+
let inPackages = false;
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (trimmed.startsWith("packages:")) {
|
|
78
|
+
inPackages = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (inPackages) {
|
|
82
|
+
if (trimmed.startsWith("-")) {
|
|
83
|
+
const pattern = trimmed.replace(/^-\s*['"]?/, "").replace(/['"]?\s*$/, "");
|
|
84
|
+
if (pattern) patterns.push(pattern);
|
|
85
|
+
} else if (trimmed && !trimmed.startsWith("#")) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (existsSync(pkgPath)) {
|
|
94
|
+
try {
|
|
95
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
96
|
+
if (pkg.workspaces) {
|
|
97
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
98
|
+
patterns.push(...pkg.workspaces);
|
|
99
|
+
} else if (Array.isArray(pkg.workspaces.packages)) {
|
|
100
|
+
patterns.push(...pkg.workspaces.packages);
|
|
101
|
+
}
|
|
102
|
+
if (!tool) tool = "npm";
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (existsSync(join(rootPath, "turbo.json"))) {
|
|
108
|
+
tool = "turbo";
|
|
109
|
+
}
|
|
110
|
+
if (existsSync(join(rootPath, "lerna.json"))) {
|
|
111
|
+
if (!tool) tool = "lerna";
|
|
112
|
+
try {
|
|
113
|
+
const lerna = JSON.parse(readFileSync(join(rootPath, "lerna.json"), "utf-8"));
|
|
114
|
+
if (Array.isArray(lerna.packages)) {
|
|
115
|
+
patterns.push(...lerna.packages);
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (existsSync(join(rootPath, "nx.json"))) {
|
|
121
|
+
if (!tool) tool = "nx";
|
|
122
|
+
}
|
|
123
|
+
if (tool && patterns.length === 0) {
|
|
124
|
+
patterns = ["apps/*", "packages/*", "services/*"];
|
|
125
|
+
}
|
|
126
|
+
const uniquePatterns = Array.from(new Set(patterns));
|
|
127
|
+
const packages = findWorkspacePackages(rootPath, uniquePatterns);
|
|
128
|
+
return {
|
|
129
|
+
isMonorepo: !!tool || packages.length > 0,
|
|
130
|
+
tool,
|
|
131
|
+
packages,
|
|
132
|
+
rootPath
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ../infrastructure/src/detector/ManifestInspector.ts
|
|
137
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
138
|
+
import { join as join2 } from "path";
|
|
139
|
+
function inspectManifest(projectPath) {
|
|
140
|
+
const pkgPath = join2(projectPath, "package.json");
|
|
141
|
+
let rawPackageJson;
|
|
142
|
+
let dependencies = {};
|
|
143
|
+
let devDependencies = {};
|
|
144
|
+
let scripts = {};
|
|
145
|
+
if (existsSync2(pkgPath)) {
|
|
146
|
+
try {
|
|
147
|
+
rawPackageJson = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
148
|
+
dependencies = rawPackageJson?.dependencies || {};
|
|
149
|
+
devDependencies = rawPackageJson?.devDependencies || {};
|
|
150
|
+
scripts = rawPackageJson?.scripts || {};
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const allDependencies = {
|
|
155
|
+
...devDependencies,
|
|
156
|
+
...dependencies
|
|
157
|
+
};
|
|
158
|
+
let packageManager = "npm";
|
|
159
|
+
if (existsSync2(join2(projectPath, "pnpm-lock.yaml"))) {
|
|
160
|
+
packageManager = "pnpm";
|
|
161
|
+
} else if (existsSync2(join2(projectPath, "yarn.lock"))) {
|
|
162
|
+
packageManager = "yarn";
|
|
163
|
+
} else if (existsSync2(join2(projectPath, "bun.lockb")) || existsSync2(join2(projectPath, "bun.lock"))) {
|
|
164
|
+
packageManager = "bun";
|
|
165
|
+
} else if (rawPackageJson?.packageManager && typeof rawPackageJson.packageManager === "string") {
|
|
166
|
+
const pm = rawPackageJson.packageManager.toLowerCase();
|
|
167
|
+
if (pm.startsWith("pnpm")) packageManager = "pnpm";
|
|
168
|
+
else if (pm.startsWith("yarn")) packageManager = "yarn";
|
|
169
|
+
else if (pm.startsWith("bun")) packageManager = "bun";
|
|
170
|
+
}
|
|
171
|
+
let hasTsConfig = false;
|
|
172
|
+
let pathAliases = {};
|
|
173
|
+
const tsConfigPath = join2(projectPath, "tsconfig.json");
|
|
174
|
+
if (existsSync2(tsConfigPath)) {
|
|
175
|
+
hasTsConfig = true;
|
|
176
|
+
try {
|
|
177
|
+
const content = readFileSync2(tsConfigPath, "utf-8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "");
|
|
178
|
+
const tsconfig = JSON.parse(content);
|
|
179
|
+
if (tsconfig?.compilerOptions?.paths) {
|
|
180
|
+
pathAliases = tsconfig.compilerOptions.paths;
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const hasTsDependency = !!(allDependencies["typescript"] || allDependencies["ts-node"] || allDependencies["tsx"]);
|
|
186
|
+
const language = hasTsConfig || hasTsDependency ? "typescript" : "javascript";
|
|
187
|
+
return {
|
|
188
|
+
rawPackageJson,
|
|
189
|
+
allDependencies,
|
|
190
|
+
dependencies,
|
|
191
|
+
devDependencies,
|
|
192
|
+
scripts,
|
|
193
|
+
packageManager,
|
|
194
|
+
hasTsConfig,
|
|
195
|
+
language,
|
|
196
|
+
pathAliases
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ../infrastructure/src/detector/StructureInspector.ts
|
|
201
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
202
|
+
import { join as join3 } from "path";
|
|
203
|
+
function hasAnyFile(projectPath, relativePaths) {
|
|
204
|
+
for (const rel of relativePaths) {
|
|
205
|
+
if (existsSync3(join3(projectPath, rel))) {
|
|
206
|
+
return rel;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
function scanCharacteristicExtensions(projectPath) {
|
|
212
|
+
const counts = {
|
|
213
|
+
vue: 0,
|
|
214
|
+
svelte: 0,
|
|
215
|
+
reactJsx: 0,
|
|
216
|
+
nestDecorators: 0
|
|
217
|
+
};
|
|
218
|
+
const srcDir = existsSync3(join3(projectPath, "src")) ? join3(projectPath, "src") : projectPath;
|
|
219
|
+
if (!existsSync3(srcDir)) return counts;
|
|
220
|
+
function traverse(dir, depth) {
|
|
221
|
+
if (depth > 3) return;
|
|
222
|
+
try {
|
|
223
|
+
const entries = readdirSync2(dir);
|
|
224
|
+
for (const entry of entries) {
|
|
225
|
+
if (entry === "node_modules" || entry === ".git" || entry === "dist" || entry === "build" || entry === ".next") {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const fullPath = join3(dir, entry);
|
|
229
|
+
try {
|
|
230
|
+
const stat = statSync2(fullPath);
|
|
231
|
+
if (stat.isDirectory()) {
|
|
232
|
+
traverse(fullPath, depth + 1);
|
|
233
|
+
} else if (stat.isFile()) {
|
|
234
|
+
const lower = entry.toLowerCase();
|
|
235
|
+
if (lower.endsWith(".vue")) counts.vue++;
|
|
236
|
+
else if (lower.endsWith(".svelte")) counts.svelte++;
|
|
237
|
+
else if (lower.endsWith(".tsx") || lower.endsWith(".jsx")) counts.reactJsx++;
|
|
238
|
+
else if (lower.endsWith(".controller.ts") || lower.endsWith(".module.ts")) counts.nestDecorators++;
|
|
239
|
+
}
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
traverse(srcDir, 0);
|
|
247
|
+
return counts;
|
|
248
|
+
}
|
|
249
|
+
function inspectStructure(projectPath, deps, scripts) {
|
|
250
|
+
const existingConfigFiles = [];
|
|
251
|
+
const candidateConfigs = [
|
|
252
|
+
"next.config.js",
|
|
253
|
+
"next.config.mjs",
|
|
254
|
+
"next.config.ts",
|
|
255
|
+
"next.config.cjs",
|
|
256
|
+
"vite.config.ts",
|
|
257
|
+
"vite.config.js",
|
|
258
|
+
"vite.config.mjs",
|
|
259
|
+
"vite.config.cjs",
|
|
260
|
+
"svelte.config.js",
|
|
261
|
+
"svelte.config.ts",
|
|
262
|
+
"angular.json",
|
|
263
|
+
"nest-cli.json",
|
|
264
|
+
"webpack.config.js",
|
|
265
|
+
"webpack.config.ts",
|
|
266
|
+
"vue.config.js"
|
|
267
|
+
];
|
|
268
|
+
for (const config of candidateConfigs) {
|
|
269
|
+
if (existsSync3(join3(projectPath, config))) {
|
|
270
|
+
existingConfigFiles.push(config);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
let bundler = "unknown";
|
|
274
|
+
if (existingConfigFiles.some((c) => c.startsWith("vite.config"))) {
|
|
275
|
+
bundler = "vite";
|
|
276
|
+
} else if (existingConfigFiles.includes("angular.json")) {
|
|
277
|
+
bundler = "angular-cli";
|
|
278
|
+
} else if (deps["next"] || existingConfigFiles.some((c) => c.startsWith("next.config"))) {
|
|
279
|
+
const scriptValues = Object.values(scripts).join(" ");
|
|
280
|
+
bundler = scriptValues.includes("--turbo") ? "turbopack" : "webpack";
|
|
281
|
+
} else if (existingConfigFiles.some((c) => c.startsWith("webpack.config")) || deps["webpack"]) {
|
|
282
|
+
bundler = "webpack";
|
|
283
|
+
} else if (deps["vite"]) {
|
|
284
|
+
bundler = "vite";
|
|
285
|
+
} else if (deps["esbuild"]) {
|
|
286
|
+
bundler = "esbuild";
|
|
287
|
+
} else if (deps["rollup"]) {
|
|
288
|
+
bundler = "rollup";
|
|
289
|
+
}
|
|
290
|
+
let routerVariant = "none";
|
|
291
|
+
const hasAppRouter = hasAnyFile(projectPath, [
|
|
292
|
+
"src/app/layout.tsx",
|
|
293
|
+
"src/app/layout.js",
|
|
294
|
+
"src/app/page.tsx",
|
|
295
|
+
"src/app/page.js",
|
|
296
|
+
"app/layout.tsx",
|
|
297
|
+
"app/layout.js",
|
|
298
|
+
"app/page.tsx",
|
|
299
|
+
"app/page.js"
|
|
300
|
+
]);
|
|
301
|
+
const hasPagesRouter = hasAnyFile(projectPath, [
|
|
302
|
+
"src/pages/_app.tsx",
|
|
303
|
+
"src/pages/_app.js",
|
|
304
|
+
"src/pages/index.tsx",
|
|
305
|
+
"src/pages/index.js",
|
|
306
|
+
"pages/_app.tsx",
|
|
307
|
+
"pages/_app.js",
|
|
308
|
+
"pages/index.tsx",
|
|
309
|
+
"pages/index.js"
|
|
310
|
+
]);
|
|
311
|
+
if (hasAppRouter) {
|
|
312
|
+
routerVariant = "next-app-router";
|
|
313
|
+
} else if (hasPagesRouter) {
|
|
314
|
+
routerVariant = "next-pages-router";
|
|
315
|
+
} else if (hasAnyFile(projectPath, ["src/routes/+page.svelte", "src/routes/+layout.svelte", "routes/+page.svelte"]) || deps["@sveltejs/kit"]) {
|
|
316
|
+
routerVariant = "sveltekit";
|
|
317
|
+
} else if (deps["vue-router"] || hasAnyFile(projectPath, ["src/router/index.ts", "src/router/index.js"])) {
|
|
318
|
+
routerVariant = "vue-router";
|
|
319
|
+
} else if (deps["react-router-dom"] || deps["react-router"] || deps["@remix-run/react"]) {
|
|
320
|
+
routerVariant = "react-router";
|
|
321
|
+
} else if (deps["@angular/router"]) {
|
|
322
|
+
routerVariant = "angular-router";
|
|
323
|
+
}
|
|
324
|
+
const extensionCounts = scanCharacteristicExtensions(projectPath);
|
|
325
|
+
return {
|
|
326
|
+
routerVariant,
|
|
327
|
+
bundler,
|
|
328
|
+
existingConfigFiles,
|
|
329
|
+
extensionCounts
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ../infrastructure/src/detector/rules/detectionRules.ts
|
|
334
|
+
var FRAMEWORK_DETECTION_RULES = [
|
|
335
|
+
{
|
|
336
|
+
framework: "next",
|
|
337
|
+
category: "frontend-meta",
|
|
338
|
+
packageKeys: ["next"],
|
|
339
|
+
configPrefixes: ["next.config"],
|
|
340
|
+
routerVariants: ["next-app-router", "next-pages-router"],
|
|
341
|
+
subsumes: ["react", "react-dom"],
|
|
342
|
+
suggestedTargets: ["react", "vue", "angular", "svelte"]
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
framework: "react",
|
|
346
|
+
category: "frontend-spa",
|
|
347
|
+
packageKeys: ["react"],
|
|
348
|
+
configPrefixes: ["vite.config", "webpack.config"],
|
|
349
|
+
routerVariants: ["react-router"],
|
|
350
|
+
suggestedTargets: ["next", "vue", "angular", "svelte"]
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
framework: "vue",
|
|
354
|
+
category: "frontend-spa",
|
|
355
|
+
packageKeys: ["vue"],
|
|
356
|
+
devPackageKeys: ["@vitejs/plugin-vue"],
|
|
357
|
+
configPrefixes: ["vue.config", "vite.config"],
|
|
358
|
+
routerVariants: ["vue-router"],
|
|
359
|
+
suggestedTargets: ["react", "next", "angular", "svelte"]
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
framework: "svelte",
|
|
363
|
+
category: "frontend-spa",
|
|
364
|
+
packageKeys: ["svelte", "@sveltejs/kit"],
|
|
365
|
+
devPackageKeys: ["@sveltejs/vite-plugin-svelte"],
|
|
366
|
+
configPrefixes: ["svelte.config", "vite.config"],
|
|
367
|
+
routerVariants: ["sveltekit"],
|
|
368
|
+
suggestedTargets: ["react", "next", "vue", "angular"]
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
framework: "angular",
|
|
372
|
+
category: "frontend-spa",
|
|
373
|
+
packageKeys: ["@angular/core"],
|
|
374
|
+
devPackageKeys: ["@angular/cli", "@angular-devkit/build-angular"],
|
|
375
|
+
configPrefixes: ["angular.json"],
|
|
376
|
+
routerVariants: ["angular-router"],
|
|
377
|
+
suggestedTargets: ["react", "next", "vue", "svelte"]
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
framework: "nestjs",
|
|
381
|
+
category: "backend-api",
|
|
382
|
+
packageKeys: ["@nestjs/core", "@nestjs/common"],
|
|
383
|
+
configPrefixes: ["nest-cli.json"],
|
|
384
|
+
subsumes: ["express", "fastify", "@nestjs/platform-express", "@nestjs/platform-fastify"],
|
|
385
|
+
suggestedTargets: ["express", "fastify"]
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
framework: "express",
|
|
389
|
+
category: "backend-api",
|
|
390
|
+
packageKeys: ["express"],
|
|
391
|
+
configPrefixes: [],
|
|
392
|
+
suggestedTargets: ["fastify", "nestjs"]
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
framework: "fastify",
|
|
396
|
+
category: "backend-api",
|
|
397
|
+
packageKeys: ["fastify"],
|
|
398
|
+
configPrefixes: [],
|
|
399
|
+
suggestedTargets: ["express", "nestjs"]
|
|
400
|
+
}
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
// ../infrastructure/src/detector/SubsumptionEngine.ts
|
|
404
|
+
function evaluateAndDisambiguate(manifest, structure) {
|
|
405
|
+
const candidates = [];
|
|
406
|
+
for (const rule of FRAMEWORK_DETECTION_RULES) {
|
|
407
|
+
const hasPackageMatch = rule.packageKeys.some((k) => manifest.dependencies[k] || manifest.devDependencies[k]) || rule.devPackageKeys && rule.devPackageKeys.some((k) => manifest.devDependencies[k]) || rule.framework === "vue" && structure.extensionCounts.vue > 0 || rule.framework === "svelte" && structure.extensionCounts.svelte > 0 || rule.framework === "nestjs" && structure.extensionCounts.nestDecorators > 0;
|
|
408
|
+
const hasDedicatedConfig = structure.existingConfigFiles.some(
|
|
409
|
+
(c) => rule.configPrefixes.some((p) => p !== "vite.config" && p !== "webpack.config" && c.startsWith(p))
|
|
410
|
+
);
|
|
411
|
+
if (!hasPackageMatch && !hasDedicatedConfig) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
let score = 0;
|
|
415
|
+
const evidence = [];
|
|
416
|
+
const subsumed = [];
|
|
417
|
+
for (const key of rule.packageKeys) {
|
|
418
|
+
if (manifest.dependencies[key]) {
|
|
419
|
+
score += 50;
|
|
420
|
+
evidence.push(`dependencies: "${key}": "${manifest.dependencies[key]}"`);
|
|
421
|
+
} else if (manifest.devDependencies[key]) {
|
|
422
|
+
score += 35;
|
|
423
|
+
evidence.push(`devDependencies: "${key}": "${manifest.devDependencies[key]}"`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (rule.devPackageKeys) {
|
|
427
|
+
for (const devKey of rule.devPackageKeys) {
|
|
428
|
+
if (manifest.devDependencies[devKey]) {
|
|
429
|
+
score += 25;
|
|
430
|
+
evidence.push(`devTooling: "${devKey}": "${manifest.devDependencies[devKey]}"`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
for (const prefix of rule.configPrefixes) {
|
|
435
|
+
const match = structure.existingConfigFiles.find((c) => c.startsWith(prefix));
|
|
436
|
+
if (match) {
|
|
437
|
+
score += 25;
|
|
438
|
+
evidence.push(`config: ${match}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (rule.routerVariants && rule.routerVariants.includes(structure.routerVariant)) {
|
|
442
|
+
score += 30;
|
|
443
|
+
evidence.push(`router: ${structure.routerVariant}`);
|
|
444
|
+
}
|
|
445
|
+
if (rule.framework === "vue" && structure.extensionCounts.vue > 0) {
|
|
446
|
+
score += 20;
|
|
447
|
+
evidence.push(`source: found ${structure.extensionCounts.vue} .vue file(s)`);
|
|
448
|
+
} else if (rule.framework === "svelte" && structure.extensionCounts.svelte > 0) {
|
|
449
|
+
score += 20;
|
|
450
|
+
evidence.push(`source: found ${structure.extensionCounts.svelte} .svelte file(s)`);
|
|
451
|
+
} else if (rule.framework === "nestjs" && structure.extensionCounts.nestDecorators > 0) {
|
|
452
|
+
score += 20;
|
|
453
|
+
evidence.push(`source: found ${structure.extensionCounts.nestDecorators} nest decorator file(s)`);
|
|
454
|
+
}
|
|
455
|
+
if (score > 0) {
|
|
456
|
+
candidates.push({
|
|
457
|
+
rule,
|
|
458
|
+
score,
|
|
459
|
+
evidence,
|
|
460
|
+
subsumed
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const candidate of candidates) {
|
|
465
|
+
if (candidate.rule.subsumes && candidate.score >= 50) {
|
|
466
|
+
for (const subsumedTarget of candidate.rule.subsumes) {
|
|
467
|
+
const targetCandidate = candidates.find((c) => c.rule.framework === subsumedTarget);
|
|
468
|
+
if (targetCandidate) {
|
|
469
|
+
candidate.subsumed.push(subsumedTarget);
|
|
470
|
+
candidate.evidence.push(
|
|
471
|
+
`subsumed "${subsumedTarget}" as an internal runtime/driver of ${candidate.rule.framework}`
|
|
472
|
+
);
|
|
473
|
+
targetCandidate.score = 0;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const activeCandidates = candidates.filter((c) => c.score > 0).sort((a, b) => b.score - a.score);
|
|
479
|
+
return activeCandidates.map((c) => {
|
|
480
|
+
return {
|
|
481
|
+
framework: c.rule.framework,
|
|
482
|
+
category: c.rule.category,
|
|
483
|
+
variant: structure.routerVariant,
|
|
484
|
+
bundler: structure.bundler,
|
|
485
|
+
packageManager: manifest.packageManager,
|
|
486
|
+
language: manifest.language,
|
|
487
|
+
hasTsConfig: manifest.hasTsConfig,
|
|
488
|
+
pathAliases: manifest.pathAliases,
|
|
489
|
+
confidence: Math.min(c.score, 100),
|
|
490
|
+
evidence: c.evidence,
|
|
491
|
+
subsumedDependencies: c.subsumed,
|
|
492
|
+
suggestedTargets: c.rule.suggestedTargets
|
|
493
|
+
};
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ../infrastructure/src/detector/ProjectDetector.ts
|
|
498
|
+
function inspectProject(targetPath) {
|
|
499
|
+
const normalizedPath = resolveProjectRoot(targetPath);
|
|
500
|
+
const monorepo = detectMonorepo(normalizedPath);
|
|
501
|
+
const manifest = inspectManifest(normalizedPath);
|
|
502
|
+
const structure = inspectStructure(normalizedPath, manifest.allDependencies, manifest.scripts);
|
|
503
|
+
const profiles = evaluateAndDisambiguate(manifest, structure);
|
|
504
|
+
for (const profile of profiles) {
|
|
505
|
+
if (monorepo.isMonorepo) {
|
|
506
|
+
profile.monorepo = monorepo;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return profiles;
|
|
510
|
+
}
|
|
511
|
+
function detectTechnologies(targetPath) {
|
|
512
|
+
const profiles = inspectProject(targetPath);
|
|
513
|
+
return profiles.map((p) => ({
|
|
514
|
+
framework: p.framework,
|
|
515
|
+
confidence: p.confidence,
|
|
516
|
+
evidence: p.evidence,
|
|
517
|
+
profile: p
|
|
518
|
+
}));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export {
|
|
522
|
+
resolveProjectRoot,
|
|
523
|
+
detectMonorepo,
|
|
524
|
+
inspectManifest,
|
|
525
|
+
inspectStructure,
|
|
526
|
+
inspectProject,
|
|
527
|
+
detectTechnologies
|
|
528
|
+
};
|