@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.
@@ -7,7 +7,7 @@ import {
7
7
  renderPage,
8
8
  resolveApexConfig,
9
9
  scanPages
10
- } from "./chunk-DVNFDYEO.js";
10
+ } from "./chunk-7CBGVRBB.js";
11
11
  import "./chunk-PMLGY6Z3.js";
12
12
 
13
13
  // src/commands/build.ts
@@ -89,164 +89,14 @@ async function resolveApexConfig(root, loadModule) {
89
89
  };
90
90
  }
91
91
 
92
- // src/islands/render.ts
93
- import { renderIslands } from "@apex-stack/kit";
94
- var ISLAND_LOADER = (
95
- /* js */
96
- `
97
- let __alpine
98
- function __ensureAlpine() {
99
- return __alpine ??= import('alpinejs').then(function (m) {
100
- const Alpine = m.default
101
- window.Alpine = Alpine
102
- Alpine.start() // islands are x-ignore'd, so this hydrates nothing on its own
103
- return Alpine
104
- })
105
- }
106
- async function __hydrate(el) {
107
- const Alpine = await __ensureAlpine()
108
- // Global Alpine.start() marked this island with the internal _x_ignore
109
- // property (from the x-ignore attribute). Clear BOTH so initTree will descend
110
- // and initialize the island's own x-data instead of early-returning.
111
- el.removeAttribute('x-ignore')
112
- delete el._x_ignore
113
- Alpine.initTree(el)
114
- el.setAttribute('data-apex-hydrated', '')
115
- }
116
- document.querySelectorAll('[data-apex-island]').forEach(function (el) {
117
- const mode = el.getAttribute('data-apex-client')
118
- if (mode === 'load') {
119
- __hydrate(el)
120
- } else if (mode === 'idle') {
121
- (window.requestIdleCallback || function (cb) { return setTimeout(cb, 200) })(function () { __hydrate(el) })
122
- } else if (mode === 'visible') {
123
- const io = new IntersectionObserver(function (entries, obs) {
124
- entries.forEach(function (e) { if (e.isIntersecting) { obs.unobserve(e.target); __hydrate(e.target) } })
125
- })
126
- io.observe(el)
127
- }
128
- // 'none' \u2192 do nothing; the SSR HTML is the final, static output.
129
- })
130
- `.trim()
131
- );
132
- async function renderIslandsPage(opts) {
133
- const mod = await opts.loadModule(opts.pageId);
134
- const loaderData = await mod.loader({
135
- params: opts.params ?? {},
136
- url: opts.url,
137
- config: opts.runtimeConfig ?? { public: {} },
138
- locals: opts.locals ?? {}
139
- }) ?? {};
140
- const { html, hydratingCount } = renderIslands(
141
- mod.template,
142
- loaderData,
143
- mod.scopeId,
144
- opts.registry
145
- );
146
- const loaderScript = hydratingCount > 0 ? `
147
- <script type="module">${ISLAND_LOADER}</script>` : "";
148
- const configScript = hydratingCount > 0 ? `
149
- ${clientConfigScript(opts.publicConfig ?? {})}` : "";
150
- const doc = `<!DOCTYPE html>
151
- <html lang="en">
152
- <head>
153
- <meta charset="utf-8" />
154
- <meta name="viewport" content="width=device-width, initial-scale=1" />
155
- <title>Apex JS \u2014 Islands</title>
156
- <style>${mod.css}${opts.componentCss ?? ""}</style>
157
- </head>
158
- <body>
159
- ${html}${configScript}${loaderScript}
160
- </body>
161
- </html>`;
162
- return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
163
- }
164
-
165
- // src/routing/router.ts
166
- import { existsSync as existsSync2, readdirSync, statSync } from "fs";
167
- import { join as join2, relative, sep } from "path";
168
- function walkAlpine(dir) {
169
- const out = [];
170
- for (const entry of readdirSync(dir)) {
171
- const abs = join2(dir, entry);
172
- if (statSync(abs).isDirectory()) out.push(...walkAlpine(abs));
173
- else if (entry.endsWith(".alpine")) out.push(abs);
174
- }
175
- return out;
176
- }
177
- function scanPages(root) {
178
- const dir = join2(root, "pages");
179
- if (!existsSync2(dir)) return [];
180
- const routes = walkAlpine(dir).map((abs) => {
181
- const rel = relative(dir, abs).split(sep).join("/");
182
- const pageId = `/pages/${rel}`;
183
- const parts = rel.replace(/\.alpine$/, "").split("/");
184
- if (parts[parts.length - 1] === "index") parts.pop();
185
- const segments = parts.map((p) => {
186
- const catchAll = /^\[\.\.\.(.+)\]$/.exec(p);
187
- if (catchAll) return { catchAll: catchAll[1] };
188
- const m = /^\[(.+)\]$/.exec(p);
189
- return m ? { param: m[1] } : { literal: p };
190
- });
191
- const isDynamic = segments.some((s) => s.param !== void 0 || s.catchAll !== void 0);
192
- const pattern = `/${segments.map((s) => s.catchAll ? `:${s.catchAll}*` : s.param ? `:${s.param}` : s.literal).join("/")}`;
193
- return { pageId, pattern, segments, isDynamic };
194
- });
195
- const rank = (r) => r.segments.some((s) => s.catchAll) ? 2 : r.isDynamic ? 1 : 0;
196
- return routes.sort((a, b) => rank(a) - rank(b));
197
- }
198
- function pathSegments(url) {
199
- const path = url.split("?")[0] ?? "/";
200
- return path.split("/").filter(Boolean);
201
- }
202
- function matchRoute(routes, url) {
203
- const segs = pathSegments(url);
204
- for (const route of routes) {
205
- const last = route.segments[route.segments.length - 1];
206
- const isCatchAll = Boolean(last?.catchAll);
207
- if (isCatchAll) {
208
- const lead = route.segments.slice(0, -1);
209
- if (segs.length < lead.length + 1) continue;
210
- const params2 = {};
211
- let ok2 = true;
212
- for (let i = 0; i < lead.length; i++) {
213
- const rs = lead[i];
214
- const value = segs[i];
215
- if (rs.param) params2[rs.param] = decodeURIComponent(value);
216
- else if (rs.literal !== value) {
217
- ok2 = false;
218
- break;
219
- }
220
- }
221
- if (!ok2) continue;
222
- params2[last?.catchAll] = segs.slice(lead.length).map(decodeURIComponent).join("/");
223
- return { pageId: route.pageId, params: params2 };
224
- }
225
- if (route.segments.length !== segs.length) continue;
226
- const params = {};
227
- let ok = true;
228
- for (let i = 0; i < route.segments.length; i++) {
229
- const rs = route.segments[i];
230
- const value = segs[i];
231
- if (rs.param) params[rs.param] = decodeURIComponent(value);
232
- else if (rs.literal !== value) {
233
- ok = false;
234
- break;
235
- }
236
- }
237
- if (ok) return { pageId: route.pageId, params };
238
- }
239
- return null;
240
- }
241
-
242
92
  // src/stores/loader.ts
