@apex-stack/core 0.1.8 → 0.1.9
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/cli.js
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
} from "./chunk-IEXQ7E5C.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
16
|
-
import { resolve as
|
|
17
|
-
import { defineCommand as
|
|
16
|
+
import { resolve as resolve6 } from "path";
|
|
17
|
+
import { defineCommand as defineCommand7, runMain } from "citty";
|
|
18
18
|
|
|
19
19
|
// src/commands/build.ts
|
|
20
20
|
import { cpSync, existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, rmSync, writeFileSync } from "fs";
|
|
@@ -417,15 +417,106 @@ var migrateCommand = defineCommand4({
|
|
|
417
417
|
}
|
|
418
418
|
});
|
|
419
419
|
|
|
420
|
-
// src/commands/
|
|
421
|
-
import {
|
|
422
|
-
import {
|
|
420
|
+
// src/commands/new.ts
|
|
421
|
+
import { spawnSync } from "child_process";
|
|
422
|
+
import { cpSync as cpSync2, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync3 } from "fs";
|
|
423
|
+
import { basename, join as join6, resolve as resolve4 } from "path";
|
|
424
|
+
import { fileURLToPath } from "url";
|
|
423
425
|
import { defineCommand as defineCommand5 } from "citty";
|
|
426
|
+
var TEMPLATE_DIR = fileURLToPath(new URL("../templates/default", import.meta.url));
|
|
427
|
+
function detectPackageManager() {
|
|
428
|
+
const ua = process.env.npm_config_user_agent || "";
|
|
429
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
430
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
431
|
+
if (ua.startsWith("bun")) return "bun";
|
|
432
|
+
return "npm";
|
|
433
|
+
}
|
|
434
|
+
function run(cmd, cmdArgs, cwd, quiet = false) {
|
|
435
|
+
const res = spawnSync(cmd, cmdArgs, {
|
|
436
|
+
cwd,
|
|
437
|
+
stdio: quiet ? "ignore" : "inherit",
|
|
438
|
+
shell: process.platform === "win32"
|
|
439
|
+
// npm/pnpm/yarn are .cmd shims on Windows
|
|
440
|
+
});
|
|
441
|
+
return res.status === 0;
|
|
442
|
+
}
|
|
443
|
+
var c = {
|
|
444
|
+
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
445
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`,
|
|
446
|
+
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
447
|
+
yellow: (s) => `\x1B[33m${s}\x1B[0m`
|
|
448
|
+
};
|
|
449
|
+
var newCommand = defineCommand5({
|
|
450
|
+
meta: { name: "new", description: "Scaffold a new Apex JS app" },
|
|
451
|
+
args: {
|
|
452
|
+
dir: { type: "positional", required: false, description: "Target directory", default: "apex-app" },
|
|
453
|
+
install: { type: "boolean", default: true, description: "Install dependencies (use --no-install to skip)" },
|
|
454
|
+
git: { type: "boolean", default: true, description: "Initialize a git repository (use --no-git to skip)" }
|
|
455
|
+
},
|
|
456
|
+
run({ args }) {
|
|
457
|
+
const target = resolve4(process.cwd(), String(args.dir));
|
|
458
|
+
const name = basename(target);
|
|
459
|
+
if (existsSync4(target) && readdirSync3(target).length > 0) {
|
|
460
|
+
console.error(`
|
|
461
|
+
\u2717 Target directory is not empty: ${target}
|
|
462
|
+
`);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
cpSync2(TEMPLATE_DIR, target, { recursive: true });
|
|
466
|
+
const gitignore = join6(target, "_gitignore");
|
|
467
|
+
if (existsSync4(gitignore)) renameSync(gitignore, join6(target, ".gitignore"));
|
|
468
|
+
for (const rel of ["package.json", "README.md"]) {
|
|
469
|
+
const file = join6(target, rel);
|
|
470
|
+
if (existsSync4(file)) writeFileSync3(file, readFileSync2(file, "utf8").replaceAll("{{name}}", name));
|
|
471
|
+
}
|
|
472
|
+
const log = console.log;
|
|
473
|
+
log(`
|
|
474
|
+
${c.cyan("Apex JS")} app created in ${args.dir}`);
|
|
475
|
+
const pm = detectPackageManager();
|
|
476
|
+
let gitOk = false;
|
|
477
|
+
if (args.git) {
|
|
478
|
+
const hasGit = spawnSync("git", ["--version"], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
|
|
479
|
+
if (hasGit && run("git", ["init", "-q"], target, true)) {
|
|
480
|
+
run("git", ["add", "-A"], target, true);
|
|
481
|
+
run("git", ["commit", "-m", "Initial commit from Apex JS", "--no-gpg-sign"], target, true);
|
|
482
|
+
gitOk = true;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
let installed = false;
|
|
486
|
+
if (args.install) {
|
|
487
|
+
log(`
|
|
488
|
+
Installing dependencies with ${c.cyan(pm)}\u2026 ${c.dim("(first install can take a minute)")}
|
|
489
|
+
`);
|
|
490
|
+
const installArgs = pm === "npm" ? ["install", "--no-audit", "--no-fund"] : ["install"];
|
|
491
|
+
installed = run(pm, installArgs, target);
|
|
492
|
+
if (!installed) log(`
|
|
493
|
+
${c.yellow("\u26A0")} Dependency install failed \u2014 run it yourself after cd'ing in.
|
|
494
|
+
`);
|
|
495
|
+
}
|
|
496
|
+
const runPrefix = pm === "npm" ? "npm run" : pm;
|
|
497
|
+
const steps = [`cd ${args.dir}`];
|
|
498
|
+
if (!installed) steps.push(pm === "yarn" ? "yarn" : `${pm} install`);
|
|
499
|
+
steps.push(`apex dev ${c.dim("# start the dev server \u2192 http://localhost:3000")}`);
|
|
500
|
+
log(`
|
|
501
|
+
${installed ? c.green("Ready.") : "Next steps:"}
|
|
502
|
+
${steps.map((s) => ` ${s}`).join("\n")}
|
|
503
|
+
|
|
504
|
+
${c.dim("Not global? Run")} ${c.cyan(runPrefix + " dev")}${c.dim(" instead of ")}${c.cyan("apex dev")}${c.dim(".")}
|
|
505
|
+
${c.dim("Islands mode:")} apex dev --islands
|
|
506
|
+
${gitOk ? c.dim("Git repository initialized. ") : ""}Your server/api/*.ts routes are also MCP tools at /mcp.
|
|
507
|
+
`);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// src/commands/start.ts
|
|
512
|
+
import { existsSync as existsSync6 } from "fs";
|
|
513
|
+
import { join as join8, resolve as resolve5 } from "path";
|
|
514
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
424
515
|
|
|
425
516
|
// src/prod/server.ts
|
|
426
517
|
import { createServer as createHttpServer } from "http";
|
|
427
|
-
import { existsSync as
|
|
428
|
-
import { join as
|
|
518
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, statSync } from "fs";
|
|
519
|
+
import { join as join7 } from "path";
|
|
429
520
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
430
521
|
import {
|
|
431
522
|
createApp,
|
|
@@ -450,8 +541,8 @@ var MIME = {
|
|
|
450
541
|
async function startProdServer(options) {
|
|
451
542
|
const dir = options.dir;
|
|
452
543
|
const port = options.port ?? 3e3;
|
|
453
|
-
const manifest = JSON.parse(
|
|
454
|
-
const importServer = (relFile) => import(pathToFileURL2(
|
|
544
|
+
const manifest = JSON.parse(readFileSync3(join7(dir, "apex-manifest.json"), "utf8"));
|
|
545
|
+
const importServer = (relFile) => import(pathToFileURL2(join7(dir, "server", relFile)).href);
|
|
455
546
|
const registry = {};
|
|
456
547
|
let componentCss = "";
|
|
457
548
|
for (const [name, file] of Object.entries(manifest.components)) {
|
|
@@ -473,12 +564,12 @@ async function startProdServer(options) {
|
|
|
473
564
|
if (event.method !== "GET") return;
|
|
474
565
|
const path = decodeURIComponent(getRequestURL(event).pathname);
|
|
475
566
|
if (path === "/" || path.startsWith("/api") || path === "/mcp") return;
|
|
476
|
-
const file =
|
|
477
|
-
if (!file.startsWith(dir) || !
|
|
567
|
+
const file = join7(dir, path);
|
|
568
|
+
if (!file.startsWith(dir) || !existsSync5(file) || !statSync(file).isFile()) return;
|
|
478
569
|
const ext = path.slice(path.lastIndexOf("."));
|
|
479
570
|
setResponseHeader(event, "Content-Type", MIME[ext] ?? "application/octet-stream");
|
|
480
571
|
if (path.startsWith("/assets/")) setResponseHeader(event, "Cache-Control", "public, max-age=31536000, immutable");
|
|
481
|
-
return
|
|
572
|
+
return readFileSync3(file);
|
|
482
573
|
})
|
|
483
574
|
);
|
|
484
575
|
if (apiEntries.length) app.use("/api", createApiHandler(apiEntries));
|
|
@@ -508,20 +599,20 @@ async function startProdServer(options) {
|
|
|
508
599
|
})
|
|
509
600
|
);
|
|
510
601
|
const server = createHttpServer(toNodeListener(app));
|
|
511
|
-
await new Promise((
|
|
602
|
+
await new Promise((resolve7) => server.listen(port, resolve7));
|
|
512
603
|
return { server, port };
|
|
513
604
|
}
|
|
514
605
|
|
|
515
606
|
// src/commands/start.ts
|
|
516
|
-
var startCommand =
|
|
607
|
+
var startCommand = defineCommand6({
|
|
517
608
|
meta: { name: "start", description: "Run a production build (from `apex build --server`)" },
|
|
518
609
|
args: {
|
|
519
610
|
dir: { type: "positional", required: false, description: "Build directory", default: "dist" },
|
|
520
611
|
port: { type: "string", description: "Port to listen on", default: "3000" }
|
|
521
612
|
},
|
|
522
613
|
async run({ args }) {
|
|
523
|
-
const dir =
|
|
524
|
-
if (!
|
|
614
|
+
const dir = resolve5(process.cwd(), args.dir);
|
|
615
|
+
if (!existsSync6(join8(dir, "apex-manifest.json"))) {
|
|
525
616
|
console.error(`
|
|
526
617
|
No build found in ${args.dir}/. Run \`apex build --server\` first.
|
|
527
618
|
`);
|
|
@@ -536,7 +627,7 @@ var startCommand = defineCommand5({
|
|
|
536
627
|
});
|
|
537
628
|
|
|
538
629
|
// src/cli.ts
|
|
539
|
-
var dev =
|
|
630
|
+
var dev = defineCommand7({
|
|
540
631
|
meta: { name: "dev", description: "Start the Apex JS development server" },
|
|
541
632
|
args: {
|
|
542
633
|
root: { type: "positional", required: false, description: "Project root", default: "." },
|
|
@@ -544,7 +635,7 @@ var dev = defineCommand6({
|
|
|
544
635
|
islands: { type: "boolean", description: "Render in islands mode (static-first)", default: false }
|
|
545
636
|
},
|
|
546
637
|
async run({ args }) {
|
|
547
|
-
const root =
|
|
638
|
+
const root = resolve6(process.cwd(), args.root);
|
|
548
639
|
const port = Number(args.port);
|
|
549
640
|
const { port: actual } = await startDevServer({ root, port, islands: args.islands });
|
|
550
641
|
console.log(`
|
|
@@ -553,12 +644,13 @@ var dev = defineCommand6({
|
|
|
553
644
|
`);
|
|
554
645
|
}
|
|
555
646
|
});
|
|
556
|
-
var main =
|
|
647
|
+
var main = defineCommand7({
|
|
557
648
|
meta: {
|
|
558
649
|
name: "apex",
|
|
559
650
|
description: "The full-stack meta-framework for Alpine.js"
|
|
560
651
|
},
|
|
561
652
|
subCommands: {
|
|
653
|
+
new: newCommand,
|
|
562
654
|
dev,
|
|
563
655
|
build: buildCommand,
|
|
564
656
|
start: startCommand,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apex-stack/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "The full-stack meta-framework for Alpine.js — CLI and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
|
-
"dist"
|
|
39
|
+
"dist",
|
|
40
|
+
"templates"
|
|
40
41
|
],
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
"node": ">=20.19"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
|
-
"build": "tsup",
|
|
58
|
+
"build": "node scripts/copy-templates.mjs && tsup",
|
|
58
59
|
"dev": "tsup --watch",
|
|
59
60
|
"typecheck": "tsc --noEmit"
|
|
60
61
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
An [Apex JS](https://github.com/andrecorugda/apexjs) app — a meta-framework for
|
|
4
|
+
Alpine.js that renders on the server and hydrates in the browser.
|
|
5
|
+
|
|
6
|
+
## Getting started
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install
|
|
10
|
+
npm run dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then open [http://localhost:3000](http://localhost:3000).
|
|
14
|
+
|
|
15
|
+
## Islands mode
|
|
16
|
+
|
|
17
|
+
Ship interactive JavaScript only where you need it:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
apex dev --islands
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Project structure
|
|
24
|
+
|
|
25
|
+
- `pages/*.alpine` — single-file components. The `<script server>` block runs on
|
|
26
|
+
the server; its `loader()` return value becomes the Alpine `x-data` scope.
|
|
27
|
+
- `server/api/*.ts` — API routes defined with `defineApexRoute`.
|
|
28
|
+
|
|
29
|
+
## AI-native API
|
|
30
|
+
|
|
31
|
+
Every route in `server/api/*.ts` is a REST endpoint **and** an MCP tool at the
|
|
32
|
+
same time. Set `mcp: true` on a route (see `server/api/hello.ts`) and it is
|
|
33
|
+
automatically exposed to AI agents at the `/mcp` endpoint — no extra wiring.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script server lang="ts">
|
|
2
|
+
export function loader() {
|
|
3
|
+
return {
|
|
4
|
+
title: 'Welcome to Apex JS',
|
|
5
|
+
tagline: 'The meta-framework for Alpine.js — server-rendered, then hydrated.',
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template x-data="{ open: false }">
|
|
11
|
+
<main>
|
|
12
|
+
<h1 x-text="title"></h1>
|
|
13
|
+
<p class="tagline" x-text="tagline"></p>
|
|
14
|
+
|
|
15
|
+
<section class="card">
|
|
16
|
+
<p>
|
|
17
|
+
This page was rendered on the server from
|
|
18
|
+
<code>pages/index.alpine</code>, then hydrated by Alpine in the browser.
|
|
19
|
+
Edit it and save to see your changes live.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<button type="button" @click="open = !open" x-text="open ? 'Hide the details' : 'Show me how'"></button>
|
|
23
|
+
|
|
24
|
+
<div x-show="open" x-transition class="details">
|
|
25
|
+
<p>
|
|
26
|
+
The <code>loader()</code> in the server block runs on the server and
|
|
27
|
+
hands its data straight to Alpine's <code>x-data</code> scope — no
|
|
28
|
+
fetch, no boilerplate.
|
|
29
|
+
</p>
|
|
30
|
+
<p>
|
|
31
|
+
Next, open <code>server/api/hello.ts</code>: it's a REST endpoint
|
|
32
|
+
<em>and</em> an MCP tool at <code>/mcp</code> at the same time.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
<p class="hint">
|
|
38
|
+
Run <code>apex dev --islands</code> to ship interactive islands only where
|
|
39
|
+
you need them.
|
|
40
|
+
</p>
|
|
41
|
+
</main>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
main {
|
|
46
|
+
max-width: 40rem;
|
|
47
|
+
margin: 4rem auto;
|
|
48
|
+
padding: 0 1.5rem;
|
|
49
|
+
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
50
|
+
line-height: 1.6;
|
|
51
|
+
color: #1e293b;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
h1 {
|
|
55
|
+
color: #2563eb;
|
|
56
|
+
font-size: 2.5rem;
|
|
57
|
+
margin-bottom: 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.tagline {
|
|
61
|
+
font-size: 1.15rem;
|
|
62
|
+
color: #475569;
|
|
63
|
+
margin-top: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.card {
|
|
67
|
+
margin-top: 2rem;
|
|
68
|
+
padding: 1.5rem;
|
|
69
|
+
border: 1px solid #e2e8f0;
|
|
70
|
+
border-radius: 0.75rem;
|
|
71
|
+
background: #f8fafc;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
button {
|
|
75
|
+
margin-top: 0.5rem;
|
|
76
|
+
padding: 0.6rem 1.1rem;
|
|
77
|
+
font-size: 1rem;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
color: #fff;
|
|
80
|
+
background: #2563eb;
|
|
81
|
+
border: none;
|
|
82
|
+
border-radius: 0.5rem;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: background 0.15s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
button:hover {
|
|
88
|
+
background: #1d4ed8;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.details {
|
|
92
|
+
margin-top: 1rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
code {
|
|
96
|
+
padding: 0.1rem 0.35rem;
|
|
97
|
+
font-size: 0.9em;
|
|
98
|
+
background: #e2e8f0;
|
|
99
|
+
border-radius: 0.35rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.hint {
|
|
103
|
+
margin-top: 2rem;
|
|
104
|
+
font-size: 0.95rem;
|
|
105
|
+
color: #64748b;
|
|
106
|
+
}
|
|
107
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineApexRoute } from '@apex-stack/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export default defineApexRoute({
|
|
5
|
+
method: 'GET',
|
|
6
|
+
description: 'Say hello to someone by name',
|
|
7
|
+
input: { name: z.string() },
|
|
8
|
+
mcp: true,
|
|
9
|
+
handler: ({ input }) => ({ message: `Hello, ${input.name}!` }),
|
|
10
|
+
})
|