@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
|
@@ -79,32 +79,98 @@ export default defineApexRoute({
|
|
|
79
79
|
})
|
|
80
80
|
`;
|
|
81
81
|
}
|
|
82
|
+
function serviceTemplate(name) {
|
|
83
|
+
const cls = `${pascalCase(name)}Service`;
|
|
84
|
+
return `/**
|
|
85
|
+
* ${cls} \u2014 business logic as a plain, testable class. Keep routes and loaders
|
|
86
|
+
* thin and delegate to services like this one (the clean-code backbone).
|
|
87
|
+
*/
|
|
88
|
+
export class ${cls} {
|
|
89
|
+
// Replace with your methods.
|
|
90
|
+
run(input: string): string {
|
|
91
|
+
return input
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
function middlewareTemplate() {
|
|
97
|
+
return `import { defineMiddleware } from '@apex-stack/core'
|
|
98
|
+
|
|
99
|
+
// Runs on every request before the page/API handler. Attach request-scoped
|
|
100
|
+
// state to ctx.locals (read in a page loader via \`loader({ locals })\` and in
|
|
101
|
+
// route handlers via \`{ locals }\`), or return ctx.redirect('/path') to
|
|
102
|
+
// short-circuit. Files run in filename order \u2014 prefix with 01. / 02. to order.
|
|
103
|
+
export default defineMiddleware((ctx) => {
|
|
104
|
+
// ctx.locals.user = await getUser(ctx.headers)
|
|
105
|
+
// if (ctx.url.startsWith('/admin') && !ctx.locals.user) return ctx.redirect('/login')
|
|
106
|
+
})
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
function testTemplate(name) {
|
|
110
|
+
return `import { describe, expect, it } from 'vitest'
|
|
111
|
+
|
|
112
|
+
describe('${name}', () => {
|
|
113
|
+
it('works', () => {
|
|
114
|
+
expect(true).toBe(true)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
82
119
|
function plan(kind, name, root) {
|
|
83
120
|
switch (kind) {
|
|
84
121
|
case "page":
|
|
85
122
|
return { path: join(root, "pages", `${name}.alpine`), contents: pageTemplate(name) };
|
|
86
123
|
case "component":
|
|
87
|
-
return {
|
|
124
|
+
return {
|
|
125
|
+
path: join(root, "components", `${pascalCase(name)}.alpine`),
|
|
126
|
+
contents: componentTemplate()
|
|
127
|
+
};
|
|
88
128
|
case "api":
|
|
89
129
|
return { path: join(root, "server", "api", `${name}.ts`), contents: apiTemplate(name) };
|
|
90
130
|
case "store":
|
|
91
131
|
return { path: join(root, "stores", `${name}.ts`), contents: storeTemplate(name) };
|
|
92
132
|
case "layout":
|
|
93
133
|
return { path: join(root, "layouts", `${name}.alpine`), contents: layoutTemplate() };
|
|
134
|
+
case "service":
|
|
135
|
+
return {
|
|
136
|
+
path: join(root, "services", `${pascalCase(name)}Service.ts`),
|
|
137
|
+
contents: serviceTemplate(name)
|
|
138
|
+
};
|
|
139
|
+
case "test":
|
|
140
|
+
return { path: join(root, "tests", `${name}.test.ts`), contents: testTemplate(name) };
|
|
141
|
+
case "middleware":
|
|
142
|
+
return { path: join(root, "middleware", `${name}.ts`), contents: middlewareTemplate() };
|
|
94
143
|
}
|
|
95
144
|
}
|
|
96
145
|
var makeCommand = defineCommand({
|
|
97
|
-
meta: {
|
|
146
|
+
meta: {
|
|
147
|
+
name: "make",
|
|
148
|
+
description: "Generate a page, component, API route, store, layout, service, test, or middleware"
|
|
149
|
+
},
|
|
98
150
|
args: {
|
|
99
|
-
kind: {
|
|
151
|
+
kind: {
|
|
152
|
+
type: "positional",
|
|
153
|
+
required: true,
|
|
154
|
+
description: "page | component | api | store | layout | service | test | middleware"
|
|
155
|
+
},
|
|
100
156
|
name: { type: "positional", required: true, description: "Name (about, Counter, todos, \u2026)" },
|
|
101
157
|
root: { type: "string", description: "Project root", default: "." }
|
|
102
158
|
},
|
|
103
159
|
run({ args }) {
|
|
104
160
|
const kind = args.kind;
|
|
105
|
-
|
|
161
|
+
const kinds = [
|
|
162
|
+
"page",
|
|
163
|
+
"component",
|
|
164
|
+
"api",
|
|
165
|
+
"store",
|
|
166
|
+
"layout",
|
|
167
|
+
"service",
|
|
168
|
+
"test",
|
|
169
|
+
"middleware"
|
|
170
|
+
];
|
|
171
|
+
if (!kinds.includes(kind)) {
|
|
106
172
|
console.error(`
|
|
107
|
-
Unknown type "${args.kind}". Use:
|
|
173
|
+
Unknown type "${args.kind}". Use: ${kinds.join(" | ")}
|
|
108
174
|
`);
|
|
109
175
|
process.exit(1);
|
|
110
176
|
}
|
|
@@ -30,7 +30,7 @@ var mcpCommand = defineCommand({
|
|
|
30
30
|
console.log(`
|
|
31
31
|
\x1B[36m${args.call}\x1B[0m(${args.args}) \u2192`);
|
|
32
32
|
for (const part of result.content) {
|
|
33
|
-
console.log(
|
|
33
|
+
console.log(` ${part.text ?? JSON.stringify(part)}`);
|
|
34
34
|
}
|
|
35
35
|
console.log();
|
|
36
36
|
} else {
|
|
@@ -19,7 +19,9 @@ var migrateCommand = defineCommand({
|
|
|
19
19
|
const require2 = createRequire(join(root, "package.json"));
|
|
20
20
|
data = await import(pathToFileURL(require2.resolve("@apex-stack/data")).href);
|
|
21
21
|
} catch {
|
|
22
|
-
console.error(
|
|
22
|
+
console.error(
|
|
23
|
+
"\n @apex-stack/data is not installed in this project. Run: npm i @apex-stack/data\n"
|
|
24
|
+
);
|
|
23
25
|
process.exit(1);
|
|
24
26
|
}
|
|
25
27
|
const config = args.driver === "postgres" ? { driver: "postgres", url: args.url } : args.driver === "pglite" ? { driver: "pglite", dir: args.url } : resolve(root, args.db);
|
|
@@ -4,17 +4,20 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createApiHandler,
|
|
6
6
|
createMcpHandler,
|
|
7
|
-
loadApiRoutes
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
loadApiRoutes,
|
|
8
|
+
loadMiddleware,
|
|
9
|
+
runMiddleware
|
|
10
|
+
} from "./chunk-HCNNKT4A.js";
|
|
11
|
+
import "./chunk-2C2HRLIY.js";
|
|
10
12
|
import {
|
|
11
13
|
loadStores,
|
|
12
14
|
matchRoute,
|
|
13
15
|
renderIslandsPage,
|
|
14
16
|
renderPage,
|
|
17
|
+
resolveApexConfig,
|
|
15
18
|
scanPages
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-XDKJO6ZC.js";
|
|
20
|
+
import "./chunk-JLIAISWM.js";
|
|
18
21
|
|
|
19
22
|
// src/dev/server.ts
|
|
20
23
|
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
@@ -27,6 +30,7 @@ import {
|
|
|
27
30
|
createApp,
|
|
28
31
|
defineEventHandler,
|
|
29
32
|
fromNodeMiddleware,
|
|
33
|
+
getRequestHeaders,
|
|
30
34
|
setResponseHeader,
|
|
31
35
|
setResponseStatus,
|
|
32
36
|
toNodeListener
|
|
@@ -36,20 +40,24 @@ import { createServer as createViteServer } from "vite";
|
|
|
36
40
|
// src/dev/errorPage.ts
|
|
37
41
|
import { existsSync, readFileSync } from "fs";
|
|
38
42
|
function esc(s) {
|
|
39
|
-
return s.replace(
|
|
43
|
+
return s.replace(
|
|
44
|
+
/[&<>"]/g,
|
|
45
|
+
(c) => c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """
|
|
46
|
+
);
|
|
40
47
|
}
|
|
41
48
|
function firstFileFrame(stack, root) {
|
|
42
49
|
const re = /(?:file:\/\/\/?)?((?:[A-Za-z]:[\\/]|\/)[^\s():]+):(\d+):(\d+)/g;
|
|
43
|
-
let m;
|
|
44
50
|
const frames = [];
|
|
45
|
-
|
|
51
|
+
let m = re.exec(stack);
|
|
52
|
+
while (m) {
|
|
46
53
|
const raw = m[1];
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
if (raw && (/^[A-Za-z]:[\\/]/.test(raw) || raw.startsWith("/"))) {
|
|
55
|
+
const file = raw.replace(/\//g, process.platform === "win32" ? "\\" : "/");
|
|
56
|
+
if (existsSync(file) && !file.includes("node_modules")) {
|
|
57
|
+
frames.push({ file, line: Number(m[2] ?? 0), col: Number(m[3] ?? 0) });
|
|
58
|
+
}
|
|
52
59
|
}
|
|
60
|
+
m = re.exec(stack);
|
|
53
61
|
}
|
|
54
62
|
return frames.find((f) => f.file.startsWith(root)) ?? frames[0];
|
|
55
63
|
}
|
|
@@ -174,7 +182,9 @@ async function startDevServer(options) {
|
|
|
174
182
|
plugins.unshift(tw());
|
|
175
183
|
} catch {
|
|
176
184
|
}
|
|
177
|
-
const appCssRel = ["app.css", "styles/app.css", "src/app.css"].find(
|
|
185
|
+
const appCssRel = ["app.css", "styles/app.css", "src/app.css"].find(
|
|
186
|
+
(p) => existsSync2(join(options.root, p))
|
|
187
|
+
);
|
|
178
188
|
const appCss = appCssRel ? `/${appCssRel}` : void 0;
|
|
179
189
|
const vite = await createViteServer({
|
|
180
190
|
root: options.root,
|
|
@@ -192,11 +202,43 @@ async function startDevServer(options) {
|
|
|
192
202
|
const resolved = id[0] === "/" && !id.startsWith(options.root) ? join(options.root, id).replace(/\\/g, "/") : id;
|
|
193
203
|
return vite.ssrLoadModule(resolved);
|
|
194
204
|
};
|
|
205
|
+
const { runtimeConfig, publicConfig } = await resolveApexConfig(
|
|
206
|
+
options.root,
|
|
207
|
+
(id) => ssrLoad(id)
|
|
208
|
+
);
|
|
195
209
|
const app = createApp();
|
|
196
210
|
app.use(fromNodeMiddleware(vite.middlewares));
|
|
211
|
+
app.use(
|
|
212
|
+
defineEventHandler(async (event) => {
|
|
213
|
+
const mws = await loadMiddleware(options.root, (id) => ssrLoad(id));
|
|
214
|
+
if (!mws.length) return;
|
|
215
|
+
const { redirect, locals } = await runMiddleware(mws, {
|
|
216
|
+
url: event.path || "/",
|
|
217
|
+
method: event.method,
|
|
218
|
+
config: runtimeConfig,
|
|
219
|
+
headers: getRequestHeaders(event)
|
|
220
|
+
});
|
|
221
|
+
event.context.apexLocals = locals;
|
|
222
|
+
if (redirect) {
|
|
223
|
+
setResponseStatus(event, redirect.status);
|
|
224
|
+
setResponseHeader(event, "Location", redirect.to);
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
);
|
|
197
229
|
const loadEntries = () => loadApiRoutes(options.root, (id) => ssrLoad(id));
|
|
198
|
-
app.use(
|
|
199
|
-
|
|
230
|
+
app.use(
|
|
231
|
+
"/api",
|
|
232
|
+
defineEventHandler(
|
|
233
|
+
(event) => loadEntries().then((e) => createApiHandler(e, runtimeConfig)(event))
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
app.use(
|
|
237
|
+
"/mcp",
|
|
238
|
+
defineEventHandler(
|
|
239
|
+
(event) => loadEntries().then((e) => createMcpHandler(e, runtimeConfig)(event))
|
|
240
|
+
)
|
|
241
|
+
);
|
|
200
242
|
app.use(
|
|
201
243
|
defineEventHandler(async (event) => {
|
|
202
244
|
const url = event.path || "/";
|
|
@@ -226,6 +268,10 @@ async function startDevServer(options) {
|
|
|
226
268
|
stores,
|
|
227
269
|
appCss,
|
|
228
270
|
layouts,
|
|
271
|
+
runtimeConfig,
|
|
272
|
+
publicConfig,
|
|
273
|
+
locals: event.context.apexLocals ?? {},
|
|
274
|
+
errorPageId: existsSync2(join(options.root, "pages", "error.alpine")) ? "/pages/error.alpine" : void 0,
|
|
229
275
|
transformHtml: (u, doc) => vite.transformIndexHtml(u, doc)
|
|
230
276
|
});
|
|
231
277
|
setResponseHeader(event, "Content-Type", "text/html");
|
|
@@ -252,9 +298,7 @@ async function startDevServer(options) {
|
|
|
252
298
|
port,
|
|
253
299
|
close: async () => {
|
|
254
300
|
await vite.close();
|
|
255
|
-
await new Promise(
|
|
256
|
-
(resolve, reject) => server.close((e) => e ? reject(e) : resolve())
|
|
257
|
-
);
|
|
301
|
+
await new Promise((resolve, reject) => server.close((e) => e ? reject(e) : resolve()));
|
|
258
302
|
}
|
|
259
303
|
};
|
|
260
304
|
}
|
|
@@ -2,15 +2,17 @@ import {
|
|
|
2
2
|
createApiHandler,
|
|
3
3
|
createMcpHandler,
|
|
4
4
|
expandApiModule,
|
|
5
|
-
hasMcpRoutes
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
hasMcpRoutes,
|
|
6
|
+
runMiddleware
|
|
7
|
+
} from "./chunk-HCNNKT4A.js";
|
|
8
|
+
import "./chunk-2C2HRLIY.js";
|
|
8
9
|
import {
|
|
10
|
+
applyEnvToRuntimeConfig,
|
|
9
11
|
matchRoute,
|
|
10
12
|
renderIslandsPage,
|
|
11
13
|
renderPage
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-XDKJO6ZC.js";
|
|
15
|
+
import "./chunk-JLIAISWM.js";
|
|
14
16
|
|
|
15
17
|
// src/commands/start.ts
|
|
16
18
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -18,13 +20,14 @@ import { join as join2, resolve } from "path";
|
|
|
18
20
|
import { defineCommand } from "citty";
|
|
19
21
|
|
|
20
22
|
// src/prod/server.ts
|
|
21
|
-
import { createServer as createHttpServer } from "http";
|
|
22
23
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
24
|
+
import { createServer as createHttpServer } from "http";
|
|
23
25
|
import { join } from "path";
|
|
24
26
|
import { pathToFileURL } from "url";
|
|
25
27
|
import {
|
|
26
28
|
createApp,
|
|
27
29
|
defineEventHandler,
|
|
30
|
+
getRequestHeaders,
|
|
28
31
|
getRequestURL,
|
|
29
32
|
setResponseHeader,
|
|
30
33
|
setResponseStatus,
|
|
@@ -46,6 +49,11 @@ async function startProdServer(options) {
|
|
|
46
49
|
const dir = options.dir;
|
|
47
50
|
const port = options.port ?? 3e3;
|
|
48
51
|
const manifest = JSON.parse(readFileSync(join(dir, "apex-manifest.json"), "utf8"));
|
|
52
|
+
const runtimeConfig = applyEnvToRuntimeConfig(
|
|
53
|
+
manifest.runtimeConfig ?? { public: {} },
|
|
54
|
+
process.cwd()
|
|
55
|
+
);
|
|
56
|
+
const publicConfig = runtimeConfig.public ?? {};
|
|
49
57
|
const importServer = (relFile) => import(pathToFileURL(join(dir, "server", relFile)).href);
|
|
50
58
|
const registry = {};
|
|
51
59
|
let componentCss = "";
|
|
@@ -60,7 +68,13 @@ async function startProdServer(options) {
|
|
|
60
68
|
const mod = await importServer(serverFile);
|
|
61
69
|
apiEntries.push(...expandApiModule(name, mod.default));
|
|
62
70
|
}
|
|
71
|
+
const middleware = [];
|
|
72
|
+
for (const { serverFile } of manifest.middleware ?? []) {
|
|
73
|
+
const mod = await importServer(serverFile);
|
|
74
|
+
if (typeof mod.default === "function") middleware.push(mod.default);
|
|
75
|
+
}
|
|
63
76
|
const serverFileFor = new Map(manifest.routes.map((r) => [r.pageId, r.serverFile]));
|
|
77
|
+
const errorPageId = serverFileFor.has("/pages/error.alpine") ? "/pages/error.alpine" : void 0;
|
|
64
78
|
const loadModule = (id) => importServer(serverFileFor.get(id));
|
|
65
79
|
const app = createApp();
|
|
66
80
|
app.use(
|
|
@@ -72,12 +86,31 @@ async function startProdServer(options) {
|
|
|
72
86
|
if (!file.startsWith(dir) || !existsSync(file) || !statSync(file).isFile()) return;
|
|
73
87
|
const ext = path.slice(path.lastIndexOf("."));
|
|
74
88
|
setResponseHeader(event, "Content-Type", MIME[ext] ?? "application/octet-stream");
|
|
75
|
-
if (path.startsWith("/assets/"))
|
|
89
|
+
if (path.startsWith("/assets/"))
|
|
90
|
+
setResponseHeader(event, "Cache-Control", "public, max-age=31536000, immutable");
|
|
76
91
|
return readFileSync(file);
|
|
77
92
|
})
|
|
78
93
|
);
|
|
79
|
-
if (
|
|
80
|
-
|
|
94
|
+
if (middleware.length) {
|
|
95
|
+
app.use(
|
|
96
|
+
defineEventHandler(async (event) => {
|
|
97
|
+
const { redirect, locals } = await runMiddleware(middleware, {
|
|
98
|
+
url: getRequestURL(event).pathname,
|
|
99
|
+
method: event.method,
|
|
100
|
+
config: runtimeConfig,
|
|
101
|
+
headers: getRequestHeaders(event)
|
|
102
|
+
});
|
|
103
|
+
event.context.apexLocals = locals;
|
|
104
|
+
if (redirect) {
|
|
105
|
+
setResponseStatus(event, redirect.status);
|
|
106
|
+
setResponseHeader(event, "Location", redirect.to);
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (apiEntries.length) app.use("/api", createApiHandler(apiEntries, runtimeConfig));
|
|
113
|
+
if (hasMcpRoutes(apiEntries)) app.use("/mcp", createMcpHandler(apiEntries, runtimeConfig));
|
|
81
114
|
app.use(
|
|
82
115
|
defineEventHandler(async (event) => {
|
|
83
116
|
const url = getRequestURL(event).pathname;
|
|
@@ -96,7 +129,11 @@ async function startProdServer(options) {
|
|
|
96
129
|
url,
|
|
97
130
|
registry,
|
|
98
131
|
componentCss,
|
|
99
|
-
clientHref: route?.clientHref
|
|
132
|
+
clientHref: route?.clientHref,
|
|
133
|
+
runtimeConfig,
|
|
134
|
+
publicConfig,
|
|
135
|
+
locals: event.context.apexLocals ?? {},
|
|
136
|
+
errorPageId
|
|
100
137
|
});
|
|
101
138
|
setResponseHeader(event, "Content-Type", "text/html");
|
|
102
139
|
return html;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
offerExtension
|
|
3
|
+
} from "./chunk-CHBSGOB3.js";
|
|
4
|
+
import {
|
|
5
|
+
VERSION,
|
|
6
|
+
banner,
|
|
7
|
+
color
|
|
8
|
+
} from "./chunk-QIXJSQLW.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/upgrade.ts
|
|
11
|
+
import { spawnSync } from "child_process";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
13
|
+
import { basename, dirname, join, relative, resolve } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { defineCommand } from "citty";
|
|
16
|
+
var TEMPLATE_DIR = fileURLToPath(new URL("../templates/default", import.meta.url));
|
|
17
|
+
var PROTECTED = /* @__PURE__ */ new Set(["package.json"]);
|
|
18
|
+
function projectName(root) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(join(root, "package.json"), "utf8")).name || basename(root);
|
|
21
|
+
} catch {
|
|
22
|
+
return basename(root);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function bumpApexDeps(root) {
|
|
26
|
+
const pkgPath = join(root, "package.json");
|
|
27
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
28
|
+
let bumped = 0;
|
|
29
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
30
|
+
const deps = pkg[field];
|
|
31
|
+
if (!deps) continue;
|
|
32
|
+
for (const dep of Object.keys(deps)) {
|
|
33
|
+
if (dep.startsWith("@apex-stack/") && /^[\d^~]/.test(deps[dep] ?? "")) {
|
|
34
|
+
if (deps[dep] !== `^${VERSION}`) {
|
|
35
|
+
deps[dep] = `^${VERSION}`;
|
|
36
|
+
bumped++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (bumped) writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
42
|
+
`);
|
|
43
|
+
return bumped;
|
|
44
|
+
}
|
|
45
|
+
function detectPm() {
|
|
46
|
+
const ua = process.env.npm_config_user_agent || "";
|
|
47
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
48
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
49
|
+
if (ua.startsWith("bun")) return "bun";
|
|
50
|
+
return "npm";
|
|
51
|
+
}
|
|
52
|
+
function walk(dir, onFile) {
|
|
53
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
const p = join(dir, entry.name);
|
|
55
|
+
if (entry.isDirectory()) walk(p, onFile);
|
|
56
|
+
else onFile(p);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function targetRelPath(rel) {
|
|
60
|
+
return basename(rel) === "_gitignore" ? join(dirname(rel), ".gitignore") : rel;
|
|
61
|
+
}
|
|
62
|
+
var upgradeCommand = defineCommand({
|
|
63
|
+
meta: {
|
|
64
|
+
name: "upgrade",
|
|
65
|
+
description: "Adopt new scaffold defaults in an existing app (non-destructive)"
|
|
66
|
+
},
|
|
67
|
+
args: {
|
|
68
|
+
root: { type: "positional", required: false, description: "Project root", default: "." },
|
|
69
|
+
force: {
|
|
70
|
+
type: "boolean",
|
|
71
|
+
default: false,
|
|
72
|
+
description: "Re-sync files that differ from the template (package.json is always preserved)"
|
|
73
|
+
},
|
|
74
|
+
install: {
|
|
75
|
+
type: "boolean",
|
|
76
|
+
default: true,
|
|
77
|
+
description: "Run the package manager after bumping @apex-stack/* (use --no-install to skip)"
|
|
78
|
+
},
|
|
79
|
+
vscode: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
description: "Install the Apex VS Code extension (skip the prompt)"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async run({ args }) {
|
|
85
|
+
const root = resolve(process.cwd(), String(args.root));
|
|
86
|
+
const log = console.log;
|
|
87
|
+
if (!existsSync(join(root, "package.json"))) {
|
|
88
|
+
console.error(`
|
|
89
|
+
${color.red("\u2717")} No package.json in ${root} \u2014 is this an Apex project?
|
|
90
|
+
`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const name = projectName(root);
|
|
94
|
+
process.stdout.write(banner());
|
|
95
|
+
const added = [];
|
|
96
|
+
const updated = [];
|
|
97
|
+
let unchanged = 0;
|
|
98
|
+
walk(TEMPLATE_DIR, (absFile) => {
|
|
99
|
+
const rel = targetRelPath(relative(TEMPLATE_DIR, absFile));
|
|
100
|
+
const target = join(root, rel);
|
|
101
|
+
let content = readFileSync(absFile, "utf8");
|
|
102
|
+
if (content.includes("{{name}}")) content = content.replaceAll("{{name}}", name);
|
|
103
|
+
if (!existsSync(target)) {
|
|
104
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
105
|
+
writeFileSync(target, content);
|
|
106
|
+
added.push(rel);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (args.force && !PROTECTED.has(rel)) {
|
|
110
|
+
if (readFileSync(target, "utf8") !== content) {
|
|
111
|
+
writeFileSync(target, content);
|
|
112
|
+
updated.push(rel);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
unchanged++;
|
|
117
|
+
});
|
|
118
|
+
if (added.length) {
|
|
119
|
+
log(`
|
|
120
|
+
${color.green("+")} Added ${added.length} new file(s):`);
|
|
121
|
+
for (const f of added.sort()) log(` ${color.green(f)}`);
|
|
122
|
+
}
|
|
123
|
+
if (updated.length) {
|
|
124
|
+
log(`
|
|
125
|
+
${color.cyan("~")} Re-synced ${updated.length} file(s):`);
|
|
126
|
+
for (const f of updated.sort()) log(` ${color.cyan(f)}`);
|
|
127
|
+
}
|
|
128
|
+
if (added.length || updated.length) {
|
|
129
|
+
log(`
|
|
130
|
+
${color.gray(`${unchanged} existing file(s) left untouched.`)}`);
|
|
131
|
+
}
|
|
132
|
+
const bumped = bumpApexDeps(root);
|
|
133
|
+
if (bumped)
|
|
134
|
+
log(`
|
|
135
|
+
${color.cyan("\u2191")} Bumped ${bumped} @apex-stack/* dependency \u2192 ^${VERSION}`);
|
|
136
|
+
if (!added.length && !updated.length && !bumped) {
|
|
137
|
+
log(`
|
|
138
|
+
${color.green("\u2713")} Already up to date.`);
|
|
139
|
+
}
|
|
140
|
+
if (bumped && args.install) {
|
|
141
|
+
const pm = detectPm();
|
|
142
|
+
log(`
|
|
143
|
+
${color.gray(`Installing with ${pm}\u2026`)}`);
|
|
144
|
+
const ok = spawnSync(pm, ["install"], {
|
|
145
|
+
cwd: root,
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
shell: process.platform === "win32"
|
|
148
|
+
}).status === 0;
|
|
149
|
+
log(
|
|
150
|
+
ok ? ` ${color.green("\u2713")} Dependencies updated` : ` ${color.red("\u2717")} Install failed \u2014 run ${color.cyan(`${pm} install`)} yourself`
|
|
151
|
+
);
|
|
152
|
+
} else if (bumped) {
|
|
153
|
+
log(
|
|
154
|
+
` ${color.gray("Run")} ${color.cyan(`${detectPm()} install`)} ${color.gray("to apply.")}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const ext = await offerExtension(args.vscode);
|
|
158
|
+
if (ext) log(` ${color.green("\u2713")} ${ext}`);
|
|
159
|
+
log(
|
|
160
|
+
`
|
|
161
|
+
${color.gray("Non-destructive: your files are never overwritten")}${args.force ? color.gray(" except with --force") : color.gray(" (use --force to re-sync)")}${color.gray("; package.json entries are only version-bumped.")}
|
|
162
|
+
`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
export {
|
|
167
|
+
upgradeCommand
|
|
168
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apex-stack/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The full-stack meta-framework for Alpine.js — CLI and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
40
|
-
"templates"
|
|
40
|
+
"templates",
|
|
41
|
+
"vscode"
|
|
41
42
|
],
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -45,8 +46,8 @@
|
|
|
45
46
|
"h3": "^1.13.0",
|
|
46
47
|
"vite": "^6.0.7",
|
|
47
48
|
"zod": "^4.4.3",
|
|
48
|
-
"@apex-stack/kit": "0.
|
|
49
|
-
"@apex-stack/vite": "0.1.
|
|
49
|
+
"@apex-stack/kit": "0.2.0",
|
|
50
|
+
"@apex-stack/vite": "0.1.6"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
53
|
"alpinejs": "^3.14.0"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copy to `.env` and fill in. These override the defaults in apex.config.ts.
|
|
2
|
+
# Private keys: APEX_<KEY>. Public keys (sent to the browser): APEX_PUBLIC_<KEY>.
|
|
3
|
+
|
|
4
|
+
# Private — server-only
|
|
5
|
+
APEX_API_SECRET=
|
|
6
|
+
|
|
7
|
+
# Public — readable in the browser
|
|
8
|
+
APEX_PUBLIC_APP_NAME={{name}}
|
|
9
|
+
APEX_PUBLIC_SITE_URL=http://localhost:3000
|