243
- import { existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
244
- import { join as join3 } from "path";
93
+ import { existsSync as existsSync2, readdirSync } from "fs";
94
+ import { join as join2 } from "path";
245
95
  async function loadStores(root, loadModule) {
246
- const dir = join3(root, "stores");
247
- if (!existsSync3(dir)) return [];
96
+ const dir = join2(root, "stores");
97
+ if (!existsSync2(dir)) return [];
248
98
  const out = [];
249
- for (const file of readdirSync2(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"))) {
99
+ for (const file of readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"))) {
250
100
  const id = `/stores/${file}`;
251
101
  const mod = await loadModule(id);
252
102
  const def = mod.default;
@@ -387,6 +237,7 @@ ${storeRegs ? `${storeRegs}
387
237
  <head>
388
238
  <meta charset="utf-8" />
389
239
  <meta name="viewport" content="width=device-width, initial-scale=1" />
240
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
390
241
  ${headTags}
391
242
  <style>${css}</style>
392
243
  </head>
@@ -399,12 +250,185 @@ ${clientScript}
399
250
  </html>`;
400
251
  }
401
252
 
253
+ // src/islands/render.ts
254
+ import { renderIslands } from "@apex-stack/kit";
255
+ var SLOT_RE = /<slot\b[^>]*>[\s\S]*?<\/slot>/;
256
+ var ISLAND_LOADER = (
257
+ /* js */
258
+ `
259
+ let __alpine
260
+ function __ensureAlpine() {
261
+ return __alpine ??= import('alpinejs').then(function (m) {
262
+ const Alpine = m.default
263
+ window.Alpine = Alpine
264
+ Alpine.start() // islands are x-ignore'd, so this hydrates nothing on its own
265
+ return Alpine
266
+ })
267
+ }
268
+ async function __hydrate(el) {
269
+ const Alpine = await __ensureAlpine()
270
+ // Global Alpine.start() marked this island with the internal _x_ignore
271
+ // property (from the x-ignore attribute). Clear BOTH so initTree will descend
272
+ // and initialize the island's own x-data instead of early-returning.
273
+ el.removeAttribute('x-ignore')
274
+ delete el._x_ignore
275
+ Alpine.initTree(el)
276
+ el.setAttribute('data-apex-hydrated', '')
277
+ }
278
+ document.querySelectorAll('[data-apex-island]').forEach(function (el) {
279
+ const mode = el.getAttribute('data-apex-client')
280
+ if (mode === 'load') {
281
+ __hydrate(el)
282
+ } else if (mode === 'idle') {
283
+ (window.requestIdleCallback || function (cb) { return setTimeout(cb, 200) })(function () { __hydrate(el) })
284
+ } else if (mode === 'visible') {
285
+ const io = new IntersectionObserver(function (entries, obs) {
286
+ entries.forEach(function (e) { if (e.isIntersecting) { obs.unobserve(e.target); __hydrate(e.target) } })
287
+ })
288
+ io.observe(el)
289
+ }
290
+ // 'none' \u2192 do nothing; the SSR HTML is the final, static output.
291
+ })
292
+ `.trim()
293
+ );
294
+ async function renderIslandsPage(opts) {
295
+ const mod = await opts.loadModule(opts.pageId);
296
+ const cfg = opts.runtimeConfig ?? { public: {} };
297
+ const locals = opts.locals ?? {};
298
+ const loaderData = await mod.loader({
299
+ params: opts.params ?? {},
300
+ url: opts.url,
301
+ config: cfg,
302
+ locals
303
+ }) ?? {};
304
+ const head = mod.head ? await mod.head({
305
+ data: loaderData,
306
+ params: opts.params ?? {},
307
+ url: opts.url,
308
+ config: cfg,
309
+ locals
310
+ }) : void 0;
311
+ const authoredDefaults = mod.rootData ? mod.rootData() : {};
312
+ const data = { ...authoredDefaults, ...loaderData };
313
+ const available = opts.layouts ?? [];
314
+ const layoutName = mod.layout === false ? null : typeof mod.layout === "string" ? mod.layout : available.includes("default") ? "default" : null;
315
+ let template = mod.template;
316
+ let layoutCss = "";
317
+ const seen = /* @__PURE__ */ new Set();
318
+ let next = layoutName;
319
+ while (typeof next === "string" && available.includes(next) && !seen.has(next)) {
320
+ seen.add(next);
321
+ const layoutMod = await opts.loadModule(`/layouts/${next}.alpine`);
322
+ template = SLOT_RE.test(layoutMod.template) ? layoutMod.template.replace(SLOT_RE, () => template) : layoutMod.template + template;
323
+ layoutCss += layoutMod.css;
324
+ next = layoutMod.layout;
325
+ }
326
+ const { html, hydratingCount } = renderIslands(template, data, mod.scopeId, opts.registry);
327
+ const loaderScript = hydratingCount > 0 ? `
328
+ <script type="module">${ISLAND_LOADER}</script>` : "";
329
+ const configScript = hydratingCount > 0 ? `
330
+ ${clientConfigScript(opts.publicConfig ?? {})}` : "";
331
+ const appCssLink = opts.appCss ? `
332
+ <link rel="stylesheet" href="${opts.appCss}" />` : "";
333
+ const doc = `<!DOCTYPE html>
334
+ <html lang="en">
335
+ <head>
336
+ <meta charset="utf-8" />
337
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
338
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
339
+ ${renderHead(head)}${appCssLink}
340
+ <style>${mod.css}${layoutCss}${opts.componentCss ?? ""}</style>
341
+ </head>
342
+ <body>
343
+ ${html}${configScript}${loaderScript}
344
+ </body>
345
+ </html>`;
346
+ return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
347
+ }
348
+
349
+ // src/routing/router.ts
350
+ import { existsSync as existsSync3, readdirSync as readdirSync2, statSync } from "fs";
351
+ import { join as join3, relative, sep } from "path";
352
+ function walkAlpine(dir) {
353
+ const out = [];
354
+ for (const entry of readdirSync2(dir)) {
355
+ const abs = join3(dir, entry);
356
+ if (statSync(abs).isDirectory()) out.push(...walkAlpine(abs));
357
+ else if (entry.endsWith(".alpine")) out.push(abs);
358
+ }
359
+ return out;
360
+ }
361
+ function scanPages(root) {
362
+ const dir = join3(root, "pages");
363
+ if (!existsSync3(dir)) return [];
364
+ const routes = walkAlpine(dir).map((abs) => {
365
+ const rel = relative(dir, abs).split(sep).join("/");
366
+ const pageId = `/pages/${rel}`;
367
+ const parts = rel.replace(/\.alpine$/, "").split("/");
368
+ if (parts[parts.length - 1] === "index") parts.pop();
369
+ const segments = parts.map((p) => {
370
+ const catchAll = /^\[\.\.\.(.+)\]$/.exec(p);
371
+ if (catchAll) return { catchAll: catchAll[1] };
372
+ const m = /^\[(.+)\]$/.exec(p);
373
+ return m ? { param: m[1] } : { literal: p };
374
+ });
375
+ const isDynamic = segments.some((s) => s.param !== void 0 || s.catchAll !== void 0);
376
+ const pattern = `/${segments.map((s) => s.catchAll ? `:${s.catchAll}*` : s.param ? `:${s.param}` : s.literal).join("/")}`;
377
+ return { pageId, pattern, segments, isDynamic };
378
+ });
379
+ const rank = (r) => r.segments.some((s) => s.catchAll) ? 2 : r.isDynamic ? 1 : 0;
380
+ return routes.sort((a, b) => rank(a) - rank(b));
381
+ }
382
+ function pathSegments(url) {
383
+ const path = url.split("?")[0] ?? "/";
384
+ return path.split("/").filter(Boolean);
385
+ }
386
+ function matchRoute(routes, url) {
387
+ const segs = pathSegments(url);
388
+ for (const route of routes) {
389
+ const last = route.segments[route.segments.length - 1];
390
+ const isCatchAll = Boolean(last?.catchAll);
391
+ if (isCatchAll) {
392
+ const lead = route.segments.slice(0, -1);
393
+ if (segs.length < lead.length + 1) continue;
394
+ const params2 = {};
395
+ let ok2 = true;
396
+ for (let i = 0; i < lead.length; i++) {
397
+ const rs = lead[i];
398
+ const value = segs[i];
399
+ if (rs.param) params2[rs.param] = decodeURIComponent(value);
400
+ else if (rs.literal !== value) {
401
+ ok2 = false;
402
+ break;
403
+ }
404
+ }
405
+ if (!ok2) continue;
406
+ params2[last?.catchAll] = segs.slice(lead.length).map(decodeURIComponent).join("/");
407
+ return { pageId: route.pageId, params: params2 };
408
+ }
409
+ if (route.segments.length !== segs.length) continue;
410
+ const params = {};
411
+ let ok = true;
412
+ for (let i = 0; i < route.segments.length; i++) {
413
+ const rs = route.segments[i];
414
+ const value = segs[i];
415
+ if (rs.param) params[rs.param] = decodeURIComponent(value);
416
+ else if (rs.literal !== value) {
417
+ ok = false;
418
+ break;
419
+ }
420
+ }
421
+ if (ok) return { pageId: route.pageId, params };
422
+ }
423
+ return null;
424
+ }
425
+
402
426
  export {
403
427
  applyEnvToRuntimeConfig,
404
428
  resolveApexConfig,
429
+ loadStores,
430
+ renderPage,
405
431
  renderIslandsPage,
406
432
  scanPages,
407
- matchRoute,
408
- loadStores,
409
- renderPage
433
+ matchRoute
410
434
  };
@@ -0,0 +1,95 @@
1
+ // src/vscode.ts
2
+ import { spawnSync } from "child_process";
3
+ import { existsSync, readFileSync } 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 VERSION_FILE = fileURLToPath(new URL("../vscode/version.txt", import.meta.url));
8
+ var EXT_ID = "apex-stack.apex-alpine";
9
+ var WIN = process.platform === "win32";
10
+ var EDITOR_CLIS = ["code", "cursor", "windsurf", "codium"];
11
+ function extensionBundled() {
12
+ return existsSync(VSIX);
13
+ }
14
+ function bundledVersion() {
15
+ try {
16
+ const v = readFileSync(VERSION_FILE, "utf8").trim();
17
+ return v || null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function detectEditorCli() {
23
+ for (const cli of EDITOR_CLIS) {
24
+ try {
25
+ if (spawnSync(cli, ["--version"], { stdio: "ignore", shell: WIN }).status === 0) return cli;
26
+ } catch {
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ function installedVersion(cli) {
32
+ try {
33
+ const r = spawnSync(cli, ["--list-extensions", "--show-versions"], {
34
+ encoding: "utf8",
35
+ shell: WIN
36
+ });
37
+ if (r.status !== 0 || !r.stdout) return null;
38
+ const line = r.stdout.split("\n").find((l) => l.trim().toLowerCase().startsWith(`${EXT_ID}@`));
39
+ return line ? line.split("@")[1]?.trim() ?? null : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ function cmpVersion(a, b) {
45
+ const pa = a.split(".").map(Number);
46
+ const pb = b.split(".").map(Number);
47
+ for (let i = 0; i < 3; i++) {
48
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
49
+ }
50
+ return 0;
51
+ }
52
+ function openInEditor(file, line = 1, col = 1) {
53
+ const cli = process.env.APEX_EDITOR || process.env.VISUAL || process.env.EDITOR || detectEditorCli();
54
+ if (!cli) return false;
55
+ try {
56
+ return spawnSync(cli, ["-g", `${file}:${line}:${col}`], { stdio: "ignore", shell: WIN }).status === 0;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ function installExtension(cli = detectEditorCli()) {
62
+ if (!cli || !existsSync(VSIX)) return false;
63
+ return spawnSync(cli, ["--install-extension", VSIX, "--force"], { stdio: "inherit", shell: WIN }).status === 0;
64
+ }
65
+ function promptYesNo(question, def = true) {
66
+ if (!process.stdin.isTTY) return Promise.resolve(def);
67
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
68
+ return new Promise((resolve) => {
69
+ rl.question(`${question} ${def ? "(Y/n) " : "(y/N) "}`, (answer) => {
70
+ rl.close();
71
+ const a = answer.trim().toLowerCase();
72
+ resolve(a === "" ? def : a === "y" || a === "yes");
73
+ });
74
+ });
75
+ }
76
+ async function offerExtension(choice) {
77
+ if (!extensionBundled()) return null;
78
+ const cli = detectEditorCli();
79
+ if (!cli) return null;
80
+ const bundled = bundledVersion();
81
+ const installed = installedVersion(cli);
82
+ if (installed && bundled && cmpVersion(installed, bundled) >= 0) {
83
+ return `.alpine extension up to date (${installed})`;
84
+ }
85
+ const prompt = installed && bundled ? `Update the Apex .alpine extension (${installed} \u2192 ${bundled})?` : "Install the Apex .alpine extension (syntax highlighting)?";
86
+ const yes = choice ?? await promptYesNo(prompt);
87
+ if (!yes) return null;
88
+ if (!installExtension(cli)) return "extension install failed";
89
+ return installed ? `.alpine extension updated to ${bundled}` : "Apex .alpine extension installed";
90
+ }
91
+
92
+ export {
93
+ openInEditor,
94
+ offerExtension
95
+ };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  offerExtension
4
- } from "./chunk-CHBSGOB3.js";
4
+ } from "./chunk-P6KQSPAV.js";
5
5
  import {
6
6
  VERSION,
7
7
  banner,
@@ -147,13 +147,13 @@ var main = defineCommand2({
147
147
  },
148
148
  subCommands: {
149
149
  new: newCommand,
150
- dev: () => import("./dev-74CABXDP.js").then((m) => m.devCommand),
151
- build: () => import("./build-XUDXRQ4W.js").then((m) => m.buildCommand),
152
- start: () => import("./start-TBG2TEOE.js").then((m) => m.startCommand),
150
+ dev: () => import("./dev-XQN7XBZ4.js").then((m) => m.devCommand),
151
+ build: () => import("./build-NA6JEP3F.js").then((m) => m.buildCommand),
152
+ start: () => import("./start-7HSIYO6C.js").then((m) => m.startCommand),
153
153
  make: () => import("./make-VAYO5GWA.js").then((m) => m.makeCommand),
154
154
  add: () => import("./add-M3YLIFF5.js").then((m) => m.addCommand),
155
155
  theme: () => import("./theme-UUOIV44V.js").then((m) => m.themeCommand),
156
- upgrade: () => import("./upgrade-GCRSV4IE.js").then((m) => m.upgradeCommand),
156
+ upgrade: () => import("./upgrade-KGUW3C3O.js").then((m) => m.upgradeCommand),
157
157
  migrate: () => import("./migrate-X6LIHMIE.js").then((m) => m.migrateCommand),
158
158
  mcp: () => import("./mcp-CH7L4GF3.js").then((m) => m.mcpCommand)
159
159
  },
@@ -24,7 +24,7 @@ var devCommand = defineCommand({
24
24
  process.stdout.write(banner());
25
25
  const sp = spinner(`Starting dev server${args.islands ? " (islands mode)" : ""}\u2026`);
26
26
  try {
27
- const { startDevServer } = await import("./server-VQSL2KPO.js");
27
+ const { startDevServer } = await import("./server-2FN6GV3M.js");
28
28
  const { port: actual } = await startDevServer({ root, port, islands: Boolean(args.islands) });
29
29
  sp.succeed("Dev server ready");
30
30
  ready([