@apex-stack/core 0.6.0 → 0.7.1

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.
@@ -0,0 +1,500 @@
1
+ import {
2
+ openInEditor
3
+ } from "./chunk-P6KQSPAV.js";
4
+ import {
5
+ loadComponents
6
+ } from "./chunk-4VG3CZ6H.js";
7
+ import {
8
+ createApiHandler,
9
+ createMcpHandler,
10
+ loadApiRoutes,
11
+ loadMiddleware,
12
+ runMiddleware
13
+ } from "./chunk-HCNNKT4A.js";
14
+ import "./chunk-2C2HRLIY.js";
15
+ import {
16
+ loadStores,
17
+ matchRoute,
18
+ renderIslandsPage,
19
+ renderPage,
20
+ resolveApexConfig,
21
+ scanPages
22
+ } from "./chunk-7CBGVRBB.js";
23
+ import "./chunk-PMLGY6Z3.js";
24
+
25
+ // src/dev/server.ts
26
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
27
+ import { createServer as createHttpServer } from "http";
28
+ import { createRequire } from "module";
29
+ import { join as join2, resolve } from "path";
30
+ import { fileURLToPath, pathToFileURL } from "url";
31
+ import { apex } from "@apex-stack/vite";
32
+ import {
33
+ createApp,
34
+ defineEventHandler,
35
+ fromNodeMiddleware,
36
+ getQuery,
37
+ getRequestHeaders,
38
+ setResponseHeader,
39
+ setResponseStatus,
40
+ toNodeListener
41
+ } from "h3";
42
+ import { createServer as createViteServer } from "vite";
43
+
44
+ // src/dev/errorPage.ts
45
+ import { existsSync, readdirSync, readFileSync } from "fs";
46
+ import { join, relative, sep } from "path";
47
+ function esc(s) {
48
+ return s.replace(
49
+ /[&<>"]/g,
50
+ (c) => c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;"
51
+ );
52
+ }
53
+ var escAttr = esc;
54
+ function parseFrames(stack, root) {
55
+ const frames = [];
56
+ const re = /^\s*at\s+(?:(.*?)\s+\()?(?:file:\/\/\/?)?((?:[A-Za-z]:[\\/]|\/)[^\s():]+):(\d+):(\d+)\)?\s*$/;
57
+ for (const line of stack.split("\n")) {
58
+ const m = line.match(re);
59
+ if (!m || !m[2]) continue;
60
+ const file = m[2].replace(/[\\/]/g, sep);
61
+ if (!existsSync(file)) continue;
62
+ frames.push({
63
+ func: m[1]?.trim() || "<anonymous>",
64
+ file,
65
+ line: Number(m[3] ?? 0),
66
+ col: Number(m[4] ?? 0),
67
+ app: file.startsWith(root) && !file.includes(`${sep}node_modules${sep}`)
68
+ });
69
+ }
70
+ return frames;
71
+ }
72
+ function codeContext(file, line) {
73
+ let lines;
74
+ try {
75
+ lines = readFileSync(file, "utf8").split("\n");
76
+ } catch {
77
+ return "";
78
+ }
79
+ const start = Math.max(0, line - 5);
80
+ const end = Math.min(lines.length, line + 4);
81
+ const rows = [];
82
+ for (let i = start; i < end; i++) {
83
+ const n = i + 1;
84
+ const active = n === line;
85
+ rows.push(
86
+ `<div class="cf-row${active ? " active" : ""}"><span class="cf-num">${String(n).padStart(4, " ")}</span><span class="cf-code">${esc(lines[i] || "")}</span></div>`
87
+ );
88
+ }
89
+ return `<pre class="cf">${rows.join("")}</pre>`;
90
+ }
91
+ var OPEN_ICON = '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M6.5 3H3.5A1.5 1.5 0 0 0 2 4.5v8A1.5 1.5 0 0 0 3.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-3M9 2h5v5M13.5 2.5 7 9"/></svg>';
92
+ function openAttrs(f) {
93
+ return `data-file="${escAttr(f.file)}" data-line="${f.line}" data-col="${f.col}"`;
94
+ }
95
+ function frameCard(f, root) {
96
+ const loc = f.app ? relative(root, f.file).replace(/\\/g, "/") : f.file.replace(/\\/g, "/");
97
+ const ctx = f.app ? codeContext(f.file, f.line) : "";
98
+ const openBtn = `<button class="fr-open" ${openAttrs(f)} title="Open in editor">${OPEN_ICON}</button>`;
99
+ return `<details class="fr${f.app ? " app" : ""}"${f.app ? " open" : ""}>
100
+ <summary><span class="fr-fn">${esc(f.func)}</span><span class="fr-loc" ${openAttrs(f)}>${esc(loc)}<span class="fr-pos">:${f.line}:${f.col}</span></span>${f.app ? openBtn : ""}</summary>
101
+ ${ctx}
102
+ </details>`;
103
+ }
104
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
105
+ "node_modules",
106
+ "dist",
107
+ "build",
108
+ "out",
109
+ "coverage",
110
+ ".git",
111
+ ".vite",
112
+ ".cache",
113
+ ".turbo",
114
+ ".next"
115
+ ]);
116
+ var FOLDER_ICON = '<svg class="ti" viewBox="0 0 16 16" fill="#f59e0b" aria-hidden="true"><path d="M1.5 3.5A1.5 1.5 0 0 1 3 2h3l1.3 1.5H13A1.5 1.5 0 0 1 14.5 5v7A1.5 1.5 0 0 1 13 13.5H3A1.5 1.5 0 0 1 1.5 12z"/></svg>';
117
+ var EXT_COLOR = {
118
+ alpine: "#818cf8",
119
+ ts: "#3b82f6",
120
+ tsx: "#3b82f6",
121
+ js: "#eab308",
122
+ mjs: "#eab308",
123
+ json: "#eab308",
124
+ css: "#22d3ee",
125
+ html: "#f97316",
126
+ svg: "#22d3ee",
127
+ md: "#94a3b8"
128
+ };
129
+ function fileIcon(name) {
130
+ const ext = name.includes(".") ? name.split(".").pop() ?? "" : "";
131
+ const c = EXT_COLOR[ext] ?? "#8892b0";
132
+ return `<svg class="ti" viewBox="0 0 16 16" fill="${c}" aria-hidden="true"><path d="M4 1.5h5L12.5 5v9a.5.5 0 0 1-.5.5H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z" opacity=".92"/></svg>`;
133
+ }
134
+ function fileTree(root, errorFile) {
135
+ const errRel = errorFile ? relative(root, errorFile).replace(/\\/g, "/") : "";
136
+ let count = 0;
137
+ const walk = (dir, rel, depth) => {
138
+ if (count > 600 || depth > 7) return "";
139
+ let entries;
140
+ try {
141
+ entries = readdirSync(dir, { withFileTypes: true });
142
+ } catch {
143
+ return "";
144
+ }
145
+ entries = entries.filter((e) => !IGNORE_DIRS.has(e.name) && !e.name.startsWith(".")).sort(
146
+ (a, b) => a.isDirectory() === b.isDirectory() ? a.name.localeCompare(b.name) : a.isDirectory() ? -1 : 1
147
+ );
148
+ const items = [];
149
+ for (const e of entries) {
150
+ if (++count > 600) break;
151
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
152
+ if (e.isDirectory()) {
153
+ const onPath = errRel === childRel || errRel.startsWith(`${childRel}/`);
154
+ items.push(
155
+ `<details class="tdir"${onPath ? " open" : ""}><summary>${FOLDER_ICON}<span>${esc(e.name)}</span></summary>${walk(join(dir, e.name), childRel, depth + 1)}</details>`
156
+ );
157
+ } else {
158
+ const isErr = childRel === errRel;
159
+ const label = isErr ? `<button class="tfile-open" ${errorFile ? `data-file="${escAttr(errorFile)}" data-line="1" data-col="1"` : ""}>${esc(e.name)}</button>` : `<span>${esc(e.name)}</span>`;
160
+ items.push(`<div class="tfile${isErr ? " err" : ""}">${fileIcon(e.name)}${label}</div>`);
161
+ }
162
+ }
163
+ return `<div class="tchildren">${items.join("")}</div>`;
164
+ };
165
+ return walk(root, "", 0);
166
+ }
167
+ var SHELL_CSS = `
168
+ :root{color-scheme:dark light}
169
+ *{box-sizing:border-box}
170
+ body{margin:0;min-height:100vh;background:#0a0e1a;color:#f4f7ff;
171
+ font:15px/1.6 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;padding:clamp(1.25rem,5vh,3rem) 1.25rem}
172
+ @media(prefers-color-scheme:light){body{background:#f4f7ff;color:#0a0e1a}}
173
+ .wrap{width:100%;max-width:1120px;margin:0 auto}
174
+ .brand{display:flex;align-items:center;gap:.55rem;font-weight:600;margin-bottom:1.4rem}
175
+ .mark{width:20px;height:20px}
176
+ .pill{font-size:.8rem;font-weight:600;padding:.15rem .6rem;border-radius:999px;margin-left:.4rem}
177
+ .pill.err{color:#fb7185;background:rgba(244,63,94,.12);border:1px solid rgba(244,63,94,.35)}
178
+ .pill.nf{color:#818cf8;background:rgba(129,140,248,.12);border:1px solid rgba(129,140,248,.35)}
179
+ .kind{font:600 .95rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#fb7185;margin:0 0 .5rem}
180
+ .kind.nf{color:#818cf8}
181
+ h1{font-size:clamp(1.3rem,3vw,1.9rem);line-height:1.25;letter-spacing:-.02em;margin:0 0 .6rem;word-break:break-word}
182
+ .sub{color:#9aa6c4;margin:0 0 1.6rem}
183
+ .sub code{background:rgba(130,140,200,.14);border-radius:.35rem;padding:.05rem .35rem;font-size:.9em}
184
+ .cols{display:grid;grid-template-columns:minmax(0,1fr);gap:1.4rem}
185
+ @media(min-width:900px){.cols{grid-template-columns:minmax(0,1fr) 19rem}}
186
+ .col-main{min-width:0}
187
+ .tabs{display:flex;gap:.4rem;margin:0 0 .9rem}
188
+ .tabs button{font:inherit;font-size:.82rem;font-weight:600;padding:.35rem .8rem;border-radius:.5rem;cursor:pointer;
189
+ color:#9aa6c4;background:transparent;border:1px solid rgba(130,140,200,.22)}
190
+ .tabs button.active{color:#0a0e1a;background:#818cf8;border-color:#818cf8}
191
+ .fr{border:1px solid rgba(130,140,200,.18);border-radius:10px;overflow:hidden;margin:0 0 .6rem;background:#11172b}
192
+ @media(prefers-color-scheme:light){.fr{background:#fff}}
193
+ .fr:not(.app){opacity:.62}
194
+ .fr summary{cursor:pointer;display:flex;align-items:center;gap:.6rem;padding:.55rem .8rem;font-size:.83rem;list-style:none}
195
+ .fr summary::-webkit-details-marker{display:none}
196
+ .fr-fn{font:600 .82rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#f4f7ff;flex-shrink:0}
197
+ @media(prefers-color-scheme:light){.fr-fn{color:#0a0e1a}}
198
+ .fr-loc{font:.78rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;color:#9aa6c4;margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
199
+ .fr.app .fr-loc:hover{color:#818cf8;text-decoration:underline}
200
+ .fr-pos{color:#fb7185}
201
+ .fr-open{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;
202
+ border:1px solid rgba(130,140,200,.25);background:transparent;color:#9aa6c4;cursor:pointer}
203
+ .fr-open:hover{color:#818cf8;border-color:#818cf8}
204
+ .fr-open.ok{color:#22c55e;border-color:#22c55e}
205
+ .cf{margin:0;padding:.5rem 0 .6rem;overflow-x:auto;font:.8rem/1.7 ui-monospace,SFMono-Regular,Menlo,monospace;border-top:1px solid rgba(130,140,200,.14)}
206
+ .cf-row{display:flex;padding:0 .8rem;white-space:pre}
207
+ .cf-row.active{background:rgba(244,63,94,.12);box-shadow:inset 3px 0 0 #f43f5e}
208
+ .cf-num{color:#5b6890;user-select:none;padding-right:1rem;text-align:right}
209
+ .cf-row.active .cf-num{color:#fb7185}
210
+ .cf-code{color:#f4f7ff}
211
+ @media(prefers-color-scheme:light){.cf-code{color:#0a0e1a}}
212
+ .raw{margin:0;padding:.9rem;border:1px solid rgba(130,140,200,.18);border-radius:10px;background:#11172b;
213
+ font:.78rem/1.7 ui-monospace,SFMono-Regular,Menlo,monospace;color:#9aa6c4;overflow-x:auto;white-space:pre-wrap;word-break:break-word}
214
+ @media(prefers-color-scheme:light){.raw{background:#fff}}
215
+ .tree{border:1px solid rgba(130,140,200,.18);border-radius:10px;background:#11172b;padding:.5rem;max-height:70vh;overflow:auto;
216
+ font:.8rem/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}
217
+ @media(prefers-color-scheme:light){.tree{background:#fff}}
218
+ .tree-head{font:600 .72rem/1 system-ui;letter-spacing:.06em;text-transform:uppercase;color:#5b6890;padding:.2rem .3rem .5rem}
219
+ .ti{width:14px;height:14px;flex-shrink:0;vertical-align:-2px;margin-right:.35rem}
220
+ .tdir>summary{cursor:pointer;display:flex;align-items:center;padding:.12rem .3rem;border-radius:5px;list-style:none;color:#c7d0ea}
221
+ .tdir>summary::-webkit-details-marker{display:none}
222
+ .tdir>summary:hover{background:rgba(130,140,200,.1)}
223
+ @media(prefers-color-scheme:light){.tdir>summary{color:#334}}
224
+ .tchildren{padding-left:.85rem;border-left:1px solid rgba(130,140,200,.14);margin-left:.55rem}
225
+ .tfile{display:flex;align-items:center;padding:.12rem .3rem;border-radius:5px;color:#9aa6c4}
226
+ .tfile.err{color:#fb7185;font-weight:700;background:rgba(244,63,94,.1)}
227
+ .tfile-open{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer;text-decoration:underline}
228
+ details{border:1px solid rgba(130,140,200,.18);border-radius:12px;background:#11172b}
229
+ @media(prefers-color-scheme:light){details{background:#fff}}
230
+ .routes{list-style:none;padding:0;margin:0 0 1.4rem;display:flex;flex-wrap:wrap;gap:.5rem}
231
+ .routes li{font:.85rem/1 ui-monospace,monospace;color:#818cf8;background:rgba(129,140,248,.1);border:1px solid rgba(129,140,248,.25);border-radius:.45rem;padding:.35rem .6rem}
232
+ .foot{margin-top:1.8rem;color:#5b6890;font-size:.82rem}
233
+ `;
234
+ var OPEN_JS = `
235
+ document.addEventListener('click', function (e) {
236
+ var el = e.target.closest('[data-file]');
237
+ if (!el) return;
238
+ e.preventDefault(); e.stopPropagation();
239
+ fetch('/__apex_open?file=' + encodeURIComponent(el.dataset.file) + '&line=' + el.dataset.line + '&col=' + el.dataset.col)
240
+ .then(function (r) { if (r.ok && el.classList.contains('fr-open')) { el.classList.add('ok'); setTimeout(function(){el.classList.remove('ok')},1200); } })
241
+ .catch(function(){});
242
+ });
243
+ var tabs = document.querySelectorAll('.tabs button');
244
+ tabs.forEach(function (b) {
245
+ b.addEventListener('click', function () {
246
+ tabs.forEach(function (x) { x.classList.toggle('active', x === b); });
247
+ ['frames','raw','tree'].forEach(function (v) {
248
+ var el = document.getElementById('view-' + v);
249
+ if (el) el.hidden = v !== b.dataset.view;
250
+ });
251
+ });
252
+ });
253
+ `;
254
+ var MARK = `<svg class="mark" viewBox="0 0 64 64" aria-hidden="true"><defs><linearGradient id="g" x1="0" y1="1" x2="1" y2="0"><stop offset="0" stop-color="#22d3ee"/><stop offset="1" stop-color="#6366f1"/></linearGradient></defs><path d="M32 7 L32 35 L20 56 L4 56 Z" fill="url(#g)"/><path d="M32 7 L60 56 L44 56 L32 35 Z" fill="#6366f1"/></svg>`;
255
+ function page(title, bodyInner, script = "") {
256
+ return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>${esc(title)}</title><style>${SHELL_CSS}</style></head><body><div class="wrap">${bodyInner}</div>${script ? `<script>${script}</script>` : ""}</body></html>`;
257
+ }
258
+ function renderErrorPage(error, opts) {
259
+ const kind = error.name || "Error";
260
+ const message = error.message || String(error);
261
+ const stack = error.stack || "";
262
+ const frames = parseFrames(stack, opts.root);
263
+ const origin = frames.find((f) => f.app) ?? frames[0];
264
+ const framesHtml = frames.length ? frames.map((f) => frameCard(f, opts.root)).join("") : '<p class="sub">No stack frames available.</p>';
265
+ const body = `
266
+ <div class="brand">${MARK} Apex JS <span class="pill err">Dev error</span></div>
267
+ <p class="kind">${esc(kind)}</p>
268
+ <h1>${esc(message)}</h1>
269
+ <p class="sub">While rendering <code>${esc(opts.url)}</code></p>
270
+ <div class="cols">
271
+ <div class="col-main">
272
+ <div class="tabs">
273
+ <button class="active" data-view="frames">Frames</button>
274
+ <button data-view="raw">Raw</button>
275
+ </div>
276
+ <div id="view-frames">${framesHtml}</div>
277
+ <div id="view-raw" hidden><pre class="raw">${esc(stack)}</pre></div>
278
+ </div>
279
+ <aside class="col-tree" id="view-tree">
280
+ <div class="tree"><div class="tree-head">Project \xB7 error origin in red</div>${fileTree(opts.root, origin?.file)}</div>
281
+ </aside>
282
+ </div>
283
+ <p class="foot">Click a frame's \u2197 to open it in your editor \xB7 fix &amp; save to auto-reload.</p>`;
284
+ return page(`${kind} \u2014 Apex JS`, body, OPEN_JS);
285
+ }
286
+ function renderNoProjectPage(root) {
287
+ let apps = [];
288
+ try {
289
+ apps = readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORE_DIRS.has(e.name) && !e.name.startsWith(".")).filter((e) => existsSync(join(root, e.name, "pages", "index.alpine"))).map((e) => e.name).slice(0, 16);
290
+ } catch {
291
+ }
292
+ const suggest = apps.length ? `<p class="sub">Did you mean to run it inside one of these apps?</p><ul class="routes">${apps.map((a) => `<li>cd ${esc(a)} &amp;&amp; apex dev</li>`).join("")}</ul>` : `<p class="sub">Scaffold a new app:</p><ul class="routes"><li>npm create apexjs@latest my-app</li></ul>`;
293
+ const body = `
294
+ <div class="brand">${MARK} Apex JS <span class="pill nf">No app here</span></div>
295
+ <p class="kind nf">No pages found</p>
296
+ <h1>This folder isn't an Apex app</h1>
297
+ <p class="sub"><code>apex dev</code> looks for <code>pages/*.alpine</code> in <code>${esc(root)}</code>, but found none \u2014 run it from inside your app's folder.</p>
298
+ ${suggest}
299
+ <p class="foot">Every Apex app has a <code>pages/</code> directory with at least <code>pages/index.alpine</code>.</p>`;
300
+ return page("No Apex app \u2014 Apex JS", body);
301
+ }
302
+ function renderNotFoundPage(url, routes) {
303
+ const list = routes.length ? routes.map((r) => `<li>${esc(r.pattern)}</li>`).join("") : "<li>no pages yet \u2014 add pages/index.alpine</li>";
304
+ const body = `
305
+ <div class="brand">${MARK} Apex JS <span class="pill nf">404</span></div>
306
+ <p class="kind nf">404 \u2014 Not found</p>
307
+ <h1>No route matches <code>${esc(url)}</code></h1>
308
+ <p class="sub">These routes are available:</p>
309
+ <ul class="routes">${list}</ul>
310
+ <p class="foot">Add a file under <code>pages/</code> to create a new route.</p>`;
311
+ return page("404 \u2014 Apex JS", body);
312
+ }
313
+
314
+ // src/dev/server.ts
315
+ async function startDevServer(options) {
316
+ const port = options.port ?? 3e3;
317
+ const pageId = options.pageId ?? "/pages/index.alpine";
318
+ const req = createRequire(import.meta.url);
319
+ const tryResolve = (spec) => {
320
+ try {
321
+ return req.resolve(spec);
322
+ } catch {
323
+ return void 0;
324
+ }
325
+ };
326
+ const alpine = tryResolve("alpinejs");
327
+ const kit = tryResolve("@apex-stack/kit");
328
+ const coreClient = fileURLToPath(new URL("./client.js", import.meta.url));
329
+ const coreSelf = fileURLToPath(new URL("./index.js", import.meta.url));
330
+ const alias = {
331
+ "@apex-stack/core/client": coreClient,
332
+ "@apex-stack/core": coreSelf
333
+ };
334
+ if (alpine) alias.alpinejs = alpine;
335
+ if (kit) alias["@apex-stack/kit"] = kit;
336
+ const plugins = [apex({ clientRuntime: "@apex-stack/core/client" })];
337
+ let hasTailwind = false;
338
+ try {
339
+ const reqProj = createRequire(join2(options.root, "package.json"));
340
+ const twMod = await import(pathToFileURL(reqProj.resolve("@tailwindcss/vite")).href);
341
+ const tw = twMod.default ?? twMod;
342
+ plugins.unshift(tw());
343
+ hasTailwind = true;
344
+ } catch {
345
+ }
346
+ const appCssRel = ["app.css", "styles/app.css", "src/app.css"].find(
347
+ (p) => existsSync2(join2(options.root, p))
348
+ );
349
+ let appCss = appCssRel ? `/${appCssRel}` : void 0;
350
+ if (appCssRel && !hasTailwind) {
351
+ const css = readFileSync2(join2(options.root, appCssRel), "utf8");
352
+ if (/@import\s+['"]tailwindcss['"]|@tailwind\b/.test(css)) {
353
+ console.warn(
354
+ `
355
+ \u26A0 ${appCssRel} imports Tailwind, but it isn't installed \u2014 skipping it (styles won't apply).
356
+ Fix: npm i -D tailwindcss @tailwindcss/vite
357
+ `
358
+ );
359
+ appCss = void 0;
360
+ }
361
+ }
362
+ const vite = await createViteServer({
363
+ root: options.root,
364
+ appType: "custom",
365
+ // Derive the HMR port from the dev port so multiple `apex dev` instances
366
+ // don't all fight over Vite's default 24678.
367
+ server: { middlewareMode: true, fs: { strict: false }, hmr: { port: port + 1 } },
368
+ resolve: { alias },
369
+ // User apps depend on `@apex-stack/core`, so the client module imports the runtime
370
+ // from `@apex-stack/core/client` (a re-export) rather than the internal kit package.
371
+ plugins,
372
+ optimizeDeps: { include: ["alpinejs"] }
373
+ });
374
+ const ssrLoad = (id) => {
375
+ const resolved = id[0] === "/" && !id.startsWith(options.root) ? join2(options.root, id).replace(/\\/g, "/") : id;
376
+ return vite.ssrLoadModule(resolved);
377
+ };
378
+ const { runtimeConfig, publicConfig } = await resolveApexConfig(
379
+ options.root,
380
+ (id) => ssrLoad(id)
381
+ );
382
+ const app = createApp();
383
+ app.use(fromNodeMiddleware(vite.middlewares));
384
+ app.use(
385
+ defineEventHandler(async (event) => {
386
+ const mws = await loadMiddleware(options.root, (id) => ssrLoad(id));
387
+ if (!mws.length) return;
388
+ const { redirect, locals } = await runMiddleware(mws, {
389
+ url: event.path || "/",
390
+ method: event.method,
391
+ config: runtimeConfig,
392
+ headers: getRequestHeaders(event)
393
+ });
394
+ event.context.apexLocals = locals;
395
+ if (redirect) {
396
+ setResponseStatus(event, redirect.status);
397
+ setResponseHeader(event, "Location", redirect.to);
398
+ return "";
399
+ }
400
+ })
401
+ );
402
+ const loadEntries = () => loadApiRoutes(options.root, (id) => ssrLoad(id));
403
+ app.use(
404
+ "/api",
405
+ defineEventHandler(
406
+ (event) => loadEntries().then((e) => createApiHandler(e, runtimeConfig)(event))
407
+ )
408
+ );
409
+ app.use(
410
+ "/mcp",
411
+ defineEventHandler(
412
+ (event) => loadEntries().then((e) => createMcpHandler(e, runtimeConfig)(event))
413
+ )
414
+ );
415
+ app.use(
416
+ "/__apex_open",
417
+ defineEventHandler((event) => {
418
+ const q = getQuery(event);
419
+ const file = String(q.file ?? "");
420
+ const resolved = resolve(file);
421
+ if (!file || !resolved.startsWith(options.root)) {
422
+ setResponseStatus(event, 400);
423
+ return { ok: false };
424
+ }
425
+ const ok = openInEditor(resolved, Number(q.line) || 1, Number(q.col) || 1);
426
+ setResponseStatus(event, ok ? 200 : 500);
427
+ return { ok };
428
+ })
429
+ );
430
+ app.use(
431
+ defineEventHandler(async (event) => {
432
+ const url = event.path || "/";
433
+ try {
434
+ const routes = scanPages(options.root);
435
+ const matched = routes.length ? matchRoute(routes, url) : { pageId, params: {} };
436
+ if (!matched) {
437
+ setResponseStatus(event, 404);
438
+ setResponseHeader(event, "Content-Type", "text/html");
439
+ return await vite.transformIndexHtml(url, renderNotFoundPage(url, routes));
440
+ }
441
+ if (!existsSync2(join2(options.root, matched.pageId))) {
442
+ setResponseStatus(event, 404);
443
+ setResponseHeader(event, "Content-Type", "text/html");
444
+ return await vite.transformIndexHtml(url, renderNoProjectPage(options.root));
445
+ }
446
+ const { registry, css: componentCss } = await loadComponents(
447
+ options.root,
448
+ (id) => ssrLoad(id)
449
+ );
450
+ const stores = await loadStores(options.root, (id) => ssrLoad(id));
451
+ const layoutsDir = join2(options.root, "layouts");
452
+ const layouts = existsSync2(layoutsDir) ? readdirSync2(layoutsDir).filter((f) => f.endsWith(".alpine")).map((f) => f.replace(/\.alpine$/, "")) : [];
453
+ const render = options.islands ? renderIslandsPage : renderPage;
454
+ const html = await render({
455
+ loadModule: (id) => ssrLoad(id),
456
+ pageId: matched.pageId,
457
+ params: matched.params,
458
+ url,
459
+ registry,
460
+ componentCss,
461
+ stores,
462
+ appCss,
463
+ layouts,
464
+ runtimeConfig,
465
+ publicConfig,
466
+ locals: event.context.apexLocals ?? {},
467
+ errorPageId: existsSync2(join2(options.root, "pages", "error.alpine")) ? "/pages/error.alpine" : void 0,
468
+ transformHtml: (u, doc) => vite.transformIndexHtml(u, doc)
469
+ });
470
+ setResponseHeader(event, "Content-Type", "text/html");
471
+ return html;
472
+ } catch (err) {
473
+ const error = err;
474
+ vite.ssrFixStacktrace(error);
475
+ setResponseStatus(event, 500);
476
+ setResponseHeader(event, "Content-Type", "text/html");
477
+ const html = renderErrorPage(error, { url, root: options.root });
478
+ try {
479
+ return await vite.transformIndexHtml(url, html);
480
+ } catch {
481
+ return html;
482
+ }
483
+ }
484
+ })
485
+ );
486
+ const server = createHttpServer(toNodeListener(app));
487
+ await new Promise((resolve2) => server.listen(port, resolve2));
488
+ return {
489
+ vite,
490
+ server,
491
+ port,
492
+ close: async () => {
493
+ await vite.close();
494
+ await new Promise((resolve2, reject) => server.close((e) => e ? reject(e) : resolve2()));
495
+ }
496
+ };
497
+ }
498
+ export {
499
+ startDevServer
500
+ };
@@ -11,7 +11,7 @@ import {
11
11
  matchRoute,
12
12
  renderIslandsPage,
13
13
  renderPage
14
- } from "./chunk-DVNFDYEO.js";
14
+ } from "./chunk-7CBGVRBB.js";
15
15
  import "./chunk-PMLGY6Z3.js";
