@apex-stack/core 0.1.10 → 0.1.12
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/dist/build-QRHQUUZC.js +235 -0
- package/dist/chunk-2L2T47AH.js +93 -0
- package/dist/chunk-4VG3CZ6H.js +21 -0
- package/dist/chunk-DSUIB3JH.js +144 -0
- package/dist/chunk-PAMD24NK.js +171 -0
- package/dist/chunk-SH3XEJGV.js +239 -0
- package/dist/cli.js +23 -664
- package/dist/dev-V3LVSS5L.js +38 -0
- package/dist/index.js +8 -3
- package/dist/make-4LINTKZH.js +89 -0
- package/dist/mcp-DL4J6JFJ.js +56 -0
- package/dist/migrate-NOGFOFV2.js +38 -0
- package/dist/server-ODH3M2IG.js +9 -0
- package/dist/start-VJJXI4W3.js +132 -0
- package/package.json +1 -1
- package/dist/chunk-IEXQ7E5C.js +0 -426
package/dist/chunk-IEXQ7E5C.js
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
// src/api/resource.ts
|
|
2
|
-
function isApexResource(x) {
|
|
3
|
-
return typeof x === "object" && x !== null && x.__apexResource === true;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
// src/dev/renderPage.ts
|
|
7
|
-
import { renderComponent, stateIsland } from "@apex-stack/kit";
|
|
8
|
-
async function renderPage(opts) {
|
|
9
|
-
const mod = await opts.loadModule(opts.pageId);
|
|
10
|
-
const loaderData = await mod.loader({ params: opts.params ?? {}, url: opts.url }) ?? {};
|
|
11
|
-
const { html } = renderComponent({
|
|
12
|
-
template: mod.template,
|
|
13
|
-
rootXData: mod.rootXData,
|
|
14
|
-
componentId: mod.componentId,
|
|
15
|
-
scopeId: mod.scopeId,
|
|
16
|
-
loaderData,
|
|
17
|
-
registry: opts.registry
|
|
18
|
-
});
|
|
19
|
-
const doc = shell({
|
|
20
|
-
body: html,
|
|
21
|
-
island: stateIsland(mod.componentId, loaderData),
|
|
22
|
-
css: mod.css + (opts.componentCss ?? ""),
|
|
23
|
-
pageId: opts.pageId,
|
|
24
|
-
clientHref: opts.clientHref
|
|
25
|
-
});
|
|
26
|
-
return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
|
|
27
|
-
}
|
|
28
|
-
function shell({ body, island, css, pageId, clientHref }) {
|
|
29
|
-
const clientScript = clientHref ? `<script type="module" src="${clientHref}"></script>` : `<script type="module">
|
|
30
|
-
import Alpine from 'alpinejs'
|
|
31
|
-
import ${JSON.stringify(pageId)}
|
|
32
|
-
window.Alpine = Alpine
|
|
33
|
-
Alpine.start()
|
|
34
|
-
</script>`;
|
|
35
|
-
return `<!DOCTYPE html>
|
|
36
|
-
<html lang="en">
|
|
37
|
-
<head>
|
|
38
|
-
<meta charset="utf-8" />
|
|
39
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
40
|
-
<title>Apex JS</title>
|
|
41
|
-
<style>${css}</style>
|
|
42
|
-
</head>
|
|
43
|
-
<body>
|
|
44
|
-
${body}
|
|
45
|
-
${island}
|
|
46
|
-
${clientScript}
|
|
47
|
-
</body>
|
|
48
|
-
</html>`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// src/dev/server.ts
|
|
52
|
-
import { createServer as createHttpServer } from "http";
|
|
53
|
-
import { apex } from "@apex-stack/vite";
|
|
54
|
-
import {
|
|
55
|
-
createApp,
|
|
56
|
-
defineEventHandler as defineEventHandler3,
|
|
57
|
-
fromNodeMiddleware,
|
|
58
|
-
setResponseHeader as setResponseHeader2,
|
|
59
|
-
setResponseStatus as setResponseStatus2,
|
|
60
|
-
toNodeListener
|
|
61
|
-
} from "h3";
|
|
62
|
-
import { createServer as createViteServer } from "vite";
|
|
63
|
-
|
|
64
|
-
// src/api/routes.ts
|
|
65
|
-
import { existsSync, readdirSync } from "fs";
|
|
66
|
-
import { join } from "path";
|
|
67
|
-
import {
|
|
68
|
-
defineEventHandler,
|
|
69
|
-
getQuery,
|
|
70
|
-
getRequestURL,
|
|
71
|
-
readBody,
|
|
72
|
-
setResponseHeader,
|
|
73
|
-
setResponseStatus
|
|
74
|
-
} from "h3";
|
|
75
|
-
import { z } from "zod";
|
|
76
|
-
function toSegments(pattern) {
|
|
77
|
-
return pattern.split("/").filter(Boolean).map((p) => p.startsWith(":") ? { param: p.slice(1) } : { literal: p });
|
|
78
|
-
}
|
|
79
|
-
function sanitizeName(name) {
|
|
80
|
-
return name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
81
|
-
}
|
|
82
|
-
function entryFor(pattern, method, mcpName, route) {
|
|
83
|
-
return { pattern, segments: toSegments(pattern), method, mcpName, route };
|
|
84
|
-
}
|
|
85
|
-
function expandApiModule(name, def) {
|
|
86
|
-
if (!def) return [];
|
|
87
|
-
if (isApexResource(def)) {
|
|
88
|
-
return def.routes.map(
|
|
89
|
-
(r) => entryFor(`/api/${def.name}${r.pathSuffix}`, r.route.method, r.mcpName, r.route)
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
if (typeof def.handler === "function") {
|
|
93
|
-
return [entryFor(`/api/${name}`, def.method, sanitizeName(name), def)];
|
|
94
|
-
}
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
async function loadApiRoutes(root, loadModule) {
|
|
98
|
-
const dir = join(root, "server", "api");
|
|
99
|
-
if (!existsSync(dir)) return [];
|
|
100
|
-
const entries = [];
|
|
101
|
-
for (const file of readdirSync(dir).filter((f) => /\.(ts|js|mjs)$/.test(f))) {
|
|
102
|
-
const name = file.replace(/\.(ts|js|mjs)$/, "");
|
|
103
|
-
const def = (await loadModule(`/server/api/${file}`)).default;
|
|
104
|
-
entries.push(...expandApiModule(name, def));
|
|
105
|
-
}
|
|
106
|
-
return entries;
|
|
107
|
-
}
|
|
108
|
-
function matchApi(entries, path, method) {
|
|
109
|
-
const segs = (path.split("?")[0] ?? "/").split("/").filter(Boolean);
|
|
110
|
-
for (const entry of entries) {
|
|
111
|
-
if (entry.method !== method) continue;
|
|
112
|
-
if (entry.segments.length !== segs.length) continue;
|
|
113
|
-
const params = {};
|
|
114
|
-
let ok = true;
|
|
115
|
-
for (let i = 0; i < entry.segments.length; i++) {
|
|
116
|
-
const s = entry.segments[i];
|
|
117
|
-
const v = segs[i];
|
|
118
|
-
if (s.param) params[s.param] = decodeURIComponent(v);
|
|
119
|
-
else if (s.literal !== v) {
|
|
120
|
-
ok = false;
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (ok) return { entry, params };
|
|
125
|
-
}
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
function createApiHandler(entries) {
|
|
129
|
-
return defineEventHandler(async (event) => {
|
|
130
|
-
const url = getRequestURL(event);
|
|
131
|
-
const matched = matchApi(entries, url.pathname, event.method);
|
|
132
|
-
if (!matched) {
|
|
133
|
-
setResponseStatus(event, 404);
|
|
134
|
-
return { error: `No API route for ${event.method} ${url.pathname}` };
|
|
135
|
-
}
|
|
136
|
-
const { entry, params } = matched;
|
|
137
|
-
const raw = {
|
|
138
|
-
...entry.method === "GET" ? getQuery(event) : await readBody(event) ?? {},
|
|
139
|
-
...params
|
|
140
|
-
};
|
|
141
|
-
let input = raw;
|
|
142
|
-
if (entry.route.inputShape) {
|
|
143
|
-
const parsed = z.object(entry.route.inputShape).safeParse(raw);
|
|
144
|
-
if (!parsed.success) {
|
|
145
|
-
setResponseStatus(event, 400);
|
|
146
|
-
return { error: "Invalid input", issues: parsed.error.issues };
|
|
147
|
-
}
|
|
148
|
-
input = parsed.data;
|
|
149
|
-
}
|
|
150
|
-
const result = await entry.route.handler({ input, url: url.toString() });
|
|
151
|
-
setResponseHeader(event, "Content-Type", "application/json");
|
|
152
|
-
return result;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// src/mcp/server.ts
|
|
157
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
158
|
-
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
159
|
-
import { defineEventHandler as defineEventHandler2, toWebRequest } from "h3";
|
|
160
|
-
function hasMcpRoutes(entries) {
|
|
161
|
-
return entries.some((e) => e.route.mcp);
|
|
162
|
-
}
|
|
163
|
-
function buildServer(entries) {
|
|
164
|
-
const server = new McpServer({ name: "apexjs", version: "0.0.0" });
|
|
165
|
-
for (const entry of entries) {
|
|
166
|
-
server.registerTool(
|
|
167
|
-
entry.mcpName,
|
|
168
|
-
{
|
|
169
|
-
description: entry.route.description ?? `Apex route ${entry.mcpName}`,
|
|
170
|
-
inputSchema: entry.route.inputShape ?? {}
|
|
171
|
-
},
|
|
172
|
-
async (args) => {
|
|
173
|
-
const result = await entry.route.handler({ input: args ?? {}, url: `mcp://${entry.mcpName}` });
|
|
174
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
175
|
-
}
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
return server;
|
|
179
|
-
}
|
|
180
|
-
function createMcpHandler(entries) {
|
|
181
|
-
const mcpEntries = entries.filter((e) => e.route.mcp);
|
|
182
|
-
return defineEventHandler2(async (event) => {
|
|
183
|
-
const server = buildServer(mcpEntries);
|
|
184
|
-
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
185
|
-
sessionIdGenerator: void 0,
|
|
186
|
-
enableJsonResponse: true
|
|
187
|
-
});
|
|
188
|
-
await server.connect(transport);
|
|
189
|
-
const response = await transport.handleRequest(toWebRequest(event));
|
|
190
|
-
void server.close();
|
|
191
|
-
return response;
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// src/components/registry.ts
|
|
196
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
|
|
197
|
-
import { join as join2 } from "path";
|
|
198
|
-
async function loadComponents(root, loadModule) {
|
|
199
|
-
const dir = join2(root, "components");
|
|
200
|
-
if (!existsSync2(dir)) return { registry: {}, css: "" };
|
|
201
|
-
const registry = {};
|
|
202
|
-
let css = "";
|
|
203
|
-
for (const file of readdirSync2(dir).filter((f) => f.endsWith(".alpine"))) {
|
|
204
|
-
const name = file.replace(/\.alpine$/, "");
|
|
205
|
-
const mod = await loadModule(`/components/${file}`);
|
|
206
|
-
registry[name] = { template: mod.template, rootXData: mod.rootXData, scopeId: mod.scopeId };
|
|
207
|
-
if (mod.css) css += `${mod.css}
|
|
208
|
-
`;
|
|
209
|
-
}
|
|
210
|
-
return { registry, css };
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// src/islands/render.ts
|
|
214
|
-
import { renderIslands } from "@apex-stack/kit";
|
|
215
|
-
var ISLAND_LOADER = (
|
|
216
|
-
/* js */
|
|
217
|
-
`
|
|
218
|
-
let __alpine
|
|
219
|
-
function __ensureAlpine() {
|
|
220
|
-
return __alpine ??= import('alpinejs').then(function (m) {
|
|
221
|
-
const Alpine = m.default
|
|
222
|
-
window.Alpine = Alpine
|
|
223
|
-
Alpine.start() // islands are x-ignore'd, so this hydrates nothing on its own
|
|
224
|
-
return Alpine
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
async function __hydrate(el) {
|
|
228
|
-
const Alpine = await __ensureAlpine()
|
|
229
|
-
// Global Alpine.start() marked this island with the internal _x_ignore
|
|
230
|
-
// property (from the x-ignore attribute). Clear BOTH so initTree will descend
|
|
231
|
-
// and initialize the island's own x-data instead of early-returning.
|
|
232
|
-
el.removeAttribute('x-ignore')
|
|
233
|
-
delete el._x_ignore
|
|
234
|
-
Alpine.initTree(el)
|
|
235
|
-
el.setAttribute('data-apex-hydrated', '')
|
|
236
|
-
}
|
|
237
|
-
document.querySelectorAll('[data-apex-island]').forEach(function (el) {
|
|
238
|
-
const mode = el.getAttribute('data-apex-client')
|
|
239
|
-
if (mode === 'load') {
|
|
240
|
-
__hydrate(el)
|
|
241
|
-
} else if (mode === 'idle') {
|
|
242
|
-
(window.requestIdleCallback || function (cb) { return setTimeout(cb, 200) })(function () { __hydrate(el) })
|
|
243
|
-
} else if (mode === 'visible') {
|
|
244
|
-
const io = new IntersectionObserver(function (entries, obs) {
|
|
245
|
-
entries.forEach(function (e) { if (e.isIntersecting) { obs.unobserve(e.target); __hydrate(e.target) } })
|
|
246
|
-
})
|
|
247
|
-
io.observe(el)
|
|
248
|
-
}
|
|
249
|
-
// 'none' \u2192 do nothing; the SSR HTML is the final, static output.
|
|
250
|
-
})
|
|
251
|
-
`.trim()
|
|
252
|
-
);
|
|
253
|
-
async function renderIslandsPage(opts) {
|
|
254
|
-
const mod = await opts.loadModule(opts.pageId);
|
|
255
|
-
const loaderData = await mod.loader({ params: opts.params ?? {}, url: opts.url }) ?? {};
|
|
256
|
-
const { html, hydratingCount } = renderIslands(
|
|
257
|
-
mod.template,
|
|
258
|
-
loaderData,
|
|
259
|
-
mod.scopeId,
|
|
260
|
-
opts.registry
|
|
261
|
-
);
|
|
262
|
-
const loaderScript = hydratingCount > 0 ? `
|
|
263
|
-
<script type="module">${ISLAND_LOADER}</script>` : "";
|
|
264
|
-
const doc = `<!DOCTYPE html>
|
|
265
|
-
<html lang="en">
|
|
266
|
-
<head>
|
|
267
|
-
<meta charset="utf-8" />
|
|
268
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
269
|
-
<title>Apex JS \u2014 Islands</title>
|
|
270
|
-
<style>${mod.css}${opts.componentCss ?? ""}</style>
|
|
271
|
-
</head>
|
|
272
|
-
<body>
|
|
273
|
-
${html}${loaderScript}
|
|
274
|
-
</body>
|
|
275
|
-
</html>`;
|
|
276
|
-
return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// src/routing/router.ts
|
|
280
|
-
import { existsSync as existsSync3, readdirSync as readdirSync3, statSync } from "fs";
|
|
281
|
-
import { join as join3, relative, sep } from "path";
|
|
282
|
-
function walkAlpine(dir) {
|
|
283
|
-
const out = [];
|
|
284
|
-
for (const entry of readdirSync3(dir)) {
|
|
285
|
-
const abs = join3(dir, entry);
|
|
286
|
-
if (statSync(abs).isDirectory()) out.push(...walkAlpine(abs));
|
|
287
|
-
else if (entry.endsWith(".alpine")) out.push(abs);
|
|
288
|
-
}
|
|
289
|
-
return out;
|
|
290
|
-
}
|
|
291
|
-
function scanPages(root) {
|
|
292
|
-
const dir = join3(root, "pages");
|
|
293
|
-
if (!existsSync3(dir)) return [];
|
|
294
|
-
const routes = walkAlpine(dir).map((abs) => {
|
|
295
|
-
const rel = relative(dir, abs).split(sep).join("/");
|
|
296
|
-
const pageId = `/pages/${rel}`;
|
|
297
|
-
const parts = rel.replace(/\.alpine$/, "").split("/");
|
|
298
|
-
if (parts[parts.length - 1] === "index") parts.pop();
|
|
299
|
-
const segments = parts.map((p) => {
|
|
300
|
-
const m = /^\[(.+)\]$/.exec(p);
|
|
301
|
-
return m ? { param: m[1] } : { literal: p };
|
|
302
|
-
});
|
|
303
|
-
const isDynamic = segments.some((s) => s.param !== void 0);
|
|
304
|
-
const pattern = `/${segments.map((s) => s.param ? `:${s.param}` : s.literal).join("/")}`;
|
|
305
|
-
return { pageId, pattern, segments, isDynamic };
|
|
306
|
-
});
|
|
307
|
-
return routes.sort((a, b) => Number(a.isDynamic) - Number(b.isDynamic));
|
|
308
|
-
}
|
|
309
|
-
function pathSegments(url) {
|
|
310
|
-
const path = url.split("?")[0] ?? "/";
|
|
311
|
-
return path.split("/").filter(Boolean);
|
|
312
|
-
}
|
|
313
|
-
function matchRoute(routes, url) {
|
|
314
|
-
const segs = pathSegments(url);
|
|
315
|
-
for (const route of routes) {
|
|
316
|
-
if (route.segments.length !== segs.length) continue;
|
|
317
|
-
const params = {};
|
|
318
|
-
let ok = true;
|
|
319
|
-
for (let i = 0; i < route.segments.length; i++) {
|
|
320
|
-
const rs = route.segments[i];
|
|
321
|
-
const value = segs[i];
|
|
322
|
-
if (rs.param) params[rs.param] = decodeURIComponent(value);
|
|
323
|
-
else if (rs.literal !== value) {
|
|
324
|
-
ok = false;
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (ok) return { pageId: route.pageId, params };
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// src/dev/server.ts
|
|
334
|
-
async function startDevServer(options) {
|
|
335
|
-
const port = options.port ?? 3e3;
|
|
336
|
-
const pageId = options.pageId ?? "/pages/index.alpine";
|
|
337
|
-
const vite = await createViteServer({
|
|
338
|
-
root: options.root,
|
|
339
|
-
appType: "custom",
|
|
340
|
-
server: { middlewareMode: true },
|
|
341
|
-
// User apps depend on `@apex-stack/core`, so the client module imports the runtime
|
|
342
|
-
// from `@apex-stack/core/client` (a re-export) rather than the internal kit package.
|
|
343
|
-
plugins: [apex({ clientRuntime: "@apex-stack/core/client" })],
|
|
344
|
-
optimizeDeps: { include: ["alpinejs"] }
|
|
345
|
-
});
|
|
346
|
-
const app = createApp();
|
|
347
|
-
app.use(fromNodeMiddleware(vite.middlewares));
|
|
348
|
-
const loadEntries = () => loadApiRoutes(options.root, (id) => vite.ssrLoadModule(id));
|
|
349
|
-
app.use("/api", defineEventHandler3((event) => loadEntries().then((e) => createApiHandler(e)(event))));
|
|
350
|
-
app.use("/mcp", defineEventHandler3((event) => loadEntries().then((e) => createMcpHandler(e)(event))));
|
|
351
|
-
app.use(
|
|
352
|
-
defineEventHandler3(async (event) => {
|
|
353
|
-
const url = event.path || "/";
|
|
354
|
-
try {
|
|
355
|
-
const routes = scanPages(options.root);
|
|
356
|
-
const matched = routes.length ? matchRoute(routes, url) : { pageId, params: {} };
|
|
357
|
-
if (!matched) {
|
|
358
|
-
setResponseStatus2(event, 404);
|
|
359
|
-
setResponseHeader2(event, "Content-Type", "text/html");
|
|
360
|
-
return notFoundPage(url, routes);
|
|
361
|
-
}
|
|
362
|
-
const { registry, css: componentCss } = await loadComponents(
|
|
363
|
-
options.root,
|
|
364
|
-
(id) => vite.ssrLoadModule(id)
|
|
365
|
-
);
|
|
366
|
-
const render = options.islands ? renderIslandsPage : renderPage;
|
|
367
|
-
const html = await render({
|
|
368
|
-
loadModule: (id) => vite.ssrLoadModule(id),
|
|
369
|
-
pageId: matched.pageId,
|
|
370
|
-
params: matched.params,
|
|
371
|
-
url,
|
|
372
|
-
registry,
|
|
373
|
-
componentCss,
|
|
374
|
-
transformHtml: (u, doc) => vite.transformIndexHtml(u, doc)
|
|
375
|
-
});
|
|
376
|
-
setResponseHeader2(event, "Content-Type", "text/html");
|
|
377
|
-
return html;
|
|
378
|
-
} catch (err) {
|
|
379
|
-
const error = err;
|
|
380
|
-
vite.ssrFixStacktrace(error);
|
|
381
|
-
setResponseStatus2(event, 500);
|
|
382
|
-
setResponseHeader2(event, "Content-Type", "text/html");
|
|
383
|
-
return `<pre>${escapeHtml(error.stack ?? error.message)}</pre>`;
|
|
384
|
-
}
|
|
385
|
-
})
|
|
386
|
-
);
|
|
387
|
-
const server = createHttpServer(toNodeListener(app));
|
|
388
|
-
await new Promise((resolve) => server.listen(port, resolve));
|
|
389
|
-
return {
|
|
390
|
-
vite,
|
|
391
|
-
server,
|
|
392
|
-
port,
|
|
393
|
-
close: async () => {
|
|
394
|
-
await vite.close();
|
|
395
|
-
await new Promise(
|
|
396
|
-
(resolve, reject) => server.close((e) => e ? reject(e) : resolve())
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
function escapeHtml(s) {
|
|
402
|
-
return s.replace(/[&<>]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : ">");
|
|
403
|
-
}
|
|
404
|
-
function notFoundPage(url, routes) {
|
|
405
|
-
const list = routes.map((r) => `<li><code>${escapeHtml(r.pattern)}</code></li>`).join("");
|
|
406
|
-
return `<!DOCTYPE html><html><head><title>404 \u2014 Apex JS</title></head>
|
|
407
|
-
<body style="font-family: system-ui, sans-serif; max-width: 40rem; margin: 3rem auto;">
|
|
408
|
-
<h1>404 \u2014 no route for <code>${escapeHtml(url)}</code></h1>
|
|
409
|
-
<p>Available routes:</p>
|
|
410
|
-
<ul>${list || "<li>(no pages found \u2014 add <code>pages/index.alpine</code>)</li>"}</ul>
|
|
411
|
-
</body></html>`;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
export {
|
|
415
|
-
isApexResource,
|
|
416
|
-
expandApiModule,
|
|
417
|
-
createApiHandler,
|
|
418
|
-
hasMcpRoutes,
|
|
419
|
-
createMcpHandler,
|
|
420
|
-
loadComponents,
|
|
421
|
-
renderIslandsPage,
|
|
422
|
-
scanPages,
|
|
423
|
-
matchRoute,
|
|
424
|
-
renderPage,
|
|
425
|
-
startDevServer
|
|
426
|
-
};
|