create-apexjs 0.0.0 → 0.1.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/index.js
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
5
|
+
import { basename, join, resolve } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { defineCommand, runMain } from "citty";
|
|
8
|
+
var TEMPLATE_DIR = fileURLToPath(new URL("../templates/default", import.meta.url));
|
|
9
|
+
var main = defineCommand({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "create-apexjs",
|
|
12
|
+
description: "Scaffold a new Apex JS app"
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
dir: { type: "positional", required: false, description: "Target directory", default: "apex-app" }
|
|
16
|
+
},
|
|
17
|
+
run({ args }) {
|
|
18
|
+
const target = resolve(process.cwd(), args.dir);
|
|
19
|
+
const name = basename(target);
|
|
20
|
+
if (existsSync(target) && readdirSync(target).length > 0) {
|
|
21
|
+
console.error(`
|
|
22
|
+
\u2717 Target directory is not empty: ${target}
|
|
23
|
+
`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
cpSync(TEMPLATE_DIR, target, { recursive: true });
|
|
27
|
+
const gitignore = join(target, "_gitignore");
|
|
28
|
+
if (existsSync(gitignore)) renameSync(gitignore, join(target, ".gitignore"));
|
|
29
|
+
for (const rel of ["package.json", "README.md"]) {
|
|
30
|
+
const file = join(target, rel);
|
|
31
|
+
if (existsSync(file)) {
|
|
32
|
+
writeFileSync(file, readFileSync(file, "utf8").replaceAll("{{name}}", name));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
console.log(`
|
|
36
|
+
\x1B[36mApex JS\x1B[0m app created in ${args.dir}
|
|
37
|
+
|
|
38
|
+
Next steps:
|
|
39
|
+
cd ${args.dir}
|
|
40
|
+
npm install
|
|
41
|
+
npm run dev # http://localhost:3000
|
|
42
|
+
npm run dev:islands # static-first islands mode
|
|
43
|
+
|
|
44
|
+
Your server/api/*.ts routes are also MCP tools at /mcp.
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
runMain(main);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-apexjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Scaffold a new Apex JS app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"create-apexjs": "./dist/index.js"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
|
-
"dist"
|
|
24
|
+
"dist",
|
|
25
|
+
"templates"
|
|
25
26
|
],
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"citty": "^0.1.6"
|
|
@@ -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 'apexjs-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
|
+
})
|