@olonjs/cli 3.0.100 → 3.0.104
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 +507 -507
- package/assets/templates/agritourism/src_tenant.sh +299 -302
- package/assets/templates/alpha/src_tenant.sh +507 -507
- package/package.json +1 -1
|
@@ -551,7 +551,6 @@ Senza questo, in Studio l'overlay non si vede nell'iframe.
|
|
|
551
551
|
Usando questo percorso hai **governance** piena: tipi, schema, editor, Add Section e overlay allineati alle spec v1.2. Per le versioni con tutti i "Perché servono" usa il file **JSONPAGES_Specs_v1.2_completo.md**.
|
|
552
552
|
|
|
553
553
|
END_OF_FILE_CONTENT
|
|
554
|
-
mkdir -p "docs/ver"
|
|
555
554
|
echo "Creating index.html..."
|
|
556
555
|
cat << 'END_OF_FILE_CONTENT' > "index.html"
|
|
557
556
|
<!DOCTYPE html>
|
|
@@ -597,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
597
596
|
"@tiptap/extension-link": "^2.11.5",
|
|
598
597
|
"@tiptap/react": "^2.11.5",
|
|
599
598
|
"@tiptap/starter-kit": "^2.11.5",
|
|
600
|
-
"@olonjs/core": "^1.0.
|
|
599
|
+
"@olonjs/core": "^1.0.91",
|
|
601
600
|
"clsx": "^2.1.1",
|
|
602
601
|
"lucide-react": "^0.474.0",
|
|
603
602
|
"react": "^19.0.0",
|
|
@@ -1120,304 +1119,304 @@ console.log('[sync-pages-to-public] Synced pages to public/pages');
|
|
|
1120
1119
|
END_OF_FILE_CONTENT
|
|
1121
1120
|
echo "Creating scripts/webmcp-feature-check.mjs..."
|
|
1122
1121
|
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
|
-
});
|
|
1122
|
+
import fs from 'fs/promises';
|
|
1123
|
+
import path from 'path';
|
|
1124
|
+
import { fileURLToPath } from 'url';
|
|
1125
|
+
import { createRequire } from 'module';
|
|
1126
|
+
|
|
1127
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1128
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
1129
|
+
const baseUrl = process.env.WEBMCP_BASE_URL ?? 'http://127.0.0.1:4173';
|
|
1130
|
+
|
|
1131
|
+
function pageFilePathFromSlug(slug) {
|
|
1132
|
+
return path.resolve(rootDir, 'src', 'data', 'pages', `${slug}.json`);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function adminUrlFromSlug(slug) {
|
|
1136
|
+
return `${baseUrl}/admin${slug === 'home' ? '' : `/${slug}`}`;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function isStringSchema(schema) {
|
|
1140
|
+
if (!schema || typeof schema !== 'object') return false;
|
|
1141
|
+
if (schema.type === 'string') return true;
|
|
1142
|
+
if (Array.isArray(schema.anyOf)) {
|
|
1143
|
+
return schema.anyOf.some((entry) => entry && typeof entry === 'object' && entry.type === 'string');
|
|
1144
|
+
}
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function findTopLevelStringField(sectionSchema) {
|
|
1149
|
+
const properties = sectionSchema?.properties;
|
|
1150
|
+
if (!properties || typeof properties !== 'object') return null;
|
|
1151
|
+
const preferred = ['title', 'sectionTitle', 'label', 'headline', 'name'];
|
|
1152
|
+
for (const key of preferred) {
|
|
1153
|
+
if (isStringSchema(properties[key])) return key;
|
|
1154
|
+
}
|
|
1155
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
1156
|
+
if (isStringSchema(value)) return key;
|
|
1157
|
+
}
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
async function loadPlaywright() {
|
|
1162
|
+
const require = createRequire(import.meta.url);
|
|
1163
|
+
try {
|
|
1164
|
+
return require('playwright');
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1167
|
+
throw new Error(
|
|
1168
|
+
`Playwright is required for WebMCP verification. Install it before running this script. Original error: ${message}`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
async function readPageJson(slug) {
|
|
1174
|
+
const pageFilePath = pageFilePathFromSlug(slug);
|
|
1175
|
+
const raw = await fs.readFile(pageFilePath, 'utf8');
|
|
1176
|
+
return { raw, json: JSON.parse(raw), pageFilePath };
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
async function waitFor(predicate, timeoutMs, label) {
|
|
1180
|
+
const startedAt = Date.now();
|
|
1181
|
+
for (;;) {
|
|
1182
|
+
const result = await predicate();
|
|
1183
|
+
if (result) return result;
|
|
1184
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
1185
|
+
throw new Error(`Timed out while waiting for ${label}.`);
|
|
1186
|
+
}
|
|
1187
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
async function waitForFileFieldValue(slug, sectionId, fieldKey, expectedValue) {
|
|
1192
|
+
await waitFor(async () => {
|
|
1193
|
+
const { json } = await readPageJson(slug);
|
|
1194
|
+
const section = Array.isArray(json.sections)
|
|
1195
|
+
? json.sections.find((item) => item?.id === sectionId)
|
|
1196
|
+
: null;
|
|
1197
|
+
return section?.data?.[fieldKey] === expectedValue;
|
|
1198
|
+
}, 8_000, `file field "${fieldKey}" = "${expectedValue}"`);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
async function ensureResponseOk(response, label) {
|
|
1202
|
+
if (!response.ok) {
|
|
1203
|
+
const text = await response.text();
|
|
1204
|
+
throw new Error(`${label} failed with ${response.status}: ${text}`);
|
|
1205
|
+
}
|
|
1206
|
+
return response;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
async function fetchJson(relativePath, label) {
|
|
1210
|
+
const response = await ensureResponseOk(await fetch(`${baseUrl}${relativePath}`), label);
|
|
1211
|
+
return response.json();
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
async function selectTarget() {
|
|
1215
|
+
const siteIndex = await fetchJson('/mcp-manifest.json', 'Manifest index request');
|
|
1216
|
+
const requestedSlug = typeof process.env.WEBMCP_TARGET_SLUG === 'string' && process.env.WEBMCP_TARGET_SLUG.trim()
|
|
1217
|
+
? process.env.WEBMCP_TARGET_SLUG.trim()
|
|
1218
|
+
: null;
|
|
1219
|
+
|
|
1220
|
+
const candidatePages = requestedSlug
|
|
1221
|
+
? (siteIndex.pages ?? []).filter((page) => page?.slug === requestedSlug)
|
|
1222
|
+
: (siteIndex.pages ?? []);
|
|
1223
|
+
|
|
1224
|
+
for (const pageEntry of candidatePages) {
|
|
1225
|
+
if (!pageEntry?.slug || !pageEntry?.manifestHref || !pageEntry?.contractHref) continue;
|
|
1226
|
+
const pageManifest = await fetchJson(pageEntry.manifestHref, `Page manifest request for ${pageEntry.slug}`);
|
|
1227
|
+
const pageContract = await fetchJson(pageEntry.contractHref, `Page contract request for ${pageEntry.slug}`);
|
|
1228
|
+
const localInstances = Array.isArray(pageContract.sectionInstances)
|
|
1229
|
+
? pageContract.sectionInstances.filter((section) => section?.scope === 'local')
|
|
1230
|
+
: [];
|
|
1231
|
+
const tools = Array.isArray(pageManifest.tools) ? pageManifest.tools : [];
|
|
1232
|
+
|
|
1233
|
+
for (const tool of tools) {
|
|
1234
|
+
const sectionType = tool?.sectionType;
|
|
1235
|
+
if (typeof tool?.name !== 'string' || typeof sectionType !== 'string') continue;
|
|
1236
|
+
const targetInstance = localInstances.find((section) => section?.type === sectionType);
|
|
1237
|
+
if (!targetInstance?.id) continue;
|
|
1238
|
+
const targetFieldKey = findTopLevelStringField(pageContract.sectionSchemas?.[sectionType]);
|
|
1239
|
+
if (!targetFieldKey) continue;
|
|
1240
|
+
const pageState = await readPageJson(pageEntry.slug);
|
|
1241
|
+
const section = Array.isArray(pageState.json.sections)
|
|
1242
|
+
? pageState.json.sections.find((item) => item?.id === targetInstance.id)
|
|
1243
|
+
: null;
|
|
1244
|
+
const originalValue = section?.data?.[targetFieldKey];
|
|
1245
|
+
if (typeof originalValue !== 'string') continue;
|
|
1246
|
+
|
|
1247
|
+
return {
|
|
1248
|
+
slug: pageEntry.slug,
|
|
1249
|
+
manifestHref: pageEntry.manifestHref,
|
|
1250
|
+
contractHref: pageEntry.contractHref,
|
|
1251
|
+
toolName: tool.name,
|
|
1252
|
+
sectionId: targetInstance.id,
|
|
1253
|
+
fieldKey: targetFieldKey,
|
|
1254
|
+
originalValue,
|
|
1255
|
+
originalState: pageState,
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
throw new Error(
|
|
1261
|
+
requestedSlug
|
|
1262
|
+
? `No valid WebMCP verification target found for page "${requestedSlug}".`
|
|
1263
|
+
: 'No valid WebMCP verification target found in manifest index.'
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async function main() {
|
|
1268
|
+
const { chromium } = await loadPlaywright();
|
|
1269
|
+
const target = await selectTarget();
|
|
1270
|
+
const nextValue = `${target.originalValue} WebMCP ${Date.now()}`;
|
|
1271
|
+
const browser = await chromium.launch({ headless: true });
|
|
1272
|
+
const page = await browser.newPage();
|
|
1273
|
+
const consoleEvents = [];
|
|
1274
|
+
let mutationApplied = false;
|
|
1275
|
+
|
|
1276
|
+
page.on('console', (message) => {
|
|
1277
|
+
if (message.type() === 'error' || message.type() === 'warning') {
|
|
1278
|
+
consoleEvents.push(`[console:${message.type()}] ${message.text()}`);
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
page.on('pageerror', (error) => {
|
|
1282
|
+
consoleEvents.push(`[pageerror] ${error.message}`);
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
const restoreOriginal = async () => {
|
|
1286
|
+
try {
|
|
1287
|
+
await page.evaluate(
|
|
1288
|
+
async ({ toolName, slug, sectionId, fieldKey, value }) => {
|
|
1289
|
+
const runtime = navigator.modelContextTesting;
|
|
1290
|
+
if (!runtime?.executeTool) return;
|
|
1291
|
+
await runtime.executeTool(
|
|
1292
|
+
toolName,
|
|
1293
|
+
JSON.stringify({
|
|
1294
|
+
slug,
|
|
1295
|
+
sectionId,
|
|
1296
|
+
fieldKey,
|
|
1297
|
+
value,
|
|
1298
|
+
})
|
|
1299
|
+
);
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
toolName: target.toolName,
|
|
1303
|
+
slug: target.slug,
|
|
1304
|
+
sectionId: target.sectionId,
|
|
1305
|
+
fieldKey: target.fieldKey,
|
|
1306
|
+
value: target.originalValue,
|
|
1307
|
+
}
|
|
1308
|
+
);
|
|
1309
|
+
await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, target.originalValue);
|
|
1310
|
+
} catch {
|
|
1311
|
+
await fs.writeFile(target.originalState.pageFilePath, target.originalState.raw, 'utf8');
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
try {
|
|
1316
|
+
const pageManifest = await fetchJson(target.manifestHref, `Manifest request for ${target.slug}`);
|
|
1317
|
+
if (!Array.isArray(pageManifest.tools) || !pageManifest.tools.some((tool) => tool?.name === target.toolName)) {
|
|
1318
|
+
throw new Error(`Manifest does not expose ${target.toolName}.`);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const pageContract = await fetchJson(target.contractHref, `Contract request for ${target.slug}`);
|
|
1322
|
+
if (!Array.isArray(pageContract.tools) || !pageContract.tools.some((tool) => tool?.name === target.toolName)) {
|
|
1323
|
+
throw new Error(`Page contract does not expose ${target.toolName}.`);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
await page.goto(adminUrlFromSlug(target.slug), { waitUntil: 'networkidle' });
|
|
1327
|
+
|
|
1328
|
+
try {
|
|
1329
|
+
await page.waitForFunction(
|
|
1330
|
+
({ manifestHref, contractHref }) => {
|
|
1331
|
+
const manifestLink = document.head.querySelector('link[rel="mcp-manifest"]');
|
|
1332
|
+
const contractLink = document.head.querySelector('link[rel="olon-contract"]');
|
|
1333
|
+
return manifestLink?.getAttribute('href') === manifestHref
|
|
1334
|
+
&& contractLink?.getAttribute('href') === contractHref;
|
|
1335
|
+
},
|
|
1336
|
+
{ manifestHref: target.manifestHref, contractHref: target.contractHref },
|
|
1337
|
+
{ timeout: 10_000 }
|
|
1338
|
+
);
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
const diagnostics = await page.evaluate(() => ({
|
|
1341
|
+
head: document.head.innerHTML,
|
|
1342
|
+
bodyText: document.body.innerText,
|
|
1343
|
+
}));
|
|
1344
|
+
throw new Error(
|
|
1345
|
+
[
|
|
1346
|
+
error instanceof Error ? error.message : String(error),
|
|
1347
|
+
`head=${diagnostics.head}`,
|
|
1348
|
+
`body=${diagnostics.bodyText}`,
|
|
1349
|
+
...consoleEvents,
|
|
1350
|
+
].join('\n')
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const toolNames = await page.evaluate(() => {
|
|
1355
|
+
const runtime = navigator.modelContextTesting;
|
|
1356
|
+
return runtime?.listTools?.().map((tool) => tool.name) ?? [];
|
|
1357
|
+
});
|
|
1358
|
+
if (!toolNames.includes(target.toolName)) {
|
|
1359
|
+
throw new Error(`Runtime did not register ${target.toolName}. Found: ${toolNames.join(', ')}`);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const rawResult = await page.evaluate(
|
|
1363
|
+
async ({ toolName, slug, sectionId, fieldKey, value }) => {
|
|
1364
|
+
const runtime = navigator.modelContextTesting;
|
|
1365
|
+
if (!runtime?.executeTool) {
|
|
1366
|
+
throw new Error('navigator.modelContextTesting.executeTool is unavailable.');
|
|
1367
|
+
}
|
|
1368
|
+
return runtime.executeTool(
|
|
1369
|
+
toolName,
|
|
1370
|
+
JSON.stringify({
|
|
1371
|
+
slug,
|
|
1372
|
+
sectionId,
|
|
1373
|
+
fieldKey,
|
|
1374
|
+
value,
|
|
1375
|
+
})
|
|
1376
|
+
);
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
toolName: target.toolName,
|
|
1380
|
+
slug: target.slug,
|
|
1381
|
+
sectionId: target.sectionId,
|
|
1382
|
+
fieldKey: target.fieldKey,
|
|
1383
|
+
value: nextValue,
|
|
1384
|
+
}
|
|
1385
|
+
);
|
|
1386
|
+
|
|
1387
|
+
const parsedResult = JSON.parse(rawResult);
|
|
1388
|
+
if (parsedResult?.isError) {
|
|
1389
|
+
throw new Error(`WebMCP tool returned an error: ${rawResult}`);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
mutationApplied = true;
|
|
1393
|
+
await waitForFileFieldValue(target.slug, target.sectionId, target.fieldKey, nextValue);
|
|
1394
|
+
await page.frameLocator('iframe').getByText(nextValue, { exact: true }).waitFor({ state: 'attached' });
|
|
1395
|
+
|
|
1396
|
+
console.log(
|
|
1397
|
+
JSON.stringify({
|
|
1398
|
+
ok: true,
|
|
1399
|
+
slug: target.slug,
|
|
1400
|
+
manifestHref: target.manifestHref,
|
|
1401
|
+
contractHref: target.contractHref,
|
|
1402
|
+
toolName: target.toolName,
|
|
1403
|
+
sectionId: target.sectionId,
|
|
1404
|
+
fieldKey: target.fieldKey,
|
|
1405
|
+
toolNames,
|
|
1406
|
+
})
|
|
1407
|
+
);
|
|
1408
|
+
} finally {
|
|
1409
|
+
if (mutationApplied) {
|
|
1410
|
+
await restoreOriginal();
|
|
1411
|
+
}
|
|
1412
|
+
await browser.close();
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
main().catch((error) => {
|
|
1417
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
});
|
|
1421
1420
|
|
|
1422
1421
|
END_OF_FILE_CONTENT
|
|
1423
1422
|
mkdir -p "src"
|
|
@@ -4750,7 +4749,6 @@ export type ImageBreakData = z.infer<typeof ImageBreakSchema>;
|
|
|
4750
4749
|
export type ImageBreakSettings = Record<string, unknown>;
|
|
4751
4750
|
|
|
4752
4751
|
END_OF_FILE_CONTENT
|
|
4753
|
-
mkdir -p "src/components/image-test"
|
|
4754
4752
|
mkdir -p "src/components/kitchen-showcase"
|
|
4755
4753
|
echo "Creating src/components/kitchen-showcase/View.tsx..."
|
|
4756
4754
|
cat << 'END_OF_FILE_CONTENT' > "src/components/kitchen-showcase/View.tsx"
|
|
@@ -11446,7 +11444,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
11446
11444
|
|
|
11447
11445
|
END_OF_FILE_CONTENT
|
|
11448
11446
|
# SKIP: src/registry-types.ts is binary and cannot be embedded as text.
|
|
11449
|
-
mkdir -p "src/server"
|
|
11450
11447
|
mkdir -p "src/types"
|
|
11451
11448
|
echo "Creating src/types.ts..."
|
|
11452
11449
|
cat << 'END_OF_FILE_CONTENT' > "src/types.ts"
|