@olonjs/cli 3.0.96 → 3.0.98
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/assets/src_tenant_alpha.sh +1104 -1570
- package/assets/templates/agritourism/src_tenant.sh +775 -5
- package/assets/templates/alpha/src_tenant.sh +1104 -1570
- package/package.json +1 -1
|
@@ -583,6 +583,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
583
583
|
"scripts": {
|
|
584
584
|
"dev": "vite",
|
|
585
585
|
"dev:clean": "vite --force",
|
|
586
|
+
"verify:webmcp": "node scripts/webmcp-feature-check.mjs",
|
|
586
587
|
"prebuild": "node scripts/sync-pages-to-public.mjs",
|
|
587
588
|
"build": "tsc && vite build",
|
|
588
589
|
"dist": "bash ./src2Code.sh --template agritourism src vercel.json index.html vite.config.ts scripts docs package.json",
|
|
@@ -596,7 +597,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
596
597
|
"@tiptap/extension-link": "^2.11.5",
|
|
597
598
|
"@tiptap/react": "^2.11.5",
|
|
598
599
|
"@tiptap/starter-kit": "^2.11.5",
|
|
599
|
-
"@olonjs/core": "^1.0.
|
|
600
|
+
"@olonjs/core": "^1.0.86",
|
|
600
601
|
"clsx": "^2.1.1",
|
|
601
602
|
"lucide-react": "^0.474.0",
|
|
602
603
|
"react": "^19.0.0",
|
|
@@ -892,6 +893,206 @@ main().catch((error) => {
|
|
|
892
893
|
process.exit(1);
|
|
893
894
|
});
|
|
894
895
|
|
|
896
|
+
END_OF_FILE_CONTENT
|
|
897
|
+
echo "Creating scripts/bake.mjs..."
|
|
898
|
+
cat << 'END_OF_FILE_CONTENT' > "scripts/bake.mjs"
|
|
899
|
+
/**
|
|
900
|
+
* olon bake - production SSG
|
|
901
|
+
*
|
|
902
|
+
* 1) Build client bundle (dist/)
|
|
903
|
+
* 2) Build SSR entry bundle (dist-ssr/)
|
|
904
|
+
* 3) Discover all page slugs from JSON files under src/data/pages
|
|
905
|
+
* 4) Render each slug via SSR and write dist/<slug>/index.html
|
|
906
|
+
*/
|
|
907
|
+
|
|
908
|
+
import { build } from 'vite';
|
|
909
|
+
import path from 'path';
|
|
910
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
911
|
+
import fs from 'fs/promises';
|
|
912
|
+
import {
|
|
913
|
+
buildPageContract,
|
|
914
|
+
buildPageManifest,
|
|
915
|
+
buildPageManifestHref,
|
|
916
|
+
buildSiteManifest,
|
|
917
|
+
} from '../../../packages/core/src/lib/webmcp-contracts.mjs';
|
|
918
|
+
|
|
919
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
920
|
+
const root = path.resolve(__dirname, '..');
|
|
921
|
+
const pagesDir = path.resolve(root, 'src/data/pages');
|
|
922
|
+
const publicDir = path.resolve(root, 'public');
|
|
923
|
+
const distDir = path.resolve(root, 'dist');
|
|
924
|
+
|
|
925
|
+
async function writeJsonTargets(relativePath, value) {
|
|
926
|
+
const targets = [
|
|
927
|
+
path.resolve(publicDir, relativePath),
|
|
928
|
+
path.resolve(distDir, relativePath),
|
|
929
|
+
];
|
|
930
|
+
|
|
931
|
+
for (const targetPath of targets) {
|
|
932
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
933
|
+
await fs.writeFile(targetPath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function escapeHtmlAttribute(value) {
|
|
938
|
+
return String(value).replace(/&/g, '&').replace(/"/g, '"');
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function toCanonicalSlug(relativeJsonPath) {
|
|
942
|
+
const normalized = relativeJsonPath.replace(/\\/g, '/');
|
|
943
|
+
const slug = normalized.replace(/\.json$/i, '').replace(/^\/+|\/+$/g, '');
|
|
944
|
+
if (!slug) throw new Error('[bake] Invalid page slug: empty path segment');
|
|
945
|
+
return slug;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
async function listJsonFilesRecursive(dir) {
|
|
949
|
+
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
950
|
+
const files = [];
|
|
951
|
+
for (const item of items) {
|
|
952
|
+
const fullPath = path.join(dir, item.name);
|
|
953
|
+
if (item.isDirectory()) {
|
|
954
|
+
files.push(...(await listJsonFilesRecursive(fullPath)));
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
if (item.isFile() && item.name.toLowerCase().endsWith('.json')) files.push(fullPath);
|
|
958
|
+
}
|
|
959
|
+
return files;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async function discoverTargets() {
|
|
963
|
+
let files = [];
|
|
964
|
+
try {
|
|
965
|
+
files = await listJsonFilesRecursive(pagesDir);
|
|
966
|
+
} catch {
|
|
967
|
+
files = [];
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const rawSlugs = files.map((fullPath) => toCanonicalSlug(path.relative(pagesDir, fullPath)));
|
|
971
|
+
const slugs = Array.from(new Set(rawSlugs)).sort((a, b) => a.localeCompare(b));
|
|
972
|
+
|
|
973
|
+
return slugs.map((slug) => {
|
|
974
|
+
const depth = slug === 'home' ? 0 : slug.split('/').length;
|
|
975
|
+
const out = slug === 'home' ? 'dist/index.html' : `dist/${slug}/index.html`;
|
|
976
|
+
return { slug, out, depth };
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
console.log('\n[bake] Building client...');
|
|
981
|
+
await build({ root, mode: 'production', logLevel: 'warn' });
|
|
982
|
+
console.log('[bake] Client build done.');
|
|
983
|
+
|
|
984
|
+
console.log('\n[bake] Building SSR bundle...');
|
|
985
|
+
await build({
|
|
986
|
+
root,
|
|
987
|
+
mode: 'production',
|
|
988
|
+
logLevel: 'warn',
|
|
989
|
+
build: {
|
|
990
|
+
ssr: 'src/entry-ssg.tsx',
|
|
991
|
+
outDir: 'dist-ssr',
|
|
992
|
+
rollupOptions: {
|
|
993
|
+
output: { format: 'esm' },
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
ssr: {
|
|
997
|
+
noExternal: ['@olonjs/core'],
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
console.log('[bake] SSR build done.');
|
|
1001
|
+
|
|
1002
|
+
const targets = await discoverTargets();
|
|
1003
|
+
if (targets.length === 0) {
|
|
1004
|
+
throw new Error('[bake] No pages discovered under src/data/pages');
|
|
1005
|
+
}
|
|
1006
|
+
console.log(`[bake] Targets: ${targets.map((t) => t.slug).join(', ')}`);
|
|
1007
|
+
|
|
1008
|
+
const ssrEntryUrl = pathToFileURL(path.resolve(root, 'dist-ssr/entry-ssg.js')).href;
|
|
1009
|
+
const { render, getCss, getPageMeta, getWebMcpBuildState } = await import(ssrEntryUrl);
|
|
1010
|
+
|
|
1011
|
+
const template = await fs.readFile(path.resolve(root, 'dist/index.html'), 'utf-8');
|
|
1012
|
+
const hasCommentMarker = template.includes('<!--app-html-->');
|
|
1013
|
+
const hasRootDivMarker = template.includes('<div id="root"></div>');
|
|
1014
|
+
if (!hasCommentMarker && !hasRootDivMarker) {
|
|
1015
|
+
throw new Error('[bake] Missing template marker. Expected <!--app-html--> or <div id="root"></div>.');
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const inlinedCss = getCss();
|
|
1019
|
+
const styleTag = `<style data-bake="inline">${inlinedCss}</style>`;
|
|
1020
|
+
const webMcpBuildState = getWebMcpBuildState();
|
|
1021
|
+
|
|
1022
|
+
for (const { slug } of targets) {
|
|
1023
|
+
const pageConfig = webMcpBuildState.pages[slug];
|
|
1024
|
+
if (!pageConfig) continue;
|
|
1025
|
+
const contract = buildPageContract({
|
|
1026
|
+
slug,
|
|
1027
|
+
pageConfig,
|
|
1028
|
+
schemas: webMcpBuildState.schemas,
|
|
1029
|
+
siteConfig: webMcpBuildState.siteConfig,
|
|
1030
|
+
});
|
|
1031
|
+
await writeJsonTargets(`schemas/${slug}.schema.json`, contract);
|
|
1032
|
+
const pageManifest = buildPageManifest({
|
|
1033
|
+
slug,
|
|
1034
|
+
pageConfig,
|
|
1035
|
+
schemas: webMcpBuildState.schemas,
|
|
1036
|
+
siteConfig: webMcpBuildState.siteConfig,
|
|
1037
|
+
});
|
|
1038
|
+
await writeJsonTargets(buildPageManifestHref(slug).replace(/^\//, ''), pageManifest);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const mcpManifest = buildSiteManifest({
|
|
1042
|
+
pages: webMcpBuildState.pages,
|
|
1043
|
+
schemas: webMcpBuildState.schemas,
|
|
1044
|
+
siteConfig: webMcpBuildState.siteConfig,
|
|
1045
|
+
});
|
|
1046
|
+
await writeJsonTargets('mcp-manifest.json', mcpManifest);
|
|
1047
|
+
|
|
1048
|
+
for (const { slug, out, depth } of targets) {
|
|
1049
|
+
console.log(`\n[bake] Rendering /${slug === 'home' ? '' : slug}...`);
|
|
1050
|
+
|
|
1051
|
+
const appHtml = render(slug);
|
|
1052
|
+
const { title, description } = getPageMeta(slug);
|
|
1053
|
+
const safeTitle = String(title).replace(/"/g, '"');
|
|
1054
|
+
const safeDescription = String(description).replace(/"/g, '"');
|
|
1055
|
+
const metaTags = [
|
|
1056
|
+
`<meta name="description" content="${safeDescription}">`,
|
|
1057
|
+
`<meta property="og:title" content="${safeTitle}">`,
|
|
1058
|
+
`<meta property="og:description" content="${safeDescription}">`,
|
|
1059
|
+
].join('\n ');
|
|
1060
|
+
const jsonLd = JSON.stringify({
|
|
1061
|
+
'@context': 'https://schema.org',
|
|
1062
|
+
'@type': 'WebPage',
|
|
1063
|
+
name: title,
|
|
1064
|
+
description,
|
|
1065
|
+
url: slug === 'home' ? '/' : `/${slug}`,
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
const prefix = depth > 0 ? '../'.repeat(depth) : './';
|
|
1069
|
+
const fixedTemplate = depth > 0 ? template.replace(/(['"])\.\//g, `$1${prefix}`) : template;
|
|
1070
|
+
const mcpManifestHref = `${prefix}${buildPageManifestHref(slug).replace(/^\//, '')}`;
|
|
1071
|
+
const contractHref = `${prefix}schemas/${slug}.schema.json`;
|
|
1072
|
+
const contractLinks = [
|
|
1073
|
+
`<link rel="mcp-manifest" href="${escapeHtmlAttribute(mcpManifestHref)}">`,
|
|
1074
|
+
`<link rel="olon-contract" href="${escapeHtmlAttribute(contractHref)}">`,
|
|
1075
|
+
`<script type="application/ld+json">${jsonLd}</script>`,
|
|
1076
|
+
].join('\n ');
|
|
1077
|
+
|
|
1078
|
+
let bakedHtml = fixedTemplate
|
|
1079
|
+
.replace('</head>', ` ${styleTag}\n ${contractLinks}\n</head>`)
|
|
1080
|
+
.replace(/<title>.*?<\/title>/, `<title>${safeTitle}</title>\n ${metaTags}`);
|
|
1081
|
+
|
|
1082
|
+
if (hasCommentMarker) {
|
|
1083
|
+
bakedHtml = bakedHtml.replace('<!--app-html-->', appHtml);
|
|
1084
|
+
} else {
|
|
1085
|
+
bakedHtml = bakedHtml.replace('<div id="root"></div>', `<div id="root">${appHtml}</div>`);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const outPath = path.resolve(root, out);
|
|
1089
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
1090
|
+
await fs.writeFile(outPath, bakedHtml, 'utf-8');
|
|
1091
|
+
console.log(`[bake] Written -> ${out} [title: "${safeTitle}"]`);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
console.log('\n[bake] All pages baked. OK\n');
|
|
1095
|
+
|
|
895
1096
|
END_OF_FILE_CONTENT
|
|
896
1097
|
echo "Creating scripts/sync-pages-to-public.mjs..."
|
|
897
1098
|
cat << 'END_OF_FILE_CONTENT' > "scripts/sync-pages-to-public.mjs"
|
|
@@ -916,6 +1117,308 @@ fs.cpSync(sourceDir, targetDir, { recursive: true });
|
|
|
916
1117
|
|
|
917
1118
|
console.log('[sync-pages-to-public] Synced pages to public/pages');
|
|
918
1119
|
|
|
1120
|
+
END_OF_FILE_CONTENT
|
|
1121
|
+
echo "Creating scripts/webmcp-feature-check.mjs..."
|
|
1122
|
+
cat << 'END_OF_FILE_CONTENT' > "scripts/webmcp-feature-check.mjs"
|
|
1123
|
+
import fs from 'fs/promises';
|
|
1124
|
+
import path from 'path';
|
|
1125
|
+
import { fileURLToPath } from 'url';
|
|
1126
|
+
import { createRequire } from 'module';
|
|
1127
|
+
|
|
1128
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1129
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
1130
|
+
const baseUrl = process.env.WEBMCP_BASE_URL ?? 'http://127.0.0.1:4173';
|
|
1131
|
+
|
|
1132
|
+
function pageFilePathFromSlug(slug) {
|
|
1133
|
+
return path.resolve(rootDir, 'src', 'data', 'pages', `${slug}.json`);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function adminUrlFromSlug(slug) {
|
|
1137
|
+
return `${baseUrl}/admin${slug === 'home' ? '' : `/${slug}`}`;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function isStringSchema(schema) {
|
|
1141
|
+
if (!schema || typeof schema !== 'object') return false;
|
|
1142
|
+
if (schema.type === 'string') return true;
|
|
1143
|
+
if (Array.isArray(schema.anyOf)) {
|
|
1144
|
+
return schema.anyOf.some((entry) => entry && typeof entry === 'object' && entry.type === 'string');
|
|
1145
|
+
}
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function findTopLevelStringField(sectionSchema) {
|
|
1150
|
+
const properties = sectionSchema?.properties;
|
|
1151
|
+
if (!properties || typeof properties !== 'object') return null;
|
|
1152
|
+
const preferred = ['title', 'sectionTitle', 'label', 'headline', 'name'];
|
|
1153
|
+
for (const key of preferred) {
|
|
1154
|
+
if (isStringSchema(properties[key])) return key;
|
|
1155
|
+
}
|
|
1156
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
1157
|
+
if (isStringSchema(value)) return key;
|
|
1158
|
+
}
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
async function loadPlaywright() {
|
|
1163
|
+
const require = createRequire(import.meta.url);
|
|
1164
|
+
try {
|
|
1165
|
+
return require('playwright');
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1168
|
+
throw new Error(
|
|
1169
|
+
`Playwright is required for WebMCP verification. Install it before running this script. Original error: ${message}`
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
async function readPageJson(slug) {
|
|
1175
|
+
const pageFilePath = pageFilePathFromSlug(slug);
|
|
1176
|
+
const raw = await fs.readFile(pageFilePath, 'utf8');
|
|
1177
|
+
return { raw, json: JSON.parse(raw), pageFilePath };
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
async function waitFor(predicate, timeoutMs, label) {
|
|
1181
|
+
const startedAt = Date.now();
|
|
1182
|
+
for (;;) {
|
|
1183
|
+
const result = await predicate();
|
|
1184
|
+
if (result) return result;
|
|
1185
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
1186
|
+
throw new Error(`Timed out while waiting for ${label}.`);
|
|
1187
|
+
}
|
|
1188
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
async function waitForFileFieldValue(slug, sectionId, fieldKey, expectedValue) {
|
|
1193
|
+
await waitFor(async () => {
|
|
1194
|
+
const { json } = await readPageJson(slug);
|
|
1195
|
+
const section = Array.isArray(json.sections)
|
|
1196
|
+
? json.sections.find((item) => item?.id === sectionId)
|
|
1197
|
+
: null;
|
|
1198
|
+
return section?.data?.[fieldKey] === expectedValue;
|
|
1199
|
+
}, 8_000, `file field "${fieldKey}" = "${expectedValue}"`);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
async function ensureResponseOk(response, label) {
|
|
1203
|
+
if (!response.ok) {
|
|
1204
|
+
const text = await response.text();
|
|
1205
|
+
throw new Error(`${label} failed with ${response.status}: ${text}`);
|
|
1206
|
+
}
|
|
1207
|
+
return response;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async function fetchJson(relativePath, label) {
|
|
1211
|
+
const response = await ensureResponseOk(await fetch(`${baseUrl}${relativePath}`), label);
|
|
1212
|
+
return response.json();
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
async function selectTarget() {
|
|
1216
|
+
const siteIndex = await fetchJson('/mcp-manifest.json', 'Manifest index request');
|
|
1217
|
+
const requestedSlug = typeof process.env.WEBMCP_TARGET_SLUG === 'string' && process.env.WEBMCP_TARGET_SLUG.trim()
|
|
1218
|
+
? process.env.WEBMCP_TARGET_SLUG.trim()
|
|
1219
|
+
: null;
|
|
1220
|
+
|
|
1221
|
+
const candidatePages = requestedSlug
|
|
1222
|
+
? (siteIndex.pages ?? []).filter((page) => page?.slug === requestedSlug)
|
|
1223
|
+
: (siteIndex.pages ?? []);
|
|
1224
|
+
|
|
1225
|
+
for (const pageEntry of candidatePages) {
|
|
1226
|
+
if (!pageEntry?.slug || !pageEntry?.manifestHref || !pageEntry?.contractHref) continue;
|
|
1227
|
+
const pageManifest = await fetchJson(pageEntry.manifestHref, `Page manifest request for ${pageEntry.slug}`);
|
|
1228
|
+
const pageContract = await fetchJson(pageEntry.contractHref, `Page contract request for ${pageEntry.slug}`);
|
|
1229
|
+
const localInstances = Array.isArray(pageContract.sectionInstances)
|
|
1230
|
+
? pageContract.sectionInstances.filter((section) => section?.scope === 'local')
|
|
1231
|
+
: [];
|
|
1232
|
+
const tools = Array.isArray(pageManifest.tools) ? pageManifest.tools : [];
|
|
1233
|
+
|
|
1234
|
+
for (const tool of tools) {
|
|
1235
|
+
const sectionType = tool?.sectionType;
|
|
1236
|
+
if (typeof tool?.name !== 'string' || typeof sectionType !== 'string') continue;
|
|
1237
|
+
const targetInstance = localInstances.find((section) => section?.type === sectionType);
|
|
1238
|
+
if (!targetInstance?.id) continue;
|
|
1239
|
+
const targetFieldKey = findTopLevelStringField(pageContract.sectionSchemas?.[sectionType]);
|
|
1240
|
+
if (!targetFieldKey) continue;
|
|
1241
|
+
const pageState = await readPageJson(pageEntry.slug);
|
|
1242
|
+
const section = Array.isArray(pageState.json.sections)
|
|
1243
|
+
? pageState.json.sections.find((item) => item?.id === targetInstance.id)
|
|
1244
|
+
: null;
|
|
1245
|
+
const originalValue = section?.data?.[targetFieldKey];
|
|
1246
|
+
if (typeof originalValue !== 'string') continue;
|
|
1247
|
+
|
|
1248
|
+
return {
|
|
1249
|
+
slug: pageEntry.slug,
|
|
1250
|
+
manifestHref: pageEntry.manifestHref,
|
|
1251
|
+
contractHref: pageEntry.contractHref,
|
|
1252
|
+
toolName: tool.name,
|
|
1253
|
+
sectionId: targetInstance.id,
|
|
1254
|
+
fieldKey: targetFieldKey,
|
|
1255
|
+
originalValue,
|
|
1256
|
+
originalState: pageState,
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
throw new Error(
|
|
1262
|
+
requestedSlug
|
|
1263
|
+
? `No valid WebMCP verification target found for page "${requestedSlug}".`
|
|
1264
|
+
: 'No valid WebMCP verification target found in manifest index.'
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
async function main() {
|
|
1269
|
+
const { chromium } = await loadPlaywright();
|
|
1270
|
+
const target = await selectTarget();
|
|
1271
|
+
const nextValue = `${target.originalValue} WebMCP ${Date.now()}`;
|
|
1272
|
+
const browser = await chromium.launch({ headless: true });
|
|
1273
|
+
const page = await browser.newPage();
|
|
1274
|
+
const consoleEvents = [];
|
|
1275
|
+
let mutationApplied = false;
|
|
1276
|
+
|
|
1277
|
+
page.on('console', (message) => {
|
|
1278
|
+
if (message.type() === 'error' || message.type() === 'warning') {
|
|
1279
|
+
consoleEvents.push(`[console:${message.type()}] ${message.text()}`);
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
page.on('pageerror', (error) => {
|
|
1283
|
+
consoleEvents.push(`[pageerror] ${error.message}`);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
const restoreOriginal = async () => {
|
|
1287
|
+
try {
|
|
1288
|
+
await page.evaluate(
|
|
1289
|
+
async ({ toolName, slug, sectionId, fieldKey, value }) => {
|
|
1290
|
+
const runtime = navigator.modelContextTesting;
|
|
1291
|
+
if (!runtime?.executeTool) return;
|
|
1292
|
+
await runtime.executeTool(
|
|
1293
|
+
toolName,
|
|
1294
|
+
JSON.stringify({
|
|
1295
|
+
slug,
|
|
1296
|
+
sectionId,
|
|
1297
|
+
fieldKey,
|
|
1298
|
+
value,
|
|
1299
|
+
})
|
|
1300
|
+
);
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
toolName: target.toolName,
|
|
1304
|
+
slug: target.slug,
|
|
1305
|
+
sectionId: target.sectionId,
|
|
1306
|
+
fieldKey: target.fieldKey,
|
|
1307
|
+
value: target.originalValue,
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, target.originalValue);
|
|
1311
|
+
} catch {
|
|
1312
|
+
await fs.writeFile(target.originalState.pageFilePath, target.originalState.raw, 'utf8');
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
try {
|
|
1317
|
+
const pageManifest = await fetchJson(target.manifestHref, `Manifest request for ${target.slug}`);
|
|
1318
|
+
if (!Array.isArray(pageManifest.tools) || !pageManifest.tools.some((tool) => tool?.name === target.toolName)) {
|
|
1319
|
+
throw new Error(`Manifest does not expose ${target.toolName}.`);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const pageContract = await fetchJson(target.contractHref, `Contract request for ${target.slug}`);
|
|
1323
|
+
if (!Array.isArray(pageContract.tools) || !pageContract.tools.some((tool) => tool?.name === target.toolName)) {
|
|
1324
|
+
throw new Error(`Page contract does not expose ${target.toolName}.`);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
await page.goto(adminUrlFromSlug(target.slug), { waitUntil: 'networkidle' });
|
|
1328
|
+
|
|
1329
|
+
try {
|
|
1330
|
+
await page.waitForFunction(
|
|
1331
|
+
({ manifestHref, contractHref }) => {
|
|
1332
|
+
const manifestLink = document.head.querySelector('link[rel="mcp-manifest"]');
|
|
1333
|
+
const contractLink = document.head.querySelector('link[rel="olon-contract"]');
|
|
1334
|
+
return manifestLink?.getAttribute('href') === manifestHref
|
|
1335
|
+
&& contractLink?.getAttribute('href') === contractHref;
|
|
1336
|
+
},
|
|
1337
|
+
{ manifestHref: target.manifestHref, contractHref: target.contractHref },
|
|
1338
|
+
{ timeout: 10_000 }
|
|
1339
|
+
);
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
const diagnostics = await page.evaluate(() => ({
|
|
1342
|
+
head: document.head.innerHTML,
|
|
1343
|
+
bodyText: document.body.innerText,
|
|
1344
|
+
}));
|
|
1345
|
+
throw new Error(
|
|
1346
|
+
[
|
|
1347
|
+
error instanceof Error ? error.message : String(error),
|
|
1348
|
+
`head=${diagnostics.head}`,
|
|
1349
|
+
`body=${diagnostics.bodyText}`,
|
|
1350
|
+
...consoleEvents,
|
|
1351
|
+
].join('\n')
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const toolNames = await page.evaluate(() => {
|
|
1356
|
+
const runtime = navigator.modelContextTesting;
|
|
1357
|
+
return runtime?.listTools?.().map((tool) => tool.name) ?? [];
|
|
1358
|
+
});
|
|
1359
|
+
if (!toolNames.includes(target.toolName)) {
|
|
1360
|
+
throw new Error(`Runtime did not register ${target.toolName}. Found: ${toolNames.join(', ')}`);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const rawResult = await page.evaluate(
|
|
1364
|
+
async ({ toolName, slug, sectionId, fieldKey, value }) => {
|
|
1365
|
+
const runtime = navigator.modelContextTesting;
|
|
1366
|
+
if (!runtime?.executeTool) {
|
|
1367
|
+
throw new Error('navigator.modelContextTesting.executeTool is unavailable.');
|
|
1368
|
+
}
|
|
1369
|
+
return runtime.executeTool(
|
|
1370
|
+
toolName,
|
|
1371
|
+
JSON.stringify({
|
|
1372
|
+
slug,
|
|
1373
|
+
sectionId,
|
|
1374
|
+
fieldKey,
|
|
1375
|
+
value,
|
|
1376
|
+
})
|
|
1377
|
+
);
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
toolName: target.toolName,
|
|
1381
|
+
slug: target.slug,
|
|
1382
|
+
sectionId: target.sectionId,
|
|
1383
|
+
fieldKey: target.fieldKey,
|
|
1384
|
+
value: nextValue,
|
|
1385
|
+
}
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
const parsedResult = JSON.parse(rawResult);
|
|
1389
|
+
if (parsedResult?.isError) {
|
|
1390
|
+
throw new Error(`WebMCP tool returned an error: ${rawResult}`);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
mutationApplied = true;
|
|
1394
|
+
await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, nextValue);
|
|
1395
|
+
await page.frameLocator('iframe').getByText(nextValue, { exact: true }).waitFor({ state: 'attached' });
|
|
1396
|
+
|
|
1397
|
+
console.log(
|
|
1398
|
+
JSON.stringify({
|
|
1399
|
+
ok: true,
|
|
1400
|
+
slug: target.slug,
|
|
1401
|
+
manifestHref: target.manifestHref,
|
|
1402
|
+
contractHref: target.contractHref,
|
|
1403
|
+
toolName: target.toolName,
|
|
1404
|
+
sectionId: target.sectionId,
|
|
1405
|
+
fieldKey: target.fieldKey,
|
|
1406
|
+
toolNames,
|
|
1407
|
+
})
|
|
1408
|
+
);
|
|
1409
|
+
} finally {
|
|
1410
|
+
if (mutationApplied) {
|
|
1411
|
+
await restoreOriginal();
|
|
1412
|
+
}
|
|
1413
|
+
await browser.close();
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
main().catch((error) => {
|
|
1418
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
1419
|
+
process.exit(1);
|
|
1420
|
+
});
|
|
1421
|
+
|
|
919
1422
|
END_OF_FILE_CONTENT
|
|
920
1423
|
mkdir -p "src"
|
|
921
1424
|
echo "Creating src/App.tsx..."
|
|
@@ -1114,6 +1617,10 @@ function App() {
|
|
|
1114
1617
|
menuConfig,
|
|
1115
1618
|
themeCss: { tenant: fontsCss + '\n' + tenantCss },
|
|
1116
1619
|
addSection: addSectionConfig,
|
|
1620
|
+
webmcp: {
|
|
1621
|
+
enabled: true,
|
|
1622
|
+
namespace: typeof window !== 'undefined' ? window.location.href : '',
|
|
1623
|
+
},
|
|
1117
1624
|
persistence: {
|
|
1118
1625
|
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
1119
1626
|
if (isCloudMode) { await runCloudSave({ state, slug }, true); return; }
|
|
@@ -9945,6 +10452,170 @@ export function LeadSenderConfirmationEmail({
|
|
|
9945
10452
|
|
|
9946
10453
|
export default LeadSenderConfirmationEmail;
|
|
9947
10454
|
|
|
10455
|
+
END_OF_FILE_CONTENT
|
|
10456
|
+
echo "Creating src/entry-ssg.tsx..."
|
|
10457
|
+
cat << 'END_OF_FILE_CONTENT' > "src/entry-ssg.tsx"
|
|
10458
|
+
import { renderToString } from 'react-dom/server';
|
|
10459
|
+
import { StaticRouter } from 'react-router-dom/server';
|
|
10460
|
+
import { ConfigProvider, PageRenderer, StudioProvider, resolveRuntimeConfig } from '@olonjs/core';
|
|
10461
|
+
import type { JsonPagesConfig, MenuConfig, PageConfig, SiteConfig, ThemeConfig } from '@/types';
|
|
10462
|
+
import { ComponentRegistry } from '@/lib/ComponentRegistry';
|
|
10463
|
+
import { SECTION_SCHEMAS } from '@/lib/schemas';
|
|
10464
|
+
import { getFilePages } from '@/lib/getFilePages';
|
|
10465
|
+
import siteData from '@/data/config/site.json';
|
|
10466
|
+
import menuData from '@/data/config/menu.json';
|
|
10467
|
+
import themeData from '@/data/config/theme.json';
|
|
10468
|
+
import tenantCss from '@/index.css?inline';
|
|
10469
|
+
|
|
10470
|
+
const siteConfig = siteData as unknown as SiteConfig;
|
|
10471
|
+
const menuConfig: MenuConfig = { main: [] };
|
|
10472
|
+
const themeConfig = themeData as unknown as ThemeConfig;
|
|
10473
|
+
const pages = getFilePages();
|
|
10474
|
+
const refDocuments = {
|
|
10475
|
+
'menu.json': menuData,
|
|
10476
|
+
'config/menu.json': menuData,
|
|
10477
|
+
'src/data/config/menu.json': menuData,
|
|
10478
|
+
} satisfies NonNullable<JsonPagesConfig['refDocuments']>;
|
|
10479
|
+
|
|
10480
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
10481
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
10482
|
+
}
|
|
10483
|
+
|
|
10484
|
+
function normalizeSlug(input: string): string {
|
|
10485
|
+
return input.trim().toLowerCase().replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
10486
|
+
}
|
|
10487
|
+
|
|
10488
|
+
function getSortedSlugs(): string[] {
|
|
10489
|
+
return Object.keys(pages).sort((a, b) => a.localeCompare(b));
|
|
10490
|
+
}
|
|
10491
|
+
|
|
10492
|
+
function resolvePage(slug: string): { slug: string; page: PageConfig } {
|
|
10493
|
+
const normalized = normalizeSlug(slug);
|
|
10494
|
+
if (normalized && pages[normalized]) {
|
|
10495
|
+
return { slug: normalized, page: pages[normalized] };
|
|
10496
|
+
}
|
|
10497
|
+
|
|
10498
|
+
const slugs = getSortedSlugs();
|
|
10499
|
+
if (slugs.length === 0) {
|
|
10500
|
+
throw new Error('[SSG_CONFIG_ERROR] No pages found under src/data/pages');
|
|
10501
|
+
}
|
|
10502
|
+
|
|
10503
|
+
const home = slugs.find((item) => item === 'home');
|
|
10504
|
+
const fallbackSlug = home ?? slugs[0];
|
|
10505
|
+
return { slug: fallbackSlug, page: pages[fallbackSlug] };
|
|
10506
|
+
}
|
|
10507
|
+
|
|
10508
|
+
function flattenThemeTokens(
|
|
10509
|
+
input: unknown,
|
|
10510
|
+
pathSegments: string[] = [],
|
|
10511
|
+
out: Array<{ name: string; value: string }> = []
|
|
10512
|
+
): Array<{ name: string; value: string }> {
|
|
10513
|
+
if (typeof input === 'string') {
|
|
10514
|
+
const cleaned = input.trim();
|
|
10515
|
+
if (cleaned.length > 0 && pathSegments.length > 0) {
|
|
10516
|
+
out.push({ name: `--theme-${pathSegments.join('-')}`, value: cleaned });
|
|
10517
|
+
}
|
|
10518
|
+
return out;
|
|
10519
|
+
}
|
|
10520
|
+
|
|
10521
|
+
if (!isRecord(input)) return out;
|
|
10522
|
+
|
|
10523
|
+
const entries = Object.entries(input).sort(([a], [b]) => a.localeCompare(b));
|
|
10524
|
+
for (const [key, value] of entries) {
|
|
10525
|
+
flattenThemeTokens(value, [...pathSegments, key], out);
|
|
10526
|
+
}
|
|
10527
|
+
return out;
|
|
10528
|
+
}
|
|
10529
|
+
|
|
10530
|
+
function buildThemeCssFromSot(theme: ThemeConfig): string {
|
|
10531
|
+
const root: Record<string, unknown> = isRecord(theme) ? theme : {};
|
|
10532
|
+
const tokens = root['tokens'];
|
|
10533
|
+
const flattened = flattenThemeTokens(tokens);
|
|
10534
|
+
if (flattened.length === 0) return '';
|
|
10535
|
+
const serialized = flattened.map((item) => `${item.name}:${item.value}`).join(';');
|
|
10536
|
+
return `:root{${serialized}}`;
|
|
10537
|
+
}
|
|
10538
|
+
|
|
10539
|
+
function resolveTenantId(): string {
|
|
10540
|
+
const site: Record<string, unknown> = isRecord(siteConfig) ? siteConfig : {};
|
|
10541
|
+
const identityRaw = site['identity'];
|
|
10542
|
+
const identity: Record<string, unknown> = isRecord(identityRaw) ? identityRaw : {};
|
|
10543
|
+
const titleRaw = typeof identity.title === 'string' ? identity.title : '';
|
|
10544
|
+
const title = titleRaw.trim();
|
|
10545
|
+
if (title.length > 0) {
|
|
10546
|
+
const normalized = title.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
10547
|
+
if (normalized.length > 0) return normalized;
|
|
10548
|
+
}
|
|
10549
|
+
|
|
10550
|
+
const slugs = getSortedSlugs();
|
|
10551
|
+
if (slugs.length === 0) {
|
|
10552
|
+
throw new Error('[SSG_CONFIG_ERROR] Cannot resolve tenantId without site.identity.title or pages');
|
|
10553
|
+
}
|
|
10554
|
+
return slugs[0].replace(/\//g, '-');
|
|
10555
|
+
}
|
|
10556
|
+
|
|
10557
|
+
export function render(slug: string): string {
|
|
10558
|
+
const resolved = resolvePage(slug);
|
|
10559
|
+
const location = resolved.slug === 'home' ? '/' : `/${resolved.slug}`;
|
|
10560
|
+
const resolvedRuntime = resolveRuntimeConfig({
|
|
10561
|
+
pages,
|
|
10562
|
+
siteConfig,
|
|
10563
|
+
themeConfig,
|
|
10564
|
+
menuConfig,
|
|
10565
|
+
refDocuments,
|
|
10566
|
+
});
|
|
10567
|
+
const resolvedPage = resolvedRuntime.pages[resolved.slug] ?? resolved.page;
|
|
10568
|
+
|
|
10569
|
+
return renderToString(
|
|
10570
|
+
<StaticRouter location={location}>
|
|
10571
|
+
<ConfigProvider
|
|
10572
|
+
config={{
|
|
10573
|
+
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
10574
|
+
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
10575
|
+
tenantId: resolveTenantId(),
|
|
10576
|
+
}}
|
|
10577
|
+
>
|
|
10578
|
+
<StudioProvider mode="visitor">
|
|
10579
|
+
<PageRenderer
|
|
10580
|
+
pageConfig={resolvedPage}
|
|
10581
|
+
siteConfig={resolvedRuntime.siteConfig}
|
|
10582
|
+
menuConfig={resolvedRuntime.menuConfig}
|
|
10583
|
+
/>
|
|
10584
|
+
</StudioProvider>
|
|
10585
|
+
</ConfigProvider>
|
|
10586
|
+
</StaticRouter>
|
|
10587
|
+
);
|
|
10588
|
+
}
|
|
10589
|
+
|
|
10590
|
+
export function getCss(): string {
|
|
10591
|
+
const themeCss = buildThemeCssFromSot(themeConfig);
|
|
10592
|
+
if (!themeCss) return tenantCss;
|
|
10593
|
+
return `${themeCss}\n${tenantCss}`;
|
|
10594
|
+
}
|
|
10595
|
+
|
|
10596
|
+
export function getPageMeta(slug: string): { title: string; description: string } {
|
|
10597
|
+
const resolved = resolvePage(slug);
|
|
10598
|
+
const rawMeta = isRecord((resolved.page as unknown as { meta?: unknown }).meta)
|
|
10599
|
+
? ((resolved.page as unknown as { meta?: Record<string, unknown> }).meta as Record<string, unknown>)
|
|
10600
|
+
: {};
|
|
10601
|
+
|
|
10602
|
+
const title = typeof rawMeta.title === 'string' ? rawMeta.title : resolved.slug;
|
|
10603
|
+
const description = typeof rawMeta.description === 'string' ? rawMeta.description : '';
|
|
10604
|
+
return { title, description };
|
|
10605
|
+
}
|
|
10606
|
+
|
|
10607
|
+
export function getWebMcpBuildState(): {
|
|
10608
|
+
pages: Record<string, PageConfig>;
|
|
10609
|
+
schemas: JsonPagesConfig['schemas'];
|
|
10610
|
+
siteConfig: SiteConfig;
|
|
10611
|
+
} {
|
|
10612
|
+
return {
|
|
10613
|
+
pages,
|
|
10614
|
+
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
10615
|
+
siteConfig,
|
|
10616
|
+
};
|
|
10617
|
+
}
|
|
10618
|
+
|
|
9948
10619
|
END_OF_FILE_CONTENT
|
|
9949
10620
|
echo "Creating src/fonts.css..."
|
|
9950
10621
|
cat << 'END_OF_FILE_CONTENT' > "src/fonts.css"
|
|
@@ -10919,14 +11590,14 @@ END_OF_FILE_CONTENT
|
|
|
10919
11590
|
echo "Creating vite.config.ts..."
|
|
10920
11591
|
cat << 'END_OF_FILE_CONTENT' > "vite.config.ts"
|
|
10921
11592
|
/**
|
|
10922
|
-
* Generated by @
|
|
11593
|
+
* Generated by @jsonpages/cli. Dev server API: /api/save-to-file, /api/upload-asset, /api/list-assets.
|
|
10923
11594
|
*/
|
|
10924
11595
|
import { defineConfig } from 'vite';
|
|
10925
11596
|
import react from '@vitejs/plugin-react';
|
|
10926
11597
|
import tailwindcss from '@tailwindcss/vite';
|
|
10927
11598
|
import path from 'path';
|
|
10928
11599
|
import fs from 'fs';
|
|
10929
|
-
import { fileURLToPath } from 'url';
|
|
11600
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
10930
11601
|
|
|
10931
11602
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10932
11603
|
|
|
@@ -10974,6 +11645,20 @@ function isTenantPageJsonRequest(req, pathname) {
|
|
|
10974
11645
|
const viteOrStaticPrefixes = ['/api/', '/assets/', '/src/', '/node_modules/', '/public/', '/@'];
|
|
10975
11646
|
return !viteOrStaticPrefixes.some((prefix) => pathname.startsWith(prefix));
|
|
10976
11647
|
}
|
|
11648
|
+
|
|
11649
|
+
function normalizeManifestSlug(raw) {
|
|
11650
|
+
return decodeURIComponent(raw || '')
|
|
11651
|
+
.replace(/^\/+|\/+$/g, '')
|
|
11652
|
+
.replace(/\\/g, '/')
|
|
11653
|
+
.replace(/(\.schema)?\.json$/i, '');
|
|
11654
|
+
}
|
|
11655
|
+
|
|
11656
|
+
async function loadWebMcpBuilders() {
|
|
11657
|
+
const moduleUrl = pathToFileURL(
|
|
11658
|
+
path.resolve(__dirname, '..', '..', 'packages', 'core', 'src', 'lib', 'webmcp-contracts.mjs')
|
|
11659
|
+
).href;
|
|
11660
|
+
return import(moduleUrl);
|
|
11661
|
+
}
|
|
10977
11662
|
export default defineConfig({
|
|
10978
11663
|
plugins: [
|
|
10979
11664
|
react(),
|
|
@@ -10985,6 +11670,75 @@ export default defineConfig({
|
|
|
10985
11670
|
const pathname = (req.url || '').split('?')[0];
|
|
10986
11671
|
const isPageJsonRequest = isTenantPageJsonRequest(req, pathname);
|
|
10987
11672
|
|
|
11673
|
+
const handleManifestRequest = async () => {
|
|
11674
|
+
const { buildPageContract, buildPageManifest, buildSiteManifest } = await loadWebMcpBuilders();
|
|
11675
|
+
const ssrEntry = await server.ssrLoadModule('/src/entry-ssg.tsx');
|
|
11676
|
+
const buildState = ssrEntry.getWebMcpBuildState();
|
|
11677
|
+
|
|
11678
|
+
if (req.method === 'GET' && pathname === '/mcp-manifest.json') {
|
|
11679
|
+
sendJson(res, 200, buildSiteManifest({
|
|
11680
|
+
pages: buildState.pages,
|
|
11681
|
+
schemas: buildState.schemas,
|
|
11682
|
+
siteConfig: buildState.siteConfig,
|
|
11683
|
+
}));
|
|
11684
|
+
return true;
|
|
11685
|
+
}
|
|
11686
|
+
|
|
11687
|
+
const pageManifestMatch = pathname.match(/^\/mcp-manifests\/(.+)\.json$/i);
|
|
11688
|
+
if (pageManifestMatch && req.method === 'GET') {
|
|
11689
|
+
const slug = normalizeManifestSlug(pageManifestMatch[1]);
|
|
11690
|
+
const pageConfig = buildState.pages[slug];
|
|
11691
|
+
if (!pageConfig) {
|
|
11692
|
+
sendJson(res, 404, { error: 'Page manifest not found' });
|
|
11693
|
+
return true;
|
|
11694
|
+
}
|
|
11695
|
+
|
|
11696
|
+
sendJson(res, 200, buildPageManifest({
|
|
11697
|
+
slug,
|
|
11698
|
+
pageConfig,
|
|
11699
|
+
schemas: buildState.schemas,
|
|
11700
|
+
siteConfig: buildState.siteConfig,
|
|
11701
|
+
}));
|
|
11702
|
+
return true;
|
|
11703
|
+
}
|
|
11704
|
+
|
|
11705
|
+
const schemaMatch = pathname.match(/^\/schemas\/(.+)\.schema\.json$/i);
|
|
11706
|
+
if (!schemaMatch || req.method !== 'GET') return false;
|
|
11707
|
+
|
|
11708
|
+
const slug = normalizeManifestSlug(schemaMatch[1]);
|
|
11709
|
+
const pageConfig = buildState.pages[slug];
|
|
11710
|
+
if (!pageConfig) {
|
|
11711
|
+
sendJson(res, 404, { error: 'Schema contract not found' });
|
|
11712
|
+
return true;
|
|
11713
|
+
}
|
|
11714
|
+
|
|
11715
|
+
sendJson(res, 200, buildPageContract({
|
|
11716
|
+
slug,
|
|
11717
|
+
pageConfig,
|
|
11718
|
+
schemas: buildState.schemas,
|
|
11719
|
+
siteConfig: buildState.siteConfig,
|
|
11720
|
+
}));
|
|
11721
|
+
return true;
|
|
11722
|
+
};
|
|
11723
|
+
|
|
11724
|
+
if (
|
|
11725
|
+
req.method === 'GET' &&
|
|
11726
|
+
(
|
|
11727
|
+
pathname === '/mcp-manifest.json'
|
|
11728
|
+
|| /^\/mcp-manifests\/.+\.json$/i.test(pathname)
|
|
11729
|
+
|| /^\/schemas\/.+\.schema\.json$/i.test(pathname)
|
|
11730
|
+
)
|
|
11731
|
+
) {
|
|
11732
|
+
void handleManifestRequest()
|
|
11733
|
+
.then((handled) => {
|
|
11734
|
+
if (!handled) next();
|
|
11735
|
+
})
|
|
11736
|
+
.catch((error) => {
|
|
11737
|
+
sendJson(res, 500, { error: error?.message || 'Manifest generation failed' });
|
|
11738
|
+
});
|
|
11739
|
+
return;
|
|
11740
|
+
}
|
|
11741
|
+
|
|
10988
11742
|
if (isPageJsonRequest) {
|
|
10989
11743
|
const normalizedPath = decodeURIComponent(pathname).replace(/\\/g, '/');
|
|
10990
11744
|
const slug = normalizedPath.replace(/^\/+/, '').replace(/\.json$/i, '').replace(/^\/+|\/+$/g, '');
|
|
@@ -11015,7 +11769,6 @@ export default defineConfig({
|
|
|
11015
11769
|
if (!fs.existsSync(DATA_PAGES_DIR)) fs.mkdirSync(DATA_PAGES_DIR, { recursive: true });
|
|
11016
11770
|
if (projectState.site != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'site.json'), JSON.stringify(projectState.site, null, 2), 'utf8');
|
|
11017
11771
|
if (projectState.theme != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'theme.json'), JSON.stringify(projectState.theme, null, 2), 'utf8');
|
|
11018
|
-
if (projectState.menu != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'menu.json'), JSON.stringify(projectState.menu, null, 2), 'utf8');
|
|
11019
11772
|
if (projectState.page != null) {
|
|
11020
11773
|
const safeSlug = (slug.replace(/[^a-zA-Z0-9-_]/g, '_') || 'page');
|
|
11021
11774
|
fs.writeFileSync(path.join(DATA_PAGES_DIR, `${safeSlug}.json`), JSON.stringify(projectState.page, null, 2), 'utf8');
|
|
@@ -11048,7 +11801,24 @@ export default defineConfig({
|
|
|
11048
11801
|
},
|
|
11049
11802
|
},
|
|
11050
11803
|
],
|
|
11051
|
-
resolve: {
|
|
11804
|
+
resolve: {
|
|
11805
|
+
alias: {
|
|
11806
|
+
'@': path.resolve(__dirname, './src'),
|
|
11807
|
+
'@olonjs/core': path.resolve(__dirname, '..', '..', 'packages', 'core', 'src', 'index.ts'),
|
|
11808
|
+
'next/link': path.resolve(__dirname, './src/shims/next-link.tsx'),
|
|
11809
|
+
},
|
|
11810
|
+
},
|
|
11811
|
+
server: {
|
|
11812
|
+
fs: {
|
|
11813
|
+
allow: [
|
|
11814
|
+
path.resolve(__dirname, '..', '..'),
|
|
11815
|
+
],
|
|
11816
|
+
},
|
|
11817
|
+
watch: {
|
|
11818
|
+
usePolling: true,
|
|
11819
|
+
interval: 300,
|
|
11820
|
+
},
|
|
11821
|
+
},
|
|
11052
11822
|
});
|
|
11053
11823
|
|
|
11054
11824
|
|