@apex-stack/core 0.1.20 → 0.2.0

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.
@@ -2,21 +2,23 @@ import {
2
2
  loadComponents
3
3
  } from "./chunk-4VG3CZ6H.js";
4
4
  import {
5
+ loadStores,
5
6
  renderIslandsPage,
6
7
  renderPage,
8
+ resolveApexConfig,
7
9
  scanPages
8
- } from "./chunk-4FUWZLVW.js";
9
- import "./chunk-MZVLRU3R.js";
10
+ } from "./chunk-XDKJO6ZC.js";
11
+ import "./chunk-JLIAISWM.js";
10
12
 
11
13
  // src/commands/build.ts
12
- import { cpSync, existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, rmSync, writeFileSync } from "fs";
14
+ import { cpSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync3, rmSync, writeFileSync } from "fs";
13
15
  import { dirname, join as join3, resolve } from "path";
14
16
  import { apex as apex3 } from "@apex-stack/vite";
15
17
  import { defineCommand } from "citty";
16
18
  import { createServer as createViteServer } from "vite";
17
19
 
18
20
  // src/build/buildClient.ts
19
- import { readFileSync } from "fs";
21
+ import { existsSync, readFileSync, readdirSync } from "fs";
20
22
  import { join } from "path";
21
23
  import { apex } from "@apex-stack/vite";
22
24
  import { build } from "vite";
