@contentrain/mcp 1.1.0 → 1.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/README.md +7 -0
- package/dist/{apply-manager-CMAoS_nu.mjs → apply-manager-Cf5OWOJ7.mjs} +7 -7
- package/dist/{apply-manager-CMAoS_nu.mjs.map → apply-manager-Cf5OWOJ7.mjs.map} +1 -1
- package/dist/{branch-lifecycle-B7TgkxGr.mjs → branch-lifecycle-wGJg25LT.mjs} +3 -3
- package/dist/{branch-lifecycle-B7TgkxGr.mjs.map → branch-lifecycle-wGJg25LT.mjs.map} +1 -1
- package/dist/{config-CPlhDwsv.mjs → config-B5kc2oeV.mjs} +2 -2
- package/dist/{config-CPlhDwsv.mjs.map → config-B5kc2oeV.mjs.map} +1 -1
- package/dist/{context-CUFulSi2.mjs → context-DIDt_TxX.mjs} +4 -4
- package/dist/{context-CUFulSi2.mjs.map → context-DIDt_TxX.mjs.map} +1 -1
- package/dist/core/apply-manager.mjs +10 -5
- package/dist/core/config.mjs +3 -1
- package/dist/core/content-manager.d.mts +1 -9
- package/dist/core/content-manager.d.mts.map +1 -1
- package/dist/core/content-manager.mjs +4 -1
- package/dist/core/context.mjs +6 -2
- package/dist/core/graph-builder.mjs +4 -1
- package/dist/core/meta-manager.mjs +3 -1
- package/dist/core/model-manager.mjs +4 -1
- package/dist/core/scan-config.mjs +3 -1
- package/dist/core/scanner.mjs +4 -1
- package/dist/core/validator.d.mts.map +1 -1
- package/dist/core/validator.mjs +6 -2
- package/dist/{detect-C8XmJ4S6.mjs → detect-DinOQAmK.mjs} +2 -2
- package/dist/{detect-C8XmJ4S6.mjs.map → detect-DinOQAmK.mjs.map} +1 -1
- package/dist/{fs-BbuGnVcG.mjs → fs-BnIQ4dKj.mjs} +2 -2
- package/dist/{fs-BbuGnVcG.mjs.map → fs-BnIQ4dKj.mjs.map} +1 -1
- package/dist/git/branch-lifecycle.mjs +4 -1
- package/dist/git/transaction.mjs +8 -3
- package/dist/{graph-builder-fPrDDwnu.mjs → graph-builder-tM7zj-SX.mjs} +3 -3
- package/dist/{graph-builder-fPrDDwnu.mjs.map → graph-builder-tM7zj-SX.mjs.map} +1 -1
- package/dist/{id-BdD4HvMY.mjs → id-DV_T9Ic8.mjs} +3 -5
- package/dist/id-DV_T9Ic8.mjs.map +1 -0
- package/dist/index.mjs +16 -9
- package/dist/index.mjs.map +1 -1
- package/dist/{meta-manager-CECrfsKm.mjs → meta-manager--0IwAPCl.mjs} +2 -2
- package/dist/{meta-manager-CECrfsKm.mjs.map → meta-manager--0IwAPCl.mjs.map} +1 -1
- package/dist/{model-manager-kl78AAMg.mjs → model-manager-DW_fOGRM.mjs} +16 -186
- package/dist/model-manager-DW_fOGRM.mjs.map +1 -0
- package/dist/{scan-config-AJGxCdNd.mjs → scan-config-QgnJ1-52.mjs} +2 -2
- package/dist/{scan-config-AJGxCdNd.mjs.map → scan-config-QgnJ1-52.mjs.map} +1 -1
- package/dist/{scanner-BdTiv8sw.mjs → scanner-DPrbU-oK.mjs} +3 -3
- package/dist/{scanner-BdTiv8sw.mjs.map → scanner-DPrbU-oK.mjs.map} +1 -1
- package/dist/serializer-CnHTtmc1.mjs +2 -0
- package/dist/{server-CDv6JNRi.mjs → server-CjK63H6X.mjs} +16 -16
- package/dist/{server-CDv6JNRi.mjs.map → server-CjK63H6X.mjs.map} +1 -1
- package/dist/server.mjs +16 -9
- package/dist/templates/index.mjs +1 -1
- package/dist/{templates-3HWi-PTu.mjs → templates-bRPzYq4M.mjs} +1 -1
- package/dist/{templates-3HWi-PTu.mjs.map → templates-bRPzYq4M.mjs.map} +1 -1
- package/dist/{transaction-CncRbpI9.mjs → transaction-B0V0_SE2.mjs} +6 -6
- package/dist/{transaction-CncRbpI9.mjs.map → transaction-B0V0_SE2.mjs.map} +1 -1
- package/dist/util/detect.mjs +3 -1
- package/dist/util/fs.mjs +2 -1
- package/dist/util/id.d.mts +2 -1
- package/dist/util/id.d.mts.map +1 -1
- package/dist/util/id.mjs +1 -1
- package/dist/util/serializer.d.mts +2 -14
- package/dist/util/serializer.mjs +2 -2
- package/dist/{validator-CyckZAVr.mjs → validator-SQYUV2JP.mjs} +23 -197
- package/dist/validator-SQYUV2JP.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/id-BdD4HvMY.mjs.map +0 -1
- package/dist/model-manager-kl78AAMg.mjs.map +0 -1
- package/dist/serializer-Dueih-i2.mjs +0 -33
- package/dist/serializer-Dueih-i2.mjs.map +0 -1
- package/dist/util/serializer.d.mts.map +0 -1
- package/dist/validator-CyckZAVr.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-C8XmJ4S6.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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"}
|
|
1
|
+
{"version":3,"file":"detect-DinOQAmK.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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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as canonicalStringify } from "./serializer-
|
|
1
|
+
import { t as canonicalStringify } from "./serializer-CnHTtmc1.mjs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
//#region src/util/fs.ts
|
|
@@ -49,4 +49,4 @@ function contentrainDir(projectRoot) {
|
|
|
49
49
|
//#endregion
|
|
50
50
|
export { readJson as a, writeText as c, readDir as i, ensureDir as n, readText as o, pathExists as r, writeJson as s, contentrainDir as t };
|
|
51
51
|
|
|
52
|
-
//# sourceMappingURL=fs-
|
|
52
|
+
//# sourceMappingURL=fs-BnIQ4dKj.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-
|
|
1
|
+
{"version":3,"file":"fs-BnIQ4dKj.mjs","names":[],"sources":["../src/util/fs.ts"],"sourcesContent":["import { readFile, readdir, access, writeFile, mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { canonicalStringify } from './serializer.js'\n\nexport async function pathExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath)\n return true\n } catch {\n return false\n }\n}\n\nexport async function readJson<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await readFile(filePath, 'utf-8')\n return JSON.parse(raw) as T\n } catch {\n return null\n }\n}\n\nexport async function readDir(dirPath: string): Promise<string[]> {\n try {\n return await readdir(dirPath)\n } catch {\n return []\n }\n}\n\nexport async function writeJson(filePath: string, data: unknown, fieldOrder?: string[]): Promise<void> {\n await mkdir(join(filePath, '..'), { recursive: true })\n await writeFile(filePath, canonicalStringify(data, fieldOrder), 'utf-8')\n}\n\nexport async function ensureDir(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true })\n}\n\nexport async function readText(filePath: string): Promise<string | null> {\n try {\n return await readFile(filePath, 'utf-8')\n } catch {\n return null\n }\n}\n\nexport async function writeText(filePath: string, content: string): Promise<void> {\n await mkdir(join(filePath, '..'), { recursive: true })\n await writeFile(filePath, content, 'utf-8')\n}\n\nexport function contentrainDir(projectRoot: string): string {\n return join(projectRoot, '.contentrain')\n}\n"],"mappings":";;;;AAIA,eAAsB,WAAW,UAAoC;AACnE,KAAI;AACF,QAAM,OAAO,SAAS;AACtB,SAAO;SACD;AACN,SAAO;;;AAIX,eAAsB,SAAY,UAAqC;AACrE,KAAI;EACF,MAAM,MAAM,MAAM,SAAS,UAAU,QAAQ;AAC7C,SAAO,KAAK,MAAM,IAAI;SAChB;AACN,SAAO;;;AAIX,eAAsB,QAAQ,SAAoC;AAChE,KAAI;AACF,SAAO,MAAM,QAAQ,QAAQ;SACvB;AACN,SAAO,EAAE;;;AAIb,eAAsB,UAAU,UAAkB,MAAe,YAAsC;AACrG,OAAM,MAAM,KAAK,UAAU,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AACtD,OAAM,UAAU,UAAU,mBAAmB,MAAM,WAAW,EAAE,QAAQ;;AAG1E,eAAsB,UAAU,SAAgC;AAC9D,OAAM,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;;AAG3C,eAAsB,SAAS,UAA0C;AACvE,KAAI;AACF,SAAO,MAAM,SAAS,UAAU,QAAQ;SAClC;AACN,SAAO;;;AAIX,eAAsB,UAAU,UAAkB,SAAgC;AAChF,OAAM,MAAM,KAAK,UAAU,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AACtD,OAAM,UAAU,UAAU,SAAS,QAAQ;;AAG7C,SAAgB,eAAe,aAA6B;AAC1D,QAAO,KAAK,aAAa,eAAe"}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../serializer-CnHTtmc1.mjs";
|
|
2
|
+
import "../fs-BnIQ4dKj.mjs";
|
|
3
|
+
import "../config-B5kc2oeV.mjs";
|
|
4
|
+
import { n as cleanupMergedBranches, t as checkBranchHealth } from "../branch-lifecycle-wGJg25LT.mjs";
|
|
2
5
|
export { checkBranchHealth, cleanupMergedBranches };
|
package/dist/git/transaction.mjs
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import "../
|
|
3
|
-
import
|
|
1
|
+
import "../serializer-CnHTtmc1.mjs";
|
|
2
|
+
import "../fs-BnIQ4dKj.mjs";
|
|
3
|
+
import "../config-B5kc2oeV.mjs";
|
|
4
|
+
import "../meta-manager--0IwAPCl.mjs";
|
|
5
|
+
import "../model-manager-DW_fOGRM.mjs";
|
|
6
|
+
import "../context-DIDt_TxX.mjs";
|
|
7
|
+
import "../id-DV_T9Ic8.mjs";
|
|
8
|
+
import { n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "../transaction-B0V0_SE2.mjs";
|
|
4
9
|
export { buildBranchName, createTransaction, ensureContentBranch };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as readText, r as pathExists } from "./fs-
|
|
2
|
-
import { a as classifyFile, i as autoDetectSourceDirs, n as SCAN_EXTENSIONS, o as discoverFiles } from "./scan-config-
|
|
1
|
+
import { o as readText, r as pathExists } from "./fs-BnIQ4dKj.mjs";
|
|
2
|
+
import { a as classifyFile, i as autoDetectSourceDirs, n as SCAN_EXTENSIONS, o as discoverFiles } from "./scan-config-QgnJ1-52.mjs";
|
|
3
3
|
import { dirname, extname, join, relative } from "node:path";
|
|
4
4
|
//#region src/core/graph-builder.ts
|
|
5
5
|
const MAX_ORPHANS = 10;
|
|
@@ -159,4 +159,4 @@ async function buildGraph(projectRoot, options) {
|
|
|
159
159
|
//#endregion
|
|
160
160
|
export { buildGraph as t };
|
|
161
161
|
|
|
162
|
-
//# sourceMappingURL=graph-builder-
|
|
162
|
+
//# sourceMappingURL=graph-builder-tM7zj-SX.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-builder-fPrDDwnu.mjs","names":[],"sources":["../src/core/graph-builder.ts"],"sourcesContent":["import type { FileCategory, GraphNode, ProjectGraph } from '@contentrain/types'\nimport { join, relative, dirname, extname } from 'node:path'\nimport { readText, pathExists } from '../util/fs.js'\nimport {\n SCAN_EXTENSIONS,\n classifyFile,\n autoDetectSourceDirs,\n discoverFiles,\n} from './scan-config.js'\n\nexport interface BuildGraphOptions {\n paths?: string[]\n include?: string[]\n exclude?: string[]\n}\n\nconst MAX_ORPHANS = 10\n\n// ---------------------------------------------------------------------------\n// Import extraction\n// ---------------------------------------------------------------------------\n\nconst IMPORT_RE = /(?:import|export)\\s+.*?from\\s+['\"]([^'\"]+)['\"]|(?:import|require)\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g\n\nfunction extractImportPaths(content: string): string[] {\n const paths: string[] = []\n let m: RegExpExecArray | null\n // Reset lastIndex before use since we reuse the regex\n IMPORT_RE.lastIndex = 0\n while ((m = IMPORT_RE.exec(content)) !== null) {\n const p = m[1] ?? m[2]\n if (p && isLocalImport(p)) {\n paths.push(p)\n }\n }\n return paths\n}\n\nfunction isLocalImport(p: string): boolean {\n return p.startsWith('.') || p.startsWith('/')\n}\n\n// ---------------------------------------------------------------------------\n// Component name extraction from imports\n// ---------------------------------------------------------------------------\n\nconst NAMED_IMPORT_RE = /import\\s+(?:type\\s+)?(?:(\\w+)(?:\\s*,\\s*)?)?(?:\\{([^}]*)\\})?\\s+from\\s+['\"][^'\"]+['\"]/g\nconst DEFAULT_IMPORT_RE = /import\\s+(?:type\\s+)?(\\w+)\\s+from\\s+['\"][^'\"]+['\"]/g\n\nfunction isPascalCase(name: string): boolean {\n return /^[A-Z][a-zA-Z0-9]*$/.test(name)\n}\n\nfunction extractComponentNames(content: string): string[] {\n const names = new Set<string>()\n\n // Default imports: `import Button from '...'`\n DEFAULT_IMPORT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = DEFAULT_IMPORT_RE.exec(content)) !== null) {\n const name = m[1]\n if (name && isPascalCase(name)) {\n names.add(name)\n }\n }\n\n // Named imports: `import { Button, useHook } from '...'`\n NAMED_IMPORT_RE.lastIndex = 0\n while ((m = NAMED_IMPORT_RE.exec(content)) !== null) {\n // Default part before destructuring\n const defaultName = m[1]\n if (defaultName && isPascalCase(defaultName)) {\n names.add(defaultName)\n }\n // Destructured names\n const namedPart = m[2]\n if (namedPart) {\n for (const segment of namedPart.split(',')) {\n // Handle `Foo as Bar` — take the local name (Bar)\n const parts = segment.trim().split(/\\s+as\\s+/)\n const localName = (parts[1] ?? parts[0])?.trim()\n if (localName && isPascalCase(localName)) {\n names.add(localName)\n }\n }\n }\n }\n\n return [...names]\n}\n\n// ---------------------------------------------------------------------------\n// String counting (estimate)\n// ---------------------------------------------------------------------------\n\nconst STRING_SINGLE_RE = /'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'/g\nconst STRING_DOUBLE_RE = /\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"/g\nconst STRING_TEMPLATE_RE = /`[^`\\\\]*(?:\\\\.[^`\\\\]*)*`/g\n\nfunction countStrings(content: string): number {\n // Strip import/export/require lines first so their string literals don't count\n const stripped = content.replace(/^(?:import|export)\\s+.*$/gm, '')\n .replace(/\\brequire\\s*\\([^)]*\\)/g, '')\n\n const singles = stripped.match(STRING_SINGLE_RE)?.length ?? 0\n const doubles = stripped.match(STRING_DOUBLE_RE)?.length ?? 0\n const templates = stripped.match(STRING_TEMPLATE_RE)?.length ?? 0\n\n return singles + doubles + templates\n}\n\n// ---------------------------------------------------------------------------\n// Import path resolution\n// ---------------------------------------------------------------------------\n\nconst RESOLVE_EXTENSIONS = [...SCAN_EXTENSIONS]\nconst RESOLVE_INDEX_FILES = ['index.ts', 'index.tsx', 'index.js']\n\nasync function resolveImportPath(\n importPath: string,\n importerDir: string,\n projectRoot: string,\n): Promise<string | null> {\n const base = importPath.startsWith('/')\n ? join(projectRoot, importPath)\n : join(importerDir, importPath)\n\n // If it already has an extension, check directly\n if (extname(base)) {\n if (await pathExists(base)) {\n return base\n }\n return null\n }\n\n // Try adding extensions\n for (const ext of RESOLVE_EXTENSIONS) {\n const candidate = base + ext\n if (await pathExists(candidate)) {\n return candidate\n }\n }\n\n // Try index files inside directory\n for (const idx of RESOLVE_INDEX_FILES) {\n const candidate = join(base, idx)\n if (await pathExists(candidate)) {\n return candidate\n }\n }\n\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\ninterface FileInfo {\n relPath: string\n imports: string[]\n components: string[]\n strings: number\n category: FileCategory\n}\n\nexport async function buildGraph(\n projectRoot: string,\n options?: BuildGraphOptions,\n): Promise<ProjectGraph> {\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n\n // ---- File discovery (shared with scanner) ----\n const filePaths = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // ---- Pass 1: Parse each file ----\n const fileMap = new Map<string, FileInfo>()\n\n const parsePromises = filePaths.map(async (relPath) => {\n const absPath = join(projectRoot, relPath)\n const content = await readText(absPath)\n if (content === null) return\n\n const imports = extractImportPaths(content)\n const components = extractComponentNames(content)\n const strings = countStrings(content)\n const category = classifyFile(relPath)\n\n fileMap.set(relPath, { relPath, imports, components, strings, category })\n })\n\n await Promise.all(parsePromises)\n\n // ---- Pass 2: Resolve imports and build reverse lookup ----\n const usedByMap = new Map<string, Set<string>>()\n\n // Initialize usedBy sets\n for (const relPath of fileMap.keys()) {\n usedByMap.set(relPath, new Set())\n }\n\n const resolvePromises: Promise<void>[] = []\n\n for (const [relPath, info] of fileMap) {\n const importerAbsDir = dirname(join(projectRoot, relPath))\n\n for (const rawImport of info.imports) {\n resolvePromises.push(\n resolveImportPath(rawImport, importerAbsDir, projectRoot).then((resolved) => {\n if (!resolved) return\n const resolvedRel = relative(projectRoot, resolved)\n const targetSet = usedByMap.get(resolvedRel)\n if (targetSet) {\n targetSet.add(relPath)\n }\n }),\n )\n }\n }\n\n await Promise.all(resolvePromises)\n\n // ---- Build nodes ----\n const pages: GraphNode[] = []\n const components: GraphNode[] = []\n const layouts: GraphNode[] = []\n const orphanCandidates: string[] = []\n\n let totalStrings = 0\n\n for (const [relPath, info] of fileMap) {\n const usedBy = [...(usedByMap.get(relPath) ?? [])].toSorted((a, b) => a.localeCompare(b))\n totalStrings += info.strings\n\n // Filter out nodes with 0 strings — not relevant for content extraction\n if (info.strings === 0 && info.category !== 'other') continue\n\n const node: GraphNode = {\n file: relPath,\n category: info.category,\n imports: info.imports,\n used_by: usedBy,\n strings: info.strings,\n }\n\n // Only attach components list for pages\n if (info.category === 'page' && info.components.length > 0) {\n node.components = info.components\n }\n\n switch (info.category) {\n case 'page':\n pages.push(node)\n break\n case 'component':\n components.push(node)\n break\n case 'layout':\n layouts.push(node)\n break\n default:\n // Orphan = \"other\" category with no inbound references and no outbound imports\n if (usedBy.length === 0 && info.imports.length === 0) {\n orphanCandidates.push(relPath)\n }\n break\n }\n }\n\n return {\n pages: pages.toSorted((a, b) => a.file.localeCompare(b.file)),\n components: components.toSorted((a, b) => a.file.localeCompare(b.file)),\n layouts: layouts.toSorted((a, b) => a.file.localeCompare(b.file)),\n orphan_files: orphanCandidates.toSorted((a, b) => a.localeCompare(b)).slice(0, MAX_ORPHANS),\n stats: {\n total_files: fileMap.size,\n total_components: components.length,\n total_pages: pages.length,\n total_strings_estimate: totalStrings,\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"graph-builder-tM7zj-SX.mjs","names":[],"sources":["../src/core/graph-builder.ts"],"sourcesContent":["import type { FileCategory, GraphNode, ProjectGraph } from '@contentrain/types'\nimport { join, relative, dirname, extname } from 'node:path'\nimport { readText, pathExists } from '../util/fs.js'\nimport {\n SCAN_EXTENSIONS,\n classifyFile,\n autoDetectSourceDirs,\n discoverFiles,\n} from './scan-config.js'\n\nexport interface BuildGraphOptions {\n paths?: string[]\n include?: string[]\n exclude?: string[]\n}\n\nconst MAX_ORPHANS = 10\n\n// ---------------------------------------------------------------------------\n// Import extraction\n// ---------------------------------------------------------------------------\n\nconst IMPORT_RE = /(?:import|export)\\s+.*?from\\s+['\"]([^'\"]+)['\"]|(?:import|require)\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g\n\nfunction extractImportPaths(content: string): string[] {\n const paths: string[] = []\n let m: RegExpExecArray | null\n // Reset lastIndex before use since we reuse the regex\n IMPORT_RE.lastIndex = 0\n while ((m = IMPORT_RE.exec(content)) !== null) {\n const p = m[1] ?? m[2]\n if (p && isLocalImport(p)) {\n paths.push(p)\n }\n }\n return paths\n}\n\nfunction isLocalImport(p: string): boolean {\n return p.startsWith('.') || p.startsWith('/')\n}\n\n// ---------------------------------------------------------------------------\n// Component name extraction from imports\n// ---------------------------------------------------------------------------\n\nconst NAMED_IMPORT_RE = /import\\s+(?:type\\s+)?(?:(\\w+)(?:\\s*,\\s*)?)?(?:\\{([^}]*)\\})?\\s+from\\s+['\"][^'\"]+['\"]/g\nconst DEFAULT_IMPORT_RE = /import\\s+(?:type\\s+)?(\\w+)\\s+from\\s+['\"][^'\"]+['\"]/g\n\nfunction isPascalCase(name: string): boolean {\n return /^[A-Z][a-zA-Z0-9]*$/.test(name)\n}\n\nfunction extractComponentNames(content: string): string[] {\n const names = new Set<string>()\n\n // Default imports: `import Button from '...'`\n DEFAULT_IMPORT_RE.lastIndex = 0\n let m: RegExpExecArray | null\n while ((m = DEFAULT_IMPORT_RE.exec(content)) !== null) {\n const name = m[1]\n if (name && isPascalCase(name)) {\n names.add(name)\n }\n }\n\n // Named imports: `import { Button, useHook } from '...'`\n NAMED_IMPORT_RE.lastIndex = 0\n while ((m = NAMED_IMPORT_RE.exec(content)) !== null) {\n // Default part before destructuring\n const defaultName = m[1]\n if (defaultName && isPascalCase(defaultName)) {\n names.add(defaultName)\n }\n // Destructured names\n const namedPart = m[2]\n if (namedPart) {\n for (const segment of namedPart.split(',')) {\n // Handle `Foo as Bar` — take the local name (Bar)\n const parts = segment.trim().split(/\\s+as\\s+/)\n const localName = (parts[1] ?? parts[0])?.trim()\n if (localName && isPascalCase(localName)) {\n names.add(localName)\n }\n }\n }\n }\n\n return [...names]\n}\n\n// ---------------------------------------------------------------------------\n// String counting (estimate)\n// ---------------------------------------------------------------------------\n\nconst STRING_SINGLE_RE = /'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'/g\nconst STRING_DOUBLE_RE = /\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"/g\nconst STRING_TEMPLATE_RE = /`[^`\\\\]*(?:\\\\.[^`\\\\]*)*`/g\n\nfunction countStrings(content: string): number {\n // Strip import/export/require lines first so their string literals don't count\n const stripped = content.replace(/^(?:import|export)\\s+.*$/gm, '')\n .replace(/\\brequire\\s*\\([^)]*\\)/g, '')\n\n const singles = stripped.match(STRING_SINGLE_RE)?.length ?? 0\n const doubles = stripped.match(STRING_DOUBLE_RE)?.length ?? 0\n const templates = stripped.match(STRING_TEMPLATE_RE)?.length ?? 0\n\n return singles + doubles + templates\n}\n\n// ---------------------------------------------------------------------------\n// Import path resolution\n// ---------------------------------------------------------------------------\n\nconst RESOLVE_EXTENSIONS = [...SCAN_EXTENSIONS]\nconst RESOLVE_INDEX_FILES = ['index.ts', 'index.tsx', 'index.js']\n\nasync function resolveImportPath(\n importPath: string,\n importerDir: string,\n projectRoot: string,\n): Promise<string | null> {\n const base = importPath.startsWith('/')\n ? join(projectRoot, importPath)\n : join(importerDir, importPath)\n\n // If it already has an extension, check directly\n if (extname(base)) {\n if (await pathExists(base)) {\n return base\n }\n return null\n }\n\n // Try adding extensions\n for (const ext of RESOLVE_EXTENSIONS) {\n const candidate = base + ext\n if (await pathExists(candidate)) {\n return candidate\n }\n }\n\n // Try index files inside directory\n for (const idx of RESOLVE_INDEX_FILES) {\n const candidate = join(base, idx)\n if (await pathExists(candidate)) {\n return candidate\n }\n }\n\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\ninterface FileInfo {\n relPath: string\n imports: string[]\n components: string[]\n strings: number\n category: FileCategory\n}\n\nexport async function buildGraph(\n projectRoot: string,\n options?: BuildGraphOptions,\n): Promise<ProjectGraph> {\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n\n // ---- File discovery (shared with scanner) ----\n const filePaths = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // ---- Pass 1: Parse each file ----\n const fileMap = new Map<string, FileInfo>()\n\n const parsePromises = filePaths.map(async (relPath) => {\n const absPath = join(projectRoot, relPath)\n const content = await readText(absPath)\n if (content === null) return\n\n const imports = extractImportPaths(content)\n const components = extractComponentNames(content)\n const strings = countStrings(content)\n const category = classifyFile(relPath)\n\n fileMap.set(relPath, { relPath, imports, components, strings, category })\n })\n\n await Promise.all(parsePromises)\n\n // ---- Pass 2: Resolve imports and build reverse lookup ----\n const usedByMap = new Map<string, Set<string>>()\n\n // Initialize usedBy sets\n for (const relPath of fileMap.keys()) {\n usedByMap.set(relPath, new Set())\n }\n\n const resolvePromises: Promise<void>[] = []\n\n for (const [relPath, info] of fileMap) {\n const importerAbsDir = dirname(join(projectRoot, relPath))\n\n for (const rawImport of info.imports) {\n resolvePromises.push(\n resolveImportPath(rawImport, importerAbsDir, projectRoot).then((resolved) => {\n if (!resolved) return\n const resolvedRel = relative(projectRoot, resolved)\n const targetSet = usedByMap.get(resolvedRel)\n if (targetSet) {\n targetSet.add(relPath)\n }\n }),\n )\n }\n }\n\n await Promise.all(resolvePromises)\n\n // ---- Build nodes ----\n const pages: GraphNode[] = []\n const components: GraphNode[] = []\n const layouts: GraphNode[] = []\n const orphanCandidates: string[] = []\n\n let totalStrings = 0\n\n for (const [relPath, info] of fileMap) {\n const usedBy = [...(usedByMap.get(relPath) ?? [])].toSorted((a, b) => a.localeCompare(b))\n totalStrings += info.strings\n\n // Filter out nodes with 0 strings — not relevant for content extraction\n if (info.strings === 0 && info.category !== 'other') continue\n\n const node: GraphNode = {\n file: relPath,\n category: info.category,\n imports: info.imports,\n used_by: usedBy,\n strings: info.strings,\n }\n\n // Only attach components list for pages\n if (info.category === 'page' && info.components.length > 0) {\n node.components = info.components\n }\n\n switch (info.category) {\n case 'page':\n pages.push(node)\n break\n case 'component':\n components.push(node)\n break\n case 'layout':\n layouts.push(node)\n break\n default:\n // Orphan = \"other\" category with no inbound references and no outbound imports\n if (usedBy.length === 0 && info.imports.length === 0) {\n orphanCandidates.push(relPath)\n }\n break\n }\n }\n\n return {\n pages: pages.toSorted((a, b) => a.file.localeCompare(b.file)),\n components: components.toSorted((a, b) => a.file.localeCompare(b.file)),\n layouts: layouts.toSorted((a, b) => a.file.localeCompare(b.file)),\n orphan_files: orphanCandidates.toSorted((a, b) => a.localeCompare(b)).slice(0, MAX_ORPHANS),\n stats: {\n total_files: fileMap.size,\n total_components: components.length,\n total_pages: pages.length,\n total_strings_estimate: totalStrings,\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import { generateEntryId as generateEntryId$1 } from "@contentrain/types";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
//#region src/util/id.ts
|
|
3
4
|
function branchTimestamp() {
|
|
4
5
|
return `${Math.floor(Date.now() / 1e3).toString()}-${randomUUID().slice(0, 4)}`;
|
|
5
6
|
}
|
|
6
|
-
function generateEntryId() {
|
|
7
|
-
return randomUUID().replace(/-/g, "").slice(0, 12);
|
|
8
|
-
}
|
|
9
7
|
//#endregion
|
|
10
|
-
export { generateEntryId as n, branchTimestamp as t };
|
|
8
|
+
export { generateEntryId$1 as n, branchTimestamp as t };
|
|
11
9
|
|
|
12
|
-
//# sourceMappingURL=id-
|
|
10
|
+
//# sourceMappingURL=id-DV_T9Ic8.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-DV_T9Ic8.mjs","names":[],"sources":["../src/util/id.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nexport function branchTimestamp(): string {\n const ts = Math.floor(Date.now() / 1000).toString()\n const suffix = randomUUID().slice(0, 4)\n return `${ts}-${suffix}`\n}\n\n// generateEntryId is now exported from @contentrain/types\nexport { generateEntryId } from '@contentrain/types'\n"],"mappings":";;;AAEA,SAAgB,kBAA0B;AAGxC,QAAO,GAFI,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,CAAC,UAAU,CAEtC,GADE,YAAY,CAAC,MAAM,GAAG,EAAE"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./
|
|
3
|
-
import "./
|
|
4
|
-
import "./
|
|
5
|
-
import
|
|
6
|
-
import "./
|
|
7
|
-
import "./
|
|
8
|
-
import "./
|
|
9
|
-
import "./
|
|
2
|
+
import "./serializer-CnHTtmc1.mjs";
|
|
3
|
+
import "./fs-BnIQ4dKj.mjs";
|
|
4
|
+
import "./config-B5kc2oeV.mjs";
|
|
5
|
+
import "./meta-manager--0IwAPCl.mjs";
|
|
6
|
+
import "./model-manager-DW_fOGRM.mjs";
|
|
7
|
+
import "./context-DIDt_TxX.mjs";
|
|
8
|
+
import "./detect-DinOQAmK.mjs";
|
|
9
|
+
import "./branch-lifecycle-wGJg25LT.mjs";
|
|
10
|
+
import { t as createServer } from "./server-CjK63H6X.mjs";
|
|
11
|
+
import "./id-DV_T9Ic8.mjs";
|
|
12
|
+
import "./transaction-B0V0_SE2.mjs";
|
|
13
|
+
import "./validator-SQYUV2JP.mjs";
|
|
14
|
+
import "./scan-config-QgnJ1-52.mjs";
|
|
15
|
+
import "./graph-builder-tM7zj-SX.mjs";
|
|
16
|
+
import "./scanner-DPrbU-oK.mjs";
|
|
10
17
|
import "./tsx-parser-CYVyBWjn.mjs";
|
|
11
|
-
import "./apply-manager-
|
|
18
|
+
import "./apply-manager-Cf5OWOJ7.mjs";
|
|
12
19
|
import { resolve } from "node:path";
|
|
13
20
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
21
|
//#region src/index.ts
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { resolve } from 'node:path'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createServer } from './server.js'\n\nconst projectRoot = resolve(process.env['CONTENTRAIN_PROJECT_ROOT'] ?? process.cwd())\nconst server = createServer(projectRoot)\nconst transport = new StdioServerTransport()\n\nawait server.connect(transport)\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { resolve } from 'node:path'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createServer } from './server.js'\n\nconst projectRoot = resolve(process.env['CONTENTRAIN_PROJECT_ROOT'] ?? process.cwd())\nconst server = createServer(projectRoot)\nconst transport = new StdioServerTransport()\n\nawait server.connect(transport)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAMA,MAAM,SAAS,aADK,QAAQ,QAAQ,IAAI,+BAA+B,QAAQ,KAAK,CAAC,CAC7C;AACxC,MAAM,YAAY,IAAI,sBAAsB;AAE5C,MAAM,OAAO,QAAQ,UAAU"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as readJson, s as writeJson, t as contentrainDir } from "./fs-
|
|
1
|
+
import { a as readJson, s as writeJson, t as contentrainDir } from "./fs-BnIQ4dKj.mjs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
//#region src/core/meta-manager.ts
|
|
@@ -50,4 +50,4 @@ async function deleteMeta(projectRoot, model, opts) {
|
|
|
50
50
|
//#endregion
|
|
51
51
|
export { readMeta as n, writeMeta as r, deleteMeta as t };
|
|
52
52
|
|
|
53
|
-
//# sourceMappingURL=meta-manager
|
|
53
|
+
//# sourceMappingURL=meta-manager--0IwAPCl.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-manager
|
|
1
|
+
{"version":3,"file":"meta-manager--0IwAPCl.mjs","names":[],"sources":["../src/core/meta-manager.ts"],"sourcesContent":["import type { ModelDefinition, EntryMeta } from '@contentrain/types'\nimport { join } from 'node:path'\nimport { rm } from 'node:fs/promises'\nimport { contentrainDir, readJson, writeJson } from '../util/fs.js'\n\ninterface MetaOpts {\n locale?: string\n entryId?: string\n slug?: string\n}\n\nfunction metaPath(projectRoot: string, model: ModelDefinition, opts: MetaOpts): string {\n const base = join(contentrainDir(projectRoot), 'meta', model.id)\n\n if (model.kind === 'document' && opts.slug) {\n return join(base, opts.slug, `${opts.locale ?? 'en'}.json`)\n }\n\n return join(base, `${opts.locale ?? 'en'}.json`)\n}\n\nexport async function readMeta(\n projectRoot: string,\n model: ModelDefinition,\n opts: MetaOpts,\n): Promise<EntryMeta | Record<string, EntryMeta> | null> {\n return readJson(metaPath(projectRoot, model, opts))\n}\n\nexport async function writeMeta(\n projectRoot: string,\n model: ModelDefinition,\n opts: MetaOpts,\n meta: EntryMeta,\n): Promise<void> {\n const filePath = metaPath(projectRoot, model, opts)\n\n if (model.kind === 'collection' && opts.entryId) {\n const existing = await readJson<Record<string, EntryMeta>>(filePath) ?? {}\n existing[opts.entryId] = meta\n await writeJson(filePath, existing)\n } else {\n await writeJson(filePath, meta)\n }\n}\n\nexport async function deleteMeta(\n projectRoot: string,\n model: ModelDefinition,\n opts: MetaOpts,\n): Promise<void> {\n if (model.kind === 'collection' && opts.entryId) {\n const filePath = metaPath(projectRoot, model, opts)\n const existing = await readJson<Record<string, EntryMeta>>(filePath)\n if (existing && opts.entryId in existing) {\n delete existing[opts.entryId]\n if (Object.keys(existing).length > 0) {\n await writeJson(filePath, existing)\n } else {\n await rm(filePath, { force: true })\n }\n }\n } else if (model.kind === 'document' && opts.slug) {\n const slugDir = join(contentrainDir(projectRoot), 'meta', model.id, opts.slug)\n if (opts.locale) {\n await rm(join(slugDir, `${opts.locale}.json`), { force: true })\n } else {\n try {\n await rm(slugDir, { recursive: true, force: true })\n } catch {\n // directory may not exist\n }\n }\n } else if (opts.locale) {\n const filePath = metaPath(projectRoot, model, opts)\n await rm(filePath, { force: true })\n } else {\n // No locale specified — remove all meta for this model (non-i18n or full cleanup)\n const metaDir = join(contentrainDir(projectRoot), 'meta', model.id)\n try {\n await rm(metaDir, { recursive: true, force: true })\n } catch {\n // directory may not exist\n }\n }\n}\n"],"mappings":";;;;AAWA,SAAS,SAAS,aAAqB,OAAwB,MAAwB;CACrF,MAAM,OAAO,KAAK,eAAe,YAAY,EAAE,QAAQ,MAAM,GAAG;AAEhE,KAAI,MAAM,SAAS,cAAc,KAAK,KACpC,QAAO,KAAK,MAAM,KAAK,MAAM,GAAG,KAAK,UAAU,KAAK,OAAO;AAG7D,QAAO,KAAK,MAAM,GAAG,KAAK,UAAU,KAAK,OAAO;;AAGlD,eAAsB,SACpB,aACA,OACA,MACuD;AACvD,QAAO,SAAS,SAAS,aAAa,OAAO,KAAK,CAAC;;AAGrD,eAAsB,UACpB,aACA,OACA,MACA,MACe;CACf,MAAM,WAAW,SAAS,aAAa,OAAO,KAAK;AAEnD,KAAI,MAAM,SAAS,gBAAgB,KAAK,SAAS;EAC/C,MAAM,WAAW,MAAM,SAAoC,SAAS,IAAI,EAAE;AAC1E,WAAS,KAAK,WAAW;AACzB,QAAM,UAAU,UAAU,SAAS;OAEnC,OAAM,UAAU,UAAU,KAAK;;AAInC,eAAsB,WACpB,aACA,OACA,MACe;AACf,KAAI,MAAM,SAAS,gBAAgB,KAAK,SAAS;EAC/C,MAAM,WAAW,SAAS,aAAa,OAAO,KAAK;EACnD,MAAM,WAAW,MAAM,SAAoC,SAAS;AACpE,MAAI,YAAY,KAAK,WAAW,UAAU;AACxC,UAAO,SAAS,KAAK;AACrB,OAAI,OAAO,KAAK,SAAS,CAAC,SAAS,EACjC,OAAM,UAAU,UAAU,SAAS;OAEnC,OAAM,GAAG,UAAU,EAAE,OAAO,MAAM,CAAC;;YAG9B,MAAM,SAAS,cAAc,KAAK,MAAM;EACjD,MAAM,UAAU,KAAK,eAAe,YAAY,EAAE,QAAQ,MAAM,IAAI,KAAK,KAAK;AAC9E,MAAI,KAAK,OACP,OAAM,GAAG,KAAK,SAAS,GAAG,KAAK,OAAO,OAAO,EAAE,EAAE,OAAO,MAAM,CAAC;MAE/D,KAAI;AACF,SAAM,GAAG,SAAS;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;UAC7C;YAID,KAAK,OAEd,OAAM,GADW,SAAS,aAAa,OAAO,KAAK,EAChC,EAAE,OAAO,MAAM,CAAC;MAC9B;EAEL,MAAM,UAAU,KAAK,eAAe,YAAY,EAAE,QAAQ,MAAM,GAAG;AACnE,MAAI;AACF,SAAM,GAAG,SAAS;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;UAC7C"}
|
|
@@ -1,180 +1,10 @@
|
|
|
1
|
-
import { a as readJson, c as writeText, i as readDir, n as ensureDir, o as readText, s as writeJson, t as contentrainDir } from "./fs-
|
|
2
|
-
import {
|
|
3
|
-
import { r as writeMeta, t as deleteMeta } from "./meta-manager-CECrfsKm.mjs";
|
|
1
|
+
import { a as readJson, c as writeText, i as readDir, n as ensureDir, o as readText, s as writeJson, t as contentrainDir } from "./fs-BnIQ4dKj.mjs";
|
|
2
|
+
import { r as writeMeta, t as deleteMeta } from "./meta-manager--0IwAPCl.mjs";
|
|
4
3
|
import { join } from "node:path";
|
|
5
4
|
import { z } from "zod";
|
|
6
5
|
import { rm } from "node:fs/promises";
|
|
6
|
+
import { generateEntryId, parseMarkdownFrontmatter, parseMarkdownFrontmatter as parseFrontmatter, serializeMarkdownFrontmatter, serializeMarkdownFrontmatter as serializeFrontmatter, validateEntryId, validateLocale, validateSlug } from "@contentrain/types";
|
|
7
7
|
//#region src/core/content-manager.ts
|
|
8
|
-
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
|
-
const ENTRY_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,39}$/;
|
|
10
|
-
const LOCALE_RE = /^[a-z]{2}(?:-[A-Z]{2})?$/;
|
|
11
|
-
function validateSlug(slug) {
|
|
12
|
-
if (!slug) return "Slug is required";
|
|
13
|
-
if (!SLUG_RE.test(slug)) return `Invalid slug "${slug}": must be kebab-case alphanumeric (a-z, 0-9, hyphens)`;
|
|
14
|
-
if (slug.startsWith(".") || slug.includes("..")) return `Invalid slug "${slug}": path traversal not allowed`;
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
function validateEntryId(id) {
|
|
18
|
-
if (!ENTRY_ID_RE.test(id)) return `Invalid entry ID "${id}": must be 1-40 alphanumeric characters (hyphens and underscores allowed)`;
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
function validateLocale(locale, config) {
|
|
22
|
-
if (!LOCALE_RE.test(locale)) return `Invalid locale "${locale}": must be ISO 639-1 format (e.g. "en", "tr", "pt-BR")`;
|
|
23
|
-
if (!config.locales.supported.includes(locale)) return `Locale "${locale}" is not in supported locales [${config.locales.supported.join(", ")}]. Update config first.`;
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
function parseFrontmatter(content) {
|
|
27
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
28
|
-
const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
29
|
-
if (!match) return {
|
|
30
|
-
frontmatter: {},
|
|
31
|
-
body: normalized
|
|
32
|
-
};
|
|
33
|
-
const frontmatterStr = match[1];
|
|
34
|
-
const body = match[2].trim();
|
|
35
|
-
const frontmatter = {};
|
|
36
|
-
let currentKey = null;
|
|
37
|
-
let currentArray = null;
|
|
38
|
-
for (const line of frontmatterStr.split("\n")) {
|
|
39
|
-
if (/^\s+-\s+/.test(line) && currentKey) {
|
|
40
|
-
const value = line.replace(/^\s+-\s+/, "").trim();
|
|
41
|
-
if (!currentArray) currentArray = [];
|
|
42
|
-
currentArray.push(value);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (currentKey && currentArray) {
|
|
46
|
-
frontmatter[currentKey] = currentArray;
|
|
47
|
-
currentKey = null;
|
|
48
|
-
currentArray = null;
|
|
49
|
-
}
|
|
50
|
-
const kvMatch = line.match(/^([\w][\w.-]*)\s*:\s*(.*)$/);
|
|
51
|
-
if (!kvMatch) continue;
|
|
52
|
-
const key = kvMatch[1];
|
|
53
|
-
const rawValue = kvMatch[2].trim();
|
|
54
|
-
if (rawValue === "") {
|
|
55
|
-
currentKey = key;
|
|
56
|
-
currentArray = [];
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
|
|
60
|
-
frontmatter[key] = rawValue.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
frontmatter[key] = parseYamlValue(rawValue);
|
|
64
|
-
}
|
|
65
|
-
if (currentKey && currentArray) frontmatter[currentKey] = currentArray;
|
|
66
|
-
return {
|
|
67
|
-
frontmatter,
|
|
68
|
-
body
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function parseYamlValue(raw) {
|
|
72
|
-
if (raw === "true") return true;
|
|
73
|
-
if (raw === "false") return false;
|
|
74
|
-
if (raw === "null") return null;
|
|
75
|
-
if (/^-?\d+$/.test(raw)) return parseInt(raw, 10);
|
|
76
|
-
if (/^-?\d+\.\d+$/.test(raw)) return parseFloat(raw);
|
|
77
|
-
if (raw.startsWith("\"") && raw.endsWith("\"") || raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
|
|
78
|
-
return raw;
|
|
79
|
-
}
|
|
80
|
-
function yamlScalar(value) {
|
|
81
|
-
const str = String(value);
|
|
82
|
-
if (str.includes(":") || str.includes("#") || str.includes("{") || str.includes("}") || str.includes("[") || str.includes("]") || str.includes("*") || str.includes("&") || str.includes("!") || str.includes("|") || str.includes(">") || str.includes("'") || str.includes("\"") || str.includes("%") || str.includes("@") || str.includes("`") || str.startsWith("-") || str.startsWith("?") || str === "true" || str === "false" || str === "null" || str === "yes" || str === "no" || str === "") return `"${str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
83
|
-
return str;
|
|
84
|
-
}
|
|
85
|
-
function serializeYamlValue(value, indent) {
|
|
86
|
-
const pad = " ".repeat(indent);
|
|
87
|
-
if (value === null || value === void 0) return [];
|
|
88
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
89
|
-
const lines = [];
|
|
90
|
-
for (const [k, v] of Object.entries(value)) {
|
|
91
|
-
if (v === null || v === void 0) continue;
|
|
92
|
-
if (typeof v === "object" && !Array.isArray(v)) {
|
|
93
|
-
lines.push(`${pad}${k}:`);
|
|
94
|
-
lines.push(...serializeYamlValue(v, indent + 1));
|
|
95
|
-
} else if (Array.isArray(v)) {
|
|
96
|
-
lines.push(`${pad}${k}:`);
|
|
97
|
-
for (const item of v) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
98
|
-
const nested = serializeYamlValue(item, indent + 2);
|
|
99
|
-
if (nested.length > 0) {
|
|
100
|
-
lines.push(`${pad} - ${nested[0].trimStart()}`);
|
|
101
|
-
lines.push(...nested.slice(1));
|
|
102
|
-
}
|
|
103
|
-
} else lines.push(`${pad} - ${yamlScalar(item)}`);
|
|
104
|
-
} else lines.push(`${pad}${k}: ${yamlScalar(v)}`);
|
|
105
|
-
}
|
|
106
|
-
return lines;
|
|
107
|
-
}
|
|
108
|
-
if (Array.isArray(value)) {
|
|
109
|
-
const lines = [];
|
|
110
|
-
for (const item of value) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
111
|
-
const nested = serializeYamlValue(item, indent + 1);
|
|
112
|
-
if (nested.length > 0) {
|
|
113
|
-
lines.push(`${pad}- ${nested[0].trimStart()}`);
|
|
114
|
-
lines.push(...nested.slice(1));
|
|
115
|
-
}
|
|
116
|
-
} else lines.push(`${pad}- ${yamlScalar(item)}`);
|
|
117
|
-
return lines;
|
|
118
|
-
}
|
|
119
|
-
return [`${pad}${yamlScalar(value)}`];
|
|
120
|
-
}
|
|
121
|
-
function serializeYamlFields(data) {
|
|
122
|
-
const lines = [];
|
|
123
|
-
for (const [key, value] of Object.entries(data)) {
|
|
124
|
-
if (key === "body") continue;
|
|
125
|
-
if (value === null || value === void 0) continue;
|
|
126
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
127
|
-
lines.push(`${key}:`);
|
|
128
|
-
lines.push(...serializeYamlValue(value, 1));
|
|
129
|
-
} else if (Array.isArray(value)) {
|
|
130
|
-
lines.push(`${key}:`);
|
|
131
|
-
for (const item of value) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
132
|
-
const nested = serializeYamlValue(item, 2);
|
|
133
|
-
if (nested.length > 0) {
|
|
134
|
-
lines.push(` - ${nested[0].trimStart()}`);
|
|
135
|
-
lines.push(...nested.slice(1));
|
|
136
|
-
}
|
|
137
|
-
} else lines.push(` - ${yamlScalar(item)}`);
|
|
138
|
-
} else lines.push(`${key}: ${yamlScalar(value)}`);
|
|
139
|
-
}
|
|
140
|
-
return lines;
|
|
141
|
-
}
|
|
142
|
-
function serializeFrontmatter(data, body) {
|
|
143
|
-
const trimmedBody = body.trimStart();
|
|
144
|
-
if (trimmedBody.startsWith("---")) {
|
|
145
|
-
const endIdx = trimmedBody.indexOf("---", 3);
|
|
146
|
-
if (endIdx > 0) {
|
|
147
|
-
const bodyFmContent = trimmedBody.slice(3, endIdx).trim();
|
|
148
|
-
const afterFm = trimmedBody.slice(endIdx + 3);
|
|
149
|
-
const lines = ["---"];
|
|
150
|
-
lines.push(...serializeYamlFields(data));
|
|
151
|
-
if (bodyFmContent) {
|
|
152
|
-
const modelKeys = new Set(Object.keys(data));
|
|
153
|
-
for (const line of bodyFmContent.split("\n")) {
|
|
154
|
-
if (line.length > 0 && line[0] !== " " && line[0] !== "-") {
|
|
155
|
-
const colonIdx = line.indexOf(":");
|
|
156
|
-
const lineKey = colonIdx > 0 ? line.slice(0, colonIdx).trim() : "";
|
|
157
|
-
if (lineKey && modelKeys.has(lineKey)) continue;
|
|
158
|
-
}
|
|
159
|
-
lines.push(line);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
lines.push("---");
|
|
163
|
-
if (afterFm.trim()) lines.push(afterFm.trimStart());
|
|
164
|
-
lines.push("");
|
|
165
|
-
return lines.join("\n");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const lines = ["---"];
|
|
169
|
-
lines.push(...serializeYamlFields(data));
|
|
170
|
-
lines.push("---");
|
|
171
|
-
lines.push("");
|
|
172
|
-
if (body) {
|
|
173
|
-
lines.push(body);
|
|
174
|
-
lines.push("");
|
|
175
|
-
}
|
|
176
|
-
return lines.join("\n");
|
|
177
|
-
}
|
|
178
8
|
function resolveContentDir(projectRoot, model) {
|
|
179
9
|
if (model.content_path) return join(projectRoot, model.content_path);
|
|
180
10
|
return join(contentrainDir(projectRoot), "content", model.domain, model.id);
|
|
@@ -268,7 +98,7 @@ async function writeContent(projectRoot, model, entries, config) {
|
|
|
268
98
|
if (!fmData["slug"]) fmData["slug"] = slug;
|
|
269
99
|
const docPath = resolveMdFilePath(resolveContentDir(projectRoot, model), model, locale, slug);
|
|
270
100
|
const action = await readText(docPath) ? "updated" : "created";
|
|
271
|
-
await writeText(docPath,
|
|
101
|
+
await writeText(docPath, serializeMarkdownFrontmatter(fmData, bodyContent));
|
|
272
102
|
await writeMeta(projectRoot, model, {
|
|
273
103
|
locale,
|
|
274
104
|
slug
|
|
@@ -423,7 +253,7 @@ async function listContent(projectRoot, model, opts, config) {
|
|
|
423
253
|
const slug = f.replace(".md", "");
|
|
424
254
|
const raw = await readText(join(cDir, f));
|
|
425
255
|
if (!raw) continue;
|
|
426
|
-
const { frontmatter, body } =
|
|
256
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
427
257
|
entries.push({
|
|
428
258
|
slug,
|
|
429
259
|
frontmatter,
|
|
@@ -435,7 +265,7 @@ async function listContent(projectRoot, model, opts, config) {
|
|
|
435
265
|
for (const slug of slugDirs) {
|
|
436
266
|
const raw = await readText(join(cDir, slug, `${locale}.md`));
|
|
437
267
|
if (!raw) continue;
|
|
438
|
-
const { frontmatter, body } =
|
|
268
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
439
269
|
entries.push({
|
|
440
270
|
slug,
|
|
441
271
|
frontmatter,
|
|
@@ -450,7 +280,7 @@ async function listContent(projectRoot, model, opts, config) {
|
|
|
450
280
|
const slug = f.slice(0, -suffix.length);
|
|
451
281
|
const raw = await readText(join(cDir, f));
|
|
452
282
|
if (!raw) continue;
|
|
453
|
-
const { frontmatter, body } =
|
|
283
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
454
284
|
entries.push({
|
|
455
285
|
slug,
|
|
456
286
|
frontmatter,
|
|
@@ -465,7 +295,7 @@ async function listContent(projectRoot, model, opts, config) {
|
|
|
465
295
|
const slug = f.replace(".md", "");
|
|
466
296
|
const raw = await readText(join(localeDir, f));
|
|
467
297
|
if (!raw) continue;
|
|
468
|
-
const { frontmatter, body } =
|
|
298
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
469
299
|
entries.push({
|
|
470
300
|
slug,
|
|
471
301
|
frontmatter,
|
|
@@ -479,7 +309,7 @@ async function listContent(projectRoot, model, opts, config) {
|
|
|
479
309
|
const slug = f.replace(".md", "");
|
|
480
310
|
const raw = await readText(join(cDir, f));
|
|
481
311
|
if (!raw) continue;
|
|
482
|
-
const { frontmatter, body } =
|
|
312
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
483
313
|
entries.push({
|
|
484
314
|
slug,
|
|
485
315
|
frontmatter,
|
|
@@ -526,7 +356,7 @@ async function readContent(projectRoot, model, opts) {
|
|
|
526
356
|
if (!opts.slug) return null;
|
|
527
357
|
const raw = await readText(resolveMdFilePath(cDir, model, opts.locale, opts.slug));
|
|
528
358
|
if (!raw) return null;
|
|
529
|
-
const { frontmatter, body } =
|
|
359
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
530
360
|
return {
|
|
531
361
|
slug: opts.slug,
|
|
532
362
|
...frontmatter,
|
|
@@ -567,7 +397,7 @@ async function resolveRelations(projectRoot, model, entries, locale) {
|
|
|
567
397
|
const slug = f.replace(".md", "");
|
|
568
398
|
const raw = await readText(join(cDir, f));
|
|
569
399
|
if (!raw) continue;
|
|
570
|
-
const { frontmatter, body } =
|
|
400
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
571
401
|
docCache[slug] = {
|
|
572
402
|
slug,
|
|
573
403
|
...frontmatter,
|
|
@@ -579,7 +409,7 @@ async function resolveRelations(projectRoot, model, entries, locale) {
|
|
|
579
409
|
for (const slug of slugDirs) {
|
|
580
410
|
const raw = await readText(join(cDir, slug, `${locale}.md`));
|
|
581
411
|
if (!raw) continue;
|
|
582
|
-
const { frontmatter, body } =
|
|
412
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
583
413
|
docCache[slug] = {
|
|
584
414
|
slug,
|
|
585
415
|
...frontmatter,
|
|
@@ -594,7 +424,7 @@ async function resolveRelations(projectRoot, model, entries, locale) {
|
|
|
594
424
|
const slug = f.slice(0, -suffix.length);
|
|
595
425
|
const raw = await readText(join(cDir, f));
|
|
596
426
|
if (!raw) continue;
|
|
597
|
-
const { frontmatter, body } =
|
|
427
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
598
428
|
docCache[slug] = {
|
|
599
429
|
slug,
|
|
600
430
|
...frontmatter,
|
|
@@ -609,7 +439,7 @@ async function resolveRelations(projectRoot, model, entries, locale) {
|
|
|
609
439
|
const slug = f.replace(".md", "");
|
|
610
440
|
const raw = await readText(join(localeDir, f));
|
|
611
441
|
if (!raw) continue;
|
|
612
|
-
const { frontmatter, body } =
|
|
442
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
613
443
|
docCache[slug] = {
|
|
614
444
|
slug,
|
|
615
445
|
...frontmatter,
|
|
@@ -623,7 +453,7 @@ async function resolveRelations(projectRoot, model, entries, locale) {
|
|
|
623
453
|
const slug = f.replace(".md", "");
|
|
624
454
|
const raw = await readText(join(cDir, f));
|
|
625
455
|
if (!raw) continue;
|
|
626
|
-
const { frontmatter, body } =
|
|
456
|
+
const { frontmatter, body } = parseMarkdownFrontmatter(raw);
|
|
627
457
|
docCache[slug] = {
|
|
628
458
|
slug,
|
|
629
459
|
...frontmatter,
|
|
@@ -1013,4 +843,4 @@ function validateModelDefinition(input) {
|
|
|
1013
843
|
//#endregion
|
|
1014
844
|
export { writeContent as S, resolveMdFilePath as _, fieldDefZodSchema as a, validateLocale as b, validateModelDefinition as c, listContent as d, parseFrontmatter as f, resolveLocaleStrategy as g, resolveJsonFilePath as h, deleteModel as i, writeModel as l, resolveContentDir as m, checkReferences as n, listModels as o, readContent as p, countEntries as r, readModel as s, FIELD_TYPE_ENUM as t, deleteContent as u, serializeFrontmatter as v, validateSlug as x, validateEntryId as y };
|
|
1015
845
|
|
|
1016
|
-
//# sourceMappingURL=model-manager-
|
|
846
|
+
//# sourceMappingURL=model-manager-DW_fOGRM.mjs.map
|