@apex-stack/core 0.1.19 → 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.
- package/dist/{build-J47A3B4Y.js → build-VHS6KZBK.js} +91 -27
- package/dist/chunk-2C2HRLIY.js +18 -0
- package/dist/chunk-CHBSGOB3.js +42 -0
- package/dist/{chunk-JWYNLP4L.js → chunk-HCNNKT4A.js} +51 -9
- package/dist/chunk-JLIAISWM.js +48 -0
- package/dist/{chunk-XSN6NDWP.js → chunk-XDKJO6ZC.js} +159 -25
- package/dist/cli.js +65 -24
- package/dist/client.d.ts +1 -1
- package/dist/client.js +2 -1
- package/dist/{dev-OCVQRCCE.js → dev-G7HPP6KW.js} +6 -2
- package/dist/index.d.ts +88 -5
- package/dist/index.js +12 -4
- package/dist/{make-JAW22LQZ.js → make-VAYO5GWA.js} +71 -5
- package/dist/{mcp-DL4J6JFJ.js → mcp-CH7L4GF3.js} +1 -1
- package/dist/{migrate-NOGFOFV2.js → migrate-X6LIHMIE.js} +3 -1
- package/dist/{server-L3V34B5X.js → server-PTHGOE42.js} +63 -19
- package/dist/{start-AUJJ7HAY.js → start-3O3E43PT.js} +47 -10
- package/dist/upgrade-WC5F5FKY.js +168 -0
- package/package.json +5 -4
- package/templates/default/.env.example +9 -0
- package/templates/default/README.md +63 -17
- package/templates/default/_gitignore +5 -0
- package/templates/default/apex.config.ts +22 -0
- package/templates/default/components/Counter.alpine +15 -0
- package/templates/default/composables/useToggle.ts +14 -0
- package/templates/default/db/README.md +18 -0
- package/templates/default/layouts/default.alpine +16 -0
- package/templates/default/package.json +11 -2
- package/templates/default/pages/index.alpine +23 -92
- package/templates/default/public/.gitkeep +0 -0
- package/templates/default/server/api/hello.ts +10 -2
- package/templates/default/services/GreetingService.ts +12 -0
- package/templates/default/shared/types.ts +11 -0
- package/templates/default/stores/ui.ts +9 -0
- package/templates/default/tests/greeting.test.ts +12 -0
- package/templates/default/tsconfig.json +15 -0
- package/templates/default/vitest.config.ts +7 -0
- package/vscode/apex-alpine.vsix +0 -0
- package/dist/chunk-HRJTOSYH.js +0 -8
- package/dist/chunk-MZVLRU3R.js +0 -15
|
@@ -1,6 +1,93 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
clientConfigScript,
|
|
3
|
+
isApexStore,
|
|
4
|
+
setRuntimeConfig
|
|
5
|
+
} from "./chunk-JLIAISWM.js";
|
|
6
|
+
|
|
7
|
+
// src/config/resolve.ts
|
|
8
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
function parseEnvFile(text) {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
13
|
+
const line = rawLine.trim();
|
|
14
|
+
if (!line || line[0] === "#") continue;
|
|
15
|
+
const eq = line.indexOf("=");
|
|
16
|
+
if (eq < 0) continue;
|
|
17
|
+
const key = line.slice(0, eq).trim().replace(/^export\s+/, "");
|
|
18
|
+
let val = line.slice(eq + 1).trim();
|
|
19
|
+
if (val[0] === '"' && val.endsWith('"') || val[0] === "'" && val.endsWith("'")) {
|
|
20
|
+
val = val.slice(1, -1);
|
|
21
|
+
}
|
|
22
|
+
out[key] = val;
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function loadDotenv(root, mode = process.env.NODE_ENV || "development") {
|
|
27
|
+
const merged = {};
|
|
28
|
+
for (const file of [".env", `.env.${mode}`, ".env.local", `.env.${mode}.local`]) {
|
|
29
|
+
const p = join(root, file);
|
|
30
|
+
if (existsSync(p)) Object.assign(merged, parseEnvFile(readFileSync(p, "utf8")));
|
|
31
|
+
}
|
|
32
|
+
for (const [k, v] of Object.entries(merged)) {
|
|
33
|
+
if (process.env[k] === void 0) process.env[k] = v;
|
|
34
|
+
}
|
|
35
|
+
return { ...merged, ...process.env };
|
|
36
|
+
}
|
|
37
|
+
function screamingSnake(key) {
|
|
38
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
39
|
+
}
|
|
40
|
+
function coerce(def, raw) {
|
|
41
|
+
if (typeof def === "number") {
|
|
42
|
+
const n = Number(raw);
|
|
43
|
+
return Number.isNaN(n) ? def : n;
|
|
44
|
+
}
|
|
45
|
+
if (typeof def === "boolean") return raw === "true" || raw === "1";
|
|
46
|
+
return raw;
|
|
47
|
+
}
|
|
48
|
+
function applyOverrides(node, env, prefix) {
|
|
49
|
+
for (const [key, val] of Object.entries(node)) {
|
|
50
|
+
if (key === "public" && prefix === "APEX_" && val && typeof val === "object") {
|
|
51
|
+
applyOverrides(val, env, "APEX_PUBLIC_");
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
55
|
+
applyOverrides(val, env, `${prefix}${screamingSnake(key)}_`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const envKey = `${prefix}${screamingSnake(key)}`;
|
|
59
|
+
if (env[envKey] !== void 0) node[key] = coerce(val, env[envKey]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function applyEnvToRuntimeConfig(runtimeConfig, root) {
|
|
63
|
+
const env = loadDotenv(root);
|
|
64
|
+
if (!runtimeConfig.public) runtimeConfig.public = {};
|
|
65
|
+
applyOverrides(runtimeConfig, env, "APEX_");
|
|
66
|
+
setRuntimeConfig(runtimeConfig);
|
|
67
|
+
return runtimeConfig;
|
|
68
|
+
}
|
|
69
|
+
async function resolveApexConfig(root, loadModule) {
|
|
70
|
+
loadDotenv(root);
|
|
71
|
+
let config = {};
|
|
72
|
+
const file = ["apex.config.ts", "apex.config.js", "apex.config.mjs"].find(
|
|
73
|
+
(f) => existsSync(join(root, f))
|
|
74
|
+
);
|
|
75
|
+
if (file) {
|
|
76
|
+
try {
|
|
77
|
+
config = (await loadModule(`/${file}`)).default ?? {};
|
|
78
|
+
} catch {
|
|
79
|
+
config = {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const runtimeConfig = structuredClone(config.runtimeConfig ?? {});
|
|
83
|
+
if (!runtimeConfig.public) runtimeConfig.public = {};
|
|
84
|
+
applyEnvToRuntimeConfig(runtimeConfig, root);
|
|
85
|
+
return {
|
|
86
|
+
config,
|
|
87
|
+
runtimeConfig,
|
|
88
|
+
publicConfig: runtimeConfig.public ?? {}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
4
91
|
|
|
5
92
|
// src/islands/render.ts
|
|
6
93
|
import { renderIslands } from "@apex-stack/kit";
|
|
@@ -44,7 +131,12 @@ document.querySelectorAll('[data-apex-island]').forEach(function (el) {
|
|
|
44
131
|
);
|
|
45
132
|
async function renderIslandsPage(opts) {
|
|
46
133
|
const mod = await opts.loadModule(opts.pageId);
|
|
47
|
-
const loaderData = await mod.loader({
|
|
134
|
+
const loaderData = await mod.loader({
|
|
135
|
+
params: opts.params ?? {},
|
|
136
|
+
url: opts.url,
|
|
137
|
+
config: opts.runtimeConfig ?? { public: {} },
|
|
138
|
+
locals: opts.locals ?? {}
|
|
139
|
+
}) ?? {};
|
|
48
140
|
const { html, hydratingCount } = renderIslands(
|
|
49
141
|
mod.template,
|
|
50
142
|
loaderData,
|
|
@@ -53,6 +145,8 @@ async function renderIslandsPage(opts) {
|
|
|
53
145
|
);
|
|
54
146
|
const loaderScript = hydratingCount > 0 ? `
|
|
55
147
|
<script type="module">${ISLAND_LOADER}</script>` : "";
|
|
148
|
+
const configScript = hydratingCount > 0 ? `
|
|
149
|
+
${clientConfigScript(opts.publicConfig ?? {})}` : "";
|
|
56
150
|
const doc = `<!DOCTYPE html>
|
|
57
151
|
<html lang="en">
|
|
58
152
|
<head>
|
|
@@ -62,27 +156,27 @@ async function renderIslandsPage(opts) {
|
|
|
62
156
|
<style>${mod.css}${opts.componentCss ?? ""}</style>
|
|
63
157
|
</head>
|
|
64
158
|
<body>
|
|
65
|
-
${html}${loaderScript}
|
|
159
|
+
${html}${configScript}${loaderScript}
|
|
66
160
|
</body>
|
|
67
161
|
</html>`;
|
|
68
162
|
return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
|
|
69
163
|
}
|
|
70
164
|
|
|
71
165
|
// src/routing/router.ts
|
|
72
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
73
|
-
import { join, relative, sep } from "path";
|
|
166
|
+
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
167
|
+
import { join as join2, relative, sep } from "path";
|
|
74
168
|
function walkAlpine(dir) {
|
|
75
169
|
const out = [];
|
|
76
170
|
for (const entry of readdirSync(dir)) {
|
|
77
|
-
const abs =
|
|
171
|
+
const abs = join2(dir, entry);
|
|
78
172
|
if (statSync(abs).isDirectory()) out.push(...walkAlpine(abs));
|
|
79
173
|
else if (entry.endsWith(".alpine")) out.push(abs);
|
|
80
174
|
}
|
|
81
175
|
return out;
|
|
82
176
|
}
|
|
83
177
|
function scanPages(root) {
|
|
84
|
-
const dir =
|
|
85
|
-
if (!
|
|
178
|
+
const dir = join2(root, "pages");
|
|
179
|
+
if (!existsSync2(dir)) return [];
|
|
86
180
|
const routes = walkAlpine(dir).map((abs) => {
|
|
87
181
|
const rel = relative(dir, abs).split(sep).join("/");
|
|
88
182
|
const pageId = `/pages/${rel}`;
|
|
@@ -146,11 +240,11 @@ function matchRoute(routes, url) {
|
|
|
146
240
|
}
|
|
147
241
|
|
|
148
242
|
// src/stores/loader.ts
|
|
149
|
-
import { existsSync as
|
|
150
|
-
import { join as
|
|
243
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
244
|
+
import { join as join3 } from "path";
|
|
151
245
|
async function loadStores(root, loadModule) {
|
|
152
|
-
const dir =
|
|
153
|
-
if (!
|
|
246
|
+
const dir = join3(root, "stores");
|
|
247
|
+
if (!existsSync3(dir)) return [];
|
|
154
248
|
const out = [];
|
|
155
249
|
for (const file of readdirSync2(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"))) {
|
|
156
250
|
const id = `/stores/${file}`;
|
|
@@ -176,7 +270,11 @@ function storesInitialState(stores) {
|
|
|
176
270
|
}
|
|
177
271
|
|
|
178
272
|
// src/dev/renderPage.ts
|
|
179
|
-
import {
|
|
273
|
+
import {
|
|
274
|
+
renderComponent,
|
|
275
|
+
renderFragment,
|
|
276
|
+
stateIsland
|
|
277
|
+
} from "@apex-stack/kit";
|
|
180
278
|
function escAttr(s) {
|
|
181
279
|
return String(s).replace(
|
|
182
280
|
/[&<>"]/g,
|
|
@@ -186,17 +284,44 @@ function escAttr(s) {
|
|
|
186
284
|
function renderHead(head) {
|
|
187
285
|
const parts = [`<title>${head?.title ? escAttr(head.title) : "Apex JS"}</title>`];
|
|
188
286
|
for (const m of head?.meta ?? []) {
|
|
189
|
-
parts.push(
|
|
287
|
+
parts.push(
|
|
288
|
+
`<meta ${Object.entries(m).map(([k, v]) => `${k}="${escAttr(v)}"`).join(" ")} />`
|
|
289
|
+
);
|
|
190
290
|
}
|
|
191
291
|
for (const l of head?.link ?? []) {
|
|
192
|
-
parts.push(
|
|
292
|
+
parts.push(
|
|
293
|
+
`<link ${Object.entries(l).map(([k, v]) => `${k}="${escAttr(v)}"`).join(" ")} />`
|
|
294
|
+
);
|
|
193
295
|
}
|
|
194
296
|
return parts.join("\n ");
|
|
195
297
|
}
|
|
196
298
|
async function renderPage(opts) {
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
const
|
|
299
|
+
let mod = await opts.loadModule(opts.pageId);
|
|
300
|
+
const cfg = opts.runtimeConfig ?? { public: {} };
|
|
301
|
+
const locals = opts.locals ?? {};
|
|
302
|
+
let loaderData;
|
|
303
|
+
try {
|
|
304
|
+
loaderData = await mod.loader({
|
|
305
|
+
params: opts.params ?? {},
|
|
306
|
+
url: opts.url,
|
|
307
|
+
config: cfg,
|
|
308
|
+
locals
|
|
309
|
+
}) ?? {};
|
|
310
|
+
} catch (err) {
|
|
311
|
+
if (!opts.errorPageId) throw err;
|
|
312
|
+
mod = await opts.loadModule(opts.errorPageId);
|
|
313
|
+
const e = err;
|
|
314
|
+
loaderData = {
|
|
315
|
+
error: { message: e.message ?? "Something went wrong", statusCode: e.statusCode ?? 500 }
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const head = mod.head ? await mod.head({
|
|
319
|
+
data: loaderData,
|
|
320
|
+
params: opts.params ?? {},
|
|
321
|
+
url: opts.url,
|
|
322
|
+
config: cfg,
|
|
323
|
+
locals
|
|
324
|
+
}) : void 0;
|
|
200
325
|
const stores = opts.stores ?? [];
|
|
201
326
|
const { html } = renderComponent({
|
|
202
327
|
template: mod.template,
|
|
@@ -212,11 +337,15 @@ async function renderPage(opts) {
|
|
|
212
337
|
const layoutName = mod.layout === false ? null : typeof mod.layout === "string" ? mod.layout : available.includes("default") ? "default" : null;
|
|
213
338
|
let body = html;
|
|
214
339
|
let layoutCss = "";
|
|
215
|
-
|
|
216
|
-
|
|
340
|
+
const seen = /* @__PURE__ */ new Set();
|
|
341
|
+
let next = layoutName;
|
|
342
|
+
while (typeof next === "string" && available.includes(next) && !seen.has(next)) {
|
|
343
|
+
seen.add(next);
|
|
344
|
+
const layoutMod = await opts.loadModule(`/layouts/${next}.alpine`);
|
|
217
345
|
const chrome = renderFragment(layoutMod.template, {}, layoutMod.scopeId, opts.registry);
|
|
218
|
-
body = /<slot\b[^>]*>[\s\S]*?<\/slot>/.test(chrome) ? chrome.replace(/<slot\b[^>]*>[\s\S]*?<\/slot>/, () =>
|
|
219
|
-
layoutCss
|
|
346
|
+
body = /<slot\b[^>]*>[\s\S]*?<\/slot>/.test(chrome) ? chrome.replace(/<slot\b[^>]*>[\s\S]*?<\/slot>/, () => body) : chrome + body;
|
|
347
|
+
layoutCss += layoutMod.css;
|
|
348
|
+
next = layoutMod.layout;
|
|
220
349
|
}
|
|
221
350
|
const doc = shell({
|
|
222
351
|
body,
|
|
@@ -226,7 +355,8 @@ async function renderPage(opts) {
|
|
|
226
355
|
clientHref: opts.clientHref,
|
|
227
356
|
storeIds: stores.map((s) => s.id),
|
|
228
357
|
appCss: opts.appCss,
|
|
229
|
-
headTags: renderHead(head)
|
|
358
|
+
headTags: renderHead(head),
|
|
359
|
+
configScript: clientConfigScript(opts.publicConfig ?? {})
|
|
230
360
|
});
|
|
231
361
|
return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
|
|
232
362
|
}
|
|
@@ -238,7 +368,8 @@ function shell({
|
|
|
238
368
|
clientHref,
|
|
239
369
|
storeIds = [],
|
|
240
370
|
appCss,
|
|
241
|
-
headTags = "<title>Apex JS</title>"
|
|
371
|
+
headTags = "<title>Apex JS</title>",
|
|
372
|
+
configScript = ""
|
|
242
373
|
}) {
|
|
243
374
|
const storeImports = storeIds.map((id, i) => ` import __s${i} from ${JSON.stringify(id)}`).join("\n");
|
|
244
375
|
const storeRegs = storeIds.map((_, i) => ` Alpine.store(__s${i}.name, __s${i}.factory())`).join("\n");
|
|
@@ -262,12 +393,15 @@ ${storeRegs ? `${storeRegs}
|
|
|
262
393
|
<body>
|
|
263
394
|
${body}
|
|
264
395
|
${island}
|
|
396
|
+
${configScript}
|
|
265
397
|
${clientScript}
|
|
266
398
|
</body>
|
|
267
399
|
</html>`;
|
|
268
400
|
}
|
|
269
401
|
|
|
270
402
|
export {
|
|
403
|
+
applyEnvToRuntimeConfig,
|
|
404
|
+
resolveApexConfig,
|
|
271
405
|
renderIslandsPage,
|
|
272
406
|
scanPages,
|
|
273
407
|
matchRoute,
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
offerExtension
|
|
4
|
+
} from "./chunk-CHBSGOB3.js";
|
|
2
5
|
import {
|
|
3
6
|
VERSION,
|
|
4
7
|
banner,
|
|
@@ -11,11 +14,21 @@ import { defineCommand as defineCommand2, runMain } from "citty";
|
|
|
11
14
|
|
|
12
15
|
// src/commands/new.ts
|
|
13
16
|
import { spawn, spawnSync } from "child_process";
|
|
14
|
-
import { cpSync, existsSync,
|
|
17
|
+
import { cpSync, existsSync, readFileSync, readdirSync, renameSync, writeFileSync } from "fs";
|
|
15
18
|
import { basename, join, resolve } from "path";
|
|
16
19
|
import { fileURLToPath } from "url";
|
|
17
20
|
import { defineCommand } from "citty";
|
|
18
21
|
var TEMPLATE_DIR = fileURLToPath(new URL("../templates/default", import.meta.url));
|
|
22
|
+
function substituteName(dir, name) {
|
|
23
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
const p = join(dir, entry.name);
|
|
25
|
+
if (entry.isDirectory()) substituteName(p, name);
|
|
26
|
+
else {
|
|
27
|
+
const txt = readFileSync(p, "utf8");
|
|
28
|
+
if (txt.includes("{{name}}")) writeFileSync(p, txt.replaceAll("{{name}}", name));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
19
32
|
function detectPackageManager() {
|
|
20
33
|
const ua = process.env.npm_config_user_agent || "";
|
|
21
34
|
if (ua.startsWith("pnpm")) return "pnpm";
|
|
@@ -37,9 +50,26 @@ function installAsync(pm, cwd) {
|
|
|
37
50
|
var newCommand = defineCommand({
|
|
38
51
|
meta: { name: "new", description: "Scaffold a new Apex JS app" },
|
|
39
52
|
args: {
|
|
40
|
-
dir: {
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
dir: {
|
|
54
|
+
type: "positional",
|
|
55
|
+
required: false,
|
|
56
|
+
description: "Target directory",
|
|
57
|
+
default: "apex-app"
|
|
58
|
+
},
|
|
59
|
+
install: {
|
|
60
|
+
type: "boolean",
|
|
61
|
+
default: true,
|
|
62
|
+
description: "Install dependencies (use --no-install to skip)"
|
|
63
|
+
},
|
|
64
|
+
git: {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
default: true,
|
|
67
|
+
description: "Initialize a git repository (use --no-git to skip)"
|
|
68
|
+
},
|
|
69
|
+
vscode: {
|
|
70
|
+
type: "boolean",
|
|
71
|
+
description: "Install the Apex VS Code extension (skip the interactive prompt)"
|
|
72
|
+
}
|
|
43
73
|
},
|
|
44
74
|
async run({ args }) {
|
|
45
75
|
const dir = String(args.dir);
|
|
@@ -55,10 +85,7 @@ var newCommand = defineCommand({
|
|
|
55
85
|
cpSync(TEMPLATE_DIR, target, { recursive: true });
|
|
56
86
|
const gitignore = join(target, "_gitignore");
|
|
57
87
|
if (existsSync(gitignore)) renameSync(gitignore, join(target, ".gitignore"));
|
|
58
|
-
|
|
59
|
-
const file = join(target, rel);
|
|
60
|
-
if (existsSync(file)) writeFileSync(file, readFileSync(file, "utf8").replaceAll("{{name}}", name));
|
|
61
|
-
}
|
|
88
|
+
substituteName(target, name);
|
|
62
89
|
log(` ${color.green("\u2713")} Created ${color.bold(dir)}`);
|
|
63
90
|
const pm = detectPackageManager();
|
|
64
91
|
let gitOk = false;
|
|
@@ -73,21 +100,29 @@ var newCommand = defineCommand({
|
|
|
73
100
|
if (gitOk) log(` ${color.green("\u2713")} Initialized a git repository`);
|
|
74
101
|
let installed = false;
|
|
75
102
|
if (args.install) {
|
|
76
|
-
const sp = spinner(
|
|
103
|
+
const sp = spinner(
|
|
104
|
+
`Installing dependencies with ${pm}\u2026 ${color.dim("(first run can take a minute)")}`
|
|
105
|
+
);
|
|
77
106
|
installed = await installAsync(pm, target);
|
|
78
107
|
if (installed) sp.succeed(`Dependencies installed with ${pm}`);
|
|
79
108
|
else sp.fail(`Install failed \u2014 run ${color.cyan(`${pm} install`)} inside ${dir}`);
|
|
80
109
|
}
|
|
110
|
+
const ext = await offerExtension(args.vscode);
|
|
111
|
+
if (ext) log(` ${color.green("\u2713")} ${ext}`);
|
|
81
112
|
const runPrefix = pm === "npm" ? "npm run" : pm;
|
|
82
113
|
log(`
|
|
83
114
|
${color.bold("Next steps")}`);
|
|
84
115
|
log(` ${color.cyan(`cd ${dir}`)}`);
|
|
85
116
|
if (!installed) log(` ${color.cyan(pm === "yarn" ? "yarn" : `${pm} install`)}`);
|
|
86
117
|
log(` ${color.cyan("apex dev")} ${color.gray("# \u2192 http://localhost:3000")}`);
|
|
87
|
-
log(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
log(
|
|
119
|
+
`
|
|
120
|
+
${color.gray("Not installed globally? Use")} ${color.cyan(`${runPrefix} dev`)}${color.gray(".")}`
|
|
121
|
+
);
|
|
122
|
+
log(
|
|
123
|
+
` ${color.gray("Islands mode:")} ${color.cyan("apex dev --islands")}${color.gray(" \xB7 API routes are also MCP tools at /mcp.")}
|
|
124
|
+
`
|
|
125
|
+
);
|
|
91
126
|
}
|
|
92
127
|
});
|
|
93
128
|
|
|
@@ -97,7 +132,8 @@ var COMMANDS = [
|
|
|
97
132
|
["dev", "Start the dev server (SSR + hydrate, API + MCP)"],
|
|
98
133
|
["build", "Build for production (static, islands, or server)"],
|
|
99
134
|
["start", "Run a production server build"],
|
|
100
|
-
["make", "Generate a page, component,
|
|
135
|
+
["make", "Generate a page, component, route, store, middleware\u2026"],
|
|
136
|
+
["upgrade", "Adopt new scaffold defaults (non-destructive)"],
|
|
101
137
|
["migrate", "Apply pending database migrations"],
|
|
102
138
|
["mcp", "Inspect the MCP server \u2014 list or call tools"]
|
|
103
139
|
];
|
|
@@ -109,27 +145,32 @@ var main = defineCommand2({
|
|
|
109
145
|
},
|
|
110
146
|
subCommands: {
|
|
111
147
|
new: newCommand,
|
|
112
|
-
dev: () => import("./dev-
|
|
113
|
-
build: () => import("./build-
|
|
114
|
-
start: () => import("./start-
|
|
115
|
-
make: () => import("./make-
|
|
116
|
-
|
|
117
|
-
|
|
148
|
+
dev: () => import("./dev-G7HPP6KW.js").then((m) => m.devCommand),
|
|
149
|
+
build: () => import("./build-VHS6KZBK.js").then((m) => m.buildCommand),
|
|
150
|
+
start: () => import("./start-3O3E43PT.js").then((m) => m.startCommand),
|
|
151
|
+
make: () => import("./make-VAYO5GWA.js").then((m) => m.makeCommand),
|
|
152
|
+
upgrade: () => import("./upgrade-WC5F5FKY.js").then((m) => m.upgradeCommand),
|
|
153
|
+
migrate: () => import("./migrate-X6LIHMIE.js").then((m) => m.migrateCommand),
|
|
154
|
+
mcp: () => import("./mcp-CH7L4GF3.js").then((m) => m.mcpCommand)
|
|
118
155
|
},
|
|
119
156
|
// Shown for a bare `apex` (no subcommand): the brand banner + a command menu.
|
|
120
157
|
run({ rawArgs }) {
|
|
121
158
|
if (rawArgs.length > 0) return;
|
|
122
159
|
process.stdout.write(banner());
|
|
123
160
|
const log = console.log;
|
|
124
|
-
log(
|
|
125
|
-
`)
|
|
161
|
+
log(
|
|
162
|
+
` ${color.bold("Usage")} ${color.gray("apex")} ${color.cyan("<command>")} ${color.gray("[options]")}
|
|
163
|
+
`
|
|
164
|
+
);
|
|
126
165
|
log(` ${color.bold("Commands")}`);
|
|
127
166
|
for (const [name, desc] of COMMANDS) {
|
|
128
167
|
log(` ${color.cyan(`apex ${name}`.padEnd(13))} ${color.gray(desc)}`);
|
|
129
168
|
}
|
|
130
|
-
log(
|
|
169
|
+
log(
|
|
170
|
+
`
|
|
131
171
|
${color.gray("Run")} ${color.cyan("apex <command> --help")} ${color.gray("for details.")}
|
|
132
|
-
`
|
|
172
|
+
`
|
|
173
|
+
);
|
|
133
174
|
}
|
|
134
175
|
});
|
|
135
176
|
runMain(main);
|
package/dist/client.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { registerApexComponent } from '@apex-stack/kit/client';
|
|
1
|
+
export { ActionOptions, ActionState, createAction, registerApexComponent } from '@apex-stack/kit/client';
|
package/dist/client.js
CHANGED
|
@@ -12,7 +12,11 @@ var devCommand = defineCommand({
|
|
|
12
12
|
args: {
|
|
13
13
|
root: { type: "positional", required: false, description: "Project root", default: "." },
|
|
14
14
|
port: { type: "string", description: "Port to listen on", default: "3000" },
|
|
15
|
-
islands: {
|
|
15
|
+
islands: {
|
|
16
|
+
type: "boolean",
|
|
17
|
+
description: "Render in islands mode (static-first)",
|
|
18
|
+
default: false
|
|
19
|
+
}
|
|
16
20
|
},
|
|
17
21
|
async run({ args }) {
|
|
18
22
|
const root = resolve(process.cwd(), String(args.root));
|
|
@@ -20,7 +24,7 @@ var devCommand = defineCommand({
|
|
|
20
24
|
process.stdout.write(banner());
|
|
21
25
|
const sp = spinner(`Starting dev server${args.islands ? " (islands mode)" : ""}\u2026`);
|
|
22
26
|
try {
|
|
23
|
-
const { startDevServer } = await import("./server-
|
|
27
|
+
const { startDevServer } = await import("./server-PTHGOE42.js");
|
|
24
28
|
const { port: actual } = await startDevServer({ root, port, islands: Boolean(args.islands) });
|
|
25
29
|
sp.succeed("Dev server ready");
|
|
26
30
|
ready([
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
import { ZodRawShape, z } from 'zod';
|
|
2
2
|
|
|
3
|
+
/** A runtime-config object. Top-level keys are private (server-only); `public` is exposed to the client. */
|
|
4
|
+
interface RuntimeConfig {
|
|
5
|
+
/** Values under `public` are serialized into the page and readable in the browser. */
|
|
6
|
+
public?: Record<string, unknown>;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** The shape of `apex.config.ts`'s default export. */
|
|
10
|
+
interface ApexConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Config resolved at runtime. Declare defaults here (the structure), then
|
|
13
|
+
* override any leaf from the environment — `APEX_<KEY>` for private keys and
|
|
14
|
+
* `APEX_PUBLIC_<KEY>` for `public` keys (camelCase ↔ SCREAMING_SNAKE).
|
|
15
|
+
*/
|
|
16
|
+
runtimeConfig?: RuntimeConfig;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
/** Author an `apex.config.ts`. Identity function — exists for types + discoverability. */
|
|
20
|
+
declare function defineConfig(config: ApexConfig): ApexConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Read the runtime config. On the server this is the full config (private +
|
|
23
|
+
* public); in the browser it's the `public` subset seeded by the SSR shell.
|
|
24
|
+
* Mirrors Nuxt's `useRuntimeConfig()` — access public values as `config.public.*`.
|
|
25
|
+
*/
|
|
26
|
+
declare function useRuntimeConfig(): RuntimeConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Read a raw environment variable with an optional fallback — the Laravel-style
|
|
29
|
+
* `env('KEY', default)` escape hatch for values not declared in `runtimeConfig`.
|
|
30
|
+
* Server-only in practice (returns the fallback in the browser).
|
|
31
|
+
*/
|
|
32
|
+
declare function env(key: string, fallback?: string): string | undefined;
|
|
33
|
+
|
|
3
34
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
4
35
|
/** Inferred, validated input object for a route's handler. */
|
|
5
|
-
type
|
|
36
|
+
type InferInputShape<Shape extends ZodRawShape | undefined> = Shape extends ZodRawShape ? z.infer<z.ZodObject<Shape>> : Record<string, never>;
|
|
6
37
|
interface ApexRouteHandlerContext<Shape extends ZodRawShape | undefined> {
|
|
7
38
|
/** The validated input (query for GET, JSON body otherwise). */
|
|
8
|
-
input:
|
|
39
|
+
input: InferInputShape<Shape>;
|
|
9
40
|
/** The raw request URL. */
|
|
10
41
|
url: string;
|
|
42
|
+
/** Resolved runtime config (server-side: private + public). */
|
|
43
|
+
config: RuntimeConfig;
|
|
44
|
+
/** Request-scoped state set by middleware (e.g. an authenticated user). */
|
|
45
|
+
locals: Record<string, unknown>;
|
|
11
46
|
}
|
|
12
47
|
interface ApexRouteConfig<Shape extends ZodRawShape | undefined, Output> {
|
|
13
48
|
/** HTTP method. Defaults to GET. */
|
|
@@ -39,17 +74,36 @@ interface ApexRoute {
|
|
|
39
74
|
handler: (ctx: {
|
|
40
75
|
input: unknown;
|
|
41
76
|
url: string;
|
|
77
|
+
config?: RuntimeConfig;
|
|
78
|
+
locals?: Record<string, unknown>;
|
|
42
79
|
}) => unknown | Promise<unknown>;
|
|
43
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* A route that carries its input/output types (phantom fields, erased at runtime)
|
|
83
|
+
* so the frontend can derive them with `InferInput`/`InferOutput` — one contract,
|
|
84
|
+
* no duplicated types across backend + frontend.
|
|
85
|
+
*/
|
|
86
|
+
interface TypedApexRoute<In, Out> extends ApexRoute {
|
|
87
|
+
/** Phantom — never present at runtime; carries the validated input type. */
|
|
88
|
+
readonly __input?: In;
|
|
89
|
+
/** Phantom — never present at runtime; carries the handler's (awaited) output type. */
|
|
90
|
+
readonly __output?: Out;
|
|
91
|
+
}
|
|
92
|
+
/** Derive a route's validated input type: `type In = InferInput<typeof route>`. */
|
|
93
|
+
type InferInput<R> = R extends TypedApexRoute<infer In, unknown> ? In : never;
|
|
94
|
+
/** Derive a route's output type: `type Out = InferOutput<typeof route>`. */
|
|
95
|
+
type InferOutput<R> = R extends TypedApexRoute<unknown, infer Out> ? Out : never;
|
|
44
96
|
/**
|
|
45
97
|
* Define a typed API route. A single definition serves as:
|
|
46
98
|
* - a validated REST endpoint, and
|
|
47
99
|
* - (when `mcp: true`) an MCP tool whose inputSchema is derived from `input`.
|
|
48
100
|
*
|
|
49
101
|
* The strict, schema-carrying contract is what makes "any Apex API can be MCP"
|
|
50
|
-
* possible with no extra library on the user's side.
|
|
102
|
+
* possible with no extra library on the user's side. The returned route also
|
|
103
|
+
* carries its input/output types — a `import type` of it on the frontend +
|
|
104
|
+
* `InferInput`/`InferOutput` gives the client the API's types with zero drift.
|
|
51
105
|
*/
|
|
52
|
-
declare function defineApexRoute<Shape extends ZodRawShape | undefined, Output>(config: ApexRouteConfig<Shape, Output>):
|
|
106
|
+
declare function defineApexRoute<Shape extends ZodRawShape | undefined, Output>(config: ApexRouteConfig<Shape, Output>): TypedApexRoute<InferInputShape<Shape>, Awaited<Output>>;
|
|
53
107
|
|
|
54
108
|
/** One route within a resource, mounted at `/api/<name><pathSuffix>`. */
|
|
55
109
|
interface ResourceRoute {
|
|
@@ -95,4 +149,33 @@ interface ApexStore {
|
|
|
95
149
|
declare function defineStore(name: string, factory: () => StoreState): ApexStore;
|
|
96
150
|
declare function isApexStore(x: unknown): x is ApexStore;
|
|
97
151
|
|
|
98
|
-
|
|
152
|
+
/** The short-circuit value returned by `ctx.redirect(...)`. */
|
|
153
|
+
interface MiddlewareResult {
|
|
154
|
+
readonly __apexRedirect: true;
|
|
155
|
+
to: string;
|
|
156
|
+
status: number;
|
|
157
|
+
}
|
|
158
|
+
interface MiddlewareContext {
|
|
159
|
+
/** Request path (e.g. `/blog/hello`). Use it to scope a middleware to certain routes. */
|
|
160
|
+
url: string;
|
|
161
|
+
/** HTTP method. */
|
|
162
|
+
method: string;
|
|
163
|
+
/** Resolved runtime config (private + public on the server). */
|
|
164
|
+
config: RuntimeConfig;
|
|
165
|
+
/** Request headers, lowercased keys. */
|
|
166
|
+
headers: Record<string, string>;
|
|
167
|
+
/**
|
|
168
|
+
* Mutable, request-scoped state. Whatever a middleware puts here is handed to
|
|
169
|
+
* the page `loader({ locals })` and every route handler (`{ locals }`) — the
|
|
170
|
+
* seam for attaching an authenticated user, a request id, feature flags, etc.
|
|
171
|
+
*/
|
|
172
|
+
locals: Record<string, unknown>;
|
|
173
|
+
/** Return this to short-circuit the request with a redirect (default 302). */
|
|
174
|
+
redirect(to: string, status?: number): MiddlewareResult;
|
|
175
|
+
}
|
|
176
|
+
type MiddlewareReturn = MiddlewareResult | void;
|
|
177
|
+
type Middleware = (ctx: MiddlewareContext) => MiddlewareReturn | Promise<MiddlewareReturn>;
|
|
178
|
+
/** Author a middleware. Identity function — for types + discoverability. */
|
|
179
|
+
declare function defineMiddleware(fn: Middleware): Middleware;
|
|
180
|
+
|
|
181
|
+
export { type ApexConfig, type ApexResource, type ApexRoute, type ApexRouteConfig, type ApexRouteHandlerContext, type ApexStore, type HttpMethod, type InferInput, type InferOutput, type Middleware, type MiddlewareContext, type MiddlewareResult, type ResourceRoute, type RuntimeConfig, type StoreState, type TypedApexRoute, defineApexRoute, defineConfig, defineMiddleware, defineStore, env, isApexResource, isApexStore, useRuntimeConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
defineMiddleware,
|
|
2
3
|
isApexResource
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2C2HRLIY.js";
|
|
4
5
|
import {
|
|
6
|
+
defineConfig,
|
|
5
7
|
defineStore,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
env,
|
|
9
|
+
isApexStore,
|
|
10
|
+
useRuntimeConfig
|
|
11
|
+
} from "./chunk-JLIAISWM.js";
|
|
8
12
|
|
|
9
13
|
// src/api/defineRoute.ts
|
|
10
14
|
function defineApexRoute(config) {
|
|
@@ -19,7 +23,11 @@ function defineApexRoute(config) {
|
|
|
19
23
|
}
|
|
20
24
|
export {
|
|
21
25
|
defineApexRoute,
|
|
26
|
+
defineConfig,
|
|
27
|
+
defineMiddleware,
|
|
22
28
|
defineStore,
|
|
29
|
+
env,
|
|
23
30
|
isApexResource,
|
|
24
|
-
isApexStore
|
|
31
|
+
isApexStore,
|
|
32
|
+
useRuntimeConfig
|
|
25
33
|
};
|