@apex-stack/core 0.1.1 → 0.1.3
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/{chunk-XB5ZYPPE.js → chunk-SGEM3N42.js} +31 -16
- package/dist/cli.js +390 -28
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/package.json +3 -3
|
@@ -20,11 +20,18 @@ async function renderPage(opts) {
|
|
|
20
20
|
body: html,
|
|
21
21
|
island: stateIsland(mod.componentId, loaderData),
|
|
22
22
|
css: mod.css + (opts.componentCss ?? ""),
|
|
23
|
-
pageId: opts.pageId
|
|
23
|
+
pageId: opts.pageId,
|
|
24
|
+
clientHref: opts.clientHref
|
|
24
25
|
});
|
|
25
26
|
return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
|
|
26
27
|
}
|
|
27
|
-
function shell({ body, island, css, pageId }) {
|
|
28
|
+
function shell({ body, island, css, pageId, clientHref }) {
|
|
29
|
+
const clientScript = clientHref ? `<script type="module" src="${clientHref}"></script>` : `<script type="module">
|
|
30
|
+
import Alpine from 'alpinejs'
|
|
31
|
+
import ${JSON.stringify(pageId)}
|
|
32
|
+
window.Alpine = Alpine
|
|
33
|
+
Alpine.start()
|
|
34
|
+
</script>`;
|
|
28
35
|
return `<!DOCTYPE html>
|
|
29
36
|
<html lang="en">
|
|
30
37
|
<head>
|
|
@@ -36,12 +43,7 @@ function shell({ body, island, css, pageId }) {
|
|
|
36
43
|
<body>
|
|
37
44
|
${body}
|
|
38
45
|
${island}
|
|
39
|
-
|
|
40
|
-
import Alpine from 'alpinejs'
|
|
41
|
-
import ${JSON.stringify(pageId)}
|
|
42
|
-
window.Alpine = Alpine
|
|
43
|
-
Alpine.start()
|
|
44
|
-
</script>
|
|
46
|
+
${clientScript}
|
|
45
47
|
</body>
|
|
46
48
|
</html>`;
|
|
47
49
|
}
|
|
@@ -80,6 +82,18 @@ function sanitizeName(name) {
|
|
|
80
82
|
function entryFor(pattern, method, mcpName, route) {
|
|
81
83
|
return { pattern, segments: toSegments(pattern), method, mcpName, route };
|
|
82
84
|
}
|
|
85
|
+
function expandApiModule(name, def) {
|
|
86
|
+
if (!def) return [];
|
|
87
|
+
if (isApexResource(def)) {
|
|
88
|
+
return def.routes.map(
|
|
89
|
+
(r) => entryFor(`/api/${def.name}${r.pathSuffix}`, r.route.method, r.mcpName, r.route)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (typeof def.handler === "function") {
|
|
93
|
+
return [entryFor(`/api/${name}`, def.method, sanitizeName(name), def)];
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
83
97
|
async function loadApiRoutes(root, loadModule) {
|
|
84
98
|
const dir = join(root, "server", "api");
|
|
85
99
|
if (!existsSync(dir)) return [];
|
|
@@ -87,14 +101,7 @@ async function loadApiRoutes(root, loadModule) {
|
|
|
87
101
|
for (const file of readdirSync(dir).filter((f) => /\.(ts|js|mjs)$/.test(f))) {
|
|
88
102
|
const name = file.replace(/\.(ts|js|mjs)$/, "");
|
|
89
103
|
const def = (await loadModule(`/server/api/${file}`)).default;
|
|
90
|
-
|
|
91
|
-
if (isApexResource(def)) {
|
|
92
|
-
for (const r of def.routes) {
|
|
93
|
-
entries.push(entryFor(`/api/${def.name}${r.pathSuffix}`, r.route.method, r.mcpName, r.route));
|
|
94
|
-
}
|
|
95
|
-
} else if (typeof def.handler === "function") {
|
|
96
|
-
entries.push(entryFor(`/api/${name}`, def.method, sanitizeName(name), def));
|
|
97
|
-
}
|
|
104
|
+
entries.push(...expandApiModule(name, def));
|
|
98
105
|
}
|
|
99
106
|
return entries;
|
|
100
107
|
}
|
|
@@ -408,6 +415,14 @@ function notFoundPage(url, routes) {
|
|
|
408
415
|
|
|
409
416
|
export {
|
|
410
417
|
isApexResource,
|
|
418
|
+
expandApiModule,
|
|
419
|
+
createApiHandler,
|
|
420
|
+
hasMcpRoutes,
|
|
421
|
+
createMcpHandler,
|
|
422
|
+
loadComponents,
|
|
423
|
+
renderIslandsPage,
|
|
424
|
+
scanPages,
|
|
425
|
+
matchRoute,
|
|
411
426
|
renderPage,
|
|
412
427
|
startDevServer
|
|
413
428
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,248 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createApiHandler,
|
|
3
|
+
createMcpHandler,
|
|
4
|
+
expandApiModule,
|
|
5
|
+
hasMcpRoutes,
|
|
6
|
+
loadComponents,
|
|
7
|
+
matchRoute,
|
|
8
|
+
renderIslandsPage,
|
|
9
|
+
renderPage,
|
|
10
|
+
scanPages,
|
|
2
11
|
startDevServer
|
|
3
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SGEM3N42.js";
|
|
4
13
|
|
|
5
14
|
// src/cli.ts
|
|
6
|
-
import { resolve as
|
|
7
|
-
import { defineCommand as
|
|
15
|
+
import { resolve as resolve5 } from "path";
|
|
16
|
+
import { defineCommand as defineCommand6, runMain } from "citty";
|
|
8
17
|
|
|
9
|
-
// src/commands/
|
|
10
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
11
|
-
import { dirname, join, resolve } from "path";
|
|
18
|
+
// src/commands/build.ts
|
|
19
|
+
import { cpSync, existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, rmSync, writeFileSync } from "fs";
|
|
20
|
+
import { dirname, join as join3, resolve } from "path";
|
|
21
|
+
import { apex as apex3 } from "@apex-stack/vite";
|
|
12
22
|
import { defineCommand } from "citty";
|
|
23
|
+
import { createServer as createViteServer } from "vite";
|
|
24
|
+
|
|
25
|
+
// src/build/buildClient.ts
|
|
26
|
+
import { readFileSync } from "fs";
|
|
27
|
+
import { join } from "path";
|
|
28
|
+
import { apex } from "@apex-stack/vite";
|
|
29
|
+
import { build } from "vite";
|
|
30
|
+
var VIRT = "virtual:apex-client:";
|
|
31
|
+
function entryName(pageId) {
|
|
32
|
+
return pageId.replace(/^\/pages\//, "").replace(/\.alpine$/, "").replace(/[^a-zA-Z0-9]+/g, "_");
|
|
33
|
+
}
|
|
34
|
+
async function buildClient(root, routes, outDir) {
|
|
35
|
+
const input = {};
|
|
36
|
+
for (const r of routes) input[entryName(r.pageId)] = `${VIRT}${r.pageId}`;
|
|
37
|
+
const entryPlugin = {
|
|
38
|
+
name: "apex:client-entries",
|
|
39
|
+
resolveId(id) {
|
|
40
|
+
if (id.startsWith(VIRT)) return `\0${id}`;
|
|
41
|
+
},
|
|
42
|
+
load(id) {
|
|
43
|
+
if (id.startsWith(`\0${VIRT}`)) {
|
|
44
|
+
const pageId = id.slice(`\0${VIRT}`.length);
|
|
45
|
+
return [
|
|
46
|
+
`import Alpine from 'alpinejs'`,
|
|
47
|
+
`import ${JSON.stringify(pageId)}`,
|
|
48
|
+
`window.Alpine = Alpine`,
|
|
49
|
+
`Alpine.start()`
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
await build({
|
|
55
|
+
root,
|
|
56
|
+
logLevel: "warn",
|
|
57
|
+
plugins: [apex({ clientRuntime: "@apex-stack/core/client" }), entryPlugin],
|
|
58
|
+
build: {
|
|
59
|
+
outDir,
|
|
60
|
+
emptyOutDir: false,
|
|
61
|
+
manifest: true,
|
|
62
|
+
rollupOptions: { input }
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const manifest = JSON.parse(readFileSync(join(outDir, ".vite", "manifest.json"), "utf8"));
|
|
66
|
+
const hrefs = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const r of routes) {
|
|
68
|
+
const virt = `${VIRT}${r.pageId}`;
|
|
69
|
+
const entry = Object.values(manifest).find(
|
|
70
|
+
(m) => m.isEntry && (m.src === virt || m.src === `\0${virt}`)
|
|
71
|
+
);
|
|
72
|
+
if (entry) hrefs.set(r.pageId, `/${entry.file}`);
|
|
73
|
+
}
|
|
74
|
+
return hrefs;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/build/buildServer.ts
|
|
78
|
+
import { existsSync, readdirSync } from "fs";
|
|
79
|
+
import { isAbsolute, join as join2 } from "path";
|
|
80
|
+
import { apex as apex2 } from "@apex-stack/vite";
|
|
81
|
+
import { build as build2 } from "vite";
|
|
82
|
+
async function buildServer(root, routes, outDir) {
|
|
83
|
+
const ids = routes.map((r) => r.pageId);
|
|
84
|
+
const compDir = join2(root, "components");
|
|
85
|
+
if (existsSync(compDir)) {
|
|
86
|
+
for (const f of readdirSync(compDir).filter((f2) => f2.endsWith(".alpine"))) {
|
|
87
|
+
ids.push(`/components/${f}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const apiDir = join2(root, "server", "api");
|
|
91
|
+
if (existsSync(apiDir)) {
|
|
92
|
+
for (const f of readdirSync(apiDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2))) {
|
|
93
|
+
ids.push(`/server/api/${f}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const input = {};
|
|
97
|
+
for (const id of ids) input[entryName2(id)] = join2(root, id.slice(1));
|
|
98
|
+
const result = await build2({
|
|
99
|
+
root,
|
|
100
|
+
logLevel: "warn",
|
|
101
|
+
plugins: [apex2({ clientRuntime: "@apex-stack/core/client" })],
|
|
102
|
+
build: {
|
|
103
|
+
ssr: true,
|
|
104
|
+
target: "esnext",
|
|
105
|
+
// Node target — allow top-level await in server modules
|
|
106
|
+
outDir: join2(outDir, "server"),
|
|
107
|
+
emptyOutDir: false,
|
|
108
|
+
rollupOptions: {
|
|
109
|
+
input,
|
|
110
|
+
// Externalize every package import (bare specifier) — deps are resolved at
|
|
111
|
+
// runtime from node_modules. Only the app's own relative/absolute files are
|
|
112
|
+
// bundled. This keeps native/workspace deps (@libsql/client, drizzle, …) out.
|
|
113
|
+
external: (id) => !id.startsWith(".") && !isAbsolute(id),
|
|
114
|
+
output: { format: "esm", entryFileNames: "[name].mjs" }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const byFacade = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const chunk of result.output) {
|
|
120
|
+
if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
|
|
121
|
+
byFacade.set(chunk.facadeModuleId, chunk.fileName);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const modules = {};
|
|
125
|
+
for (const id of ids) {
|
|
126
|
+
const abs = join2(root, id.slice(1));
|
|
127
|
+
const file = byFacade.get(abs);
|
|
128
|
+
if (file) modules[id] = file;
|
|
129
|
+
}
|
|
130
|
+
return { modules };
|
|
131
|
+
}
|
|
132
|
+
function entryName2(id) {
|
|
133
|
+
return id.replace(/^\//, "").replace(/\.(alpine|ts|js|mjs)$/, "").replace(/[^a-zA-Z0-9]+/g, "_");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/commands/build.ts
|
|
137
|
+
function outFile(pattern) {
|
|
138
|
+
const clean = pattern.replace(/^\//, "");
|
|
139
|
+
return clean === "" ? "index.html" : `${clean}/index.html`;
|
|
140
|
+
}
|
|
141
|
+
var buildCommand = defineCommand({
|
|
142
|
+
meta: { name: "build", description: "Prerender pages to deployable HTML + client bundles" },
|
|
143
|
+
args: {
|
|
144
|
+
root: { type: "positional", required: false, description: "Project root", default: "." },
|
|
145
|
+
outDir: { type: "string", description: "Output directory", default: "dist" },
|
|
146
|
+
islands: { type: "boolean", description: "Static-first islands mode (zero-JS static)", default: false },
|
|
147
|
+
server: { type: "boolean", description: "Build a Node server (dynamic routes + API/MCP)", default: false }
|
|
148
|
+
},
|
|
149
|
+
async run({ args }) {
|
|
150
|
+
const root = resolve(process.cwd(), args.root);
|
|
151
|
+
const outDir = resolve(root, args.outDir);
|
|
152
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
153
|
+
const routes = scanPages(root);
|
|
154
|
+
const staticRoutes = routes.filter((r) => !r.isDynamic);
|
|
155
|
+
const dynamic = routes.filter((r) => r.isDynamic);
|
|
156
|
+
if (args.server) {
|
|
157
|
+
return buildServerTarget(root, outDir, args.outDir, routes);
|
|
158
|
+
}
|
|
159
|
+
const hrefs = args.islands ? /* @__PURE__ */ new Map() : await buildClient(root, staticRoutes, outDir);
|
|
160
|
+
const vite = await createViteServer({
|
|
161
|
+
root,
|
|
162
|
+
appType: "custom",
|
|
163
|
+
server: { middlewareMode: true },
|
|
164
|
+
plugins: [apex3({ clientRuntime: "@apex-stack/core/client" })]
|
|
165
|
+
});
|
|
166
|
+
try {
|
|
167
|
+
const { registry, css: componentCss } = await loadComponents(
|
|
168
|
+
root,
|
|
169
|
+
(id) => vite.ssrLoadModule(id)
|
|
170
|
+
);
|
|
171
|
+
for (const route of staticRoutes) {
|
|
172
|
+
const common = {
|
|
173
|
+
loadModule: (id) => vite.ssrLoadModule(id),
|
|
174
|
+
pageId: route.pageId,
|
|
175
|
+
url: route.pattern,
|
|
176
|
+
registry,
|
|
177
|
+
componentCss
|
|
178
|
+
};
|
|
179
|
+
const html = args.islands ? await renderIslandsPage(common) : await renderPage({ ...common, clientHref: hrefs.get(route.pageId) });
|
|
180
|
+
const dest = join3(outDir, outFile(route.pattern));
|
|
181
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
182
|
+
writeFileSync(dest, html);
|
|
183
|
+
console.log(` \u2713 ${route.pattern} \u2192 ${outFile(route.pattern)}`);
|
|
184
|
+
}
|
|
185
|
+
const pub = join3(root, "public");
|
|
186
|
+
if (existsSync2(pub)) cpSync(pub, outDir, { recursive: true });
|
|
187
|
+
console.log(
|
|
188
|
+
`
|
|
189
|
+
Built ${staticRoutes.length} page(s) \u2192 ${args.outDir}/` + (args.islands ? " (islands / static-first)" : " (prerendered + hydrated)")
|
|
190
|
+
);
|
|
191
|
+
if (dynamic.length) {
|
|
192
|
+
console.log(
|
|
193
|
+
` Skipped ${dynamic.length} dynamic route(s): ${dynamic.map((r) => r.pattern).join(", ")} (server target on the roadmap).`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
console.log();
|
|
197
|
+
} finally {
|
|
198
|
+
await vite.close();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
async function buildServerTarget(root, outDir, outLabel, routes) {
|
|
203
|
+
const clientHrefs = await buildClient(root, routes, outDir);
|
|
204
|
+
const server = await buildServer(root, routes, outDir);
|
|
205
|
+
const components = {};
|
|
206
|
+
const compDir = join3(root, "components");
|
|
207
|
+
if (existsSync2(compDir)) {
|
|
208
|
+
for (const f of readdirSync2(compDir).filter((f2) => f2.endsWith(".alpine"))) {
|
|
209
|
+
const sf = server.modules[`/components/${f}`];
|
|
210
|
+
if (sf) components[f.replace(/\.alpine$/, "")] = sf;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const api = [];
|
|
214
|
+
const apiDir = join3(root, "server", "api");
|
|
215
|
+
if (existsSync2(apiDir)) {
|
|
216
|
+
for (const f of readdirSync2(apiDir).filter((f2) => /\.(ts|js|mjs)$/.test(f2))) {
|
|
217
|
+
const sf = server.modules[`/server/api/${f}`];
|
|
218
|
+
if (sf) api.push({ name: f.replace(/\.(ts|js|mjs)$/, ""), serverFile: sf });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const manifest = {
|
|
222
|
+
islands: false,
|
|
223
|
+
routes: routes.map((r) => ({
|
|
224
|
+
...r,
|
|
225
|
+
serverFile: server.modules[r.pageId],
|
|
226
|
+
clientHref: clientHrefs.get(r.pageId)
|
|
227
|
+
})),
|
|
228
|
+
components,
|
|
229
|
+
api
|
|
230
|
+
};
|
|
231
|
+
writeFileSync(join3(outDir, "apex-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
232
|
+
const pub = join3(root, "public");
|
|
233
|
+
if (existsSync2(pub)) cpSync(pub, outDir, { recursive: true });
|
|
234
|
+
console.log(
|
|
235
|
+
`
|
|
236
|
+
Built server target \u2192 ${outLabel}/ (${routes.length} route(s), ${api.length} API module(s))
|
|
237
|
+
Run it: apex start
|
|
238
|
+
`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/commands/make.ts
|
|
243
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
244
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
|
|
245
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
13
246
|
function pageTemplate(name) {
|
|
14
247
|
return `<script server lang="ts">
|
|
15
248
|
export function loader() {
|
|
@@ -55,14 +288,14 @@ export default defineApexRoute({
|
|
|
55
288
|
function plan(kind, name, root) {
|
|
56
289
|
switch (kind) {
|
|
57
290
|
case "page":
|
|
58
|
-
return { path:
|
|
291
|
+
return { path: join4(root, "pages", `${name}.alpine`), contents: pageTemplate(name) };
|
|
59
292
|
case "component":
|
|
60
|
-
return { path:
|
|
293
|
+
return { path: join4(root, "components", `${name}.alpine`), contents: componentTemplate() };
|
|
61
294
|
case "api":
|
|
62
|
-
return { path:
|
|
295
|
+
return { path: join4(root, "server", "api", `${name}.ts`), contents: apiTemplate(name) };
|
|
63
296
|
}
|
|
64
297
|
}
|
|
65
|
-
var makeCommand =
|
|
298
|
+
var makeCommand = defineCommand2({
|
|
66
299
|
meta: { name: "make", description: "Generate a page, component, or API route" },
|
|
67
300
|
args: {
|
|
68
301
|
kind: { type: "positional", required: true, description: "page | component | api" },
|
|
@@ -77,16 +310,16 @@ var makeCommand = defineCommand({
|
|
|
77
310
|
`);
|
|
78
311
|
process.exit(1);
|
|
79
312
|
}
|
|
80
|
-
const root =
|
|
313
|
+
const root = resolve2(process.cwd(), args.root);
|
|
81
314
|
const { path, contents } = plan(kind, args.name, root);
|
|
82
|
-
if (
|
|
315
|
+
if (existsSync3(path)) {
|
|
83
316
|
console.error(`
|
|
84
317
|
\u2717 Already exists: ${path}
|
|
85
318
|
`);
|
|
86
319
|
process.exit(1);
|
|
87
320
|
}
|
|
88
|
-
|
|
89
|
-
|
|
321
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
322
|
+
writeFileSync2(path, contents);
|
|
90
323
|
console.log(`
|
|
91
324
|
\u2713 Created ${path.replace(`${root}/`, "")}
|
|
92
325
|
`);
|
|
@@ -94,8 +327,8 @@ var makeCommand = defineCommand({
|
|
|
94
327
|
});
|
|
95
328
|
|
|
96
329
|
// src/commands/mcp.ts
|
|
97
|
-
import { defineCommand as
|
|
98
|
-
var mcpCommand =
|
|
330
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
331
|
+
var mcpCommand = defineCommand3({
|
|
99
332
|
meta: { name: "mcp", description: "Inspect the local MCP server (list or call tools)" },
|
|
100
333
|
args: {
|
|
101
334
|
url: { type: "string", description: "MCP endpoint URL", default: "http://localhost:3000/mcp" },
|
|
@@ -149,28 +382,32 @@ var mcpCommand = defineCommand2({
|
|
|
149
382
|
|
|
150
383
|
// src/commands/migrate.ts
|
|
151
384
|
import { createRequire } from "module";
|
|
152
|
-
import { join as
|
|
385
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
153
386
|
import { pathToFileURL } from "url";
|
|
154
|
-
import { defineCommand as
|
|
155
|
-
var migrateCommand =
|
|
387
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
388
|
+
var migrateCommand = defineCommand4({
|
|
156
389
|
meta: { name: "migrate", description: "Apply pending SQL migrations (db/migrations/*.sql)" },
|
|
157
390
|
args: {
|
|
158
|
-
db: { type: "string", description: "SQLite file path", default: "data.db" },
|
|
391
|
+
db: { type: "string", description: "SQLite file path (libSQL)", default: "data.db" },
|
|
392
|
+
driver: { type: "string", description: "sqlite | postgres | pglite", default: "sqlite" },
|
|
393
|
+
url: { type: "string", description: "Connection URL (postgres) \u2014 overrides --db" },
|
|
159
394
|
dir: { type: "string", description: "Migrations directory", default: "db/migrations" },
|
|
160
395
|
root: { type: "string", description: "Project root", default: "." }
|
|
161
396
|
},
|
|
162
397
|
async run({ args }) {
|
|
163
|
-
const root =
|
|
398
|
+
const root = resolve3(process.cwd(), args.root);
|
|
164
399
|
let data;
|
|
165
400
|
try {
|
|
166
|
-
const require2 = createRequire(
|
|
401
|
+
const require2 = createRequire(join5(root, "package.json"));
|
|
167
402
|
data = await import(pathToFileURL(require2.resolve("@apex-stack/data")).href);
|
|
168
403
|
} catch {
|
|
169
404
|
console.error("\n @apex-stack/data is not installed in this project. Run: npm i @apex-stack/data\n");
|
|
170
405
|
process.exit(1);
|
|
171
406
|
}
|
|
172
|
-
const {
|
|
173
|
-
const
|
|
407
|
+
const config = args.driver === "postgres" ? { driver: "postgres", url: args.url } : args.driver === "pglite" ? { driver: "pglite", dir: args.url } : resolve3(root, args.db);
|
|
408
|
+
const handle = await data.createDb(config);
|
|
409
|
+
const applied = await data.applyMigrations(handle, resolve3(root, args.dir));
|
|
410
|
+
await handle.close();
|
|
174
411
|
console.log(
|
|
175
412
|
applied.length ? `
|
|
176
413
|
\u2713 Applied ${applied.length} migration(s): ${applied.join(", ")}
|
|
@@ -179,8 +416,126 @@ var migrateCommand = defineCommand3({
|
|
|
179
416
|
}
|
|
180
417
|
});
|
|
181
418
|
|
|
419
|
+
// src/commands/start.ts
|
|
420
|
+
import { existsSync as existsSync5 } from "fs";
|
|
421
|
+
import { join as join7, resolve as resolve4 } from "path";
|
|
422
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
423
|
+
|
|
424
|
+
// src/prod/server.ts
|
|
425
|
+
import { createServer as createHttpServer } from "http";
|
|
426
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, statSync } from "fs";
|
|
427
|
+
import { join as join6 } from "path";
|
|
428
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
429
|
+
import {
|
|
430
|
+
createApp,
|
|
431
|
+
defineEventHandler,
|
|
432
|
+
getRequestURL,
|
|
433
|
+
setResponseHeader,
|
|
434
|
+
setResponseStatus,
|
|
435
|
+
toNodeListener
|
|
436
|
+
} from "h3";
|
|
437
|
+
var MIME = {
|
|
438
|
+
".js": "text/javascript",
|
|
439
|
+
".mjs": "text/javascript",
|
|
440
|
+
".css": "text/css",
|
|
441
|
+
".html": "text/html",
|
|
442
|
+
".json": "application/json",
|
|
443
|
+
".svg": "image/svg+xml",
|
|
444
|
+
".png": "image/png",
|
|
445
|
+
".jpg": "image/jpeg",
|
|
446
|
+
".ico": "image/x-icon",
|
|
447
|
+
".woff2": "font/woff2"
|
|
448
|
+
};
|
|
449
|
+
async function startProdServer(options) {
|
|
450
|
+
const dir = options.dir;
|
|
451
|
+
const port = options.port ?? 3e3;
|
|
452
|
+
const manifest = JSON.parse(readFileSync2(join6(dir, "apex-manifest.json"), "utf8"));
|
|
453
|
+
const importServer = (relFile) => import(pathToFileURL2(join6(dir, "server", relFile)).href);
|
|
454
|
+
const registry = {};
|
|
455
|
+
let componentCss = "";
|
|
456
|
+
for (const [name, file] of Object.entries(manifest.components)) {
|
|
457
|
+
const mod = await importServer(file);
|
|
458
|
+
registry[name] = { template: mod.template, rootXData: mod.rootXData, scopeId: mod.scopeId };
|
|
459
|
+
if (mod.css) componentCss += `${mod.css}
|
|
460
|
+
`;
|
|
461
|
+
}
|
|
462
|
+
const apiEntries = [];
|
|
463
|
+
for (const { name, serverFile } of manifest.api) {
|
|
464
|
+
const mod = await importServer(serverFile);
|
|
465
|
+
apiEntries.push(...expandApiModule(name, mod.default));
|
|
466
|
+
}
|
|
467
|
+
const serverFileFor = new Map(manifest.routes.map((r) => [r.pageId, r.serverFile]));
|
|
468
|
+
const loadModule = (id) => importServer(serverFileFor.get(id));
|
|
469
|
+
const app = createApp();
|
|
470
|
+
app.use(
|
|
471
|
+
defineEventHandler((event) => {
|
|
472
|
+
if (event.method !== "GET") return;
|
|
473
|
+
const path = decodeURIComponent(getRequestURL(event).pathname);
|
|
474
|
+
if (path === "/" || path.startsWith("/api") || path === "/mcp") return;
|
|
475
|
+
const file = join6(dir, path);
|
|
476
|
+
if (!file.startsWith(dir) || !existsSync4(file) || !statSync(file).isFile()) return;
|
|
477
|
+
const ext = path.slice(path.lastIndexOf("."));
|
|
478
|
+
setResponseHeader(event, "Content-Type", MIME[ext] ?? "application/octet-stream");
|
|
479
|
+
if (path.startsWith("/assets/")) setResponseHeader(event, "Cache-Control", "public, max-age=31536000, immutable");
|
|
480
|
+
return readFileSync2(file);
|
|
481
|
+
})
|
|
482
|
+
);
|
|
483
|
+
if (apiEntries.length) app.use("/api", createApiHandler(apiEntries));
|
|
484
|
+
if (hasMcpRoutes(apiEntries)) app.use("/mcp", createMcpHandler(apiEntries));
|
|
485
|
+
app.use(
|
|
486
|
+
defineEventHandler(async (event) => {
|
|
487
|
+
const url = getRequestURL(event).pathname;
|
|
488
|
+
const matched = matchRoute(manifest.routes, url);
|
|
489
|
+
if (!matched) {
|
|
490
|
+
setResponseStatus(event, 404);
|
|
491
|
+
setResponseHeader(event, "Content-Type", "text/html");
|
|
492
|
+
return `<!DOCTYPE html><h1>404 \u2014 ${url}</h1>`;
|
|
493
|
+
}
|
|
494
|
+
const route = manifest.routes.find((r) => r.pageId === matched.pageId);
|
|
495
|
+
const render = manifest.islands ? renderIslandsPage : renderPage;
|
|
496
|
+
const html = await render({
|
|
497
|
+
loadModule,
|
|
498
|
+
pageId: matched.pageId,
|
|
499
|
+
params: matched.params,
|
|
500
|
+
url,
|
|
501
|
+
registry,
|
|
502
|
+
componentCss,
|
|
503
|
+
clientHref: route?.clientHref
|
|
504
|
+
});
|
|
505
|
+
setResponseHeader(event, "Content-Type", "text/html");
|
|
506
|
+
return html;
|
|
507
|
+
})
|
|
508
|
+
);
|
|
509
|
+
const server = createHttpServer(toNodeListener(app));
|
|
510
|
+
await new Promise((resolve6) => server.listen(port, resolve6));
|
|
511
|
+
return { server, port };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/commands/start.ts
|
|
515
|
+
var startCommand = defineCommand5({
|
|
516
|
+
meta: { name: "start", description: "Run a production build (from `apex build --server`)" },
|
|
517
|
+
args: {
|
|
518
|
+
dir: { type: "positional", required: false, description: "Build directory", default: "dist" },
|
|
519
|
+
port: { type: "string", description: "Port to listen on", default: "3000" }
|
|
520
|
+
},
|
|
521
|
+
async run({ args }) {
|
|
522
|
+
const dir = resolve4(process.cwd(), args.dir);
|
|
523
|
+
if (!existsSync5(join7(dir, "apex-manifest.json"))) {
|
|
524
|
+
console.error(`
|
|
525
|
+
No build found in ${args.dir}/. Run \`apex build --server\` first.
|
|
526
|
+
`);
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
const { port } = await startProdServer({ dir, port: Number(args.port) });
|
|
530
|
+
console.log(`
|
|
531
|
+
\x1B[36mApex JS\x1B[0m production server
|
|
532
|
+
\u2192 http://localhost:${port}
|
|
533
|
+
`);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
182
537
|
// src/cli.ts
|
|
183
|
-
var dev =
|
|
538
|
+
var dev = defineCommand6({
|
|
184
539
|
meta: { name: "dev", description: "Start the Apex JS development server" },
|
|
185
540
|
args: {
|
|
186
541
|
root: { type: "positional", required: false, description: "Project root", default: "." },
|
|
@@ -188,7 +543,7 @@ var dev = defineCommand4({
|
|
|
188
543
|
islands: { type: "boolean", description: "Render in islands mode (static-first)", default: false }
|
|
189
544
|
},
|
|
190
545
|
async run({ args }) {
|
|
191
|
-
const root =
|
|
546
|
+
const root = resolve5(process.cwd(), args.root);
|
|
192
547
|
const port = Number(args.port);
|
|
193
548
|
const { port: actual } = await startDevServer({ root, port, islands: args.islands });
|
|
194
549
|
console.log(`
|
|
@@ -197,11 +552,18 @@ var dev = defineCommand4({
|
|
|
197
552
|
`);
|
|
198
553
|
}
|
|
199
554
|
});
|
|
200
|
-
var main =
|
|
555
|
+
var main = defineCommand6({
|
|
201
556
|
meta: {
|
|
202
557
|
name: "apex",
|
|
203
558
|
description: "The full-stack meta-framework for Alpine.js"
|
|
204
559
|
},
|
|
205
|
-
subCommands: {
|
|
560
|
+
subCommands: {
|
|
561
|
+
dev,
|
|
562
|
+
build: buildCommand,
|
|
563
|
+
start: startCommand,
|
|
564
|
+
make: makeCommand,
|
|
565
|
+
migrate: migrateCommand,
|
|
566
|
+
mcp: mcpCommand
|
|
567
|
+
}
|
|
206
568
|
});
|
|
207
569
|
runMain(main);
|
package/dist/index.d.ts
CHANGED
|
@@ -121,6 +121,9 @@ interface RenderPageOptions {
|
|
|
121
121
|
componentCss?: string;
|
|
122
122
|
/** Post-process the shell HTML (dev: vite.transformIndexHtml). */
|
|
123
123
|
transformHtml?: (url: string, html: string) => string | Promise<string>;
|
|
124
|
+
/** In a production build, the href of the built client bundle for this page.
|
|
125
|
+
* When set, the shell references it instead of the inline dev module. */
|
|
126
|
+
clientHref?: string;
|
|
124
127
|
}
|
|
125
128
|
/**
|
|
126
129
|
* The framework's render seam — deliberately dev-server-agnostic so it can move
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apex-stack/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "The full-stack meta-framework for Alpine.js — CLI and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"h3": "^1.13.0",
|
|
41
41
|
"vite": "^6.0.7",
|
|
42
42
|
"zod": "^4.4.3",
|
|
43
|
-
"@apex-stack/
|
|
44
|
-
"@apex-stack/
|
|
43
|
+
"@apex-stack/kit": "0.1.1",
|
|
44
|
+
"@apex-stack/vite": "0.1.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"alpinejs": "^3.14.0"
|