@squadbase/vantage 0.0.1
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/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/chunk-73J5ZD4C.js +96 -0
- package/dist/chunk-73J5ZD4C.js.map +1 -0
- package/dist/chunk-ATYZ45XL.js +19 -0
- package/dist/chunk-ATYZ45XL.js.map +1 -0
- package/dist/chunk-B7LLBNLV.js +23 -0
- package/dist/chunk-B7LLBNLV.js.map +1 -0
- package/dist/chunk-ZGDU5YLH.js +753 -0
- package/dist/chunk-ZGDU5YLH.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +765 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/index.d.ts +45 -0
- package/dist/client/index.js +141 -0
- package/dist/client/index.js.map +1 -0
- package/dist/define-page-B6y9TOfZ.d.ts +44 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/query/index.d.ts +11 -0
- package/dist/query/index.js +4 -0
- package/dist/query/index.js.map +1 -0
- package/dist/router/index.d.ts +36 -0
- package/dist/router/index.js +27 -0
- package/dist/router/index.js.map +1 -0
- package/dist/server/index.d.ts +53 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/node.d.ts +35 -0
- package/dist/server/node.js +84 -0
- package/dist/server/node.js.map +1 -0
- package/dist/ui/index.d.ts +217 -0
- package/dist/ui/index.js +616 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/vite/index.d.ts +15 -0
- package/dist/vite/index.js +5 -0
- package/dist/vite/index.js.map +1 -0
- package/package.json +108 -0
- package/registry/blocks/sales-overview.tsx +37 -0
- package/registry/ui/data-table.tsx +115 -0
- package/theme.css +101 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import { matchApiRoute, dispatchApi, sortApiRoutes } from './chunk-73J5ZD4C.js';
|
|
2
|
+
import fs2 from 'fs';
|
|
3
|
+
import path3 from 'path';
|
|
4
|
+
import react from '@vitejs/plugin-react';
|
|
5
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
import { Hono } from 'hono';
|
|
9
|
+
import { getRequestListener } from '@hono/node-server';
|
|
10
|
+
|
|
11
|
+
// @squadbase/vantage — generated build. Do not edit.
|
|
12
|
+
|
|
13
|
+
// src/core/constants.ts
|
|
14
|
+
var VANTAGE_DIR = ".vantage";
|
|
15
|
+
var NON_ROUTE_DIRS = /* @__PURE__ */ new Set([
|
|
16
|
+
"components",
|
|
17
|
+
"hooks",
|
|
18
|
+
"lib",
|
|
19
|
+
"server",
|
|
20
|
+
"public",
|
|
21
|
+
"node_modules",
|
|
22
|
+
VANTAGE_DIR,
|
|
23
|
+
"dist"
|
|
24
|
+
]);
|
|
25
|
+
var PAGE_EXTENSIONS = [".tsx", ".jsx"];
|
|
26
|
+
var API_EXTENSIONS = [".ts", ".tsx", ".js"];
|
|
27
|
+
var RESERVED_PAGE_NAMES = {
|
|
28
|
+
layout: "_layout",
|
|
29
|
+
notFound: "_404",
|
|
30
|
+
error: "_error"
|
|
31
|
+
};
|
|
32
|
+
var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
33
|
+
var FORBIDDEN_FILES = [
|
|
34
|
+
{
|
|
35
|
+
pattern: /^vite\.config\.(ts|js|mjs|cjs|mts|cts)$/,
|
|
36
|
+
label: "Vite config",
|
|
37
|
+
remedy: "Vantage owns the Vite configuration. Remove this file."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pattern: /^tailwind\.config\.(ts|js|mjs|cjs)$/,
|
|
41
|
+
label: "Tailwind config",
|
|
42
|
+
remedy: "Customize theme tokens in styles.css instead."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
pattern: /^postcss\.config\.(ts|js|mjs|cjs)$/,
|
|
46
|
+
label: "PostCSS config",
|
|
47
|
+
remedy: "Vantage manages PostCSS/Tailwind. Remove this file."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
pattern: /^components\.json$/,
|
|
51
|
+
label: "shadcn components.json",
|
|
52
|
+
remedy: "Use `vantage add ui <name>` to copy UI source instead."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
pattern: /^vantage\.config\.(ts|js|mjs|cjs)$/,
|
|
56
|
+
label: "Vantage config",
|
|
57
|
+
remedy: "v1 has no vantage.config. Configuration lives in file conventions."
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
var PUBLIC_ENV_PREFIX = "PUBLIC_";
|
|
61
|
+
var MANIFEST_SCHEMA_VERSION = 1;
|
|
62
|
+
function segToRouter(s) {
|
|
63
|
+
if (s.kind === "static") return s.value;
|
|
64
|
+
if (s.kind === "param") return `$${s.name}`;
|
|
65
|
+
return "$";
|
|
66
|
+
}
|
|
67
|
+
function dirToRouterPath(relDir) {
|
|
68
|
+
return relDir.split("/").filter(Boolean).map((token) => {
|
|
69
|
+
const catchall = token.match(/^\[\.\.\.(.+)\]$/);
|
|
70
|
+
if (catchall) return "$";
|
|
71
|
+
const param = token.match(/^\[(.+)\]$/);
|
|
72
|
+
if (param) return `$${param[1]}`;
|
|
73
|
+
return token;
|
|
74
|
+
}).join("/");
|
|
75
|
+
}
|
|
76
|
+
function isAncestorDir(ancestor, dir) {
|
|
77
|
+
if (ancestor === "") return true;
|
|
78
|
+
return dir === ancestor || dir.startsWith(ancestor + "/");
|
|
79
|
+
}
|
|
80
|
+
function isStrictAncestorDir(ancestor, dir) {
|
|
81
|
+
return dir.startsWith(ancestor + "/");
|
|
82
|
+
}
|
|
83
|
+
function depth(dir) {
|
|
84
|
+
return dir === "" ? 0 : dir.split("/").length;
|
|
85
|
+
}
|
|
86
|
+
function extractDefinePageArgs(src) {
|
|
87
|
+
const call = src.match(/\bdefinePage\s*\(/);
|
|
88
|
+
if (!call) return null;
|
|
89
|
+
let i = (call.index ?? 0) + call[0].length;
|
|
90
|
+
while (i < src.length && /\s/.test(src[i])) i++;
|
|
91
|
+
if (src[i] !== "{") return null;
|
|
92
|
+
const start = i;
|
|
93
|
+
let depth2 = 0;
|
|
94
|
+
for (; i < src.length; i++) {
|
|
95
|
+
const c = src[i];
|
|
96
|
+
if (c === "{") depth2++;
|
|
97
|
+
else if (c === "}" && --depth2 === 0) return src.slice(start + 1, i);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function matchStringField(scope, key) {
|
|
102
|
+
const re = new RegExp(
|
|
103
|
+
`\\b${key}\\s*:\\s*(?:"((?:\\\\.|[^"\\\\])*)"|'((?:\\\\.|[^'\\\\])*)'|\`((?:\\\\.|[^\`\\\\])*)\`)`
|
|
104
|
+
);
|
|
105
|
+
const m = scope.match(re);
|
|
106
|
+
if (!m) return void 0;
|
|
107
|
+
return (m[1] ?? m[2] ?? m[3] ?? "").replace(/\\(.)/g, "$1");
|
|
108
|
+
}
|
|
109
|
+
function extractPageMeta(absFile) {
|
|
110
|
+
let src;
|
|
111
|
+
try {
|
|
112
|
+
src = fs2.readFileSync(absFile, "utf8");
|
|
113
|
+
} catch {
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
const args = extractDefinePageArgs(src);
|
|
117
|
+
if (args == null) return {};
|
|
118
|
+
const meta = {};
|
|
119
|
+
const title = matchStringField(args, "title");
|
|
120
|
+
const description = matchStringField(args, "description");
|
|
121
|
+
if (title) meta.title = title;
|
|
122
|
+
if (description) meta.description = description;
|
|
123
|
+
return meta;
|
|
124
|
+
}
|
|
125
|
+
function buildRouteTree(model) {
|
|
126
|
+
const rootLayoutRoute = model.layouts.find((l) => l.dir === "") ?? null;
|
|
127
|
+
const nestedLayouts = model.layouts.filter((l) => l.dir !== "").sort((a, b) => depth(a.dir) - depth(b.dir));
|
|
128
|
+
const built = [];
|
|
129
|
+
const dirToId = /* @__PURE__ */ new Map();
|
|
130
|
+
nestedLayouts.forEach((layout, i) => {
|
|
131
|
+
const id = `L${i}`;
|
|
132
|
+
let parent = null;
|
|
133
|
+
for (const cand of built) {
|
|
134
|
+
if (isStrictAncestorDir(cand.dir, layout.dir)) {
|
|
135
|
+
if (!parent || depth(cand.dir) > depth(parent.dir)) parent = { id: cand.id, dir: cand.dir };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const parentDir = parent ? parent.dir : "";
|
|
139
|
+
const relDir = parentDir === "" ? layout.dir : layout.dir.slice(parentDir.length + 1);
|
|
140
|
+
built.push({
|
|
141
|
+
id,
|
|
142
|
+
dir: layout.dir,
|
|
143
|
+
parentId: parent ? parent.id : null,
|
|
144
|
+
path: dirToRouterPath(relDir),
|
|
145
|
+
file: layout.file
|
|
146
|
+
});
|
|
147
|
+
dirToId.set(layout.dir, id);
|
|
148
|
+
});
|
|
149
|
+
const pages = model.pages.map((page) => {
|
|
150
|
+
let owner = null;
|
|
151
|
+
for (const l of built) {
|
|
152
|
+
if (isAncestorDir(l.dir, page.dir)) {
|
|
153
|
+
if (!owner || depth(l.dir) > depth(owner.dir)) owner = l;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const ownerSegCount = owner ? depth(owner.dir) : 0;
|
|
157
|
+
const remaining = page.segments.slice(ownerSegCount);
|
|
158
|
+
const index = remaining.length === 0;
|
|
159
|
+
const path4 = index ? "/" : remaining.map(segToRouter).join("/");
|
|
160
|
+
const meta = extractPageMeta(page.absFile);
|
|
161
|
+
return {
|
|
162
|
+
file: page.file,
|
|
163
|
+
routePath: page.routePath,
|
|
164
|
+
parentLayoutId: owner ? owner.id : null,
|
|
165
|
+
path: path4,
|
|
166
|
+
index,
|
|
167
|
+
...meta.title ? { title: meta.title } : {},
|
|
168
|
+
...meta.description ? { description: meta.description } : {}
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
rootLayout: rootLayoutRoute ? rootLayoutRoute.file : null,
|
|
173
|
+
notFound: model.notFound,
|
|
174
|
+
error: model.error,
|
|
175
|
+
layouts: built,
|
|
176
|
+
pages
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/core/generate.ts
|
|
181
|
+
var PKG = "@squadbase/vantage";
|
|
182
|
+
function resolveVantageDir(root) {
|
|
183
|
+
try {
|
|
184
|
+
const require2 = createRequire(path3.join(root, "noop.js"));
|
|
185
|
+
const pkgJson = require2.resolve(`${PKG}/package.json`);
|
|
186
|
+
return path3.dirname(pkgJson);
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function importFromVantage(relFile) {
|
|
192
|
+
return `../${relFile}`;
|
|
193
|
+
}
|
|
194
|
+
function ensureDir(dir) {
|
|
195
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
function writeIfChanged(file, content) {
|
|
198
|
+
try {
|
|
199
|
+
if (fs2.existsSync(file) && fs2.readFileSync(file, "utf8") === content) return;
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
fs2.writeFileSync(file, content);
|
|
203
|
+
}
|
|
204
|
+
function serializeSegments(segments) {
|
|
205
|
+
return "[" + segments.map((s) => {
|
|
206
|
+
if (s.kind === "static") return `{ kind: "static", value: ${JSON.stringify(s.value)} }`;
|
|
207
|
+
if (s.kind === "param") return `{ kind: "param", name: ${JSON.stringify(s.name)} }`;
|
|
208
|
+
return `{ kind: "catchall", name: ${JSON.stringify(s.name)} }`;
|
|
209
|
+
}).join(", ") + "]";
|
|
210
|
+
}
|
|
211
|
+
var BANNER = "// AUTO-GENERATED by @squadbase/vantage. Do not edit \u2014 regenerated on every run.\n";
|
|
212
|
+
function renderRouteTree(model) {
|
|
213
|
+
const tree = buildRouteTree(model);
|
|
214
|
+
const lines = [];
|
|
215
|
+
lines.push(BANNER);
|
|
216
|
+
lines.push(`import type { ClientManifest } from "${PKG}/client"`);
|
|
217
|
+
lines.push("");
|
|
218
|
+
lines.push("export const manifest: ClientManifest = {");
|
|
219
|
+
lines.push(
|
|
220
|
+
` rootLayout: ${tree.rootLayout ? `() => import(${JSON.stringify(importFromVantage(tree.rootLayout))})` : "null"},`
|
|
221
|
+
);
|
|
222
|
+
lines.push(
|
|
223
|
+
` notFound: ${tree.notFound ? `() => import(${JSON.stringify(importFromVantage(tree.notFound))})` : "null"},`
|
|
224
|
+
);
|
|
225
|
+
lines.push(
|
|
226
|
+
` error: ${tree.error ? `() => import(${JSON.stringify(importFromVantage(tree.error))})` : "null"},`
|
|
227
|
+
);
|
|
228
|
+
lines.push(" layouts: [");
|
|
229
|
+
for (const l of tree.layouts) {
|
|
230
|
+
lines.push(
|
|
231
|
+
` { id: ${JSON.stringify(l.id)}, parentId: ${JSON.stringify(l.parentId)}, path: ${JSON.stringify(
|
|
232
|
+
l.path
|
|
233
|
+
)}, load: () => import(${JSON.stringify(importFromVantage(l.file))}) },`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
lines.push(" ],");
|
|
237
|
+
lines.push(" pages: [");
|
|
238
|
+
for (const p of tree.pages) {
|
|
239
|
+
const extra = [];
|
|
240
|
+
if (p.title) extra.push(`title: ${JSON.stringify(p.title)}`);
|
|
241
|
+
if (p.description) extra.push(`description: ${JSON.stringify(p.description)}`);
|
|
242
|
+
lines.push(
|
|
243
|
+
` { parentLayoutId: ${JSON.stringify(p.parentLayoutId)}, path: ${JSON.stringify(
|
|
244
|
+
p.path
|
|
245
|
+
)}, index: ${p.index}, routePath: ${JSON.stringify(p.routePath)}${extra.length ? ", " + extra.join(", ") : ""}, load: () => import(${JSON.stringify(importFromVantage(p.file))}) },`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
lines.push(" ],");
|
|
249
|
+
lines.push("}");
|
|
250
|
+
lines.push("");
|
|
251
|
+
return lines.join("\n");
|
|
252
|
+
}
|
|
253
|
+
function renderServerRoutes(model) {
|
|
254
|
+
const lines = [];
|
|
255
|
+
lines.push(BANNER);
|
|
256
|
+
lines.push(`import type { ProdApiRoute } from "${PKG}/server/node"`);
|
|
257
|
+
model.apis.forEach((a, i) => {
|
|
258
|
+
lines.push(`import * as m${i} from ${JSON.stringify(importFromVantage(a.file))}`);
|
|
259
|
+
});
|
|
260
|
+
lines.push("");
|
|
261
|
+
lines.push("export const apiRoutes: ProdApiRoute[] = [");
|
|
262
|
+
model.apis.forEach((a, i) => {
|
|
263
|
+
lines.push(
|
|
264
|
+
` { routePath: ${JSON.stringify(a.routePath)}, segments: ${serializeSegments(
|
|
265
|
+
a.segments
|
|
266
|
+
)}, module: m${i} },`
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
lines.push("]");
|
|
270
|
+
lines.push("");
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
function renderIndexHtml() {
|
|
274
|
+
return `<!doctype html>
|
|
275
|
+
<html lang="en">
|
|
276
|
+
<head>
|
|
277
|
+
<meta charset="UTF-8" />
|
|
278
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
279
|
+
<title>Vantage</title>
|
|
280
|
+
</head>
|
|
281
|
+
<body>
|
|
282
|
+
<div id="root"></div>
|
|
283
|
+
<script type="module" src="/.vantage/client-entry.tsx"></script>
|
|
284
|
+
</body>
|
|
285
|
+
</html>
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
function renderClientEntry() {
|
|
289
|
+
return `${BANNER}import { bootstrap } from "${PKG}/client"
|
|
290
|
+
import { manifest } from "./route-tree.gen"
|
|
291
|
+
import "./runtime.css"
|
|
292
|
+
|
|
293
|
+
bootstrap(manifest)
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
function renderRuntimeCss(root) {
|
|
297
|
+
const parts = [];
|
|
298
|
+
parts.push('@import "tailwindcss";');
|
|
299
|
+
parts.push(`@import "${PKG}/theme.css";`);
|
|
300
|
+
if (fs2.existsSync(path3.join(root, "styles.css"))) {
|
|
301
|
+
parts.push('@import "../styles.css";');
|
|
302
|
+
}
|
|
303
|
+
const vantageDir = resolveVantageDir(root);
|
|
304
|
+
if (vantageDir) {
|
|
305
|
+
const uiDir = path3.join(vantageDir, "dist", "ui");
|
|
306
|
+
parts.push(`@source ${JSON.stringify(uiDir)};`);
|
|
307
|
+
}
|
|
308
|
+
return parts.join("\n") + "\n";
|
|
309
|
+
}
|
|
310
|
+
function renderEnvTypes() {
|
|
311
|
+
return `${BANNER}
|
|
312
|
+
interface ImportMetaEnv {
|
|
313
|
+
readonly MODE: string
|
|
314
|
+
readonly DEV: boolean
|
|
315
|
+
readonly PROD: boolean
|
|
316
|
+
readonly SSR: boolean
|
|
317
|
+
readonly BASE_URL: string
|
|
318
|
+
readonly [key: \`PUBLIC_\${string}\`]: string | undefined
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
interface ImportMeta {
|
|
322
|
+
readonly env: ImportMetaEnv
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
declare module "*.css" {
|
|
326
|
+
const content: string
|
|
327
|
+
export default content
|
|
328
|
+
}
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
function renderInternalManifest(model) {
|
|
332
|
+
return JSON.stringify(
|
|
333
|
+
{
|
|
334
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
335
|
+
pages: model.pages.map((p) => ({ file: p.file, route: p.routePath })),
|
|
336
|
+
apis: model.apis.map((a) => ({ file: a.file, route: a.routePath })),
|
|
337
|
+
hasServer: model.hasServer
|
|
338
|
+
},
|
|
339
|
+
null,
|
|
340
|
+
2
|
|
341
|
+
) + "\n";
|
|
342
|
+
}
|
|
343
|
+
function generateArtifacts({ root, model }) {
|
|
344
|
+
const vantageDir = path3.join(root, VANTAGE_DIR);
|
|
345
|
+
ensureDir(vantageDir);
|
|
346
|
+
ensureDir(path3.join(vantageDir, "types"));
|
|
347
|
+
writeIfChanged(path3.join(vantageDir, ".gitignore"), "*\n");
|
|
348
|
+
writeIfChanged(path3.join(vantageDir, "index.html"), renderIndexHtml());
|
|
349
|
+
writeIfChanged(path3.join(vantageDir, "client-entry.tsx"), renderClientEntry());
|
|
350
|
+
writeIfChanged(path3.join(vantageDir, "route-tree.gen.ts"), renderRouteTree(model));
|
|
351
|
+
writeIfChanged(path3.join(vantageDir, "runtime.css"), renderRuntimeCss(root));
|
|
352
|
+
writeIfChanged(path3.join(vantageDir, "types", "env.d.ts"), renderEnvTypes());
|
|
353
|
+
writeIfChanged(path3.join(vantageDir, "manifest.json"), renderInternalManifest(model));
|
|
354
|
+
if (model.hasServer) {
|
|
355
|
+
writeIfChanged(path3.join(vantageDir, "server-routes.gen.ts"), renderServerRoutes(model));
|
|
356
|
+
}
|
|
357
|
+
return vantageDir;
|
|
358
|
+
}
|
|
359
|
+
function toPosix(root, abs) {
|
|
360
|
+
return path3.relative(root, abs).split(path3.sep).join("/");
|
|
361
|
+
}
|
|
362
|
+
function parseRawSegment(raw) {
|
|
363
|
+
const catchall = raw.match(/^\[\.\.\.(.+)\]$/);
|
|
364
|
+
if (catchall) return { kind: "catchall", name: catchall[1] };
|
|
365
|
+
const param = raw.match(/^\[(.+)\]$/);
|
|
366
|
+
if (param) return { kind: "param", name: param[1] };
|
|
367
|
+
return { kind: "static", value: raw };
|
|
368
|
+
}
|
|
369
|
+
function displayPath(segments) {
|
|
370
|
+
if (segments.length === 0) return "/";
|
|
371
|
+
const parts = segments.map((s) => {
|
|
372
|
+
if (s.kind === "static") return s.value;
|
|
373
|
+
if (s.kind === "param") return `:${s.name}`;
|
|
374
|
+
return "*";
|
|
375
|
+
});
|
|
376
|
+
return "/" + parts.join("/");
|
|
377
|
+
}
|
|
378
|
+
function fileToSegments(relNoExt) {
|
|
379
|
+
const raw = relNoExt.split("/");
|
|
380
|
+
if (raw[raw.length - 1] === "index") raw.pop();
|
|
381
|
+
return raw.map(parseRawSegment);
|
|
382
|
+
}
|
|
383
|
+
function stripExt(file, exts) {
|
|
384
|
+
for (const ext of exts) {
|
|
385
|
+
if (file.endsWith(ext)) return file.slice(0, -ext.length);
|
|
386
|
+
}
|
|
387
|
+
return file;
|
|
388
|
+
}
|
|
389
|
+
function hasExt(file, exts) {
|
|
390
|
+
return exts.some((ext) => file.endsWith(ext));
|
|
391
|
+
}
|
|
392
|
+
function walk(dir, root, out) {
|
|
393
|
+
let entries;
|
|
394
|
+
try {
|
|
395
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
396
|
+
} catch {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
for (const entry of entries) {
|
|
400
|
+
const abs = path3.join(dir, entry.name);
|
|
401
|
+
if (entry.isDirectory()) {
|
|
402
|
+
if (NON_ROUTE_DIRS.has(entry.name)) continue;
|
|
403
|
+
if (entry.name.startsWith(".")) continue;
|
|
404
|
+
walk(abs, root, out);
|
|
405
|
+
} else if (entry.isFile()) {
|
|
406
|
+
out.push(abs);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function scanPages(root) {
|
|
411
|
+
const files = [];
|
|
412
|
+
walk(root, root, files);
|
|
413
|
+
const pages = [];
|
|
414
|
+
const layouts = [];
|
|
415
|
+
let notFound = null;
|
|
416
|
+
let error = null;
|
|
417
|
+
for (const abs of files) {
|
|
418
|
+
const rel = toPosix(root, abs);
|
|
419
|
+
const base = path3.posix.basename(rel);
|
|
420
|
+
if (!hasExt(base, PAGE_EXTENSIONS)) continue;
|
|
421
|
+
const nameNoExt = stripExt(base, PAGE_EXTENSIONS);
|
|
422
|
+
const dir = path3.posix.dirname(rel);
|
|
423
|
+
const dirPosix = dir === "." ? "" : dir;
|
|
424
|
+
if (nameNoExt === RESERVED_PAGE_NAMES.layout) {
|
|
425
|
+
layouts.push({ file: rel, absFile: abs, dir: dirPosix });
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
if (nameNoExt === RESERVED_PAGE_NAMES.notFound) {
|
|
429
|
+
if (dirPosix === "") notFound = rel;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (nameNoExt === RESERVED_PAGE_NAMES.error) {
|
|
433
|
+
if (dirPosix === "") error = rel;
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (nameNoExt.startsWith("_")) continue;
|
|
437
|
+
const relNoExt = stripExt(rel, PAGE_EXTENSIONS);
|
|
438
|
+
const segments = fileToSegments(relNoExt);
|
|
439
|
+
const routePath = displayPath(segments);
|
|
440
|
+
const dynamic = segments.some((s) => s.kind !== "static");
|
|
441
|
+
pages.push({ file: rel, absFile: abs, routePath, segments, dir: dirPosix, dynamic });
|
|
442
|
+
}
|
|
443
|
+
pages.sort((a, b) => routeSortKey(a).localeCompare(routeSortKey(b)));
|
|
444
|
+
layouts.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
445
|
+
return { pages, layouts, notFound, error };
|
|
446
|
+
}
|
|
447
|
+
function routeSortKey(p) {
|
|
448
|
+
return p.segments.map((s) => s.kind === "static" ? "0" + s.value : s.kind === "param" ? "1" : "2").join("/");
|
|
449
|
+
}
|
|
450
|
+
function scanApis(root) {
|
|
451
|
+
const apiDir = path3.join(root, "server", "api");
|
|
452
|
+
if (!fs2.existsSync(apiDir)) return [];
|
|
453
|
+
const files = [];
|
|
454
|
+
const walkApi = (dir) => {
|
|
455
|
+
let entries;
|
|
456
|
+
try {
|
|
457
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
458
|
+
} catch {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
for (const entry of entries) {
|
|
462
|
+
const abs = path3.join(dir, entry.name);
|
|
463
|
+
if (entry.isDirectory()) walkApi(abs);
|
|
464
|
+
else if (entry.isFile()) files.push(abs);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
walkApi(apiDir);
|
|
468
|
+
const apis = [];
|
|
469
|
+
for (const abs of files) {
|
|
470
|
+
const base = path3.basename(abs);
|
|
471
|
+
if (!hasExt(base, API_EXTENSIONS)) continue;
|
|
472
|
+
const nameNoExt = stripExt(base, API_EXTENSIONS);
|
|
473
|
+
if (base.endsWith(".d.ts")) continue;
|
|
474
|
+
if (nameNoExt.startsWith("_")) continue;
|
|
475
|
+
const relFromServer = toPosix(path3.join(root, "server"), abs);
|
|
476
|
+
const relNoExt = stripExt(relFromServer, API_EXTENSIONS);
|
|
477
|
+
const withoutApiPrefix = relNoExt.replace(/^api\/?/, "");
|
|
478
|
+
const segTokens = withoutApiPrefix === "" ? [] : withoutApiPrefix.split("/");
|
|
479
|
+
if (segTokens[segTokens.length - 1] === "index") segTokens.pop();
|
|
480
|
+
const segments = segTokens.map(parseRawSegment);
|
|
481
|
+
const routePath = "/api" + (segments.length ? displayPath(segments) : "");
|
|
482
|
+
const params = segments.filter((s) => s.kind !== "static").map((s) => s.name);
|
|
483
|
+
apis.push({
|
|
484
|
+
file: toPosix(root, abs),
|
|
485
|
+
absFile: abs,
|
|
486
|
+
routePath,
|
|
487
|
+
pattern: routePath,
|
|
488
|
+
segments,
|
|
489
|
+
params
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
apis.sort((a, b) => a.routePath.localeCompare(b.routePath));
|
|
493
|
+
return apis;
|
|
494
|
+
}
|
|
495
|
+
function scanProject(root) {
|
|
496
|
+
const { pages, layouts, notFound, error } = scanPages(root);
|
|
497
|
+
const hasServer = fs2.existsSync(path3.join(root, "server"));
|
|
498
|
+
const apis = hasServer ? scanApis(root) : [];
|
|
499
|
+
return { pages, layouts, notFound, error, apis, hasServer };
|
|
500
|
+
}
|
|
501
|
+
function createApiDevMiddleware(opts) {
|
|
502
|
+
const { server, getModel, getEnv } = opts;
|
|
503
|
+
const buildRoutes = () => sortApiRoutes(
|
|
504
|
+
getModel().apis.map((a) => ({
|
|
505
|
+
routePath: a.routePath,
|
|
506
|
+
segments: a.segments,
|
|
507
|
+
hasCatchall: a.segments.some((s) => s.kind === "catchall"),
|
|
508
|
+
staticCount: a.segments.filter((s) => s.kind === "static").length,
|
|
509
|
+
load: () => server.ssrLoadModule(a.absFile)
|
|
510
|
+
}))
|
|
511
|
+
);
|
|
512
|
+
const app = new Hono();
|
|
513
|
+
const handle = async (c) => {
|
|
514
|
+
const url = new URL(c.req.url);
|
|
515
|
+
const match = matchApiRoute(buildRoutes(), url.pathname);
|
|
516
|
+
if (!match) {
|
|
517
|
+
return Response.json({ error: "Not Found", path: url.pathname }, { status: 404 });
|
|
518
|
+
}
|
|
519
|
+
let mod;
|
|
520
|
+
try {
|
|
521
|
+
mod = await match.route.load();
|
|
522
|
+
} catch (err) {
|
|
523
|
+
if (err instanceof Error) server.ssrFixStacktrace(err);
|
|
524
|
+
throw err;
|
|
525
|
+
}
|
|
526
|
+
return dispatchApi(mod, c.req.raw, match.params, getEnv());
|
|
527
|
+
};
|
|
528
|
+
app.all("/api", handle);
|
|
529
|
+
app.all("/api/*", handle);
|
|
530
|
+
const listener = getRequestListener(app.fetch);
|
|
531
|
+
return (req, res, next) => {
|
|
532
|
+
const url = req.url ?? "/";
|
|
533
|
+
const path4 = url.split("?")[0];
|
|
534
|
+
if (path4 === "/api" || path4.startsWith("/api/")) {
|
|
535
|
+
Promise.resolve(listener(req, res)).catch((err) => {
|
|
536
|
+
console.error("[vantage] API middleware failure:", err);
|
|
537
|
+
if (!res.headersSent) {
|
|
538
|
+
res.statusCode = 500;
|
|
539
|
+
res.setHeader("content-type", "application/json");
|
|
540
|
+
res.end(JSON.stringify({ error: "Internal Server Error" }));
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
} else {
|
|
544
|
+
next();
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/vite/index.ts
|
|
550
|
+
var DEDUPE = ["react", "react-dom", "@tanstack/react-router", "@tanstack/react-query"];
|
|
551
|
+
function isPageFile(root, file) {
|
|
552
|
+
const rel = path3.relative(root, file);
|
|
553
|
+
if (rel.startsWith("..")) return false;
|
|
554
|
+
const segments = rel.split(path3.sep);
|
|
555
|
+
if (segments.some((s) => NON_ROUTE_DIRS.has(s) || s.startsWith(".") && s !== "..")) return false;
|
|
556
|
+
return PAGE_EXTENSIONS.some((ext) => file.endsWith(ext));
|
|
557
|
+
}
|
|
558
|
+
function isApiFile(root, file) {
|
|
559
|
+
const rel = path3.relative(root, file);
|
|
560
|
+
return rel.startsWith(`server${path3.sep}api${path3.sep}`) && /\.(ts|tsx|js)$/.test(file) && !file.endsWith(".d.ts");
|
|
561
|
+
}
|
|
562
|
+
function isServerFile(root, file) {
|
|
563
|
+
const rel = path3.relative(root, file);
|
|
564
|
+
return rel === "server" || rel.startsWith(`server${path3.sep}`);
|
|
565
|
+
}
|
|
566
|
+
function vantagePlugin(options) {
|
|
567
|
+
const root = path3.resolve(options.root);
|
|
568
|
+
const vantageDir = path3.join(root, VANTAGE_DIR);
|
|
569
|
+
let model = scanProject(root);
|
|
570
|
+
const regenerate = () => {
|
|
571
|
+
model = scanProject(root);
|
|
572
|
+
generateArtifacts({ root, model });
|
|
573
|
+
};
|
|
574
|
+
const core = {
|
|
575
|
+
name: "vantage:core",
|
|
576
|
+
enforce: "pre",
|
|
577
|
+
config() {
|
|
578
|
+
return {
|
|
579
|
+
root,
|
|
580
|
+
appType: "custom",
|
|
581
|
+
envPrefix: ["PUBLIC_"],
|
|
582
|
+
resolve: { dedupe: DEDUPE },
|
|
583
|
+
optimizeDeps: {
|
|
584
|
+
// Only list deps resolvable from the project root here; the managed
|
|
585
|
+
// @tanstack/* packages are discovered through the module graph.
|
|
586
|
+
include: ["react", "react-dom", "react-dom/client", "react/jsx-runtime"]
|
|
587
|
+
},
|
|
588
|
+
build: {
|
|
589
|
+
outDir: path3.join(root, "dist", "client"),
|
|
590
|
+
emptyOutDir: true,
|
|
591
|
+
rollupOptions: {
|
|
592
|
+
input: path3.join(vantageDir, "index.html")
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
},
|
|
597
|
+
configResolved() {
|
|
598
|
+
regenerate();
|
|
599
|
+
},
|
|
600
|
+
buildStart() {
|
|
601
|
+
regenerate();
|
|
602
|
+
},
|
|
603
|
+
// --- Server-import boundary: client code may never import server/. ---
|
|
604
|
+
resolveId(source, importer, opts) {
|
|
605
|
+
if (opts?.ssr) return null;
|
|
606
|
+
if (!importer) return null;
|
|
607
|
+
const imp = importer.split("?")[0];
|
|
608
|
+
if (!imp.startsWith(root)) return null;
|
|
609
|
+
const relImp = path3.relative(root, imp);
|
|
610
|
+
if (relImp.startsWith(`server${path3.sep}`) || relImp.startsWith(VANTAGE_DIR) || relImp.includes("node_modules")) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
let target = null;
|
|
614
|
+
if (source.startsWith(".")) target = path3.resolve(path3.dirname(imp), source);
|
|
615
|
+
else if (source.startsWith("/")) target = path3.join(root, source.slice(1));
|
|
616
|
+
else if (source === "server" || source.startsWith("server/")) target = path3.join(root, source);
|
|
617
|
+
if (!target) return null;
|
|
618
|
+
const relTarget = path3.relative(root, target);
|
|
619
|
+
if (relTarget === "server" || relTarget.startsWith(`server${path3.sep}`)) {
|
|
620
|
+
this.error(
|
|
621
|
+
`Client module "${relImp}" imports from server/ ("${source}"). server/ code must never reach the client bundle \u2014 move shared code to lib/.`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
return null;
|
|
625
|
+
},
|
|
626
|
+
// --- Keep React Fast Refresh working on page modules. ---
|
|
627
|
+
// `export const page = definePage(...)` is a non-component export, which
|
|
628
|
+
// makes @vitejs/plugin-react bail out of Fast Refresh (full reload). The
|
|
629
|
+
// runtime never reads this export (title/description are extracted at
|
|
630
|
+
// generate time), so we drop the `export` keyword — turning it into a local
|
|
631
|
+
// const — before the React plugin analyzes the module. Offsets are
|
|
632
|
+
// preserved (6 spaces for "export") so sourcemaps stay accurate.
|
|
633
|
+
transform(code, id) {
|
|
634
|
+
const clean = id.split("?")[0];
|
|
635
|
+
if (!isPageFile(root, clean)) return null;
|
|
636
|
+
if (!/export\s+const\s+page\b/.test(code)) return null;
|
|
637
|
+
const out = code.replace(
|
|
638
|
+
/^([ \t]*)export(\s+const\s+page\b)/gm,
|
|
639
|
+
(_m, indent, rest) => indent + " " + rest
|
|
640
|
+
);
|
|
641
|
+
if (out === code) return null;
|
|
642
|
+
return { code: out, map: null };
|
|
643
|
+
},
|
|
644
|
+
// --- Dev console + runtime errors forwarded to the terminal. ---
|
|
645
|
+
transformIndexHtml: {
|
|
646
|
+
order: "pre",
|
|
647
|
+
handler(_html, ctx) {
|
|
648
|
+
if (!ctx.server) return [];
|
|
649
|
+
return [
|
|
650
|
+
{
|
|
651
|
+
tag: "script",
|
|
652
|
+
attrs: { type: "module" },
|
|
653
|
+
injectTo: "head-prepend",
|
|
654
|
+
children: CONSOLE_BRIDGE
|
|
655
|
+
}
|
|
656
|
+
];
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
configureServer(server) {
|
|
660
|
+
const getEnv = () => options.env ?? process.env;
|
|
661
|
+
const apiMw = createApiDevMiddleware({ server, getModel: () => model, getEnv });
|
|
662
|
+
server.middlewares.use((req, res, next) => {
|
|
663
|
+
const url = (req.url ?? "/").split("?")[0];
|
|
664
|
+
if (url === "/__vantage_console" && req.method === "POST") {
|
|
665
|
+
return handleConsoleBeacon(req, res);
|
|
666
|
+
}
|
|
667
|
+
if (model.hasServer) return apiMw(req, res, next);
|
|
668
|
+
return next();
|
|
669
|
+
});
|
|
670
|
+
const isStylesFile = (file) => path3.relative(root, file) === "styles.css";
|
|
671
|
+
const onStructural = (file) => {
|
|
672
|
+
if (isPageFile(root, file) || isApiFile(root, file) || isStylesFile(file)) {
|
|
673
|
+
regenerate();
|
|
674
|
+
server.ws.send({ type: "full-reload" });
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
server.watcher.on("add", onStructural);
|
|
678
|
+
server.watcher.on("unlink", onStructural);
|
|
679
|
+
server.watcher.on("addDir", () => {
|
|
680
|
+
regenerate();
|
|
681
|
+
});
|
|
682
|
+
server.watcher.on("unlinkDir", () => {
|
|
683
|
+
regenerate();
|
|
684
|
+
server.ws.send({ type: "full-reload" });
|
|
685
|
+
});
|
|
686
|
+
server.watcher.on("change", (file) => {
|
|
687
|
+
if (isServerFile(root, file)) {
|
|
688
|
+
const mods = server.moduleGraph.getModulesByFile(file);
|
|
689
|
+
if (mods) for (const m of mods) server.moduleGraph.invalidateModule(m);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
return () => {
|
|
693
|
+
server.middlewares.use(async (req, res, next) => {
|
|
694
|
+
if (req.method !== "GET" && req.method !== "HEAD") return next();
|
|
695
|
+
try {
|
|
696
|
+
const raw = fs2.readFileSync(path3.join(vantageDir, "index.html"), "utf8");
|
|
697
|
+
const html = await server.transformIndexHtml(req.url ?? "/", raw, req.originalUrl);
|
|
698
|
+
res.statusCode = 200;
|
|
699
|
+
res.setHeader("Content-Type", "text/html");
|
|
700
|
+
res.end(html);
|
|
701
|
+
} catch (err) {
|
|
702
|
+
next(err);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
return [core, react(), tailwindcss()];
|
|
709
|
+
}
|
|
710
|
+
var CONSOLE_BRIDGE = `
|
|
711
|
+
(() => {
|
|
712
|
+
const send = (level, message) => {
|
|
713
|
+
try {
|
|
714
|
+
const body = JSON.stringify({ level, message });
|
|
715
|
+
if (navigator.sendBeacon) navigator.sendBeacon("/__vantage_console", body);
|
|
716
|
+
else fetch("/__vantage_console", { method: "POST", body, keepalive: true });
|
|
717
|
+
} catch (_) {}
|
|
718
|
+
};
|
|
719
|
+
const fmt = (args) => args.map((a) => {
|
|
720
|
+
if (a instanceof Error) return a.stack || a.message;
|
|
721
|
+
if (typeof a === "object") { try { return JSON.stringify(a); } catch { return String(a); } }
|
|
722
|
+
return String(a);
|
|
723
|
+
}).join(" ");
|
|
724
|
+
for (const lvl of ["log", "info", "warn", "error"]) {
|
|
725
|
+
const orig = console[lvl].bind(console);
|
|
726
|
+
console[lvl] = (...args) => { send(lvl, fmt(args)); orig(...args); };
|
|
727
|
+
}
|
|
728
|
+
window.addEventListener("error", (e) => send("error", (e.message || "Error") + " @ " + (e.filename || "") + ":" + e.lineno));
|
|
729
|
+
window.addEventListener("unhandledrejection", (e) => send("error", "UnhandledRejection: " + ((e.reason && e.reason.stack) || e.reason)));
|
|
730
|
+
})();
|
|
731
|
+
`;
|
|
732
|
+
function handleConsoleBeacon(req, res) {
|
|
733
|
+
let body = "";
|
|
734
|
+
req.on("data", (chunk) => {
|
|
735
|
+
body += chunk;
|
|
736
|
+
if (body.length > 1e6) req.destroy();
|
|
737
|
+
});
|
|
738
|
+
req.on("end", () => {
|
|
739
|
+
try {
|
|
740
|
+
const { level, message } = JSON.parse(body);
|
|
741
|
+
const tag = pc.dim(`[browser:${level}]`);
|
|
742
|
+
const color = level === "error" ? pc.red : level === "warn" ? pc.yellow : (s) => s;
|
|
743
|
+
console.log(`${tag} ${color(String(message))}`);
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
res.statusCode = 204;
|
|
747
|
+
res.end();
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export { API_METHODS, FORBIDDEN_FILES, MANIFEST_SCHEMA_VERSION, PAGE_EXTENSIONS, PUBLIC_ENV_PREFIX, VANTAGE_DIR, generateArtifacts, scanProject, vantagePlugin };
|
|
752
|
+
//# sourceMappingURL=chunk-ZGDU5YLH.js.map
|
|
753
|
+
//# sourceMappingURL=chunk-ZGDU5YLH.js.map
|