@@ -24,9 +26,11 @@ var VIRT = "virtual:apex-client:";
24
26
  function entryName(pageId) {
25
27
  return pageId.replace(/^\/pages\//, "").replace(/\.alpine$/, "").replace(/[^a-zA-Z0-9]+/g, "_");
26
28
  }
27
- async function buildClient(root, routes, outDir) {
29
+ async function buildClient(root, routes, outDir, base = "/") {
28
30
  const input = {};
29
31
  for (const r of routes) input[entryName(r.pageId)] = `${VIRT}${r.pageId}`;
32
+ const storesDir = join(root, "stores");
33
+ const storeIds = existsSync(storesDir) ? readdirSync(storesDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).map((f) => `/stores/${f}`) : [];
30
34
  const entryPlugin = {
31
35
  name: "apex:client-entries",
32
36
  resolveId(id) {
@@ -37,8 +41,10 @@ async function buildClient(root, routes, outDir) {
37
41
  const pageId = id.slice(`\0${VIRT}`.length);
38
42
  return [
39
43
  `import Alpine from 'alpinejs'`,
44
+ ...storeIds.map((sid, i) => `import __s${i} from ${JSON.stringify(sid)}`),
40
45
  `import ${JSON.stringify(pageId)}`,
41
46
  "window.Alpine = Alpine",
47
+ ...storeIds.map((_, i) => `Alpine.store(__s${i}.name, __s${i}.factory())`),
42
48
  "Alpine.start()"
43
49
  ].join("\n");
44
50
  }
@@ -46,6 +52,7 @@ async function buildClient(root, routes, outDir) {
46
52
  };
47
53
  await build({
48
54
  root,
55
+ base,
49
56
  logLevel: "warn",
50
57
  plugins: [apex({ clientRuntime: "@apex-stack/core/client" }), entryPlugin],
51
58
  build: {
@@ -58,36 +65,43 @@ async function buildClient(root, routes, outDir) {
58
65
  const manifest = JSON.parse(
59
66
  readFileSync(join(outDir, ".vite", "manifest.json"), "utf8")
60
67
  );
68
+ const prefix = base.endsWith("/") ? base : `${base}/`;
61
69
  const hrefs = /* @__PURE__ */ new Map();
62
70
  for (const r of routes) {
63
71
  const virt = `${VIRT}${r.pageId}`;
64
72
  const entry = Object.values(manifest).find(
65
- (m) => m.isEntry && (m.src === virt || m.src === `\0${virt}`)
73
+ (m) => m.isEntry && typeof m.src === "string" && (m.src === virt || m.src.endsWith(virt))
66
74
  );
67
- if (entry) hrefs.set(r.pageId, `/${entry.file}`);
75
+ if (entry) hrefs.set(r.pageId, `${prefix}${entry.file}`);
68
76
  }
69
77
  return hrefs;
70
78
  }
71
79
 
72
80
  // src/build/buildServer.ts
73
- import { existsSync, readdirSync } from "fs";
81
+ import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
74
82
  import { isAbsolute, join as join2 } from "path";
75
83
  import { apex as apex2 } from "@apex-stack/vite";
76
84
  import { build as build2 } from "vite";
77
85
  async function buildServer(root, routes, outDir) {
78
86
  const ids = routes.map((r) => r.pageId);
79
87
  const compDir = join2(root, "components");
80
- if (existsSync(compDir)) {
81
- for (const f of readdirSync(compDir).filter((f2) => f2.endsWith(".alpine"))) {
88
+ if (existsSync2(compDir)) {
89
+ for (const f of readdirSync2(compDir).filter((f2) => f2.endsWith(".alpine"))) {
82
90
  ids.push(`/components/${f}`);
83
91
  }
84
92
  }
85
93
  const apiDir = join2(root, "server", "api");
86
- if (existsSync(apiDir)) {
87
- for (const f of readdirSync(apiDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2))) {
94
+ if (existsSync2(apiDir)) {
95
+ for (const f of readdirSync2(apiDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2))) {
88
96
  ids.push(`/server/api/${f}`);
89
97
  }
90
98
  }
99
+ const mwDir = join2(root, "middleware");
100
+ if (existsSync2(mwDir)) {
101
+ for (const f of readdirSync2(mwDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2))) {
102
+ ids.push(`/middleware/${f}`);
103
+ }
104
+ }
91
105
  const input = {};
92
106
  for (const id of ids) input[entryName2(id)] = join2(root, id.slice(1));
93
107
  const result = await build2({
@@ -147,6 +161,11 @@ var buildCommand = defineCommand({
147
161
  type: "boolean",
148
162
  description: "Build a Node server (dynamic routes + API/MCP)",
149
163
  default: false
164
+ },
165
+ base: {
166
+ type: "string",
167
+ description: "Public base path for a subpath deploy (e.g. /demo/)",
168
+ default: "/"
150
169
  }
151
170
  },
152
171
  async run({ args }) {
@@ -159,7 +178,7 @@ var buildCommand = defineCommand({
159
178
  if (args.server) {
160
179
  return buildServerTarget(root, outDir, args.outDir, routes);
161
180
  }
162
- const hrefs = args.islands ? /* @__PURE__ */ new Map() : await buildClient(root, staticRoutes, outDir);
181
+ const hrefs = args.islands ? /* @__PURE__ */ new Map() : await buildClient(root, staticRoutes, outDir, args.base);
163
182
  const vite = await createViteServer({
164
183
  root,
165
184
  appType: "custom",
@@ -171,13 +190,24 @@ var buildCommand = defineCommand({
171
190
  root,
172
191
  (id) => vite.ssrLoadModule(id)
173
192
  );
193
+ const stores = await loadStores(root, (id) => vite.ssrLoadModule(id));
194
+ const { runtimeConfig, publicConfig } = await resolveApexConfig(
195
+ root,
196
+ (id) => vite.ssrLoadModule(id)
197
+ );
198
+ const layoutsDir = join3(root, "layouts");
199
+ const layouts = existsSync3(layoutsDir) ? readdirSync3(layoutsDir).filter((f) => f.endsWith(".alpine")).map((f) => f.replace(/\.alpine$/, "")) : [];
174
200
  for (const route of staticRoutes) {
175
201
  const common = {
176
202
  loadModule: (id) => vite.ssrLoadModule(id),
177
203
  pageId: route.pageId,
178
204
  url: route.pattern,
179
205
  registry,
180
- componentCss
206
+ componentCss,
207
+ stores,
208
+ layouts,
209
+ runtimeConfig,
210
+ publicConfig
181
211
  };
182
212
  const html = args.islands ? await renderIslandsPage(common) : await renderPage({ ...common, clientHref: hrefs.get(route.pageId) });
183
213
  const dest = join3(outDir, outFile(route.pattern));
@@ -186,7 +216,7 @@ var buildCommand = defineCommand({
186
216
  console.log(` \u2713 ${route.pattern} \u2192 ${outFile(route.pattern)}`);
187
217
  }
188
218
  const pub = join3(root, "public");
189
- if (existsSync2(pub)) cpSync(pub, outDir, { recursive: true });
219
+ if (existsSync3(pub)) cpSync(pub, outDir, { recursive: true });
190
220
  console.log(
191
221
  `
192
222
  Built ${staticRoutes.length} page(s) \u2192 ${args.outDir}/${args.islands ? " (islands / static-first)" : " (prerendered + hydrated)"}`
@@ -205,22 +235,44 @@ var buildCommand = defineCommand({
205
235
  async function buildServerTarget(root, outDir, outLabel, routes) {
206
236
  const clientHrefs = await buildClient(root, routes, outDir);
207
237
  const server = await buildServer(root, routes, outDir);
238
+ let runtimeConfig = { public: {} };
239
+ const cfgVite = await createViteServer({
240
+ root,
241
+ appType: "custom",
242
+ server: { middlewareMode: true },
243
+ plugins: [apex3({ clientRuntime: "@apex-stack/core/client" })]
244
+ });
245
+ try {
246
+ const resolved = await resolveApexConfig(root, (id) => cfgVite.ssrLoadModule(id));
247
+ runtimeConfig = { public: {}, ...resolved.config.runtimeConfig ?? {} };
248
+ if (!runtimeConfig.public) runtimeConfig.public = {};
249
+ } finally {
250
+ await cfgVite.close();
251
+ }
208
252
  const components = {};
209
253
  const compDir = join3(root, "components");
210
- if (existsSync2(compDir)) {
211
- for (const f of readdirSync2(compDir).filter((f2) => f2.endsWith(".alpine"))) {
254
+ if (existsSync3(compDir)) {
255
+ for (const f of readdirSync3(compDir).filter((f2) => f2.endsWith(".alpine"))) {
212
256
  const sf = server.modules[`/components/${f}`];
213
257
  if (sf) components[f.replace(/\.alpine$/, "")] = sf;
214
258
  }
215
259
  }
216
260
  const api = [];
217
261
  const apiDir = join3(root, "server", "api");
218
- if (existsSync2(apiDir)) {
219
- for (const f of readdirSync2(apiDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2))) {
262
+ if (existsSync3(apiDir)) {
263
+ for (const f of readdirSync3(apiDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2))) {
220
264
  const sf = server.modules[`/server/api/${f}`];
221
265
  if (sf) api.push({ name: f.replace(/\.(ts|js|mjs)$/, ""), serverFile: sf });
222
266
  }
223
267
  }
268
+ const middleware = [];
269
+ const mwDir = join3(root, "middleware");
270
+ if (existsSync3(mwDir)) {
271
+ for (const f of readdirSync3(mwDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2)).sort()) {
272
+ const sf = server.modules[`/middleware/${f}`];
273
+ if (sf) middleware.push({ serverFile: sf });
274
+ }
275
+ }
224
276
  const manifest = {
225
277
  islands: false,
226
278
  routes: routes.map((r) => ({
@@ -229,11 +281,13 @@ async function buildServerTarget(root, outDir, outLabel, routes) {
229
281
  clientHref: clientHrefs.get(r.pageId)
230
282
  })),
231
283
  components,
232
- api
284
+ api,
285
+ middleware,
286
+ runtimeConfig
233
287
  };
234
288
  writeFileSync(join3(outDir, "apex-manifest.json"), JSON.stringify(manifest, null, 2));
235
289
  const pub = join3(root, "public");
236
- if (existsSync2(pub)) cpSync(pub, outDir, { recursive: true });
290
+ if (existsSync3(pub)) cpSync(pub, outDir, { recursive: true });
237
291
  console.log(
238
292
  `
239
293
  Built server target \u2192 ${outLabel}/ (${routes.length} route(s), ${api.length} API module(s))
@@ -0,0 +1,18 @@
1
+ // src/api/resource.ts
2
+ function isApexResource(x) {
3
+ return typeof x === "object" && x !== null && x.__apexResource === true;
4
+ }
5
+
6
+ // src/middleware/define.ts
7
+ function defineMiddleware(fn) {
8
+ return fn;
9
+ }
10
+ function isRedirect(value) {
11
+ return !!value && typeof value === "object" && value.__apexRedirect === true;
12
+ }
13
+
14
+ export {
15
+ isApexResource,
16
+ defineMiddleware,
17
+ isRedirect
18
+ };
@@ -0,0 +1,42 @@
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
+ };
@@ -1,6 +1,7 @@
1
1
  import {
2
- isApexResource
3
- } from "./chunk-HRJTOSYH.js";
2
+ isApexResource,
3
+ isRedirect
4
+ } from "./chunk-2C2HRLIY.js";
4
5
 
5
6
  // src/api/routes.ts
6
7
  import { existsSync, readdirSync } from "fs";
@@ -66,7 +67,7 @@ function matchApi(entries, path, method) {
66
67
  }
67
68
  return null;
68
69
  }
69
- function createApiHandler(entries) {
70
+ function createApiHandler(entries, config) {
70
71
  return defineEventHandler(async (event) => {
71
72
  const url = getRequestURL(event);
72
73
  const matched = matchApi(entries, url.pathname, event.method);
@@ -88,7 +89,12 @@ function createApiHandler(entries) {
88
89
  }
89
90
  input = parsed.data;
90
91
  }
91
- const result = await entry.route.handler({ input, url: url.toString() });
92
+ const result = await entry.route.handler({
93
+ input,
94
+ url: url.toString(),
95
+ config: config ?? { public: {} },
96
+ locals: event.context.apexLocals ?? {}
97
+ });
92
98
  setResponseHeader(event, "Content-Type", "application/json");
93
99
  return result;
94
100
  });
@@ -101,7 +107,7 @@ import { defineEventHandler as defineEventHandler2, toWebRequest } from "h3";
101
107
  function hasMcpRoutes(entries) {
102
108
  return entries.some((e) => e.route.mcp);
103
109
  }
104
- function buildServer(entries) {
110
+ function buildServer(entries, config) {
105
111
  const server = new McpServer({ name: "apexjs", version: "0.0.0" });
106
112
  for (const entry of entries) {
107
113
  server.registerTool(
@@ -113,7 +119,8 @@ function buildServer(entries) {
113
119
  async (args) => {
114
120
  const result = await entry.route.handler({
115
121
  input: args ?? {},
116
- url: `mcp://${entry.mcpName}`
122
+ url: `mcp://${entry.mcpName}`,
123
+ config: config ?? { public: {} }
117
124
  });
118
125
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
119
126
  }
@@ -121,10 +128,10 @@ function buildServer(entries) {
121
128
  }
122
129
  return server;
123
130
  }
124
- function createMcpHandler(entries) {
131
+ function createMcpHandler(entries, config) {
125
132
  const mcpEntries = entries.filter((e) => e.route.mcp);
126
133
  return defineEventHandler2(async (event) => {
127
- const server = buildServer(mcpEntries);
134
+ const server = buildServer(mcpEntries, config);
128
135
  const transport = new WebStandardStreamableHTTPServerTransport({
129
136
  sessionIdGenerator: void 0,
130
137
  enableJsonResponse: true
@@ -136,10 +143,42 @@ function createMcpHandler(entries) {
136
143
  });
137
144
  }
138
145
 
146
+ // src/middleware/run.ts
147
+ import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
148
+ import { join as join2 } from "path";
149
+ async function loadMiddleware(root, loadModule) {
150
+ const dir = join2(root, "middleware");
151
+ if (!existsSync2(dir)) return [];
152
+ const files = readdirSync2(dir).filter((f) => /\.(ts|js|mjs)$/.test(f)).sort();
153
+ const out = [];
154
+ for (const f of files) {
155
+ const mod = await loadModule(`/middleware/${f}`);
156
+ if (typeof mod.default === "function") out.push(mod.default);
157
+ }
158
+ return out;
159
+ }
160
+ async function runMiddleware(middleware, input) {
161
+ const locals = {};
162
+ for (const mw of middleware) {
163
+ const result = await mw({
164
+ url: input.url,
165
+ method: input.method,
166
+ config: input.config,
167
+ headers: input.headers,
168
+ locals,
169
+ redirect: (to, status = 302) => ({ __apexRedirect: true, to, status })
170
+ });
171
+ if (isRedirect(result)) return { redirect: result, locals };
172
+ }
173
+ return { locals };
174
+ }
175
+
139
176
  export {
140
177
  expandApiModule,
141
178
  loadApiRoutes,
142
179
  createApiHandler,
143
180
  hasMcpRoutes,
144
- createMcpHandler
181
+ createMcpHandler,
182
+ loadMiddleware,
183
+ runMiddleware
145
184
  };
@@ -0,0 +1,48 @@
1
+ // src/store.ts
2
+ function defineStore(name, factory) {
3
+ if (!name || /[^a-zA-Z0-9_$]/.test(name)) {
4
+ throw new Error(`defineStore: invalid store name "${name}" \u2014 use letters, digits, _ or $.`);
5
+ }
6
+ return { __apexStore: true, name, factory };
7
+ }
8
+ function isApexStore(x) {
9
+ return typeof x === "object" && x !== null && x.__apexStore === true;
10
+ }
11
+
12
+ // src/config/runtime.ts
13
+ function defineConfig(config) {
14
+ return config;
15
+ }
16
+ var SERVER_KEY = "__APEX_RUNTIME_CONFIG__";
17
+ var CLIENT_KEY = "__APEX_CONFIG__";
18
+ function setRuntimeConfig(cfg) {
19
+ ;
20
+ globalThis[SERVER_KEY] = cfg;
21
+ }
22
+ function clientConfigScript(publicConfig) {
23
+ return `<script>window.${CLIENT_KEY}=${JSON.stringify({ public: publicConfig }).replace(/</g, "\\u003c")}</script>`;
24
+ }
25
+ function useRuntimeConfig() {
26
+ if (typeof window !== "undefined") {
27
+ return window[CLIENT_KEY] ?? {
28
+ public: {}
29
+ };
30
+ }
31
+ return globalThis[SERVER_KEY] ?? { public: {} };
32
+ }
33
+ function env(key, fallback) {
34
+ if (typeof process !== "undefined" && process.env && process.env[key] !== void 0) {
35
+ return process.env[key];
36
+ }
37
+ return fallback;
38
+ }
39
+
40
+ export {
41
+ defineStore,
42
+ isApexStore,
43
+ defineConfig,
44
+ setRuntimeConfig,
45
+ clientConfigScript,
46
+ useRuntimeConfig,
47
+ env
48
+ };