16
16
 
17
17
  // src/commands/start.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  offerExtension
3
- } from "./chunk-CHBSGOB3.js";
3
+ } from "./chunk-P6KQSPAV.js";
4
4
  import {
5
5
  VERSION,
6
6
  banner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apex-stack/core",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "The full-stack meta-framework for Alpine.js — CLI and runtime",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -7,9 +7,11 @@
7
7
  <nav
8
8
  class="mx-auto flex max-w-5xl items-center gap-2 px-6 py-4"
9
9
  x-data="{ dark: false }"
10
- x-init="dark = localStorage.getItem('theme') === 'dark'; document.documentElement.classList.toggle('dark', dark)"
10
+ x-init="dark = localStorage.getItem('theme') ? localStorage.getItem('theme') === 'dark' : (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches); document.documentElement.classList.toggle('dark', dark)"
11
+ client:load
11
12
  >
12
- <a href="/" class="mr-auto font-title text-lg font-bold text-on-surface-strong dark:text-on-surface-dark-strong">
13
+ <a href="/" class="mr-auto flex items-center gap-2 font-title text-lg font-bold text-on-surface-strong dark:text-on-surface-dark-strong">
14
+ <img src="/favicon.svg" alt="" width="24" height="24" class="size-6" />
13
15
  {{name}}
14
16
  </a>
15
17
  <a href="/" class="rounded-radius px-3 py-1.5 text-sm font-medium hover:bg-surface-alt dark:hover:bg-surface-dark-alt">Home</a>
@@ -14,12 +14,16 @@
14
14
  }
