MCPcopy Create free account
hub / github.com/UsefulSoftwareCo/executor / mcpAppsShellAsset

Function mcpAppsShellAsset

packages/hosts/mcp-apps-shell/src/vite.ts:289–411  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

287 * a rebuild, matching how `workerBundlerArtifact` addresses its own artifacts.
288 */
289export const mcpAppsShellAsset = (): McpAppsShellAssetVitePlugin => {
290 let command: "build" | "serve" = "build";
291 let ssr = false;
292 let cached: { readonly html: string; readonly url: string } | undefined;
293
294 const load = async (): Promise<{ readonly html: string; readonly url: string }> => {
295 if (cached) return cached;
296 const html = await buildMcpAppsShellHtml();
297 // Build-blocking: runtime loaders authenticate the document by this
298 // fragment (an ASSETS-binding miss under SPA not-found handling answers
299 // with index.html and a 200), so a shell that lost it would make every
300 // deployed resource read fail. Catch that here, in the build.
301 if (!html.includes(MCP_APPS_SHELL_DOCUMENT_MARKER)) {
302 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: Vite plugin hooks report build failures by throwing
303 throw new Error(
304 `Built MCP-Apps shell is missing its identity marker (${MCP_APPS_SHELL_DOCUMENT_MARKER}); ` +
305 "check the <meta> in src/shell/mcp-app.html.",
306 );
307 }
308 const digest = createHash("sha256").update(html).digest("hex").slice(0, 12);
309 cached = { html, url: `/assets/executor-mcp-apps-shell-${digest}.html` };
310 return cached;
311 };
312
313 return {
314 name: "executor-mcp-apps-shell-asset",
315 enforce: "pre",
316 configResolved(config) {
317 command = config.command;
318 // Recorded per environment, but ONE plugin instance sees every
319 // environment's `configResolved` before any of their bundles are
320 // generated — so the last write wins and this cannot be trusted at emit
321 // time. `generateBundle` reads Rollup's own output options instead; this
322 // only carries the single-environment case, where the two agree.
323 ssr = Boolean(config.build?.ssr);
324 },
325 resolveId: (id) => {
326 if (id === SHELL_VIRTUAL_ID) return SHELL_RESOLVED_ID;
327 if (id === DEV_HTML_VIRTUAL_ID) return DEV_HTML_RESOLVED_ID;
328 return undefined;
329 },
330 load: async (id) => {
331 if (id === DEV_HTML_RESOLVED_ID) {
332 // The Worker's dev-only escape hatch: under `build` it exports
333 // `undefined` and `makeAssetsShellHtmlLoader` uses the ASSETS binding;
334 // under `serve` it carries the built document inline, because no
335 // assets have been emitted for the binding to find. Guarded the same
336 // way as the URL module below — the build must not run at startup.
337 if (command !== "serve") {
338 return "export const devShellHtml = undefined;\nexport default devShellHtml;\n";
339 }
340 const { html } = await load();
341 return `export const devShellHtml = ${JSON.stringify(html)};\nexport default devShellHtml;\n`;
342 }
343 if (id !== SHELL_RESOLVED_ID) return undefined;
344 // Resolving the virtual module is part of Vite's startup graph. Building
345 // the multi-megabyte shell here makes every dev server pay that cost
346 // before it can answer even an unrelated route. In serve mode the

Callers 5

vite.test.tsFile · 0.90
appPluginFunction · 0.90
vite.config.tsFile · 0.90
vite.config.tsFile · 0.90
vite.config.tsFile · 0.90

Calls 1

loadFunction · 0.70

Tested by

no test coverage detected