@contentrain/mcp
Advanced tools
| import { a as readJson, o as readText, r as pathExists } from "./fs-DLbVB-Ek.mjs"; | ||
| import { dirname, join } from "node:path"; | ||
| import { readdir } from "node:fs/promises"; | ||
| //#region src/util/detect.ts | ||
| const STACK_META = { | ||
| nuxt: { | ||
| name: "Nuxt", | ||
| description: "Vue meta-framework with SSR/SSG" | ||
| }, | ||
| next: { | ||
| name: "Next.js", | ||
| description: "React framework with SSR/SSG" | ||
| }, | ||
| astro: { | ||
| name: "Astro", | ||
| description: "Content-first web framework" | ||
| }, | ||
| sveltekit: { | ||
| name: "SvelteKit", | ||
| description: "Svelte meta-framework" | ||
| }, | ||
| remix: { | ||
| name: "Remix", | ||
| description: "Full-stack React framework" | ||
| }, | ||
| analog: { | ||
| name: "Analog", | ||
| description: "Angular meta-framework" | ||
| }, | ||
| vue: { | ||
| name: "Vue", | ||
| description: "Progressive JavaScript framework" | ||
| }, | ||
| react: { | ||
| name: "React", | ||
| description: "UI library for web and native" | ||
| }, | ||
| svelte: { | ||
| name: "Svelte", | ||
| description: "Compile-time UI framework" | ||
| }, | ||
| solid: { | ||
| name: "Solid", | ||
| description: "Reactive UI library" | ||
| }, | ||
| angular: { | ||
| name: "Angular", | ||
| description: "Platform for web applications" | ||
| }, | ||
| "react-native": { | ||
| name: "React Native", | ||
| description: "Cross-platform mobile framework" | ||
| }, | ||
| expo: { | ||
| name: "Expo", | ||
| description: "React Native toolchain" | ||
| }, | ||
| flutter: { | ||
| name: "Flutter", | ||
| description: "Cross-platform UI toolkit (Dart)" | ||
| }, | ||
| node: { | ||
| name: "Node.js", | ||
| description: "Server-side JavaScript runtime" | ||
| }, | ||
| express: { | ||
| name: "Express", | ||
| description: "Minimal Node.js web framework" | ||
| }, | ||
| fastify: { | ||
| name: "Fastify", | ||
| description: "Fast Node.js web framework" | ||
| }, | ||
| nestjs: { | ||
| name: "NestJS", | ||
| description: "Progressive Node.js framework" | ||
| }, | ||
| django: { | ||
| name: "Django", | ||
| description: "Python web framework" | ||
| }, | ||
| rails: { | ||
| name: "Rails", | ||
| description: "Ruby web framework" | ||
| }, | ||
| laravel: { | ||
| name: "Laravel", | ||
| description: "PHP web framework" | ||
| }, | ||
| go: { | ||
| name: "Go", | ||
| description: "Go programming language" | ||
| }, | ||
| rust: { | ||
| name: "Rust", | ||
| description: "Rust programming language" | ||
| }, | ||
| dotnet: { | ||
| name: ".NET", | ||
| description: "Microsoft .NET platform" | ||
| }, | ||
| hugo: { | ||
| name: "Hugo", | ||
| description: "Go-based static site generator" | ||
| }, | ||
| jekyll: { | ||
| name: "Jekyll", | ||
| description: "Ruby-based static site generator" | ||
| }, | ||
| eleventy: { | ||
| name: "Eleventy", | ||
| description: "JavaScript static site generator" | ||
| }, | ||
| electron: { | ||
| name: "Electron", | ||
| description: "Cross-platform desktop apps" | ||
| }, | ||
| tauri: { | ||
| name: "Tauri", | ||
| description: "Lightweight desktop apps (Rust)" | ||
| }, | ||
| other: { | ||
| name: "Other", | ||
| description: "Unknown or unsupported framework" | ||
| } | ||
| }; | ||
| function inferPlatform(stack) { | ||
| switch (stack) { | ||
| case "nuxt": | ||
| case "next": | ||
| case "astro": | ||
| case "sveltekit": | ||
| case "remix": | ||
| case "analog": | ||
| case "vue": | ||
| case "react": | ||
| case "svelte": | ||
| case "solid": | ||
| case "angular": return "web"; | ||
| case "react-native": | ||
| case "expo": | ||
| case "flutter": return "mobile"; | ||
| case "node": | ||
| case "express": | ||
| case "fastify": | ||
| case "nestjs": | ||
| case "django": | ||
| case "rails": | ||
| case "laravel": | ||
| case "go": | ||
| case "rust": | ||
| case "dotnet": return "api"; | ||
| case "electron": | ||
| case "tauri": return "desktop"; | ||
| case "hugo": | ||
| case "jekyll": | ||
| case "eleventy": return "static"; | ||
| default: return "other"; | ||
| } | ||
| } | ||
| /** Backward-compatible: returns just the StackType string */ | ||
| async function detectStack(projectRoot) { | ||
| return (await detectStackInfo(projectRoot)).stack; | ||
| } | ||
| /** Rich detection: stack + platform + monorepo + features */ | ||
| async function detectStackInfo(projectRoot) { | ||
| let stack = await detectFromDeps(projectRoot); | ||
| if (stack === "other") { | ||
| let dir = dirname(projectRoot); | ||
| for (let i = 0; i < 5; i++) { | ||
| if ((await readJson(join(dir, "package.json")))?.workspaces) { | ||
| const rootStack = await detectFromDeps(dir); | ||
| if (rootStack !== "other") { | ||
| stack = rootStack; | ||
| break; | ||
| } | ||
| } | ||
| const parent = dirname(dir); | ||
| if (parent === dir) break; | ||
| dir = parent; | ||
| } | ||
| } | ||
| if (stack === "other") stack = await detectFromWorkspaces(projectRoot); | ||
| if (stack === "other") stack = await detectNonJs(projectRoot); | ||
| const mono = await detectMonorepo(projectRoot); | ||
| const features = await detectFeatures(projectRoot); | ||
| const meta = STACK_META[stack]; | ||
| return { | ||
| stack, | ||
| platform: inferPlatform(stack), | ||
| name: meta.name, | ||
| description: meta.description, | ||
| monorepo: mono.isMonorepo, | ||
| monorepoTool: mono.tool, | ||
| features | ||
| }; | ||
| } | ||
| async function detectFromDeps(dir) { | ||
| const pkg = await readJson(join(dir, "package.json")); | ||
| if (!pkg) return "other"; | ||
| const allDeps = { | ||
| ...pkg.dependencies, | ||
| ...pkg.devDependencies | ||
| }; | ||
| if ("nuxt" in allDeps) return "nuxt"; | ||
| if ("next" in allDeps) return "next"; | ||
| if ("astro" in allDeps) return "astro"; | ||
| if ("@sveltejs/kit" in allDeps) return "sveltekit"; | ||
| if ("@remix-run/node" in allDeps || "remix" in allDeps) return "remix"; | ||
| if ("@analogjs/platform" in allDeps) return "analog"; | ||
| if ("expo" in allDeps) return "expo"; | ||
| if ("react-native" in allDeps) return "react-native"; | ||
| if ("vue" in allDeps) return "vue"; | ||
| if ("svelte" in allDeps) return "svelte"; | ||
| if ("solid-js" in allDeps) return "solid"; | ||
| if ("@angular/core" in allDeps) return "angular"; | ||
| if ("react" in allDeps) return "react"; | ||
| if ("electron" in allDeps) return "electron"; | ||
| if ("@tauri-apps/api" in allDeps) return "tauri"; | ||
| if ("@nestjs/core" in allDeps) return "nestjs"; | ||
| if ("fastify" in allDeps) return "fastify"; | ||
| if ("express" in allDeps) return "express"; | ||
| if ("@11ty/eleventy" in allDeps) return "eleventy"; | ||
| if ("node" in (pkg.dependencies ?? {})) return "node"; | ||
| return "other"; | ||
| } | ||
| /** | ||
| * Ranked by how strongly the stack determines content conventions — a | ||
| * meta-framework dictates the i18n setup and the replacement expression, | ||
| * a plain UI library does not. Frequency would be the wrong tie-break: a | ||
| * monorepo with one Next app and two React packages is a Next project. | ||
| */ | ||
| const STACK_PRIORITY = [ | ||
| "nuxt", | ||
| "next", | ||
| "astro", | ||
| "sveltekit", | ||
| "remix", | ||
| "analog", | ||
| "expo", | ||
| "react-native", | ||
| "vue", | ||
| "react", | ||
| "svelte", | ||
| "solid", | ||
| "angular", | ||
| "electron", | ||
| "tauri", | ||
| "nestjs", | ||
| "fastify", | ||
| "express", | ||
| "eleventy", | ||
| "node" | ||
| ]; | ||
| /** Cap the fan-out so a large monorepo cannot turn detection into a crawl. */ | ||
| const MAX_WORKSPACE_DIRS = 50; | ||
| /** | ||
| * Workspace globs from `pnpm-workspace.yaml` or the `workspaces` field. | ||
| * | ||
| * The pnpm file is parsed line by line rather than with a YAML library: we | ||
| * need exactly one list of strings with a fixed shape, and a parser | ||
| * dependency for that is not worth the install size. | ||
| */ | ||
| async function readWorkspacePatterns(root) { | ||
| const pnpm = await readText(join(root, "pnpm-workspace.yaml")); | ||
| if (pnpm) { | ||
| const patterns = []; | ||
| let inPackages = false; | ||
| for (const raw of pnpm.split("\n")) { | ||
| const line = raw.replace(/#.*$/, "").trimEnd(); | ||
| if (/^packages:\s*$/.test(line)) { | ||
| inPackages = true; | ||
| continue; | ||
| } | ||
| if (!inPackages) continue; | ||
| const item = /^\s+-\s*['"]?([^'"]+?)['"]?\s*$/.exec(line); | ||
| if (item?.[1]) { | ||
| patterns.push(item[1]); | ||
| continue; | ||
| } | ||
| if (line.trim() !== "") break; | ||
| } | ||
| if (patterns.length > 0) return patterns; | ||
| } | ||
| const ws = (await readJson(join(root, "package.json")))?.workspaces; | ||
| if (Array.isArray(ws)) return ws; | ||
| if (ws && Array.isArray(ws.packages)) return ws.packages; | ||
| return []; | ||
| } | ||
| /** | ||
| * Expand workspace patterns to directories. Only a trailing `*` segment is | ||
| * expanded — that covers `apps/*` and `packages/*`, which is what real | ||
| * workspace files use. Deeper globs and negations are skipped rather than | ||
| * half-supported. | ||
| */ | ||
| async function listWorkspaceDirs(root) { | ||
| const patterns = (await readWorkspacePatterns(root)).filter((p) => !p.startsWith("!")); | ||
| return (await Promise.all(patterns.map(async (pattern) => { | ||
| const star = pattern.indexOf("*"); | ||
| if (star === -1) return [join(root, pattern)]; | ||
| const base = join(root, pattern.slice(0, star).replace(/\/+$/, "")); | ||
| try { | ||
| return (await readdir(base, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => join(base, e.name)); | ||
| } catch { | ||
| return []; | ||
| } | ||
| }))).flat().slice(0, MAX_WORKSPACE_DIRS); | ||
| } | ||
| /** Highest-priority stack across the workspace packages. */ | ||
| async function detectFromWorkspaces(root) { | ||
| const dirs = await listWorkspaceDirs(root); | ||
| if (dirs.length === 0) return "other"; | ||
| const found = new Set(await Promise.all(dirs.map((d) => detectFromDeps(d)))); | ||
| return STACK_PRIORITY.find((s) => found.has(s)) ?? "other"; | ||
| } | ||
| async function detectNonJs(projectRoot) { | ||
| if (await pathExists(join(projectRoot, "go.mod"))) return "go"; | ||
| if (await pathExists(join(projectRoot, "Cargo.toml"))) return "rust"; | ||
| for (const f of [ | ||
| "requirements.txt", | ||
| "pyproject.toml", | ||
| "setup.py" | ||
| ]) if (await pathExists(join(projectRoot, f))) { | ||
| try { | ||
| const content = await readText(join(projectRoot, f)); | ||
| if (content?.includes("django") || content?.includes("Django")) return "django"; | ||
| } catch {} | ||
| return "other"; | ||
| } | ||
| if (await pathExists(join(projectRoot, "Gemfile"))) { | ||
| try { | ||
| if ((await readText(join(projectRoot, "Gemfile")))?.includes("rails")) return "rails"; | ||
| } catch {} | ||
| return "other"; | ||
| } | ||
| for (const pattern of ["*.csproj", "*.sln"]) { | ||
| if (pattern === "*.csproj" && await pathExists(join(projectRoot, "Program.cs"))) return "dotnet"; | ||
| if (pattern === "*.sln" && await pathExists(join(projectRoot, "Program.cs"))) return "dotnet"; | ||
| } | ||
| if (await pathExists(join(projectRoot, "composer.json"))) { | ||
| try { | ||
| if (((await readJson(join(projectRoot, "composer.json")))?.require)?.["laravel/framework"]) return "laravel"; | ||
| } catch {} | ||
| return "other"; | ||
| } | ||
| if (await pathExists(join(projectRoot, "pubspec.yaml"))) return "flutter"; | ||
| if (await pathExists(join(projectRoot, "hugo.toml"))) return "hugo"; | ||
| if (await pathExists(join(projectRoot, "config.toml"))) try { | ||
| const content = await readText(join(projectRoot, "config.toml")); | ||
| if (content?.includes("baseURL") || content?.includes("hugo")) return "hugo"; | ||
| } catch {} | ||
| if (await pathExists(join(projectRoot, "_config.yml"))) return "jekyll"; | ||
| return "other"; | ||
| } | ||
| async function detectMonorepo(projectRoot) { | ||
| if (await pathExists(join(projectRoot, "pnpm-workspace.yaml"))) return { | ||
| isMonorepo: true, | ||
| tool: "pnpm workspaces" | ||
| }; | ||
| if (await pathExists(join(projectRoot, "lerna.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Lerna" | ||
| }; | ||
| if (await pathExists(join(projectRoot, "nx.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Nx" | ||
| }; | ||
| if (await pathExists(join(projectRoot, "turbo.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Turborepo" | ||
| }; | ||
| if ((await readJson(join(projectRoot, "package.json")))?.workspaces) return { | ||
| isMonorepo: true, | ||
| tool: "npm/yarn workspaces" | ||
| }; | ||
| let dir = dirname(projectRoot); | ||
| for (let i = 0; i < 5; i++) { | ||
| if (await pathExists(join(dir, "pnpm-workspace.yaml"))) return { | ||
| isMonorepo: true, | ||
| tool: "pnpm workspaces" | ||
| }; | ||
| if (await pathExists(join(dir, "lerna.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Lerna" | ||
| }; | ||
| if (await pathExists(join(dir, "nx.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Nx" | ||
| }; | ||
| if (await pathExists(join(dir, "turbo.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Turborepo" | ||
| }; | ||
| if ((await readJson(join(dir, "package.json")))?.workspaces) return { | ||
| isMonorepo: true, | ||
| tool: "npm/yarn workspaces" | ||
| }; | ||
| const parent = dirname(dir); | ||
| if (parent === dir) break; | ||
| dir = parent; | ||
| } | ||
| return { isMonorepo: false }; | ||
| } | ||
| async function detectFeatures(projectRoot) { | ||
| const features = []; | ||
| const allDeps = await collectAllDeps(projectRoot); | ||
| if ("typescript" in allDeps || await pathExists(join(projectRoot, "tsconfig.json"))) features.push("TypeScript"); | ||
| for (const lib of [ | ||
| "vue-i18n", | ||
| "@nuxtjs/i18n", | ||
| "next-intl", | ||
| "react-intl", | ||
| "i18next", | ||
| "react-i18next" | ||
| ]) if (lib in allDeps) { | ||
| features.push(`i18n (${lib})`); | ||
| break; | ||
| } | ||
| if ("tailwindcss" in allDeps) features.push("Tailwind CSS"); | ||
| else if ("sass" in allDeps || "scss" in allDeps) features.push("Sass/SCSS"); | ||
| if ("vitest" in allDeps) features.push("Vitest"); | ||
| else if ("jest" in allDeps) features.push("Jest"); | ||
| if ("pinia" in allDeps) features.push("Pinia"); | ||
| else if ("zustand" in allDeps) features.push("Zustand"); | ||
| else if ("@reduxjs/toolkit" in allDeps || "redux" in allDeps) features.push("Redux"); | ||
| return features; | ||
| } | ||
| async function collectAllDeps(projectRoot) { | ||
| const pkg = await readJson(join(projectRoot, "package.json")); | ||
| const allDeps = { | ||
| ...pkg?.dependencies, | ||
| ...pkg?.devDependencies | ||
| }; | ||
| let dir = dirname(projectRoot); | ||
| for (let i = 0; i < 5; i++) { | ||
| const parentPkg = await readJson(join(dir, "package.json")); | ||
| if (parentPkg?.workspaces) { | ||
| Object.assign(allDeps, parentPkg.dependencies, parentPkg.devDependencies); | ||
| break; | ||
| } | ||
| const parent = dirname(dir); | ||
| if (parent === dir) break; | ||
| dir = parent; | ||
| } | ||
| const workspaceDirs = await listWorkspaceDirs(projectRoot); | ||
| if (workspaceDirs.length > 0) { | ||
| const pkgs = await Promise.all(workspaceDirs.map((d) => readJson(join(d, "package.json")))); | ||
| for (const wsPkg of pkgs) if (wsPkg) Object.assign(allDeps, wsPkg.dependencies, wsPkg.devDependencies); | ||
| } | ||
| return allDeps; | ||
| } | ||
| //#endregion | ||
| export { detectStackInfo as n, inferPlatform as r, detectStack as t }; | ||
| //# sourceMappingURL=detect-CdDU4Ew_.mjs.map |
| {"version":3,"file":"detect-CdDU4Ew_.mjs","names":[],"sources":["../src/util/detect.ts"],"sourcesContent":["import type { StackType, Platform } from '@contentrain/types'\nimport { join, dirname } from 'node:path'\nimport { readdir } from 'node:fs/promises'\nimport { readJson, pathExists, readText } from './fs.js'\n\ninterface PackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n workspaces?: string[] | { packages: string[] }\n}\n\nexport interface StackInfo {\n stack: StackType\n platform: Platform\n name: string\n description: string\n monorepo: boolean\n monorepoTool?: string\n features: string[]\n}\n\nconst STACK_META: Record<StackType, { name: string; description: string }> = {\n // Meta-frameworks\n nuxt: { name: 'Nuxt', description: 'Vue meta-framework with SSR/SSG' },\n next: { name: 'Next.js', description: 'React framework with SSR/SSG' },\n astro: { name: 'Astro', description: 'Content-first web framework' },\n sveltekit: { name: 'SvelteKit', description: 'Svelte meta-framework' },\n remix: { name: 'Remix', description: 'Full-stack React framework' },\n analog: { name: 'Analog', description: 'Angular meta-framework' },\n // Plain frameworks\n vue: { name: 'Vue', description: 'Progressive JavaScript framework' },\n react: { name: 'React', description: 'UI library for web and native' },\n svelte: { name: 'Svelte', description: 'Compile-time UI framework' },\n solid: { name: 'Solid', description: 'Reactive UI library' },\n angular: { name: 'Angular', description: 'Platform for web applications' },\n // Mobile\n 'react-native': { name: 'React Native', description: 'Cross-platform mobile framework' },\n expo: { name: 'Expo', description: 'React Native toolchain' },\n flutter: { name: 'Flutter', description: 'Cross-platform UI toolkit (Dart)' },\n // Backend\n node: { name: 'Node.js', description: 'Server-side JavaScript runtime' },\n express: { name: 'Express', description: 'Minimal Node.js web framework' },\n fastify: { name: 'Fastify', description: 'Fast Node.js web framework' },\n nestjs: { name: 'NestJS', description: 'Progressive Node.js framework' },\n django: { name: 'Django', description: 'Python web framework' },\n rails: { name: 'Rails', description: 'Ruby web framework' },\n laravel: { name: 'Laravel', description: 'PHP web framework' },\n go: { name: 'Go', description: 'Go programming language' },\n rust: { name: 'Rust', description: 'Rust programming language' },\n dotnet: { name: '.NET', description: 'Microsoft .NET platform' },\n // Static\n hugo: { name: 'Hugo', description: 'Go-based static site generator' },\n jekyll: { name: 'Jekyll', description: 'Ruby-based static site generator' },\n eleventy: { name: 'Eleventy', description: 'JavaScript static site generator' },\n // Desktop\n electron: { name: 'Electron', description: 'Cross-platform desktop apps' },\n tauri: { name: 'Tauri', description: 'Lightweight desktop apps (Rust)' },\n // Catch-all\n other: { name: 'Other', description: 'Unknown or unsupported framework' },\n}\n\nexport function inferPlatform(stack: StackType): Platform {\n switch (stack) {\n case 'nuxt': case 'next': case 'astro': case 'sveltekit': case 'remix': case 'analog':\n case 'vue': case 'react': case 'svelte': case 'solid': case 'angular':\n return 'web'\n case 'react-native': case 'expo': case 'flutter':\n return 'mobile'\n case 'node': case 'express': case 'fastify': case 'nestjs':\n case 'django': case 'rails': case 'laravel': case 'go': case 'rust': case 'dotnet':\n return 'api'\n case 'electron': case 'tauri':\n return 'desktop'\n case 'hugo': case 'jekyll': case 'eleventy':\n return 'static'\n default:\n return 'other'\n }\n}\n\n/** Backward-compatible: returns just the StackType string */\nexport async function detectStack(projectRoot: string): Promise<StackType> {\n const info = await detectStackInfo(projectRoot)\n return info.stack\n}\n\n/** Rich detection: stack + platform + monorepo + features */\nexport async function detectStackInfo(projectRoot: string): Promise<StackInfo> {\n let stack = await detectFromDeps(projectRoot)\n\n // Walk up for monorepo root if local didn't match\n if (stack === 'other') {\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (pkg?.workspaces) {\n const rootStack = await detectFromDeps(dir)\n if (rootStack !== 'other') {\n stack = rootStack\n break\n }\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n }\n\n // Look DOWN into the workspaces. The walk above handles \"I am inside a\n // package, find the monorepo root\"; this handles the opposite and more\n // common case — projectRoot IS the monorepo root, whose package.json holds\n // only tooling (turbo, nx) while the frameworks live in apps/* and\n // packages/*. Without this a Next.js monorepo reports `other`, and the\n // stack is what picks the replacement conventions during normalize.\n if (stack === 'other') {\n stack = await detectFromWorkspaces(projectRoot)\n }\n\n // Non-JS detection if still 'other'\n if (stack === 'other') {\n stack = await detectNonJs(projectRoot)\n }\n\n const mono = await detectMonorepo(projectRoot)\n const features = await detectFeatures(projectRoot)\n const meta = STACK_META[stack]\n\n return {\n stack,\n platform: inferPlatform(stack),\n name: meta.name,\n description: meta.description,\n monorepo: mono.isMonorepo,\n monorepoTool: mono.tool,\n features,\n }\n}\n\nasync function detectFromDeps(dir: string): Promise<StackType> {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (!pkg) return 'other'\n\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }\n\n // Meta-frameworks (check BEFORE plain frameworks)\n if ('nuxt' in allDeps) return 'nuxt'\n if ('next' in allDeps) return 'next'\n if ('astro' in allDeps) return 'astro'\n if ('@sveltejs/kit' in allDeps) return 'sveltekit'\n if ('@remix-run/node' in allDeps || 'remix' in allDeps) return 'remix'\n if ('@analogjs/platform' in allDeps) return 'analog'\n\n // Mobile (check before plain react)\n if ('expo' in allDeps) return 'expo'\n if ('react-native' in allDeps) return 'react-native'\n\n // Plain frameworks\n if ('vue' in allDeps) return 'vue'\n if ('svelte' in allDeps) return 'svelte'\n if ('solid-js' in allDeps) return 'solid'\n if ('@angular/core' in allDeps) return 'angular'\n if ('react' in allDeps) return 'react'\n\n // Desktop\n if ('electron' in allDeps) return 'electron'\n if ('@tauri-apps/api' in allDeps) return 'tauri'\n\n // Backend (Node.js)\n if ('@nestjs/core' in allDeps) return 'nestjs'\n if ('fastify' in allDeps) return 'fastify'\n if ('express' in allDeps) return 'express'\n\n // Static site generators\n if ('@11ty/eleventy' in allDeps) return 'eleventy'\n\n // Generic Node.js (has package.json but no framework)\n if ('node' in (pkg.dependencies ?? {})) return 'node'\n\n return 'other'\n}\n\n/**\n * Ranked by how strongly the stack determines content conventions — a\n * meta-framework dictates the i18n setup and the replacement expression,\n * a plain UI library does not. Frequency would be the wrong tie-break: a\n * monorepo with one Next app and two React packages is a Next project.\n */\nconst STACK_PRIORITY: readonly StackType[] = [\n 'nuxt', 'next', 'astro', 'sveltekit', 'remix', 'analog',\n 'expo', 'react-native',\n 'vue', 'react', 'svelte', 'solid', 'angular',\n 'electron', 'tauri',\n 'nestjs', 'fastify', 'express',\n 'eleventy', 'node',\n]\n\n/** Cap the fan-out so a large monorepo cannot turn detection into a crawl. */\nconst MAX_WORKSPACE_DIRS = 50\n\n/**\n * Workspace globs from `pnpm-workspace.yaml` or the `workspaces` field.\n *\n * The pnpm file is parsed line by line rather than with a YAML library: we\n * need exactly one list of strings with a fixed shape, and a parser\n * dependency for that is not worth the install size.\n */\nasync function readWorkspacePatterns(root: string): Promise<string[]> {\n const pnpm = await readText(join(root, 'pnpm-workspace.yaml'))\n if (pnpm) {\n const patterns: string[] = []\n let inPackages = false\n for (const raw of pnpm.split('\\n')) {\n const line = raw.replace(/#.*$/, '').trimEnd()\n if (/^packages:\\s*$/.test(line)) { inPackages = true; continue }\n if (!inPackages) continue\n const item = /^\\s+-\\s*['\"]?([^'\"]+?)['\"]?\\s*$/.exec(line)\n if (item?.[1]) { patterns.push(item[1]); continue }\n if (line.trim() !== '') break // a new top-level key ends the list\n }\n if (patterns.length > 0) return patterns\n }\n\n const pkg = await readJson<PackageJson>(join(root, 'package.json'))\n const ws = pkg?.workspaces\n if (Array.isArray(ws)) return ws\n if (ws && Array.isArray(ws.packages)) return ws.packages\n return []\n}\n\n/**\n * Expand workspace patterns to directories. Only a trailing `*` segment is\n * expanded — that covers `apps/*` and `packages/*`, which is what real\n * workspace files use. Deeper globs and negations are skipped rather than\n * half-supported.\n */\nasync function listWorkspaceDirs(root: string): Promise<string[]> {\n const patterns = (await readWorkspacePatterns(root)).filter(p => !p.startsWith('!'))\n\n const expanded = await Promise.all(patterns.map(async (pattern) => {\n const star = pattern.indexOf('*')\n if (star === -1) return [join(root, pattern)]\n const base = join(root, pattern.slice(0, star).replace(/\\/+$/, ''))\n try {\n const entries = await readdir(base, { withFileTypes: true })\n return entries.filter(e => e.isDirectory()).map(e => join(base, e.name))\n } catch {\n return []\n }\n }))\n\n return expanded.flat().slice(0, MAX_WORKSPACE_DIRS)\n}\n\n/** Highest-priority stack across the workspace packages. */\nasync function detectFromWorkspaces(root: string): Promise<StackType> {\n const dirs = await listWorkspaceDirs(root)\n if (dirs.length === 0) return 'other'\n\n const found = new Set(await Promise.all(dirs.map(d => detectFromDeps(d))))\n return STACK_PRIORITY.find(s => found.has(s)) ?? 'other'\n}\n\nasync function detectNonJs(projectRoot: string): Promise<StackType> {\n // Go\n if (await pathExists(join(projectRoot, 'go.mod'))) return 'go'\n\n // Rust\n if (await pathExists(join(projectRoot, 'Cargo.toml'))) return 'rust'\n\n // Python\n for (const f of ['requirements.txt', 'pyproject.toml', 'setup.py']) {\n if (await pathExists(join(projectRoot, f))) {\n // Check for Django\n try {\n const content = await readText(join(projectRoot, f))\n if (content?.includes('django') || content?.includes('Django')) return 'django'\n } catch { /* ignore */ }\n return 'other' // Generic Python — no specific stack type yet\n }\n }\n\n // Ruby\n if (await pathExists(join(projectRoot, 'Gemfile'))) {\n try {\n const content = await readText(join(projectRoot, 'Gemfile'))\n if (content?.includes('rails')) return 'rails'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // .NET\n const dotnetFiles = ['*.csproj', '*.sln']\n for (const pattern of dotnetFiles) {\n if (pattern === '*.csproj' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n if (pattern === '*.sln' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n }\n\n // PHP / Laravel\n if (await pathExists(join(projectRoot, 'composer.json'))) {\n try {\n const composer = await readJson<Record<string, unknown>>(join(projectRoot, 'composer.json'))\n const require = composer?.require as Record<string, string> | undefined\n if (require?.['laravel/framework']) return 'laravel'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // Flutter (Dart)\n if (await pathExists(join(projectRoot, 'pubspec.yaml'))) return 'flutter'\n\n // Hugo\n if (await pathExists(join(projectRoot, 'hugo.toml'))) return 'hugo'\n if (await pathExists(join(projectRoot, 'config.toml'))) {\n try {\n const content = await readText(join(projectRoot, 'config.toml'))\n if (content?.includes('baseURL') || content?.includes('hugo')) return 'hugo'\n } catch { /* ignore */ }\n }\n\n // Jekyll\n if (await pathExists(join(projectRoot, '_config.yml'))) return 'jekyll'\n\n return 'other'\n}\n\nasync function detectMonorepo(projectRoot: string): Promise<{ isMonorepo: boolean; tool?: string }> {\n if (await pathExists(join(projectRoot, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(projectRoot, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(projectRoot, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(projectRoot, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n if (pkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n\n // Walk up\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n if (await pathExists(join(dir, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(dir, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(dir, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(dir, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n return { isMonorepo: false }\n}\n\nasync function detectFeatures(projectRoot: string): Promise<string[]> {\n const features: string[] = []\n const allDeps = await collectAllDeps(projectRoot)\n\n if ('typescript' in allDeps || await pathExists(join(projectRoot, 'tsconfig.json'))) features.push('TypeScript')\n\n const i18nLibs = ['vue-i18n', '@nuxtjs/i18n', 'next-intl', 'react-intl', 'i18next', 'react-i18next']\n for (const lib of i18nLibs) {\n if (lib in allDeps) { features.push(`i18n (${lib})`); break }\n }\n\n if ('tailwindcss' in allDeps) features.push('Tailwind CSS')\n else if ('sass' in allDeps || 'scss' in allDeps) features.push('Sass/SCSS')\n\n if ('vitest' in allDeps) features.push('Vitest')\n else if ('jest' in allDeps) features.push('Jest')\n\n if ('pinia' in allDeps) features.push('Pinia')\n else if ('zustand' in allDeps) features.push('Zustand')\n else if ('@reduxjs/toolkit' in allDeps || 'redux' in allDeps) features.push('Redux')\n\n return features\n}\n\nasync function collectAllDeps(projectRoot: string): Promise<Record<string, string>> {\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n const allDeps: Record<string, string> = { ...pkg?.dependencies, ...pkg?.devDependencies }\n\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) {\n Object.assign(allDeps, parentPkg.dependencies, parentPkg.devDependencies)\n break\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n // Same blind spot as stack detection: at a monorepo root the interesting\n // dependencies (the i18n library above all) sit in the workspace packages,\n // not in the root manifest. Reporting \"no i18n\" for a project that has it\n // would send normalize down the wrong path.\n const workspaceDirs = await listWorkspaceDirs(projectRoot)\n if (workspaceDirs.length > 0) {\n const pkgs = await Promise.all(\n workspaceDirs.map(d => readJson<PackageJson>(join(d, 'package.json'))),\n )\n for (const wsPkg of pkgs) {\n if (wsPkg) Object.assign(allDeps, wsPkg.dependencies, wsPkg.devDependencies)\n }\n }\n\n return allDeps\n}\n"],"mappings":";;;;AAqBA,MAAM,aAAuE;CAE3E,MAAM;EAAE,MAAM;EAAQ,aAAa;EAAmC;CACtE,MAAM;EAAE,MAAM;EAAW,aAAa;EAAgC;CACtE,OAAO;EAAE,MAAM;EAAS,aAAa;EAA+B;CACpE,WAAW;EAAE,MAAM;EAAa,aAAa;EAAyB;CACtE,OAAO;EAAE,MAAM;EAAS,aAAa;EAA8B;CACnE,QAAQ;EAAE,MAAM;EAAU,aAAa;EAA0B;CAEjE,KAAK;EAAE,MAAM;EAAO,aAAa;EAAoC;CACrE,OAAO;EAAE,MAAM;EAAS,aAAa;EAAiC;CACtE,QAAQ;EAAE,MAAM;EAAU,aAAa;EAA6B;CACpE,OAAO;EAAE,MAAM;EAAS,aAAa;EAAuB;CAC5D,SAAS;EAAE,MAAM;EAAW,aAAa;EAAiC;CAE1E,gBAAgB;EAAE,MAAM;EAAgB,aAAa;EAAmC;CACxF,MAAM;EAAE,MAAM;EAAQ,aAAa;EAA0B;CAC7D,SAAS;EAAE,MAAM;EAAW,aAAa;EAAoC;CAE7E,MAAM;EAAE,MAAM;EAAW,aAAa;EAAkC;CACxE,SAAS;EAAE,MAAM;EAAW,aAAa;EAAiC;CAC1E,SAAS;EAAE,MAAM;EAAW,aAAa;EAA8B;CACvE,QAAQ;EAAE,MAAM;EAAU,aAAa;EAAiC;CACxE,QAAQ;EAAE,MAAM;EAAU,aAAa;EAAwB;CAC/D,OAAO;EAAE,MAAM;EAAS,aAAa;EAAsB;CAC3D,SAAS;EAAE,MAAM;EAAW,aAAa;EAAqB;CAC9D,IAAI;EAAE,MAAM;EAAM,aAAa;EAA2B;CAC1D,MAAM;EAAE,MAAM;EAAQ,aAAa;EAA6B;CAChE,QAAQ;EAAE,MAAM;EAAQ,aAAa;EAA2B;CAEhE,MAAM;EAAE,MAAM;EAAQ,aAAa;EAAkC;CACrE,QAAQ;EAAE,MAAM;EAAU,aAAa;EAAoC;CAC3E,UAAU;EAAE,MAAM;EAAY,aAAa;EAAoC;CAE/E,UAAU;EAAE,MAAM;EAAY,aAAa;EAA+B;CAC1E,OAAO;EAAE,MAAM;EAAS,aAAa;EAAmC;CAExE,OAAO;EAAE,MAAM;EAAS,aAAa;EAAoC;CAC1E;AAED,SAAgB,cAAc,OAA4B;AACxD,SAAQ,OAAR;EACE,KAAK;EAAQ,KAAK;EAAQ,KAAK;EAAS,KAAK;EAAa,KAAK;EAAS,KAAK;EAC7E,KAAK;EAAO,KAAK;EAAS,KAAK;EAAU,KAAK;EAAS,KAAK,UAC1D,QAAO;EACT,KAAK;EAAgB,KAAK;EAAQ,KAAK,UACrC,QAAO;EACT,KAAK;EAAQ,KAAK;EAAW,KAAK;EAAW,KAAK;EAClD,KAAK;EAAU,KAAK;EAAS,KAAK;EAAW,KAAK;EAAM,KAAK;EAAQ,KAAK,SACxE,QAAO;EACT,KAAK;EAAY,KAAK,QACpB,QAAO;EACT,KAAK;EAAQ,KAAK;EAAU,KAAK,WAC/B,QAAO;EACT,QACE,QAAO;;;;AAKb,eAAsB,YAAY,aAAyC;AAEzE,SADa,MAAM,gBAAgB,YAAY,EACnC;;;AAId,eAAsB,gBAAgB,aAAyC;CAC7E,IAAI,QAAQ,MAAM,eAAe,YAAY;AAG7C,KAAI,UAAU,SAAS;EACrB,IAAI,MAAM,QAAQ,YAAY;AAC9B,OAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;AAE1B,QADY,MAAM,SAAsB,KAAK,KAAK,eAAe,CAAC,GACzD,YAAY;IACnB,MAAM,YAAY,MAAM,eAAe,IAAI;AAC3C,QAAI,cAAc,SAAS;AACzB,aAAQ;AACR;;;GAGJ,MAAM,SAAS,QAAQ,IAAI;AAC3B,OAAI,WAAW,IAAK;AACpB,SAAM;;;AAUV,KAAI,UAAU,QACZ,SAAQ,MAAM,qBAAqB,YAAY;AAIjD,KAAI,UAAU,QACZ,SAAQ,MAAM,YAAY,YAAY;CAGxC,MAAM,OAAO,MAAM,eAAe,YAAY;CAC9C,MAAM,WAAW,MAAM,eAAe,YAAY;CAClD,MAAM,OAAO,WAAW;AAExB,QAAO;EACL;EACA,UAAU,cAAc,MAAM;EAC9B,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,UAAU,KAAK;EACf,cAAc,KAAK;EACnB;EACD;;AAGH,eAAe,eAAe,KAAiC;CAC7D,MAAM,MAAM,MAAM,SAAsB,KAAK,KAAK,eAAe,CAAC;AAClE,KAAI,CAAC,IAAK,QAAO;CAEjB,MAAM,UAAU;EAAE,GAAG,IAAI;EAAc,GAAG,IAAI;EAAiB;AAG/D,KAAI,UAAU,QAAS,QAAO;AAC9B,KAAI,UAAU,QAAS,QAAO;AAC9B,KAAI,WAAW,QAAS,QAAO;AAC/B,KAAI,mBAAmB,QAAS,QAAO;AACvC,KAAI,qBAAqB,WAAW,WAAW,QAAS,QAAO;AAC/D,KAAI,wBAAwB,QAAS,QAAO;AAG5C,KAAI,UAAU,QAAS,QAAO;AAC9B,KAAI,kBAAkB,QAAS,QAAO;AAGtC,KAAI,SAAS,QAAS,QAAO;AAC7B,KAAI,YAAY,QAAS,QAAO;AAChC,KAAI,cAAc,QAAS,QAAO;AAClC,KAAI,mBAAmB,QAAS,QAAO;AACvC,KAAI,WAAW,QAAS,QAAO;AAG/B,KAAI,cAAc,QAAS,QAAO;AAClC,KAAI,qBAAqB,QAAS,QAAO;AAGzC,KAAI,kBAAkB,QAAS,QAAO;AACtC,KAAI,aAAa,QAAS,QAAO;AACjC,KAAI,aAAa,QAAS,QAAO;AAGjC,KAAI,oBAAoB,QAAS,QAAO;AAGxC,KAAI,WAAW,IAAI,gBAAgB,EAAE,EAAG,QAAO;AAE/C,QAAO;;;;;;;;AAST,MAAM,iBAAuC;CAC3C;CAAQ;CAAQ;CAAS;CAAa;CAAS;CAC/C;CAAQ;CACR;CAAO;CAAS;CAAU;CAAS;CACnC;CAAY;CACZ;CAAU;CAAW;CACrB;CAAY;CACb;;AAGD,MAAM,qBAAqB;;;;;;;;AAS3B,eAAe,sBAAsB,MAAiC;CACpE,MAAM,OAAO,MAAM,SAAS,KAAK,MAAM,sBAAsB,CAAC;AAC9D,KAAI,MAAM;EACR,MAAM,WAAqB,EAAE;EAC7B,IAAI,aAAa;AACjB,OAAK,MAAM,OAAO,KAAK,MAAM,KAAK,EAAE;GAClC,MAAM,OAAO,IAAI,QAAQ,QAAQ,GAAG,CAAC,SAAS;AAC9C,OAAI,iBAAiB,KAAK,KAAK,EAAE;AAAE,iBAAa;AAAM;;AACtD,OAAI,CAAC,WAAY;GACjB,MAAM,OAAO,kCAAkC,KAAK,KAAK;AACzD,OAAI,OAAO,IAAI;AAAE,aAAS,KAAK,KAAK,GAAG;AAAE;;AACzC,OAAI,KAAK,MAAM,KAAK,GAAI;;AAE1B,MAAI,SAAS,SAAS,EAAG,QAAO;;CAIlC,MAAM,MADM,MAAM,SAAsB,KAAK,MAAM,eAAe,CAAC,GACnD;AAChB,KAAI,MAAM,QAAQ,GAAG,CAAE,QAAO;AAC9B,KAAI,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAE,QAAO,GAAG;AAChD,QAAO,EAAE;;;;;;;;AASX,eAAe,kBAAkB,MAAiC;CAChE,MAAM,YAAY,MAAM,sBAAsB,KAAK,EAAE,QAAO,MAAK,CAAC,EAAE,WAAW,IAAI,CAAC;AAcpF,SAZiB,MAAM,QAAQ,IAAI,SAAS,IAAI,OAAO,YAAY;EACjE,MAAM,OAAO,QAAQ,QAAQ,IAAI;AACjC,MAAI,SAAS,GAAI,QAAO,CAAC,KAAK,MAAM,QAAQ,CAAC;EAC7C,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,GAAG,KAAK,CAAC,QAAQ,QAAQ,GAAG,CAAC;AACnE,MAAI;AAEF,WADgB,MAAM,QAAQ,MAAM,EAAE,eAAe,MAAM,CAAC,EAC7C,QAAO,MAAK,EAAE,aAAa,CAAC,CAAC,KAAI,MAAK,KAAK,MAAM,EAAE,KAAK,CAAC;UAClE;AACN,UAAO,EAAE;;GAEX,CAAC,EAEa,MAAM,CAAC,MAAM,GAAG,mBAAmB;;;AAIrD,eAAe,qBAAqB,MAAkC;CACpE,MAAM,OAAO,MAAM,kBAAkB,KAAK;AAC1C,KAAI,KAAK,WAAW,EAAG,QAAO;CAE9B,MAAM,QAAQ,IAAI,IAAI,MAAM,QAAQ,IAAI,KAAK,KAAI,MAAK,eAAe,EAAE,CAAC,CAAC,CAAC;AAC1E,QAAO,eAAe,MAAK,MAAK,MAAM,IAAI,EAAE,CAAC,IAAI;;AAGnD,eAAe,YAAY,aAAyC;AAElE,KAAI,MAAM,WAAW,KAAK,aAAa,SAAS,CAAC,CAAE,QAAO;AAG1D,KAAI,MAAM,WAAW,KAAK,aAAa,aAAa,CAAC,CAAE,QAAO;AAG9D,MAAK,MAAM,KAAK;EAAC;EAAoB;EAAkB;EAAW,CAChE,KAAI,MAAM,WAAW,KAAK,aAAa,EAAE,CAAC,EAAE;AAE1C,MAAI;GACF,MAAM,UAAU,MAAM,SAAS,KAAK,aAAa,EAAE,CAAC;AACpD,OAAI,SAAS,SAAS,SAAS,IAAI,SAAS,SAAS,SAAS,CAAE,QAAO;UACjE;AACR,SAAO;;AAKX,KAAI,MAAM,WAAW,KAAK,aAAa,UAAU,CAAC,EAAE;AAClD,MAAI;AAEF,QADgB,MAAM,SAAS,KAAK,aAAa,UAAU,CAAC,GAC/C,SAAS,QAAQ,CAAE,QAAO;UACjC;AACR,SAAO;;AAKT,MAAK,MAAM,WADS,CAAC,YAAY,QAAQ,EACN;AACjC,MAAI,YAAY,cAAc,MAAM,WAAW,KAAK,aAAa,aAAa,CAAC,CAAE,QAAO;AACxF,MAAI,YAAY,WAAW,MAAM,WAAW,KAAK,aAAa,aAAa,CAAC,CAAE,QAAO;;AAIvF,KAAI,MAAM,WAAW,KAAK,aAAa,gBAAgB,CAAC,EAAE;AACxD,MAAI;AAGF,SAFiB,MAAM,SAAkC,KAAK,aAAa,gBAAgB,CAAC,GAClE,WACZ,qBAAsB,QAAO;UACrC;AACR,SAAO;;AAIT,KAAI,MAAM,WAAW,KAAK,aAAa,eAAe,CAAC,CAAE,QAAO;AAGhE,KAAI,MAAM,WAAW,KAAK,aAAa,YAAY,CAAC,CAAE,QAAO;AAC7D,KAAI,MAAM,WAAW,KAAK,aAAa,cAAc,CAAC,CACpD,KAAI;EACF,MAAM,UAAU,MAAM,SAAS,KAAK,aAAa,cAAc,CAAC;AAChE,MAAI,SAAS,SAAS,UAAU,IAAI,SAAS,SAAS,OAAO,CAAE,QAAO;SAChE;AAIV,KAAI,MAAM,WAAW,KAAK,aAAa,cAAc,CAAC,CAAE,QAAO;AAE/D,QAAO;;AAGT,eAAe,eAAe,aAAsE;AAClG,KAAI,MAAM,WAAW,KAAK,aAAa,sBAAsB,CAAC,CAAE,QAAO;EAAE,YAAY;EAAM,MAAM;EAAmB;AACpH,KAAI,MAAM,WAAW,KAAK,aAAa,aAAa,CAAC,CAAE,QAAO;EAAE,YAAY;EAAM,MAAM;EAAS;AACjG,KAAI,MAAM,WAAW,KAAK,aAAa,UAAU,CAAC,CAAE,QAAO;EAAE,YAAY;EAAM,MAAM;EAAM;AAC3F,KAAI,MAAM,WAAW,KAAK,aAAa,aAAa,CAAC,CAAE,QAAO;EAAE,YAAY;EAAM,MAAM;EAAa;AAGrG,MADY,MAAM,SAAsB,KAAK,aAAa,eAAe,CAAC,GACjE,WAAY,QAAO;EAAE,YAAY;EAAM,MAAM;EAAuB;CAG7E,IAAI,MAAM,QAAQ,YAAY;AAC9B,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,MAAI,MAAM,WAAW,KAAK,KAAK,sBAAsB,CAAC,CAAE,QAAO;GAAE,YAAY;GAAM,MAAM;GAAmB;AAC5G,MAAI,MAAM,WAAW,KAAK,KAAK,aAAa,CAAC,CAAE,QAAO;GAAE,YAAY;GAAM,MAAM;GAAS;AACzF,MAAI,MAAM,WAAW,KAAK,KAAK,UAAU,CAAC,CAAE,QAAO;GAAE,YAAY;GAAM,MAAM;GAAM;AACnF,MAAI,MAAM,WAAW,KAAK,KAAK,aAAa,CAAC,CAAE,QAAO;GAAE,YAAY;GAAM,MAAM;GAAa;AAE7F,OADkB,MAAM,SAAsB,KAAK,KAAK,eAAe,CAAC,GACzD,WAAY,QAAO;GAAE,YAAY;GAAM,MAAM;GAAuB;EACnF,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAGR,QAAO,EAAE,YAAY,OAAO;;AAG9B,eAAe,eAAe,aAAwC;CACpE,MAAM,WAAqB,EAAE;CAC7B,MAAM,UAAU,MAAM,eAAe,YAAY;AAEjD,KAAI,gBAAgB,WAAW,MAAM,WAAW,KAAK,aAAa,gBAAgB,CAAC,CAAE,UAAS,KAAK,aAAa;AAGhH,MAAK,MAAM,OADM;EAAC;EAAY;EAAgB;EAAa;EAAc;EAAW;EAAgB,CAElG,KAAI,OAAO,SAAS;AAAE,WAAS,KAAK,SAAS,IAAI,GAAG;AAAE;;AAGxD,KAAI,iBAAiB,QAAS,UAAS,KAAK,eAAe;UAClD,UAAU,WAAW,UAAU,QAAS,UAAS,KAAK,YAAY;AAE3E,KAAI,YAAY,QAAS,UAAS,KAAK,SAAS;UACvC,UAAU,QAAS,UAAS,KAAK,OAAO;AAEjD,KAAI,WAAW,QAAS,UAAS,KAAK,QAAQ;UACrC,aAAa,QAAS,UAAS,KAAK,UAAU;UAC9C,sBAAsB,WAAW,WAAW,QAAS,UAAS,KAAK,QAAQ;AAEpF,QAAO;;AAGT,eAAe,eAAe,aAAsD;CAClF,MAAM,MAAM,MAAM,SAAsB,KAAK,aAAa,eAAe,CAAC;CAC1E,MAAM,UAAkC;EAAE,GAAG,KAAK;EAAc,GAAG,KAAK;EAAiB;CAEzF,IAAI,MAAM,QAAQ,YAAY;AAC9B,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,YAAY,MAAM,SAAsB,KAAK,KAAK,eAAe,CAAC;AACxE,MAAI,WAAW,YAAY;AACzB,UAAO,OAAO,SAAS,UAAU,cAAc,UAAU,gBAAgB;AACzE;;EAEF,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;CAOR,MAAM,gBAAgB,MAAM,kBAAkB,YAAY;AAC1D,KAAI,cAAc,SAAS,GAAG;EAC5B,MAAM,OAAO,MAAM,QAAQ,IACzB,cAAc,KAAI,MAAK,SAAsB,KAAK,GAAG,eAAe,CAAC,CAAC,CACvE;AACD,OAAK,MAAM,SAAS,KAClB,KAAI,MAAO,QAAO,OAAO,SAAS,MAAM,cAAc,MAAM,gBAAgB;;AAIhF,QAAO"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
+2
-2
@@ -14,5 +14,5 @@ #!/usr/bin/env node | ||
| import "./local-8dJ5NIWL.mjs"; | ||
| import "./detect-wVSI9VuY.mjs"; | ||
| import "./detect-CdDU4Ew_.mjs"; | ||
| import "./annotations-D3tlsF38.mjs"; | ||
| import { n as createServer } from "./server-I6sqKvO8.mjs"; | ||
| import { n as createServer } from "./server-CUZnuHQx.mjs"; | ||
| import "./validator-ChiYk6ap.mjs"; | ||
@@ -19,0 +19,0 @@ import "./scan-config-BlNLRCMx.mjs"; |
+2
-2
@@ -13,5 +13,5 @@ import "./contracts-DfL0BfrD.mjs"; | ||
| import "./local-8dJ5NIWL.mjs"; | ||
| import "./detect-wVSI9VuY.mjs"; | ||
| import "./detect-CdDU4Ew_.mjs"; | ||
| import "./annotations-D3tlsF38.mjs"; | ||
| import { n as createServer, t as DEFAULT_INSTRUCTIONS } from "./server-I6sqKvO8.mjs"; | ||
| import { n as createServer, t as DEFAULT_INSTRUCTIONS } from "./server-CUZnuHQx.mjs"; | ||
| import "./validator-ChiYk6ap.mjs"; | ||
@@ -18,0 +18,0 @@ import "./scan-config-BlNLRCMx.mjs"; |
@@ -13,5 +13,5 @@ import "../../contracts-DfL0BfrD.mjs"; | ||
| import "../../local-8dJ5NIWL.mjs"; | ||
| import "../../detect-wVSI9VuY.mjs"; | ||
| import "../../detect-CdDU4Ew_.mjs"; | ||
| import "../../annotations-D3tlsF38.mjs"; | ||
| import { n as createServer } from "../../server-I6sqKvO8.mjs"; | ||
| import { n as createServer } from "../../server-CUZnuHQx.mjs"; | ||
| import "../../validator-ChiYk6ap.mjs"; | ||
@@ -18,0 +18,0 @@ import "../../scan-config-BlNLRCMx.mjs"; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"detect.d.mts","names":[],"sources":["../../src/util/detect.ts"],"mappings":";;;UAUiB,SAAA;EACf,KAAA,EAAO,SAAA;EACP,QAAA,EAAU,QAAA;EACV,IAAA;EACA,WAAA;EACA,QAAA;EACA,YAAA;EACA,QAAA;AAAA;AAAA,iBA2Cc,aAAA,CAAc,KAAA,EAAO,SAAA,GAAY,QAAA;;iBAoB3B,WAAA,CAAY,WAAA,WAAsB,OAAA,CAAQ,SAAA;;iBAM1C,eAAA,CAAgB,WAAA,WAAsB,OAAA,CAAQ,SAAA"} | ||
| {"version":3,"file":"detect.d.mts","names":[],"sources":["../../src/util/detect.ts"],"mappings":";;;UAWiB,SAAA;EACf,KAAA,EAAO,SAAA;EACP,QAAA,EAAU,QAAA;EACV,IAAA;EACA,WAAA;EACA,QAAA;EACA,YAAA;EACA,QAAA;AAAA;AAAA,iBA2Cc,aAAA,CAAc,KAAA,EAAO,SAAA,GAAY,QAAA;;iBAoB3B,WAAA,CAAY,WAAA,WAAsB,OAAA,CAAQ,SAAA;;iBAM1C,eAAA,CAAgB,WAAA,WAAsB,OAAA,CAAQ,SAAA"} |
| import "../fs-DLbVB-Ek.mjs"; | ||
| import { n as detectStackInfo, r as inferPlatform, t as detectStack } from "../detect-wVSI9VuY.mjs"; | ||
| import { n as detectStackInfo, r as inferPlatform, t as detectStack } from "../detect-CdDU4Ew_.mjs"; | ||
| export { detectStack, detectStackInfo, inferPlatform }; |
+1
-1
| { | ||
| "name": "@contentrain/mcp", | ||
| "version": "2.3.0", | ||
| "version": "2.3.1", | ||
| "mcpName": "io.github.Contentrain/contentrain", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
| import { a as readJson, r as pathExists } from "./fs-DLbVB-Ek.mjs"; | ||
| import { dirname, join } from "node:path"; | ||
| //#region src/util/detect.ts | ||
| const STACK_META = { | ||
| nuxt: { | ||
| name: "Nuxt", | ||
| description: "Vue meta-framework with SSR/SSG" | ||
| }, | ||
| next: { | ||
| name: "Next.js", | ||
| description: "React framework with SSR/SSG" | ||
| }, | ||
| astro: { | ||
| name: "Astro", | ||
| description: "Content-first web framework" | ||
| }, | ||
| sveltekit: { | ||
| name: "SvelteKit", | ||
| description: "Svelte meta-framework" | ||
| }, | ||
| remix: { | ||
| name: "Remix", | ||
| description: "Full-stack React framework" | ||
| }, | ||
| analog: { | ||
| name: "Analog", | ||
| description: "Angular meta-framework" | ||
| }, | ||
| vue: { | ||
| name: "Vue", | ||
| description: "Progressive JavaScript framework" | ||
| }, | ||
| react: { | ||
| name: "React", | ||
| description: "UI library for web and native" | ||
| }, | ||
| svelte: { | ||
| name: "Svelte", | ||
| description: "Compile-time UI framework" | ||
| }, | ||
| solid: { | ||
| name: "Solid", | ||
| description: "Reactive UI library" | ||
| }, | ||
| angular: { | ||
| name: "Angular", | ||
| description: "Platform for web applications" | ||
| }, | ||
| "react-native": { | ||
| name: "React Native", | ||
| description: "Cross-platform mobile framework" | ||
| }, | ||
| expo: { | ||
| name: "Expo", | ||
| description: "React Native toolchain" | ||
| }, | ||
| flutter: { | ||
| name: "Flutter", | ||
| description: "Cross-platform UI toolkit (Dart)" | ||
| }, | ||
| node: { | ||
| name: "Node.js", | ||
| description: "Server-side JavaScript runtime" | ||
| }, | ||
| express: { | ||
| name: "Express", | ||
| description: "Minimal Node.js web framework" | ||
| }, | ||
| fastify: { | ||
| name: "Fastify", | ||
| description: "Fast Node.js web framework" | ||
| }, | ||
| nestjs: { | ||
| name: "NestJS", | ||
| description: "Progressive Node.js framework" | ||
| }, | ||
| django: { | ||
| name: "Django", | ||
| description: "Python web framework" | ||
| }, | ||
| rails: { | ||
| name: "Rails", | ||
| description: "Ruby web framework" | ||
| }, | ||
| laravel: { | ||
| name: "Laravel", | ||
| description: "PHP web framework" | ||
| }, | ||
| go: { | ||
| name: "Go", | ||
| description: "Go programming language" | ||
| }, | ||
| rust: { | ||
| name: "Rust", | ||
| description: "Rust programming language" | ||
| }, | ||
| dotnet: { | ||
| name: ".NET", | ||
| description: "Microsoft .NET platform" | ||
| }, | ||
| hugo: { | ||
| name: "Hugo", | ||
| description: "Go-based static site generator" | ||
| }, | ||
| jekyll: { | ||
| name: "Jekyll", | ||
| description: "Ruby-based static site generator" | ||
| }, | ||
| eleventy: { | ||
| name: "Eleventy", | ||
| description: "JavaScript static site generator" | ||
| }, | ||
| electron: { | ||
| name: "Electron", | ||
| description: "Cross-platform desktop apps" | ||
| }, | ||
| tauri: { | ||
| name: "Tauri", | ||
| description: "Lightweight desktop apps (Rust)" | ||
| }, | ||
| other: { | ||
| name: "Other", | ||
| description: "Unknown or unsupported framework" | ||
| } | ||
| }; | ||
| function inferPlatform(stack) { | ||
| switch (stack) { | ||
| case "nuxt": | ||
| case "next": | ||
| case "astro": | ||
| case "sveltekit": | ||
| case "remix": | ||
| case "analog": | ||
| case "vue": | ||
| case "react": | ||
| case "svelte": | ||
| case "solid": | ||
| case "angular": return "web"; | ||
| case "react-native": | ||
| case "expo": | ||
| case "flutter": return "mobile"; | ||
| case "node": | ||
| case "express": | ||
| case "fastify": | ||
| case "nestjs": | ||
| case "django": | ||
| case "rails": | ||
| case "laravel": | ||
| case "go": | ||
| case "rust": | ||
| case "dotnet": return "api"; | ||
| case "electron": | ||
| case "tauri": return "desktop"; | ||
| case "hugo": | ||
| case "jekyll": | ||
| case "eleventy": return "static"; | ||
| default: return "other"; | ||
| } | ||
| } | ||
| /** Backward-compatible: returns just the StackType string */ | ||
| async function detectStack(projectRoot) { | ||
| return (await detectStackInfo(projectRoot)).stack; | ||
| } | ||
| /** Rich detection: stack + platform + monorepo + features */ | ||
| async function detectStackInfo(projectRoot) { | ||
| let stack = await detectFromDeps(projectRoot); | ||
| if (stack === "other") { | ||
| let dir = dirname(projectRoot); | ||
| for (let i = 0; i < 5; i++) { | ||
| if ((await readJson(join(dir, "package.json")))?.workspaces) { | ||
| const rootStack = await detectFromDeps(dir); | ||
| if (rootStack !== "other") { | ||
| stack = rootStack; | ||
| break; | ||
| } | ||
| } | ||
| const parent = dirname(dir); | ||
| if (parent === dir) break; | ||
| dir = parent; | ||
| } | ||
| } | ||
| if (stack === "other") stack = await detectNonJs(projectRoot); | ||
| const mono = await detectMonorepo(projectRoot); | ||
| const features = await detectFeatures(projectRoot); | ||
| const meta = STACK_META[stack]; | ||
| return { | ||
| stack, | ||
| platform: inferPlatform(stack), | ||
| name: meta.name, | ||
| description: meta.description, | ||
| monorepo: mono.isMonorepo, | ||
| monorepoTool: mono.tool, | ||
| features | ||
| }; | ||
| } | ||
| async function detectFromDeps(dir) { | ||
| const pkg = await readJson(join(dir, "package.json")); | ||
| if (!pkg) return "other"; | ||
| const allDeps = { | ||
| ...pkg.dependencies, | ||
| ...pkg.devDependencies | ||
| }; | ||
| if ("nuxt" in allDeps) return "nuxt"; | ||
| if ("next" in allDeps) return "next"; | ||
| if ("astro" in allDeps) return "astro"; | ||
| if ("@sveltejs/kit" in allDeps) return "sveltekit"; | ||
| if ("@remix-run/node" in allDeps || "remix" in allDeps) return "remix"; | ||
| if ("@analogjs/platform" in allDeps) return "analog"; | ||
| if ("expo" in allDeps) return "expo"; | ||
| if ("react-native" in allDeps) return "react-native"; | ||
| if ("vue" in allDeps) return "vue"; | ||
| if ("svelte" in allDeps) return "svelte"; | ||
| if ("solid-js" in allDeps) return "solid"; | ||
| if ("@angular/core" in allDeps) return "angular"; | ||
| if ("react" in allDeps) return "react"; | ||
| if ("electron" in allDeps) return "electron"; | ||
| if ("@tauri-apps/api" in allDeps) return "tauri"; | ||
| if ("@nestjs/core" in allDeps) return "nestjs"; | ||
| if ("fastify" in allDeps) return "fastify"; | ||
| if ("express" in allDeps) return "express"; | ||
| if ("@11ty/eleventy" in allDeps) return "eleventy"; | ||
| if ("node" in (pkg.dependencies ?? {})) return "node"; | ||
| return "other"; | ||
| } | ||
| async function detectNonJs(projectRoot) { | ||
| if (await pathExists(join(projectRoot, "go.mod"))) return "go"; | ||
| if (await pathExists(join(projectRoot, "Cargo.toml"))) return "rust"; | ||
| for (const f of [ | ||
| "requirements.txt", | ||
| "pyproject.toml", | ||
| "setup.py" | ||
| ]) if (await pathExists(join(projectRoot, f))) { | ||
| try { | ||
| const { readText } = await import("./util/fs.mjs"); | ||
| const content = await readText(join(projectRoot, f)); | ||
| if (content?.includes("django") || content?.includes("Django")) return "django"; | ||
| } catch {} | ||
| return "other"; | ||
| } | ||
| if (await pathExists(join(projectRoot, "Gemfile"))) { | ||
| try { | ||
| const { readText } = await import("./util/fs.mjs"); | ||
| if ((await readText(join(projectRoot, "Gemfile")))?.includes("rails")) return "rails"; | ||
| } catch {} | ||
| return "other"; | ||
| } | ||
| for (const pattern of ["*.csproj", "*.sln"]) { | ||
| if (pattern === "*.csproj" && await pathExists(join(projectRoot, "Program.cs"))) return "dotnet"; | ||
| if (pattern === "*.sln" && await pathExists(join(projectRoot, "Program.cs"))) return "dotnet"; | ||
| } | ||
| if (await pathExists(join(projectRoot, "composer.json"))) { | ||
| try { | ||
| if (((await readJson(join(projectRoot, "composer.json")))?.require)?.["laravel/framework"]) return "laravel"; | ||
| } catch {} | ||
| return "other"; | ||
| } | ||
| if (await pathExists(join(projectRoot, "pubspec.yaml"))) return "flutter"; | ||
| if (await pathExists(join(projectRoot, "hugo.toml"))) return "hugo"; | ||
| if (await pathExists(join(projectRoot, "config.toml"))) try { | ||
| const { readText } = await import("./util/fs.mjs"); | ||
| const content = await readText(join(projectRoot, "config.toml")); | ||
| if (content?.includes("baseURL") || content?.includes("hugo")) return "hugo"; | ||
| } catch {} | ||
| if (await pathExists(join(projectRoot, "_config.yml"))) return "jekyll"; | ||
| return "other"; | ||
| } | ||
| async function detectMonorepo(projectRoot) { | ||
| if (await pathExists(join(projectRoot, "pnpm-workspace.yaml"))) return { | ||
| isMonorepo: true, | ||
| tool: "pnpm workspaces" | ||
| }; | ||
| if (await pathExists(join(projectRoot, "lerna.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Lerna" | ||
| }; | ||
| if (await pathExists(join(projectRoot, "nx.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Nx" | ||
| }; | ||
| if (await pathExists(join(projectRoot, "turbo.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Turborepo" | ||
| }; | ||
| if ((await readJson(join(projectRoot, "package.json")))?.workspaces) return { | ||
| isMonorepo: true, | ||
| tool: "npm/yarn workspaces" | ||
| }; | ||
| let dir = dirname(projectRoot); | ||
| for (let i = 0; i < 5; i++) { | ||
| if (await pathExists(join(dir, "pnpm-workspace.yaml"))) return { | ||
| isMonorepo: true, | ||
| tool: "pnpm workspaces" | ||
| }; | ||
| if (await pathExists(join(dir, "lerna.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Lerna" | ||
| }; | ||
| if (await pathExists(join(dir, "nx.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Nx" | ||
| }; | ||
| if (await pathExists(join(dir, "turbo.json"))) return { | ||
| isMonorepo: true, | ||
| tool: "Turborepo" | ||
| }; | ||
| if ((await readJson(join(dir, "package.json")))?.workspaces) return { | ||
| isMonorepo: true, | ||
| tool: "npm/yarn workspaces" | ||
| }; | ||
| const parent = dirname(dir); | ||
| if (parent === dir) break; | ||
| dir = parent; | ||
| } | ||
| return { isMonorepo: false }; | ||
| } | ||
| async function detectFeatures(projectRoot) { | ||
| const features = []; | ||
| const allDeps = await collectAllDeps(projectRoot); | ||
| if ("typescript" in allDeps || await pathExists(join(projectRoot, "tsconfig.json"))) features.push("TypeScript"); | ||
| for (const lib of [ | ||
| "vue-i18n", | ||
| "@nuxtjs/i18n", | ||
| "next-intl", | ||
| "react-intl", | ||
| "i18next", | ||
| "react-i18next" | ||
| ]) if (lib in allDeps) { | ||
| features.push(`i18n (${lib})`); | ||
| break; | ||
| } | ||
| if ("tailwindcss" in allDeps) features.push("Tailwind CSS"); | ||
| else if ("sass" in allDeps || "scss" in allDeps) features.push("Sass/SCSS"); | ||
| if ("vitest" in allDeps) features.push("Vitest"); | ||
| else if ("jest" in allDeps) features.push("Jest"); | ||
| if ("pinia" in allDeps) features.push("Pinia"); | ||
| else if ("zustand" in allDeps) features.push("Zustand"); | ||
| else if ("@reduxjs/toolkit" in allDeps || "redux" in allDeps) features.push("Redux"); | ||
| return features; | ||
| } | ||
| async function collectAllDeps(projectRoot) { | ||
| const pkg = await readJson(join(projectRoot, "package.json")); | ||
| const allDeps = { | ||
| ...pkg?.dependencies, | ||
| ...pkg?.devDependencies | ||
| }; | ||
| let dir = dirname(projectRoot); | ||
| for (let i = 0; i < 5; i++) { | ||
| const parentPkg = await readJson(join(dir, "package.json")); | ||
| if (parentPkg?.workspaces) { | ||
| Object.assign(allDeps, parentPkg.dependencies, parentPkg.devDependencies); | ||
| break; | ||
| } | ||
| const parent = dirname(dir); | ||
| if (parent === dir) break; | ||
| dir = parent; | ||
| } | ||
| return allDeps; | ||
| } | ||
| //#endregion | ||
| export { detectStackInfo as n, inferPlatform as r, detectStack as t }; | ||
| //# sourceMappingURL=detect-wVSI9VuY.mjs.map |
| {"version":3,"file":"detect-wVSI9VuY.mjs","names":[],"sources":["../src/util/detect.ts"],"sourcesContent":["import type { StackType, Platform } from '@contentrain/types'\nimport { join, dirname } from 'node:path'\nimport { readJson, pathExists } from './fs.js'\n\ninterface PackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n workspaces?: string[] | { packages: string[] }\n}\n\nexport interface StackInfo {\n stack: StackType\n platform: Platform\n name: string\n description: string\n monorepo: boolean\n monorepoTool?: string\n features: string[]\n}\n\nconst STACK_META: Record<StackType, { name: string; description: string }> = {\n // Meta-frameworks\n nuxt: { name: 'Nuxt', description: 'Vue meta-framework with SSR/SSG' },\n next: { name: 'Next.js', description: 'React framework with SSR/SSG' },\n astro: { name: 'Astro', description: 'Content-first web framework' },\n sveltekit: { name: 'SvelteKit', description: 'Svelte meta-framework' },\n remix: { name: 'Remix', description: 'Full-stack React framework' },\n analog: { name: 'Analog', description: 'Angular meta-framework' },\n // Plain frameworks\n vue: { name: 'Vue', description: 'Progressive JavaScript framework' },\n react: { name: 'React', description: 'UI library for web and native' },\n svelte: { name: 'Svelte', description: 'Compile-time UI framework' },\n solid: { name: 'Solid', description: 'Reactive UI library' },\n angular: { name: 'Angular', description: 'Platform for web applications' },\n // Mobile\n 'react-native': { name: 'React Native', description: 'Cross-platform mobile framework' },\n expo: { name: 'Expo', description: 'React Native toolchain' },\n flutter: { name: 'Flutter', description: 'Cross-platform UI toolkit (Dart)' },\n // Backend\n node: { name: 'Node.js', description: 'Server-side JavaScript runtime' },\n express: { name: 'Express', description: 'Minimal Node.js web framework' },\n fastify: { name: 'Fastify', description: 'Fast Node.js web framework' },\n nestjs: { name: 'NestJS', description: 'Progressive Node.js framework' },\n django: { name: 'Django', description: 'Python web framework' },\n rails: { name: 'Rails', description: 'Ruby web framework' },\n laravel: { name: 'Laravel', description: 'PHP web framework' },\n go: { name: 'Go', description: 'Go programming language' },\n rust: { name: 'Rust', description: 'Rust programming language' },\n dotnet: { name: '.NET', description: 'Microsoft .NET platform' },\n // Static\n hugo: { name: 'Hugo', description: 'Go-based static site generator' },\n jekyll: { name: 'Jekyll', description: 'Ruby-based static site generator' },\n eleventy: { name: 'Eleventy', description: 'JavaScript static site generator' },\n // Desktop\n electron: { name: 'Electron', description: 'Cross-platform desktop apps' },\n tauri: { name: 'Tauri', description: 'Lightweight desktop apps (Rust)' },\n // Catch-all\n other: { name: 'Other', description: 'Unknown or unsupported framework' },\n}\n\nexport function inferPlatform(stack: StackType): Platform {\n switch (stack) {\n case 'nuxt': case 'next': case 'astro': case 'sveltekit': case 'remix': case 'analog':\n case 'vue': case 'react': case 'svelte': case 'solid': case 'angular':\n return 'web'\n case 'react-native': case 'expo': case 'flutter':\n return 'mobile'\n case 'node': case 'express': case 'fastify': case 'nestjs':\n case 'django': case 'rails': case 'laravel': case 'go': case 'rust': case 'dotnet':\n return 'api'\n case 'electron': case 'tauri':\n return 'desktop'\n case 'hugo': case 'jekyll': case 'eleventy':\n return 'static'\n default:\n return 'other'\n }\n}\n\n/** Backward-compatible: returns just the StackType string */\nexport async function detectStack(projectRoot: string): Promise<StackType> {\n const info = await detectStackInfo(projectRoot)\n return info.stack\n}\n\n/** Rich detection: stack + platform + monorepo + features */\nexport async function detectStackInfo(projectRoot: string): Promise<StackInfo> {\n let stack = await detectFromDeps(projectRoot)\n\n // Walk up for monorepo root if local didn't match\n if (stack === 'other') {\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (pkg?.workspaces) {\n const rootStack = await detectFromDeps(dir)\n if (rootStack !== 'other') {\n stack = rootStack\n break\n }\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n }\n\n // Non-JS detection if still 'other'\n if (stack === 'other') {\n stack = await detectNonJs(projectRoot)\n }\n\n const mono = await detectMonorepo(projectRoot)\n const features = await detectFeatures(projectRoot)\n const meta = STACK_META[stack]\n\n return {\n stack,\n platform: inferPlatform(stack),\n name: meta.name,\n description: meta.description,\n monorepo: mono.isMonorepo,\n monorepoTool: mono.tool,\n features,\n }\n}\n\nasync function detectFromDeps(dir: string): Promise<StackType> {\n const pkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (!pkg) return 'other'\n\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }\n\n // Meta-frameworks (check BEFORE plain frameworks)\n if ('nuxt' in allDeps) return 'nuxt'\n if ('next' in allDeps) return 'next'\n if ('astro' in allDeps) return 'astro'\n if ('@sveltejs/kit' in allDeps) return 'sveltekit'\n if ('@remix-run/node' in allDeps || 'remix' in allDeps) return 'remix'\n if ('@analogjs/platform' in allDeps) return 'analog'\n\n // Mobile (check before plain react)\n if ('expo' in allDeps) return 'expo'\n if ('react-native' in allDeps) return 'react-native'\n\n // Plain frameworks\n if ('vue' in allDeps) return 'vue'\n if ('svelte' in allDeps) return 'svelte'\n if ('solid-js' in allDeps) return 'solid'\n if ('@angular/core' in allDeps) return 'angular'\n if ('react' in allDeps) return 'react'\n\n // Desktop\n if ('electron' in allDeps) return 'electron'\n if ('@tauri-apps/api' in allDeps) return 'tauri'\n\n // Backend (Node.js)\n if ('@nestjs/core' in allDeps) return 'nestjs'\n if ('fastify' in allDeps) return 'fastify'\n if ('express' in allDeps) return 'express'\n\n // Static site generators\n if ('@11ty/eleventy' in allDeps) return 'eleventy'\n\n // Generic Node.js (has package.json but no framework)\n if ('node' in (pkg.dependencies ?? {})) return 'node'\n\n return 'other'\n}\n\nasync function detectNonJs(projectRoot: string): Promise<StackType> {\n // Go\n if (await pathExists(join(projectRoot, 'go.mod'))) return 'go'\n\n // Rust\n if (await pathExists(join(projectRoot, 'Cargo.toml'))) return 'rust'\n\n // Python\n for (const f of ['requirements.txt', 'pyproject.toml', 'setup.py']) {\n if (await pathExists(join(projectRoot, f))) {\n // Check for Django\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, f))\n if (content?.includes('django') || content?.includes('Django')) return 'django'\n } catch { /* ignore */ }\n return 'other' // Generic Python — no specific stack type yet\n }\n }\n\n // Ruby\n if (await pathExists(join(projectRoot, 'Gemfile'))) {\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, 'Gemfile'))\n if (content?.includes('rails')) return 'rails'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // .NET\n const dotnetFiles = ['*.csproj', '*.sln']\n for (const pattern of dotnetFiles) {\n if (pattern === '*.csproj' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n if (pattern === '*.sln' && await pathExists(join(projectRoot, 'Program.cs'))) return 'dotnet'\n }\n\n // PHP / Laravel\n if (await pathExists(join(projectRoot, 'composer.json'))) {\n try {\n const composer = await readJson<Record<string, unknown>>(join(projectRoot, 'composer.json'))\n const require = composer?.require as Record<string, string> | undefined\n if (require?.['laravel/framework']) return 'laravel'\n } catch { /* ignore */ }\n return 'other'\n }\n\n // Flutter (Dart)\n if (await pathExists(join(projectRoot, 'pubspec.yaml'))) return 'flutter'\n\n // Hugo\n if (await pathExists(join(projectRoot, 'hugo.toml'))) return 'hugo'\n if (await pathExists(join(projectRoot, 'config.toml'))) {\n try {\n const { readText } = await import('./fs.js')\n const content = await readText(join(projectRoot, 'config.toml'))\n if (content?.includes('baseURL') || content?.includes('hugo')) return 'hugo'\n } catch { /* ignore */ }\n }\n\n // Jekyll\n if (await pathExists(join(projectRoot, '_config.yml'))) return 'jekyll'\n\n return 'other'\n}\n\nasync function detectMonorepo(projectRoot: string): Promise<{ isMonorepo: boolean; tool?: string }> {\n if (await pathExists(join(projectRoot, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(projectRoot, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(projectRoot, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(projectRoot, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n if (pkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n\n // Walk up\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n if (await pathExists(join(dir, 'pnpm-workspace.yaml'))) return { isMonorepo: true, tool: 'pnpm workspaces' }\n if (await pathExists(join(dir, 'lerna.json'))) return { isMonorepo: true, tool: 'Lerna' }\n if (await pathExists(join(dir, 'nx.json'))) return { isMonorepo: true, tool: 'Nx' }\n if (await pathExists(join(dir, 'turbo.json'))) return { isMonorepo: true, tool: 'Turborepo' }\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) return { isMonorepo: true, tool: 'npm/yarn workspaces' }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n return { isMonorepo: false }\n}\n\nasync function detectFeatures(projectRoot: string): Promise<string[]> {\n const features: string[] = []\n const allDeps = await collectAllDeps(projectRoot)\n\n if ('typescript' in allDeps || await pathExists(join(projectRoot, 'tsconfig.json'))) features.push('TypeScript')\n\n const i18nLibs = ['vue-i18n', '@nuxtjs/i18n', 'next-intl', 'react-intl', 'i18next', 'react-i18next']\n for (const lib of i18nLibs) {\n if (lib in allDeps) { features.push(`i18n (${lib})`); break }\n }\n\n if ('tailwindcss' in allDeps) features.push('Tailwind CSS')\n else if ('sass' in allDeps || 'scss' in allDeps) features.push('Sass/SCSS')\n\n if ('vitest' in allDeps) features.push('Vitest')\n else if ('jest' in allDeps) features.push('Jest')\n\n if ('pinia' in allDeps) features.push('Pinia')\n else if ('zustand' in allDeps) features.push('Zustand')\n else if ('@reduxjs/toolkit' in allDeps || 'redux' in allDeps) features.push('Redux')\n\n return features\n}\n\nasync function collectAllDeps(projectRoot: string): Promise<Record<string, string>> {\n const pkg = await readJson<PackageJson>(join(projectRoot, 'package.json'))\n const allDeps: Record<string, string> = { ...pkg?.dependencies, ...pkg?.devDependencies }\n\n let dir = dirname(projectRoot)\n for (let i = 0; i < 5; i++) {\n const parentPkg = await readJson<PackageJson>(join(dir, 'package.json'))\n if (parentPkg?.workspaces) {\n Object.assign(allDeps, parentPkg.dependencies, parentPkg.devDependencies)\n break\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n\n return allDeps\n}\n"],"mappings":"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"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
11084594
0.08%109699
0.08%12
-7.69%34
3.03%