@contentrain/mcp 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +147 -238
- package/dist/apply-manager-CaTuu7Lj.mjs +751 -0
- package/dist/apply-manager-CaTuu7Lj.mjs.map +1 -0
- package/dist/astro-parser-RofKmKQi.mjs +166 -0
- package/dist/astro-parser-RofKmKQi.mjs.map +1 -0
- package/dist/branch-lifecycle-DB6sgeJi.mjs +97 -0
- package/dist/branch-lifecycle-DB6sgeJi.mjs.map +1 -0
- package/dist/chunk-BEJ448es.mjs +35 -0
- package/dist/compiler-rIC4ETHv.mjs +44509 -0
- package/dist/compiler-rIC4ETHv.mjs.map +1 -0
- package/dist/compiler-sfc.cjs-BbD04rn_.mjs +50279 -0
- package/dist/compiler-sfc.cjs-BbD04rn_.mjs.map +1 -0
- package/dist/config-CPlhDwsv.mjs +27 -0
- package/dist/config-CPlhDwsv.mjs.map +1 -0
- package/dist/context-yh3cuw5N.mjs +46 -0
- package/dist/context-yh3cuw5N.mjs.map +1 -0
- package/dist/core/apply-manager.d.mts +144 -0
- package/dist/core/apply-manager.d.mts.map +1 -0
- package/dist/core/apply-manager.mjs +6 -0
- package/dist/core/config.d.mts +8 -0
- package/dist/core/config.d.mts.map +1 -0
- package/dist/core/config.mjs +2 -0
- package/dist/core/content-manager.d.mts +53 -0
- package/dist/core/content-manager.d.mts.map +1 -0
- package/dist/core/content-manager.mjs +2 -0
- package/dist/core/context.d.mts +13 -0
- package/dist/core/context.d.mts.map +1 -0
- package/dist/core/context.mjs +3 -0
- package/dist/core/graph-builder.d.mts +12 -0
- package/dist/core/graph-builder.d.mts.map +1 -0
- package/dist/core/graph-builder.mjs +2 -0
- package/dist/core/meta-manager.d.mts +14 -0
- package/dist/core/meta-manager.d.mts.map +1 -0
- package/dist/core/meta-manager.mjs +2 -0
- package/dist/core/model-manager.d.mts +44 -0
- package/dist/core/model-manager.d.mts.map +1 -0
- package/dist/core/model-manager.mjs +2 -0
- package/dist/core/scan-config.d.mts +24 -0
- package/dist/core/scan-config.d.mts.map +1 -0
- package/dist/core/scan-config.mjs +2 -0
- package/dist/core/scanner.d.mts +25 -0
- package/dist/core/scanner.d.mts.map +1 -0
- package/dist/core/scanner.mjs +3 -0
- package/dist/core/validator.d.mts +23 -0
- package/dist/core/validator.d.mts.map +1 -0
- package/dist/core/validator.mjs +3 -0
- package/dist/detect-C8XmJ4S6.mjs +362 -0
- package/dist/detect-C8XmJ4S6.mjs.map +1 -0
- package/dist/fs-BbuGnVcG.mjs +52 -0
- package/dist/fs-BbuGnVcG.mjs.map +1 -0
- package/dist/git/branch-lifecycle.d.mts +28 -0
- package/dist/git/branch-lifecycle.d.mts.map +1 -0
- package/dist/git/branch-lifecycle.mjs +2 -0
- package/dist/git/transaction.d.mts +25 -0
- package/dist/git/transaction.d.mts.map +1 -0
- package/dist/git/transaction.mjs +4 -0
- package/dist/graph-builder-fPrDDwnu.mjs +162 -0
- package/dist/graph-builder-fPrDDwnu.mjs.map +1 -0
- package/dist/id-BdD4HvMY.mjs +12 -0
- package/dist/id-BdD4HvMY.mjs.map +1 -0
- package/dist/index.d.mts +1 -359
- package/dist/index.mjs +15 -2161
- package/dist/index.mjs.map +1 -1
- package/dist/meta-manager-CECrfsKm.mjs +53 -0
- package/dist/meta-manager-CECrfsKm.mjs.map +1 -0
- package/dist/model-manager-BcWWu5l2.mjs +963 -0
- package/dist/model-manager-BcWWu5l2.mjs.map +1 -0
- package/dist/node-Cbq5eR5i.mjs +300 -0
- package/dist/node-Cbq5eR5i.mjs.map +1 -0
- package/dist/scan-config-AJGxCdNd.mjs +159 -0
- package/dist/scan-config-AJGxCdNd.mjs.map +1 -0
- package/dist/scanner-BdTiv8sw.mjs +619 -0
- package/dist/scanner-BdTiv8sw.mjs.map +1 -0
- package/dist/serializer-Dueih-i2.mjs +33 -0
- package/dist/serializer-Dueih-i2.mjs.map +1 -0
- package/dist/server-CTcDpxwW.mjs +1861 -0
- package/dist/server-CTcDpxwW.mjs.map +1 -0
- package/dist/server.d.mts +7 -0
- package/dist/server.d.mts.map +1 -0
- package/dist/server.mjs +11 -0
- package/dist/source-map-generator-DpO-ejrt.mjs +893 -0
- package/dist/source-map-generator-DpO-ejrt.mjs.map +1 -0
- package/dist/svelte-parser-COTNG92w.mjs +225 -0
- package/dist/svelte-parser-COTNG92w.mjs.map +1 -0
- package/dist/templates/index.d.mts +8 -0
- package/dist/templates/index.d.mts.map +1 -0
- package/dist/templates/index.mjs +2 -0
- package/dist/templates-3HWi-PTu.mjs +824 -0
- package/dist/templates-3HWi-PTu.mjs.map +1 -0
- package/dist/transaction-BsECnmL5.mjs +128 -0
- package/dist/transaction-BsECnmL5.mjs.map +1 -0
- package/dist/tsx-parser-Bf8Firrq.mjs +2 -0
- package/dist/tsx-parser-CYVyBWjn.mjs +234 -0
- package/dist/tsx-parser-CYVyBWjn.mjs.map +1 -0
- package/dist/util/detect.d.mts +20 -0
- package/dist/util/detect.d.mts.map +1 -0
- package/dist/util/detect.mjs +2 -0
- package/dist/util/fs.d.mts +12 -0
- package/dist/util/fs.d.mts.map +1 -0
- package/dist/util/fs.mjs +2 -0
- package/dist/util/id.d.mts +6 -0
- package/dist/util/id.d.mts.map +1 -0
- package/dist/util/id.mjs +2 -0
- package/dist/util/serializer.d.mts +14 -0
- package/dist/util/serializer.d.mts.map +1 -0
- package/dist/util/serializer.mjs +2 -0
- package/dist/validator-ku84AyFr.mjs +755 -0
- package/dist/validator-ku84AyFr.mjs.map +1 -0
- package/dist/vue-parser-CjS4YAoQ.mjs +225 -0
- package/dist/vue-parser-CjS4YAoQ.mjs.map +1 -0
- package/package.json +107 -43
- package/dist/fs-D83-Ii_y.mjs +0 -75
- package/dist/fs-D83-Ii_y.mjs.map +0 -1
- package/dist/index.d.mts.map +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { o as readText, r as pathExists } from "./fs-BbuGnVcG.mjs";
|
|
2
|
+
import { a as classifyFile, i as autoDetectSourceDirs, n as SCAN_EXTENSIONS, o as discoverFiles } from "./scan-config-AJGxCdNd.mjs";
|
|
3
|
+
import { dirname, extname, join, relative } from "node:path";
|
|
4
|
+
//#region src/core/graph-builder.ts
|
|
5
|
+
const MAX_ORPHANS = 10;
|
|
6
|
+
const IMPORT_RE = /(?:import|export)\s+.*?from\s+['"]([^'"]+)['"]|(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
7
|
+
function extractImportPaths(content) {
|
|
8
|
+
const paths = [];
|
|
9
|
+
let m;
|
|
10
|
+
IMPORT_RE.lastIndex = 0;
|
|
11
|
+
while ((m = IMPORT_RE.exec(content)) !== null) {
|
|
12
|
+
const p = m[1] ?? m[2];
|
|
13
|
+
if (p && isLocalImport(p)) paths.push(p);
|
|
14
|
+
}
|
|
15
|
+
return paths;
|
|
16
|
+
}
|
|
17
|
+
function isLocalImport(p) {
|
|
18
|
+
return p.startsWith(".") || p.startsWith("/");
|
|
19
|
+
}
|
|
20
|
+
const NAMED_IMPORT_RE = /import\s+(?:type\s+)?(?:(\w+)(?:\s*,\s*)?)?(?:\{([^}]*)\})?\s+from\s+['"][^'"]+['"]/g;
|
|
21
|
+
const DEFAULT_IMPORT_RE = /import\s+(?:type\s+)?(\w+)\s+from\s+['"][^'"]+['"]/g;
|
|
22
|
+
function isPascalCase(name) {
|
|
23
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
24
|
+
}
|
|
25
|
+
function extractComponentNames(content) {
|
|
26
|
+
const names = /* @__PURE__ */ new Set();
|
|
27
|
+
DEFAULT_IMPORT_RE.lastIndex = 0;
|
|
28
|
+
let m;
|
|
29
|
+
while ((m = DEFAULT_IMPORT_RE.exec(content)) !== null) {
|
|
30
|
+
const name = m[1];
|
|
31
|
+
if (name && isPascalCase(name)) names.add(name);
|
|
32
|
+
}
|
|
33
|
+
NAMED_IMPORT_RE.lastIndex = 0;
|
|
34
|
+
while ((m = NAMED_IMPORT_RE.exec(content)) !== null) {
|
|
35
|
+
const defaultName = m[1];
|
|
36
|
+
if (defaultName && isPascalCase(defaultName)) names.add(defaultName);
|
|
37
|
+
const namedPart = m[2];
|
|
38
|
+
if (namedPart) for (const segment of namedPart.split(",")) {
|
|
39
|
+
const parts = segment.trim().split(/\s+as\s+/);
|
|
40
|
+
const localName = (parts[1] ?? parts[0])?.trim();
|
|
41
|
+
if (localName && isPascalCase(localName)) names.add(localName);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...names];
|
|
45
|
+
}
|
|
46
|
+
const STRING_SINGLE_RE = /'[^'\\]*(?:\\.[^'\\]*)*'/g;
|
|
47
|
+
const STRING_DOUBLE_RE = /"[^"\\]*(?:\\.[^"\\]*)*"/g;
|
|
48
|
+
const STRING_TEMPLATE_RE = /`[^`\\]*(?:\\.[^`\\]*)*`/g;
|
|
49
|
+
function countStrings(content) {
|
|
50
|
+
const stripped = content.replace(/^(?:import|export)\s+.*$/gm, "").replace(/\brequire\s*\([^)]*\)/g, "");
|
|
51
|
+
const singles = stripped.match(STRING_SINGLE_RE)?.length ?? 0;
|
|
52
|
+
const doubles = stripped.match(STRING_DOUBLE_RE)?.length ?? 0;
|
|
53
|
+
const templates = stripped.match(STRING_TEMPLATE_RE)?.length ?? 0;
|
|
54
|
+
return singles + doubles + templates;
|
|
55
|
+
}
|
|
56
|
+
const RESOLVE_EXTENSIONS = [...SCAN_EXTENSIONS];
|
|
57
|
+
const RESOLVE_INDEX_FILES = [
|
|
58
|
+
"index.ts",
|
|
59
|
+
"index.tsx",
|
|
60
|
+
"index.js"
|
|
61
|
+
];
|
|
62
|
+
async function resolveImportPath(importPath, importerDir, projectRoot) {
|
|
63
|
+
const base = importPath.startsWith("/") ? join(projectRoot, importPath) : join(importerDir, importPath);
|
|
64
|
+
if (extname(base)) {
|
|
65
|
+
if (await pathExists(base)) return base;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
69
|
+
const candidate = base + ext;
|
|
70
|
+
if (await pathExists(candidate)) return candidate;
|
|
71
|
+
}
|
|
72
|
+
for (const idx of RESOLVE_INDEX_FILES) {
|
|
73
|
+
const candidate = join(base, idx);
|
|
74
|
+
if (await pathExists(candidate)) return candidate;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
async function buildGraph(projectRoot, options) {
|
|
79
|
+
const filePaths = await discoverFiles(projectRoot, {
|
|
80
|
+
paths: options?.paths ?? await autoDetectSourceDirs(projectRoot),
|
|
81
|
+
include: options?.include,
|
|
82
|
+
exclude: options?.exclude
|
|
83
|
+
});
|
|
84
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
85
|
+
const parsePromises = filePaths.map(async (relPath) => {
|
|
86
|
+
const content = await readText(join(projectRoot, relPath));
|
|
87
|
+
if (content === null) return;
|
|
88
|
+
const imports = extractImportPaths(content);
|
|
89
|
+
const components = extractComponentNames(content);
|
|
90
|
+
const strings = countStrings(content);
|
|
91
|
+
const category = classifyFile(relPath);
|
|
92
|
+
fileMap.set(relPath, {
|
|
93
|
+
relPath,
|
|
94
|
+
imports,
|
|
95
|
+
components,
|
|
96
|
+
strings,
|
|
97
|
+
category
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
await Promise.all(parsePromises);
|
|
101
|
+
const usedByMap = /* @__PURE__ */ new Map();
|
|
102
|
+
for (const relPath of fileMap.keys()) usedByMap.set(relPath, /* @__PURE__ */ new Set());
|
|
103
|
+
const resolvePromises = [];
|
|
104
|
+
for (const [relPath, info] of fileMap) {
|
|
105
|
+
const importerAbsDir = dirname(join(projectRoot, relPath));
|
|
106
|
+
for (const rawImport of info.imports) resolvePromises.push(resolveImportPath(rawImport, importerAbsDir, projectRoot).then((resolved) => {
|
|
107
|
+
if (!resolved) return;
|
|
108
|
+
const resolvedRel = relative(projectRoot, resolved);
|
|
109
|
+
const targetSet = usedByMap.get(resolvedRel);
|
|
110
|
+
if (targetSet) targetSet.add(relPath);
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
await Promise.all(resolvePromises);
|
|
114
|
+
const pages = [];
|
|
115
|
+
const components = [];
|
|
116
|
+
const layouts = [];
|
|
117
|
+
const orphanCandidates = [];
|
|
118
|
+
let totalStrings = 0;
|
|
119
|
+
for (const [relPath, info] of fileMap) {
|
|
120
|
+
const usedBy = [...usedByMap.get(relPath) ?? []].toSorted((a, b) => a.localeCompare(b));
|
|
121
|
+
totalStrings += info.strings;
|
|
122
|
+
if (info.strings === 0 && info.category !== "other") continue;
|
|
123
|
+
const node = {
|
|
124
|
+
file: relPath,
|
|
125
|
+
category: info.category,
|
|
126
|
+
imports: info.imports,
|
|
127
|
+
used_by: usedBy,
|
|
128
|
+
strings: info.strings
|
|
129
|
+
};
|
|
130
|
+
if (info.category === "page" && info.components.length > 0) node.components = info.components;
|
|
131
|
+
switch (info.category) {
|
|
132
|
+
case "page":
|
|
133
|
+
pages.push(node);
|
|
134
|
+
break;
|
|
135
|
+
case "component":
|
|
136
|
+
components.push(node);
|
|
137
|
+
break;
|
|
138
|
+
case "layout":
|
|
139
|
+
layouts.push(node);
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
if (usedBy.length === 0 && info.imports.length === 0) orphanCandidates.push(relPath);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
pages: pages.toSorted((a, b) => a.file.localeCompare(b.file)),
|
|
148
|
+
components: components.toSorted((a, b) => a.file.localeCompare(b.file)),
|
|
149
|
+
layouts: layouts.toSorted((a, b) => a.file.localeCompare(b.file)),
|
|
150
|
+
orphan_files: orphanCandidates.toSorted((a, b) => a.localeCompare(b)).slice(0, MAX_ORPHANS),
|
|
151
|
+
stats: {
|
|
152
|
+
total_files: fileMap.size,
|
|
153
|
+
total_components: components.length,
|
|
154
|
+
total_pages: pages.length,
|
|
155
|
+
total_strings_estimate: totalStrings
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
export { buildGraph as t };
|
|
161
|
+
|
|
162
|
+
//# sourceMappingURL=graph-builder-fPrDDwnu.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
//#region src/util/id.ts
|
|
3
|
+
function branchTimestamp() {
|
|
4
|
+
return `${Math.floor(Date.now() / 1e3).toString()}-${randomUUID().slice(0, 4)}`;
|
|
5
|
+
}
|
|
6
|
+
function generateEntryId() {
|
|
7
|
+
return randomUUID().replace(/-/g, "").slice(0, 12);
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { generateEntryId as n, branchTimestamp as t };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=id-BdD4HvMY.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-BdD4HvMY.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\nexport function generateEntryId(): string {\n return randomUUID().replace(/-/g, '').slice(0, 12)\n}\n"],"mappings":";;AAEA,SAAgB,kBAA0B;AAGxC,QAAO,GAFI,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,CAAC,UAAU,CAEtC,GADE,YAAY,CAAC,MAAM,GAAG,EAAE;;AAIzC,SAAgB,kBAA0B;AACxC,QAAO,YAAY,CAAC,QAAQ,MAAM,GAAG,CAAC,MAAM,GAAG,GAAG"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,359 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//#region src/types/index.d.ts
|
|
4
|
-
interface ModelMetadata {
|
|
5
|
-
name: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
modelId: string;
|
|
8
|
-
path?: string;
|
|
9
|
-
localization: boolean;
|
|
10
|
-
type: 'JSON' | 'MD' | 'MDX';
|
|
11
|
-
createdBy?: string;
|
|
12
|
-
isServerless: boolean;
|
|
13
|
-
}
|
|
14
|
-
interface FieldDefinition {
|
|
15
|
-
name: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
fieldId: string;
|
|
18
|
-
modelId: string;
|
|
19
|
-
componentId: ComponentId;
|
|
20
|
-
fieldType: FieldType;
|
|
21
|
-
options: FieldOptions;
|
|
22
|
-
validations: FieldValidations;
|
|
23
|
-
system: boolean;
|
|
24
|
-
defaultField: boolean;
|
|
25
|
-
}
|
|
26
|
-
type FieldType = 'string' | 'number' | 'boolean' | 'array' | 'date' | 'media' | 'relation';
|
|
27
|
-
type ComponentId = 'single-line-text' | 'multi-line-text' | 'email' | 'url' | 'slug' | 'color' | 'json' | 'md-editor' | 'rich-text-editor' | 'integer' | 'decimal' | 'rating' | 'percent' | 'phone-number' | 'checkbox' | 'switch' | 'date' | 'date-time' | 'media' | 'one-to-one' | 'one-to-many';
|
|
28
|
-
interface FieldOptions {
|
|
29
|
-
'title-field'?: {
|
|
30
|
-
value: boolean;
|
|
31
|
-
};
|
|
32
|
-
'default-value'?: {
|
|
33
|
-
value: boolean;
|
|
34
|
-
form?: Record<string, {
|
|
35
|
-
value: unknown;
|
|
36
|
-
}>;
|
|
37
|
-
};
|
|
38
|
-
reference?: {
|
|
39
|
-
value: boolean;
|
|
40
|
-
form?: {
|
|
41
|
-
reference: {
|
|
42
|
-
value: string;
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
[key: string]: unknown;
|
|
47
|
-
}
|
|
48
|
-
interface FieldValidations {
|
|
49
|
-
'required-field'?: {
|
|
50
|
-
value: boolean;
|
|
51
|
-
};
|
|
52
|
-
'unique-field'?: {
|
|
53
|
-
value: boolean;
|
|
54
|
-
};
|
|
55
|
-
[key: string]: unknown;
|
|
56
|
-
}
|
|
57
|
-
interface ContentEntry {
|
|
58
|
-
ID: string;
|
|
59
|
-
createdAt: string;
|
|
60
|
-
updatedAt: string;
|
|
61
|
-
status: 'draft' | 'publish' | 'changed';
|
|
62
|
-
scheduled?: boolean;
|
|
63
|
-
[key: string]: unknown;
|
|
64
|
-
}
|
|
65
|
-
interface MDContentEntry extends ContentEntry {
|
|
66
|
-
slug: string;
|
|
67
|
-
path?: string;
|
|
68
|
-
}
|
|
69
|
-
interface AssetEntry {
|
|
70
|
-
path: string;
|
|
71
|
-
mimetype: string;
|
|
72
|
-
size: number;
|
|
73
|
-
alt?: string;
|
|
74
|
-
meta?: {
|
|
75
|
-
user?: {
|
|
76
|
-
name: string;
|
|
77
|
-
email: string;
|
|
78
|
-
avatar?: string;
|
|
79
|
-
};
|
|
80
|
-
createdAt?: string;
|
|
81
|
-
updatedAt?: string;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
interface ContentrainConfig {
|
|
85
|
-
assetsPath: string;
|
|
86
|
-
}
|
|
87
|
-
interface WriterConfig {
|
|
88
|
-
repoPath: string;
|
|
89
|
-
branch: string;
|
|
90
|
-
contentrainDir?: string;
|
|
91
|
-
remote?: string;
|
|
92
|
-
author?: {
|
|
93
|
-
name: string;
|
|
94
|
-
email: string;
|
|
95
|
-
};
|
|
96
|
-
commitPrefix?: string;
|
|
97
|
-
retryAttempts?: number;
|
|
98
|
-
dryRun?: boolean;
|
|
99
|
-
}
|
|
100
|
-
interface WorktreeContext {
|
|
101
|
-
path: string;
|
|
102
|
-
branch: string;
|
|
103
|
-
id: string;
|
|
104
|
-
}
|
|
105
|
-
interface TransactionOptions {
|
|
106
|
-
message?: string;
|
|
107
|
-
}
|
|
108
|
-
interface ValidationResult {
|
|
109
|
-
valid: boolean;
|
|
110
|
-
errors: ValidationFieldError[];
|
|
111
|
-
}
|
|
112
|
-
interface ValidationFieldError {
|
|
113
|
-
fieldId: string;
|
|
114
|
-
message: string;
|
|
115
|
-
rule: string;
|
|
116
|
-
}
|
|
117
|
-
interface ToolResult {
|
|
118
|
-
success: boolean;
|
|
119
|
-
data?: unknown;
|
|
120
|
-
error?: string;
|
|
121
|
-
}
|
|
122
|
-
//#endregion
|
|
123
|
-
//#region src/server.d.ts
|
|
124
|
-
declare function createServer(config: WriterConfig): McpServer;
|
|
125
|
-
//#endregion
|
|
126
|
-
//#region src/core/model/manager.d.ts
|
|
127
|
-
declare class ModelManager {
|
|
128
|
-
private basePath;
|
|
129
|
-
constructor(basePath: string);
|
|
130
|
-
private get modelsDir();
|
|
131
|
-
private get metadataPath();
|
|
132
|
-
list(): Promise<ModelMetadata[]>;
|
|
133
|
-
get(modelId: string): Promise<ModelMetadata>;
|
|
134
|
-
getFields(modelId: string): Promise<FieldDefinition[]>;
|
|
135
|
-
describe(modelId: string): Promise<{
|
|
136
|
-
model: ModelMetadata;
|
|
137
|
-
fields: FieldDefinition[];
|
|
138
|
-
customFields: FieldDefinition[];
|
|
139
|
-
}>;
|
|
140
|
-
create(input: {
|
|
141
|
-
name: string;
|
|
142
|
-
description?: string;
|
|
143
|
-
modelId: string;
|
|
144
|
-
localization: boolean;
|
|
145
|
-
type: 'JSON' | 'MD' | 'MDX';
|
|
146
|
-
path?: string;
|
|
147
|
-
fields?: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>[];
|
|
148
|
-
}): Promise<ModelMetadata>;
|
|
149
|
-
addField(modelId: string, field: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>): Promise<FieldDefinition>;
|
|
150
|
-
removeField(modelId: string, fieldId: string): Promise<void>;
|
|
151
|
-
delete(modelId: string): Promise<void>;
|
|
152
|
-
}
|
|
153
|
-
//#endregion
|
|
154
|
-
//#region src/core/content/manager.d.ts
|
|
155
|
-
declare class ContentManager {
|
|
156
|
-
private basePath;
|
|
157
|
-
private repoPath;
|
|
158
|
-
private modelManager;
|
|
159
|
-
private markdownSync;
|
|
160
|
-
constructor(basePath: string, repoPath: string);
|
|
161
|
-
private contentDir;
|
|
162
|
-
private contentFilePath;
|
|
163
|
-
list(modelId: string, locale?: string): Promise<ContentEntry[]>;
|
|
164
|
-
get(modelId: string, entryId: string, locale?: string): Promise<ContentEntry>;
|
|
165
|
-
getLocales(modelId: string): Promise<string[]>;
|
|
166
|
-
create(modelId: string, data: Record<string, unknown>, options?: {
|
|
167
|
-
locale?: string;
|
|
168
|
-
id?: string;
|
|
169
|
-
status?: 'draft' | 'publish';
|
|
170
|
-
content?: string;
|
|
171
|
-
}): Promise<string>;
|
|
172
|
-
update(modelId: string, entryId: string, data: Record<string, unknown>, options?: {
|
|
173
|
-
locale?: string;
|
|
174
|
-
content?: string;
|
|
175
|
-
}): Promise<ContentEntry>;
|
|
176
|
-
delete(modelId: string, entryId: string): Promise<void>;
|
|
177
|
-
private resolveContentPath;
|
|
178
|
-
private createLocalized;
|
|
179
|
-
private appendEntry;
|
|
180
|
-
private deleteFromFile;
|
|
181
|
-
}
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region src/core/asset/manager.d.ts
|
|
184
|
-
declare class AssetManager {
|
|
185
|
-
private basePath;
|
|
186
|
-
private repoPath;
|
|
187
|
-
private config;
|
|
188
|
-
private assetsPath;
|
|
189
|
-
constructor(basePath: string, repoPath: string, config: WriterConfig);
|
|
190
|
-
list(): Promise<AssetEntry[]>;
|
|
191
|
-
get(assetPath: string): Promise<AssetEntry | undefined>;
|
|
192
|
-
register(input: {
|
|
193
|
-
path: string;
|
|
194
|
-
mimetype: string;
|
|
195
|
-
alt?: string;
|
|
196
|
-
}): Promise<AssetEntry>;
|
|
197
|
-
deregister(assetPath: string): Promise<void>;
|
|
198
|
-
update(assetPath: string, updates: {
|
|
199
|
-
alt?: string;
|
|
200
|
-
mimetype?: string;
|
|
201
|
-
}): Promise<AssetEntry>;
|
|
202
|
-
}
|
|
203
|
-
//#endregion
|
|
204
|
-
//#region src/core/validation/validator.d.ts
|
|
205
|
-
declare class Validator {
|
|
206
|
-
private basePath;
|
|
207
|
-
private modelManager;
|
|
208
|
-
constructor(basePath: string);
|
|
209
|
-
/**
|
|
210
|
-
* Validate content data against model field definitions.
|
|
211
|
-
*/
|
|
212
|
-
validate(modelId: string, data: Record<string, unknown>, options?: {
|
|
213
|
-
isUpdate?: boolean;
|
|
214
|
-
}): Promise<ValidationResult>;
|
|
215
|
-
/**
|
|
216
|
-
* Validate ID format.
|
|
217
|
-
*/
|
|
218
|
-
validateId(id: string): ValidationResult;
|
|
219
|
-
/**
|
|
220
|
-
* Validate status value.
|
|
221
|
-
*/
|
|
222
|
-
validateStatus(status: string): ValidationResult;
|
|
223
|
-
private checkRelationIntegrity;
|
|
224
|
-
private checkAssetExists;
|
|
225
|
-
private checkUniqueness;
|
|
226
|
-
}
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region src/core/writer.d.ts
|
|
229
|
-
/**
|
|
230
|
-
* Main orchestrator — coordinates all managers and git sync.
|
|
231
|
-
*
|
|
232
|
-
* Three modes:
|
|
233
|
-
* 1. Read mode: reads from repoPath directly (no git sync)
|
|
234
|
-
* 2. Transaction mode: creates a worktree, applies changes, commits + pushes
|
|
235
|
-
* 3. Dry-run mode: copies contentrain dir to temp, applies changes there, then discards
|
|
236
|
-
*/
|
|
237
|
-
declare class ContentrainWriter {
|
|
238
|
-
private config;
|
|
239
|
-
private contentrainDir;
|
|
240
|
-
readonly model: ModelManager;
|
|
241
|
-
readonly content: ContentManager;
|
|
242
|
-
readonly asset: AssetManager;
|
|
243
|
-
readonly validator: Validator;
|
|
244
|
-
private gitSync;
|
|
245
|
-
constructor(config: WriterConfig);
|
|
246
|
-
/**
|
|
247
|
-
* Execute operations in a git-synced transaction.
|
|
248
|
-
* Creates worktree → runs operations → validates → commits → pushes.
|
|
249
|
-
* In dry-run mode, copies to a temp directory and discards after execution.
|
|
250
|
-
*/
|
|
251
|
-
transaction<T>(fn: (managers: TransactionManagers) => Promise<T>, options?: {
|
|
252
|
-
message?: string;
|
|
253
|
-
}): Promise<T>;
|
|
254
|
-
/**
|
|
255
|
-
* Execute operations and push to a staging branch instead of the target branch.
|
|
256
|
-
* Returns the result and whether any changes were pushed.
|
|
257
|
-
*/
|
|
258
|
-
stagingTransaction<T>(fn: (managers: TransactionManagers) => Promise<T>, options: {
|
|
259
|
-
message: string;
|
|
260
|
-
stagingBranch: string;
|
|
261
|
-
}): Promise<{
|
|
262
|
-
result: T;
|
|
263
|
-
hasChanges: boolean;
|
|
264
|
-
}>;
|
|
265
|
-
/**
|
|
266
|
-
* Merge a staging branch into the target branch.
|
|
267
|
-
*/
|
|
268
|
-
applyStagingBranch(stagingBranch: string): Promise<string>;
|
|
269
|
-
/**
|
|
270
|
-
* Delete a staging branch without merging.
|
|
271
|
-
*/
|
|
272
|
-
discardStagingBranch(stagingBranch: string): Promise<void>;
|
|
273
|
-
/**
|
|
274
|
-
* Dry-run: copy contentrain dir to temp, run operations there, then discard.
|
|
275
|
-
* The original repository is never modified.
|
|
276
|
-
*/
|
|
277
|
-
private dryRunTransaction;
|
|
278
|
-
/**
|
|
279
|
-
* Create managers for a specific repo path (used in transactions).
|
|
280
|
-
*/
|
|
281
|
-
private createManagers;
|
|
282
|
-
}
|
|
283
|
-
interface TransactionManagers {
|
|
284
|
-
basePath: string;
|
|
285
|
-
repoPath: string;
|
|
286
|
-
model: ModelManager;
|
|
287
|
-
content: ContentManager;
|
|
288
|
-
asset: AssetManager;
|
|
289
|
-
validator: Validator;
|
|
290
|
-
}
|
|
291
|
-
//#endregion
|
|
292
|
-
//#region src/core/doctor.d.ts
|
|
293
|
-
type IssueSeverity = 'error' | 'warning';
|
|
294
|
-
type IssueCategory = 'structure' | 'id' | 'schema' | 'relation' | 'asset' | 'sync';
|
|
295
|
-
interface DoctorIssue {
|
|
296
|
-
severity: IssueSeverity;
|
|
297
|
-
category: IssueCategory;
|
|
298
|
-
modelId?: string;
|
|
299
|
-
entryId?: string;
|
|
300
|
-
field?: string;
|
|
301
|
-
file?: string;
|
|
302
|
-
message: string;
|
|
303
|
-
fix?: string;
|
|
304
|
-
}
|
|
305
|
-
interface DoctorResult {
|
|
306
|
-
summary: {
|
|
307
|
-
total: number;
|
|
308
|
-
errors: number;
|
|
309
|
-
warnings: number;
|
|
310
|
-
fixed: number;
|
|
311
|
-
models: number;
|
|
312
|
-
entries: number;
|
|
313
|
-
};
|
|
314
|
-
issues: DoctorIssue[];
|
|
315
|
-
}
|
|
316
|
-
interface DoctorOptions {
|
|
317
|
-
fix?: boolean;
|
|
318
|
-
modelId?: string;
|
|
319
|
-
}
|
|
320
|
-
declare class Doctor {
|
|
321
|
-
private basePath;
|
|
322
|
-
private repoPath;
|
|
323
|
-
private issues;
|
|
324
|
-
private fixed;
|
|
325
|
-
private totalEntries;
|
|
326
|
-
private scannedEntries;
|
|
327
|
-
private idRemap;
|
|
328
|
-
private contentFiles;
|
|
329
|
-
private fieldsByModel;
|
|
330
|
-
constructor(basePath: string, repoPath: string);
|
|
331
|
-
diagnose(options?: DoctorOptions): Promise<DoctorResult>;
|
|
332
|
-
private loadAllContentFiles;
|
|
333
|
-
private checkMetadata;
|
|
334
|
-
private checkFieldFiles;
|
|
335
|
-
private checkOrphanDirectories;
|
|
336
|
-
private checkModelStructure;
|
|
337
|
-
private checkModelContent;
|
|
338
|
-
private checkEntry;
|
|
339
|
-
private checkRelation;
|
|
340
|
-
private checkUniqueConstraints;
|
|
341
|
-
private checkLocaleIdConsistency;
|
|
342
|
-
private checkMarkdownSync;
|
|
343
|
-
private loadAssets;
|
|
344
|
-
/**
|
|
345
|
-
* After all ID fixes are collected in idRemap, propagate changes
|
|
346
|
-
* to ALL relation fields across ALL loaded content files.
|
|
347
|
-
*
|
|
348
|
-
* This is the critical two-way binding: when entry A's ID changes,
|
|
349
|
-
* every relation field in every model that points to A must be updated.
|
|
350
|
-
*/
|
|
351
|
-
private propagateIdChanges;
|
|
352
|
-
private fixDuplicateIds;
|
|
353
|
-
private writeDirtyFiles;
|
|
354
|
-
private addIssue;
|
|
355
|
-
private buildResult;
|
|
356
|
-
}
|
|
357
|
-
//#endregion
|
|
358
|
-
export { AssetEntry, ComponentId, ContentEntry, ContentrainConfig, ContentrainWriter, Doctor, FieldDefinition, FieldOptions, FieldType, FieldValidations, MDContentEntry, ModelMetadata, ToolResult, TransactionOptions, ValidationFieldError, ValidationResult, WorktreeContext, WriterConfig, createServer };
|
|
359
|
-
//# sourceMappingURL=index.d.mts.map
|
|
1
|
+
export { };
|