15
15
  </script>
16
16
 
17
- <template x-data="{ show: false }">
17
+ <template x-data>
18
18
  <!-- Hero -->
19
- <section class="py-8 text-center">
20
- <h1 class="font-title text-4xl font-extrabold tracking-tight text-on-surface-strong sm:text-5xl dark:text-on-surface-dark-strong" x-text="title"></h1>
19
+ <section class="flex flex-col items-center py-10 text-center sm:py-16">
20
+ <img src="/favicon.svg" alt="Apex JS" width="72" height="72" class="size-16 sm:size-20 drop-shadow" />
21
+ <p class="mt-5 inline-flex items-center gap-2 rounded-full border border-outline px-3 py-1 text-xs font-medium text-on-surface/80 dark:border-outline-dark dark:text-on-surface-dark/80">
22
+ Built with <span class="font-title font-bold text-on-surface-strong dark:text-on-surface-dark-strong">Apex&nbsp;JS</span>
23
+ </p>
24
+ <h1 class="mt-5 font-title text-4xl font-extrabold tracking-tight text-on-surface-strong sm:text-6xl dark:text-on-surface-dark-strong" x-text="title"></h1>
21
25
  <p class="mx-auto mt-4 max-w-2xl text-lg" x-text="tagline"></p>
22
- <div class="mt-6 flex flex-wrap justify-center gap-3">
26
+ <div class="mt-7 flex flex-wrap justify-center gap-3">
23
27
  <a href="/blog"><Button>Read the blog</Button></a>
24
28
  <a href="https://apexjs.site" class="inline-flex items-center justify-center rounded-radius border border-outline px-4 py-2 text-sm font-medium text-on-surface transition hover:opacity-75 dark:border-outline-dark dark:text-on-surface-dark">Docs</a>
25
29
  </div>
@@ -54,8 +58,9 @@
54
58
  </ul>
55
59
  </section>
56
60
 
57
- <!-- A hydrated, interactive component -->
58
- <section class="mt-10 flex flex-col items-start gap-3">
61
+ <!-- A hydrated, interactive island. `client:load` hydrates it immediately in
62
+ islands mode; in default mode the whole page hydrates anyway. -->
63
+ <section class="mt-10 flex flex-col items-start gap-3" x-data="{ show: false }" client:load>
59
64
  <p class="text-sm text-on-surface/80 dark:text-on-surface-dark/80">Hydrated in the browser — click it:</p>
60
65
  <Counter start="0" label="Clicks" />
61
66
  <button type="button" class="text-sm text-primary hover:opacity-75 dark:text-primary-dark" @click="show = !show" x-text="show ? 'Hide details' : 'How does this work?'"></button>
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Apex">
2
+ <defs>
3
+ <linearGradient id="apexLit" x1="0" y1="1" x2="1" y2="0">
4
+ <stop offset="0" stop-color="#22d3ee"/>
5
+ <stop offset="1" stop-color="#6366f1"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <!-- Apex mark: an "A" that is also a summit. Left face lit, right face in shadow. -->
9
+ <path d="M32 7 L32 35 L20 56 L4 56 Z" fill="url(#apexLit)"/>
10
+ <path d="M32 7 L60 56 L44 56 L32 35 Z" fill="#6366f1"/>
11
+ </svg>
Binary file
@@ -0,0 +1 @@
1
+ 0.1.3
@@ -1,42 +0,0 @@
1
- // src/vscode.ts
2
- import { spawnSync } from "child_process";
3
- import { existsSync } from "fs";
4
- import { createInterface } from "readline";
5
- import { fileURLToPath } from "url";
6
- var VSIX = fileURLToPath(new URL("../vscode/apex-alpine.vsix", import.meta.url));
7
- var WIN = process.platform === "win32";
8
- function extensionBundled() {
9
- return existsSync(VSIX);
10
- }
11
- function hasCodeCli() {
12
- try {
13
- return spawnSync("code", ["--version"], { stdio: "ignore", shell: WIN }).status === 0;
14
- } catch {
15
- return false;
16
- }
17
- }
18
- function installExtension() {
19
- if (!existsSync(VSIX)) return false;
20
- return spawnSync("code", ["--install-extension", VSIX, "--force"], { stdio: "inherit", shell: WIN }).status === 0;
21
- }
22
- function promptYesNo(question, def = true) {
23
- if (!process.stdin.isTTY) return Promise.resolve(def);
24
- const rl = createInterface({ input: process.stdin, output: process.stdout });
25
- return new Promise((resolve) => {
26
- rl.question(`${question} ${def ? "(Y/n) " : "(y/N) "}`, (answer) => {
27
- rl.close();
28
- const a = answer.trim().toLowerCase();
29
- resolve(a === "" ? def : a === "y" || a === "yes");
30
- });
31
- });
32
- }
33
- async function offerExtension(choice) {
34
- if (!extensionBundled() || !hasCodeCli()) return null;
35
- const yes = choice ?? await promptYesNo("Install the Apex .alpine VS Code extension (syntax highlighting)?");
36
- if (!yes) return null;
37
- return installExtension() ? "VS Code extension installed" : "VS Code extension install failed";
38
- }
39
-
40
- export {
41
- offerExtension
42
- };