@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
package/dist/cli.js
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { PAGE_EXTENSIONS, scanProject, vantagePlugin, generateArtifacts, VANTAGE_DIR, MANIFEST_SCHEMA_VERSION, FORBIDDEN_FILES, API_METHODS, PUBLIC_ENV_PREFIX } from './chunk-ZGDU5YLH.js';
|
|
3
|
+
import './chunk-73J5ZD4C.js';
|
|
4
|
+
import './chunk-B7LLBNLV.js';
|
|
5
|
+
import path2 from 'path';
|
|
6
|
+
import pc2 from 'picocolors';
|
|
7
|
+
import fs2 from 'fs';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
import { loadEnv, createServer, build as build$1 } from 'vite';
|
|
10
|
+
import { build as build$2 } from 'esbuild';
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
|
+
|
|
13
|
+
// @squadbase/vantage — generated build. Do not edit.
|
|
14
|
+
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "force", "verbose", "version", "h", "v"]);
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const positionals = [];
|
|
17
|
+
const flags = {};
|
|
18
|
+
for (let i = 0; i < argv.length; i++) {
|
|
19
|
+
const arg = argv[i];
|
|
20
|
+
if (arg.startsWith("--")) {
|
|
21
|
+
const eq = arg.indexOf("=");
|
|
22
|
+
if (eq !== -1) {
|
|
23
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const key = arg.slice(2);
|
|
27
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
28
|
+
flags[key] = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const next = argv[i + 1];
|
|
32
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
33
|
+
flags[key] = next;
|
|
34
|
+
i++;
|
|
35
|
+
} else {
|
|
36
|
+
flags[key] = true;
|
|
37
|
+
}
|
|
38
|
+
} else if (arg.length > 1 && arg.startsWith("-") && !/^-\d/.test(arg)) {
|
|
39
|
+
for (const ch of arg.slice(1)) flags[ch] = true;
|
|
40
|
+
} else {
|
|
41
|
+
positionals.push(arg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { positionals, flags };
|
|
45
|
+
}
|
|
46
|
+
function parsePort(value, fallback) {
|
|
47
|
+
if (typeof value === "string") {
|
|
48
|
+
const n = Number(value);
|
|
49
|
+
if (Number.isInteger(n) && n > 0 && n < 65536) return n;
|
|
50
|
+
}
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
53
|
+
function resolveRoot(flags) {
|
|
54
|
+
const raw = typeof flags.root === "string" ? flags.root : process.cwd();
|
|
55
|
+
return path2.resolve(raw);
|
|
56
|
+
}
|
|
57
|
+
function resolveQuickRoot(target) {
|
|
58
|
+
try {
|
|
59
|
+
const abs = path2.resolve(target);
|
|
60
|
+
const stat = fs2.statSync(abs);
|
|
61
|
+
if (stat.isDirectory()) return abs;
|
|
62
|
+
if (stat.isFile() && PAGE_EXTENSIONS.some((ext) => abs.endsWith(ext))) {
|
|
63
|
+
return path2.dirname(abs);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
var log = {
|
|
70
|
+
info: (msg) => console.log(msg),
|
|
71
|
+
step: (msg) => console.log(pc2.cyan("\u2192 ") + msg),
|
|
72
|
+
ok: (msg) => console.log(pc2.green("\u2713 ") + msg),
|
|
73
|
+
warn: (msg) => console.log(pc2.yellow("! ") + msg),
|
|
74
|
+
error: (msg) => console.error(pc2.red("\u2717 ") + msg),
|
|
75
|
+
brand: (msg) => console.log(pc2.bold(pc2.magenta("vantage ")) + msg)
|
|
76
|
+
};
|
|
77
|
+
function printDiagnostics(diags) {
|
|
78
|
+
for (const d of diags) {
|
|
79
|
+
const badge = d.severity === "error" ? pc2.red(pc2.bold(" ERROR ")) : pc2.yellow(pc2.bold(" WARN "));
|
|
80
|
+
console.log(`${badge} ${pc2.dim(d.code)} ${d.message}`);
|
|
81
|
+
if (d.files?.length) console.log(` ${pc2.dim("files:")} ${d.files.join(", ")}`);
|
|
82
|
+
if (d.fix) console.log(` ${pc2.dim("fix:")} ${d.fix}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function printJson(payload) {
|
|
86
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
87
|
+
}
|
|
88
|
+
function frameworkVersion() {
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL("../package.json", import.meta.url);
|
|
91
|
+
const pkg = JSON.parse(fs2.readFileSync(url, "utf8"));
|
|
92
|
+
return pkg.version;
|
|
93
|
+
} catch {
|
|
94
|
+
return "0.0.0";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/cli/add.ts
|
|
99
|
+
var PKG = "@squadbase/vantage";
|
|
100
|
+
function toPascalCase(segment) {
|
|
101
|
+
const cleaned = segment.replace(/\[|\]|\./g, "").replace(/\.\.\./g, "");
|
|
102
|
+
const words = cleaned.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
103
|
+
const pascal = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
104
|
+
return /^[A-Za-z]/.test(pascal) ? pascal : "Page";
|
|
105
|
+
}
|
|
106
|
+
function pageTemplate(name) {
|
|
107
|
+
const last = name.split("/").pop() ?? name;
|
|
108
|
+
const component = last === "index" ? toPascalCase(name.split("/").slice(-2)[0] ?? "Home") : toPascalCase(last);
|
|
109
|
+
const title = last.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
110
|
+
return `import { definePage } from "${PKG}"
|
|
111
|
+
|
|
112
|
+
export const page = definePage({ title: ${JSON.stringify(title)} })
|
|
113
|
+
|
|
114
|
+
export default function ${component}() {
|
|
115
|
+
return (
|
|
116
|
+
<main className="p-6">
|
|
117
|
+
<h1 className="text-2xl font-semibold">${title}</h1>
|
|
118
|
+
</main>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
function apiTemplate() {
|
|
124
|
+
return `import type { ApiContext } from "${PKG}/server"
|
|
125
|
+
|
|
126
|
+
export async function GET({ request, params }: ApiContext) {
|
|
127
|
+
return Response.json({ ok: true, params })
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
function resolveRegistryDir(root) {
|
|
132
|
+
try {
|
|
133
|
+
const require2 = createRequire(path2.join(root, "noop.js"));
|
|
134
|
+
const pkgJson = require2.resolve(`${PKG}/package.json`);
|
|
135
|
+
return path2.join(path2.dirname(pkgJson), "registry");
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function copyFromRegistry(root, kind, name, force) {
|
|
141
|
+
const registry = resolveRegistryDir(root);
|
|
142
|
+
if (!registry) {
|
|
143
|
+
log.error("Could not resolve the Vantage registry.");
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
const sub = kind === "ui" ? "ui" : "blocks";
|
|
147
|
+
const src = path2.join(registry, sub, `${name}.tsx`);
|
|
148
|
+
if (!fs2.existsSync(src)) {
|
|
149
|
+
const available = fs2.existsSync(path2.join(registry, sub)) ? fs2.readdirSync(path2.join(registry, sub)).map((f) => f.replace(/\.tsx$/, "")) : [];
|
|
150
|
+
log.error(`Unknown ${kind} "${name}". Available: ${available.join(", ") || "(none)"}`);
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
const destDir = path2.join(root, "components", kind === "ui" ? "ui" : "blocks");
|
|
154
|
+
const dest = path2.join(destDir, `${name}.tsx`);
|
|
155
|
+
if (fs2.existsSync(dest) && !force) {
|
|
156
|
+
log.error(`${path2.relative(root, dest)} already exists. Use --force to overwrite.`);
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
fs2.mkdirSync(destDir, { recursive: true });
|
|
160
|
+
fs2.copyFileSync(src, dest);
|
|
161
|
+
log.ok(`Added ${path2.relative(root, dest)}`);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
async function add(root, args) {
|
|
165
|
+
const kind = args.positionals[1];
|
|
166
|
+
const name = args.positionals[2];
|
|
167
|
+
const force = Boolean(args.flags.force);
|
|
168
|
+
if (!kind || !name) {
|
|
169
|
+
log.error("Usage: vantage add <page|api|ui|block> <name>");
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
if (kind === "page") {
|
|
173
|
+
const file = path2.join(root, name.endsWith(".tsx") ? name : `${name}.tsx`);
|
|
174
|
+
if (fs2.existsSync(file) && !force) {
|
|
175
|
+
log.error(`${path2.relative(root, file)} already exists. Use --force to overwrite.`);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
179
|
+
fs2.writeFileSync(file, pageTemplate(name));
|
|
180
|
+
log.ok(`Added page ${path2.relative(root, file)}`);
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
if (kind === "api") {
|
|
184
|
+
const rel2 = name.startsWith("server/api/") ? name : path2.join("server", "api", name);
|
|
185
|
+
const file = path2.join(root, rel2.endsWith(".ts") ? rel2 : `${rel2}.ts`);
|
|
186
|
+
if (fs2.existsSync(file) && !force) {
|
|
187
|
+
log.error(`${path2.relative(root, file)} already exists. Use --force to overwrite.`);
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
191
|
+
fs2.writeFileSync(file, apiTemplate());
|
|
192
|
+
log.ok(`Added API ${path2.relative(root, file)}`);
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
if (kind === "ui") return copyFromRegistry(root, "ui", name, force);
|
|
196
|
+
if (kind === "block") return copyFromRegistry(root, "block", name, force);
|
|
197
|
+
log.error(`Unknown add target "${kind}". Use one of: page, api, ui, block.`);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["MODE", "DEV", "PROD", "SSR", "BASE_URL", "PUBLIC_"]);
|
|
201
|
+
function stripComments(src) {
|
|
202
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`\\])\/\/[^\n]*/g, "$1");
|
|
203
|
+
}
|
|
204
|
+
function hasDefaultExport(src) {
|
|
205
|
+
return /\bexport\s+default\b/.test(src) || /\bexport\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(src) || /\bexport\s*\{[^}]*\bdefault\b[^}]*\}\s*from\b/.test(src);
|
|
206
|
+
}
|
|
207
|
+
function collectClientFiles(root) {
|
|
208
|
+
const out = [];
|
|
209
|
+
const skipDirs = /* @__PURE__ */ new Set(["node_modules", ".vantage", "dist", "server", "public"]);
|
|
210
|
+
const walk = (dir) => {
|
|
211
|
+
let entries;
|
|
212
|
+
try {
|
|
213
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
214
|
+
} catch {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
if (entry.name.startsWith(".")) continue;
|
|
219
|
+
const abs = path2.join(dir, entry.name);
|
|
220
|
+
if (entry.isDirectory()) {
|
|
221
|
+
if (skipDirs.has(entry.name)) continue;
|
|
222
|
+
walk(abs);
|
|
223
|
+
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
224
|
+
out.push(abs);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
walk(root);
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
function rel(root, abs) {
|
|
232
|
+
return path2.relative(root, abs).split(path2.sep).join("/");
|
|
233
|
+
}
|
|
234
|
+
function checkForbiddenFiles(root) {
|
|
235
|
+
const diags = [];
|
|
236
|
+
let entries = [];
|
|
237
|
+
try {
|
|
238
|
+
entries = fs2.readdirSync(root);
|
|
239
|
+
} catch {
|
|
240
|
+
return diags;
|
|
241
|
+
}
|
|
242
|
+
for (const name of entries) {
|
|
243
|
+
for (const rule of FORBIDDEN_FILES) {
|
|
244
|
+
if (rule.pattern.test(name)) {
|
|
245
|
+
diags.push({
|
|
246
|
+
code: "FORBIDDEN_FILE",
|
|
247
|
+
severity: "error",
|
|
248
|
+
message: `${rule.label} file "${name}" is not allowed in a Vantage project.`,
|
|
249
|
+
files: [name],
|
|
250
|
+
fix: rule.remedy
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return diags;
|
|
256
|
+
}
|
|
257
|
+
function checkRouteConflicts(model) {
|
|
258
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
259
|
+
for (const page of model.pages) {
|
|
260
|
+
const list = byPath.get(page.routePath) ?? [];
|
|
261
|
+
list.push(page.file);
|
|
262
|
+
byPath.set(page.routePath, list);
|
|
263
|
+
}
|
|
264
|
+
const diags = [];
|
|
265
|
+
for (const [routePath, files] of byPath) {
|
|
266
|
+
if (files.length > 1) {
|
|
267
|
+
diags.push({
|
|
268
|
+
code: "ROUTE_CONFLICT",
|
|
269
|
+
severity: "error",
|
|
270
|
+
message: `${files.length} files resolve to ${routePath}`,
|
|
271
|
+
files,
|
|
272
|
+
fix: "Rename or remove one route file so each route is unique."
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return diags;
|
|
277
|
+
}
|
|
278
|
+
function checkPageExports(root, model) {
|
|
279
|
+
const diags = [];
|
|
280
|
+
const pageFiles = [
|
|
281
|
+
...model.pages.map((p) => p.absFile),
|
|
282
|
+
...model.layouts.map((l) => l.absFile),
|
|
283
|
+
model.notFound && path2.join(root, model.notFound),
|
|
284
|
+
model.error && path2.join(root, model.error)
|
|
285
|
+
].filter((x) => Boolean(x));
|
|
286
|
+
for (const abs of pageFiles) {
|
|
287
|
+
let src;
|
|
288
|
+
try {
|
|
289
|
+
src = stripComments(fs2.readFileSync(abs, "utf8"));
|
|
290
|
+
} catch {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!hasDefaultExport(src)) {
|
|
294
|
+
diags.push({
|
|
295
|
+
code: "MISSING_DEFAULT_EXPORT",
|
|
296
|
+
severity: "error",
|
|
297
|
+
message: `Page module has no default export.`,
|
|
298
|
+
files: [rel(root, abs)],
|
|
299
|
+
fix: "A page/layout must `export default` a React component."
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return diags;
|
|
304
|
+
}
|
|
305
|
+
function checkApiExports(root, model) {
|
|
306
|
+
const diags = [];
|
|
307
|
+
for (const api of model.apis) {
|
|
308
|
+
let src;
|
|
309
|
+
try {
|
|
310
|
+
src = stripComments(fs2.readFileSync(api.absFile, "utf8"));
|
|
311
|
+
} catch {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const exported = API_METHODS.filter(
|
|
315
|
+
(m) => new RegExp(`export\\s+(async\\s+)?(function|const|let|var)\\s+${m}\\b`).test(src) || new RegExp(`export\\s*\\{[^}]*\\b${m}\\b[^}]*\\}`).test(src)
|
|
316
|
+
);
|
|
317
|
+
if (exported.length === 0) {
|
|
318
|
+
const lower = ["get", "post", "put", "patch", "delete", "options"].filter(
|
|
319
|
+
(m) => new RegExp(`export\\s+(async\\s+)?function\\s+${m}\\b`).test(src)
|
|
320
|
+
);
|
|
321
|
+
diags.push({
|
|
322
|
+
code: "INVALID_API_EXPORT",
|
|
323
|
+
severity: "error",
|
|
324
|
+
message: `API module exports no HTTP method handler${lower.length ? ` (found lowercase "${lower.join(", ")}")` : ""}.`,
|
|
325
|
+
files: [api.file],
|
|
326
|
+
fix: `Export one of: ${API_METHODS.join(", ")} (uppercase).`
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return diags;
|
|
331
|
+
}
|
|
332
|
+
function checkClientServerBoundary(root) {
|
|
333
|
+
const diags = [];
|
|
334
|
+
const importRe = /(?:import|export)[^'"]*?from\s+["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
335
|
+
for (const abs of collectClientFiles(root)) {
|
|
336
|
+
let src;
|
|
337
|
+
try {
|
|
338
|
+
src = stripComments(fs2.readFileSync(abs, "utf8"));
|
|
339
|
+
} catch {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
let m;
|
|
343
|
+
importRe.lastIndex = 0;
|
|
344
|
+
while (m = importRe.exec(src)) {
|
|
345
|
+
const spec = m[1] ?? m[2];
|
|
346
|
+
if (!spec) continue;
|
|
347
|
+
const isServer = spec === "server" || spec.startsWith("server/") || /(^|\/)\.\.?\/server(\/|$)/.test(spec) || /\/server\//.test(spec);
|
|
348
|
+
if (isServer) {
|
|
349
|
+
diags.push({
|
|
350
|
+
code: "CLIENT_IMPORTS_SERVER",
|
|
351
|
+
severity: "error",
|
|
352
|
+
message: `Client module imports from server/ ("${spec}").`,
|
|
353
|
+
files: [rel(root, abs)],
|
|
354
|
+
fix: "server/ code must never be imported by the client. Move shared code to lib/."
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return diags;
|
|
360
|
+
}
|
|
361
|
+
function checkPublicEnv(root) {
|
|
362
|
+
const diags = [];
|
|
363
|
+
const envRe = /import\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
364
|
+
for (const abs of collectClientFiles(root)) {
|
|
365
|
+
let src;
|
|
366
|
+
try {
|
|
367
|
+
src = stripComments(fs2.readFileSync(abs, "utf8"));
|
|
368
|
+
} catch {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
let m;
|
|
372
|
+
envRe.lastIndex = 0;
|
|
373
|
+
const bad = /* @__PURE__ */ new Set();
|
|
374
|
+
while (m = envRe.exec(src)) {
|
|
375
|
+
const key = m[1];
|
|
376
|
+
if (ALLOWED_ENV_KEYS.has(key)) continue;
|
|
377
|
+
if (key.startsWith(PUBLIC_ENV_PREFIX)) continue;
|
|
378
|
+
bad.add(key);
|
|
379
|
+
}
|
|
380
|
+
for (const key of bad) {
|
|
381
|
+
diags.push({
|
|
382
|
+
code: "PUBLIC_ENV_MISUSE",
|
|
383
|
+
severity: "warning",
|
|
384
|
+
message: `Client reads non-public env "import.meta.env.${key}".`,
|
|
385
|
+
files: [rel(root, abs)],
|
|
386
|
+
fix: `Only ${PUBLIC_ENV_PREFIX}* vars reach the client. Read secrets via ApiContext.env in server/.`
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return diags;
|
|
391
|
+
}
|
|
392
|
+
function runDiagnostics(root, model) {
|
|
393
|
+
return [
|
|
394
|
+
...checkForbiddenFiles(root),
|
|
395
|
+
...checkRouteConflicts(model),
|
|
396
|
+
...checkPageExports(root, model),
|
|
397
|
+
...checkApiExports(root, model),
|
|
398
|
+
...checkClientServerBoundary(root),
|
|
399
|
+
...checkPublicEnv(root)
|
|
400
|
+
];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/cli/build.ts
|
|
404
|
+
var SERVER_ENTRY = `// AUTO-GENERATED by @squadbase/vantage.
|
|
405
|
+
import path from "node:path"
|
|
406
|
+
import { fileURLToPath } from "node:url"
|
|
407
|
+
import { startNodeServer } from "@squadbase/vantage/server/node"
|
|
408
|
+
import { apiRoutes } from "./server-routes.gen"
|
|
409
|
+
|
|
410
|
+
const clientDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../client")
|
|
411
|
+
startNodeServer({ apiRoutes, clientDir })
|
|
412
|
+
`;
|
|
413
|
+
async function build(root, args) {
|
|
414
|
+
const model = scanProject(root);
|
|
415
|
+
const errors = runDiagnostics(root, model).filter((d) => d.severity === "error");
|
|
416
|
+
if (errors.length) {
|
|
417
|
+
printDiagnostics(errors);
|
|
418
|
+
log.error("Fix the errors above before building.");
|
|
419
|
+
return 1;
|
|
420
|
+
}
|
|
421
|
+
const mode = "production";
|
|
422
|
+
const env = { ...process.env, ...loadEnv(mode, root, "") };
|
|
423
|
+
const quiet = Boolean(args.flags.json);
|
|
424
|
+
if (!quiet) log.step("Building client\u2026");
|
|
425
|
+
await build$1({
|
|
426
|
+
configFile: false,
|
|
427
|
+
root,
|
|
428
|
+
mode,
|
|
429
|
+
plugins: [vantagePlugin({ root, env })],
|
|
430
|
+
logLevel: quiet ? "silent" : args.flags.verbose ? "info" : "warn"
|
|
431
|
+
});
|
|
432
|
+
relocateClientHtml(root);
|
|
433
|
+
if (model.hasServer) {
|
|
434
|
+
if (!quiet) log.step("Building server\u2026");
|
|
435
|
+
await buildServer(root);
|
|
436
|
+
}
|
|
437
|
+
writeDeployManifest(root, model);
|
|
438
|
+
if (!quiet) log.ok(`Build complete \u2192 ${path2.relative(process.cwd(), path2.join(root, "dist")) || "dist"}/`);
|
|
439
|
+
if (args.flags.json) {
|
|
440
|
+
process.stdout.write(
|
|
441
|
+
JSON.stringify({
|
|
442
|
+
ok: true,
|
|
443
|
+
mode: model.hasServer ? "fullstack" : "spa",
|
|
444
|
+
output: "dist",
|
|
445
|
+
pages: model.pages.length,
|
|
446
|
+
apis: model.apis.length
|
|
447
|
+
}) + "\n"
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
function relocateClientHtml(root) {
|
|
453
|
+
const clientDir = path2.join(root, "dist", "client");
|
|
454
|
+
const nested = path2.join(clientDir, VANTAGE_DIR, "index.html");
|
|
455
|
+
const target = path2.join(clientDir, "index.html");
|
|
456
|
+
if (fs2.existsSync(nested)) {
|
|
457
|
+
fs2.mkdirSync(clientDir, { recursive: true });
|
|
458
|
+
fs2.renameSync(nested, target);
|
|
459
|
+
fs2.rmSync(path2.join(clientDir, VANTAGE_DIR), { recursive: true, force: true });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function buildServer(root) {
|
|
463
|
+
const vantageDir = path2.join(root, VANTAGE_DIR);
|
|
464
|
+
const entry = path2.join(vantageDir, "server-entry.ts");
|
|
465
|
+
fs2.writeFileSync(entry, SERVER_ENTRY);
|
|
466
|
+
await build$2({
|
|
467
|
+
entryPoints: [entry],
|
|
468
|
+
outfile: path2.join(root, "dist", "server", "index.mjs"),
|
|
469
|
+
bundle: true,
|
|
470
|
+
platform: "node",
|
|
471
|
+
format: "esm",
|
|
472
|
+
target: "node20",
|
|
473
|
+
logLevel: "warning",
|
|
474
|
+
banner: { js: "// @squadbase/vantage server bundle" }
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function writeDeployManifest(root, model) {
|
|
478
|
+
const dist = path2.join(root, "dist");
|
|
479
|
+
fs2.mkdirSync(dist, { recursive: true });
|
|
480
|
+
const manifest = {
|
|
481
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
482
|
+
mode: model.hasServer ? "fullstack" : "spa",
|
|
483
|
+
client: "./client",
|
|
484
|
+
...model.hasServer ? { server: "./server/index.mjs" } : {},
|
|
485
|
+
spaFallback: "./client/index.html",
|
|
486
|
+
runtime: "node"
|
|
487
|
+
};
|
|
488
|
+
fs2.writeFileSync(path2.join(dist, "vantage-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/cli/check.ts
|
|
492
|
+
async function check(root, args) {
|
|
493
|
+
const model = scanProject(root);
|
|
494
|
+
const diags = runDiagnostics(root, model);
|
|
495
|
+
const errors = diags.filter((d) => d.severity === "error");
|
|
496
|
+
const warnings = diags.filter((d) => d.severity === "warning");
|
|
497
|
+
if (args.flags.json) {
|
|
498
|
+
printJson({
|
|
499
|
+
ok: errors.length === 0,
|
|
500
|
+
errorCount: errors.length,
|
|
501
|
+
warningCount: warnings.length,
|
|
502
|
+
diagnostics: diags
|
|
503
|
+
});
|
|
504
|
+
return errors.length ? 1 : 0;
|
|
505
|
+
}
|
|
506
|
+
if (diags.length === 0) {
|
|
507
|
+
log.ok("No problems found.");
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
printDiagnostics(diags);
|
|
511
|
+
console.log("");
|
|
512
|
+
if (errors.length) log.error(`${errors.length} error(s), ${warnings.length} warning(s).`);
|
|
513
|
+
else log.warn(`${warnings.length} warning(s).`);
|
|
514
|
+
return errors.length ? 1 : 0;
|
|
515
|
+
}
|
|
516
|
+
async function dev(root, args) {
|
|
517
|
+
const model = scanProject(root);
|
|
518
|
+
const errors = runDiagnostics(root, model).filter((d) => d.severity === "error");
|
|
519
|
+
if (errors.length) {
|
|
520
|
+
printDiagnostics(errors);
|
|
521
|
+
log.error("Fix the errors above before starting the dev server.");
|
|
522
|
+
return 1;
|
|
523
|
+
}
|
|
524
|
+
const mode = "development";
|
|
525
|
+
const env = { ...process.env, ...loadEnv(mode, root, "") };
|
|
526
|
+
const port = parsePort(args.flags.port, 5173);
|
|
527
|
+
const server = await createServer({
|
|
528
|
+
configFile: false,
|
|
529
|
+
root,
|
|
530
|
+
mode,
|
|
531
|
+
plugins: [vantagePlugin({ root, env })],
|
|
532
|
+
server: { port },
|
|
533
|
+
clearScreen: false
|
|
534
|
+
});
|
|
535
|
+
await server.listen();
|
|
536
|
+
log.brand("dev server ready");
|
|
537
|
+
server.printUrls();
|
|
538
|
+
if (model.hasServer) log.info(" server/api enabled");
|
|
539
|
+
const shutdown = async () => {
|
|
540
|
+
await server.close();
|
|
541
|
+
process.exit(0);
|
|
542
|
+
};
|
|
543
|
+
process.on("SIGINT", shutdown);
|
|
544
|
+
process.on("SIGTERM", shutdown);
|
|
545
|
+
return new Promise(() => {
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
async function doctor(root, args) {
|
|
549
|
+
const facets = [];
|
|
550
|
+
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
551
|
+
facets.push({
|
|
552
|
+
name: "node",
|
|
553
|
+
ok: nodeMajor >= 20,
|
|
554
|
+
detail: `v${process.versions.node}${nodeMajor >= 20 ? "" : " (requires >= 20)"}`
|
|
555
|
+
});
|
|
556
|
+
const pkgPath = path2.join(root, "package.json");
|
|
557
|
+
facets.push({ name: "package.json", ok: fs2.existsSync(pkgPath), detail: fs2.existsSync(pkgPath) ? "found" : "missing" });
|
|
558
|
+
const lockfiles = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
|
|
559
|
+
const lock = lockfiles.find((f) => fs2.existsSync(path2.join(root, f)) || fs2.existsSync(path2.join(root, "..", f)) || fs2.existsSync(path2.join(root, "..", "..", f)));
|
|
560
|
+
facets.push({ name: "lockfile", ok: Boolean(lock), detail: lock ?? "none found" });
|
|
561
|
+
let installedVantage = "not installed";
|
|
562
|
+
let vantageOk = false;
|
|
563
|
+
try {
|
|
564
|
+
const require2 = createRequire(path2.join(root, "noop.js"));
|
|
565
|
+
const p = require2.resolve("@squadbase/vantage/package.json");
|
|
566
|
+
installedVantage = JSON.parse(fs2.readFileSync(p, "utf8")).version;
|
|
567
|
+
vantageOk = true;
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
570
|
+
facets.push({ name: "@squadbase/vantage", ok: vantageOk, detail: installedVantage });
|
|
571
|
+
const model = scanProject(root);
|
|
572
|
+
const errors = runDiagnostics(root, model).filter((d) => d.severity === "error");
|
|
573
|
+
facets.push({
|
|
574
|
+
name: "project",
|
|
575
|
+
ok: errors.length === 0,
|
|
576
|
+
detail: errors.length === 0 ? "no blocking errors" : `${errors.length} error(s) (run \`vantage check\`)`
|
|
577
|
+
});
|
|
578
|
+
const allOk = facets.every((f) => f.ok);
|
|
579
|
+
if (args.flags.json) {
|
|
580
|
+
printJson({ ok: allOk, cli: frameworkVersion(), facets });
|
|
581
|
+
return allOk ? 0 : 1;
|
|
582
|
+
}
|
|
583
|
+
log.brand(`doctor (cli v${frameworkVersion()})`);
|
|
584
|
+
for (const f of facets) {
|
|
585
|
+
const line = `${f.name.padEnd(22)} ${f.detail}`;
|
|
586
|
+
if (f.ok) log.ok(line);
|
|
587
|
+
else log.error(line);
|
|
588
|
+
}
|
|
589
|
+
return allOk ? 0 : 1;
|
|
590
|
+
}
|
|
591
|
+
async function preview(root, args) {
|
|
592
|
+
const manifestPath = path2.join(root, "dist", "vantage-manifest.json");
|
|
593
|
+
if (!fs2.existsSync(manifestPath)) {
|
|
594
|
+
log.error("No build found. Run `vantage build` first.");
|
|
595
|
+
return 1;
|
|
596
|
+
}
|
|
597
|
+
const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
|
|
598
|
+
const port = parsePort(args.flags.port, 4173);
|
|
599
|
+
if (manifest.mode === "fullstack") {
|
|
600
|
+
const serverPath = path2.join(root, "dist", "server", "index.mjs");
|
|
601
|
+
if (!fs2.existsSync(serverPath)) {
|
|
602
|
+
log.error("Server bundle missing. Re-run `vantage build`.");
|
|
603
|
+
return 1;
|
|
604
|
+
}
|
|
605
|
+
log.brand(`preview (fullstack) \u2192 http://localhost:${port}`);
|
|
606
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
607
|
+
stdio: "inherit",
|
|
608
|
+
env: { ...process.env, PORT: String(port) }
|
|
609
|
+
});
|
|
610
|
+
return new Promise((resolve) => {
|
|
611
|
+
child.on("exit", (code) => resolve(code ?? 0));
|
|
612
|
+
const stop = () => child.kill("SIGINT");
|
|
613
|
+
process.on("SIGINT", stop);
|
|
614
|
+
process.on("SIGTERM", stop);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
const { preview: vitePreview } = await import('vite');
|
|
618
|
+
const server = await vitePreview({
|
|
619
|
+
configFile: false,
|
|
620
|
+
root,
|
|
621
|
+
build: { outDir: path2.join(root, "dist", "client") },
|
|
622
|
+
preview: { port }
|
|
623
|
+
});
|
|
624
|
+
log.brand("preview (spa)");
|
|
625
|
+
server.printUrls();
|
|
626
|
+
return new Promise(() => {
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
async function routes(root, args) {
|
|
630
|
+
const model = scanProject(root);
|
|
631
|
+
if (args.flags.json) {
|
|
632
|
+
printJson({
|
|
633
|
+
pages: model.pages.map((p) => ({ route: p.routePath, file: p.file, dynamic: p.dynamic })),
|
|
634
|
+
layouts: model.layouts.map((l) => ({ dir: l.dir === "" ? "/" : l.dir, file: l.file })),
|
|
635
|
+
notFound: model.notFound,
|
|
636
|
+
error: model.error,
|
|
637
|
+
apis: model.apis.map((a) => ({ route: a.routePath, file: a.file, params: a.params })),
|
|
638
|
+
hasServer: model.hasServer
|
|
639
|
+
});
|
|
640
|
+
return 0;
|
|
641
|
+
}
|
|
642
|
+
console.log(pc2.bold("\nPages"));
|
|
643
|
+
if (model.pages.length === 0) console.log(pc2.dim(" (none)"));
|
|
644
|
+
for (const p of model.pages) {
|
|
645
|
+
console.log(` ${pc2.green(p.routePath.padEnd(28))} ${pc2.dim(p.file)}`);
|
|
646
|
+
}
|
|
647
|
+
if (model.layouts.length || model.notFound || model.error) {
|
|
648
|
+
console.log(pc2.bold("\nSpecial"));
|
|
649
|
+
for (const l of model.layouts) {
|
|
650
|
+
console.log(` ${pc2.cyan(("layout " + (l.dir === "" ? "/" : "/" + l.dir)).padEnd(28))} ${pc2.dim(l.file)}`);
|
|
651
|
+
}
|
|
652
|
+
if (model.notFound) console.log(` ${pc2.cyan("404".padEnd(28))} ${pc2.dim(model.notFound)}`);
|
|
653
|
+
if (model.error) console.log(` ${pc2.cyan("error".padEnd(28))} ${pc2.dim(model.error)}`);
|
|
654
|
+
}
|
|
655
|
+
if (model.hasServer) {
|
|
656
|
+
console.log(pc2.bold("\nAPI"));
|
|
657
|
+
if (model.apis.length === 0) console.log(pc2.dim(" (none)"));
|
|
658
|
+
for (const a of model.apis) {
|
|
659
|
+
console.log(` ${pc2.yellow(a.routePath.padEnd(28))} ${pc2.dim(a.file)}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
console.log("");
|
|
663
|
+
return 0;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/cli/upgrade.ts
|
|
667
|
+
async function upgrade(root, args) {
|
|
668
|
+
const version = frameworkVersion();
|
|
669
|
+
const model = scanProject(root);
|
|
670
|
+
generateArtifacts({ root, model });
|
|
671
|
+
if (args.flags.json) {
|
|
672
|
+
printJson({ ok: true, version, transforms: [], regenerated: true });
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
log.brand(`upgrade \u2192 v${version}`);
|
|
676
|
+
log.ok("Regenerated .vantage artifacts.");
|
|
677
|
+
log.info("No breaking code transforms required for this version.");
|
|
678
|
+
return 0;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/cli.ts
|
|
682
|
+
var HELP = `${pc2.bold(pc2.magenta("vantage"))} \u2014 data dashboard framework for Squadbase
|
|
683
|
+
|
|
684
|
+
${pc2.bold("Usage")}
|
|
685
|
+
vantage <command> [path] [options]
|
|
686
|
+
vantage <path> Quick dev shortcut (folder or entry file)
|
|
687
|
+
|
|
688
|
+
${pc2.bold("Commands")}
|
|
689
|
+
dev Start the dev server (HMR + optional API)
|
|
690
|
+
build Build client and optional server into dist/
|
|
691
|
+
preview Run the production build locally
|
|
692
|
+
check Static verification (routes, boundaries, forbidden files)
|
|
693
|
+
routes Show page and API URL map
|
|
694
|
+
add <kind> <name> Scaffold a page | api | ui | block
|
|
695
|
+
doctor Environment and installation health checks
|
|
696
|
+
upgrade Regenerate artifacts / apply migrations
|
|
697
|
+
|
|
698
|
+
${pc2.bold("Options")}
|
|
699
|
+
--json Machine-readable output (check, routes, doctor, build)
|
|
700
|
+
--port <n> Port for dev/preview
|
|
701
|
+
--root <dir> Project root (default: cwd)
|
|
702
|
+
--force Overwrite when scaffolding
|
|
703
|
+
-v, --version Print version
|
|
704
|
+
-h, --help Print this help
|
|
705
|
+
`;
|
|
706
|
+
var COMMANDS = {
|
|
707
|
+
dev,
|
|
708
|
+
build,
|
|
709
|
+
preview,
|
|
710
|
+
check,
|
|
711
|
+
routes,
|
|
712
|
+
add,
|
|
713
|
+
doctor,
|
|
714
|
+
upgrade
|
|
715
|
+
};
|
|
716
|
+
async function main() {
|
|
717
|
+
const args = parseArgs(process.argv.slice(2));
|
|
718
|
+
const command = args.positionals[0];
|
|
719
|
+
if (args.flags.version || args.flags.v) {
|
|
720
|
+
console.log(frameworkVersion());
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (!command || args.flags.help || args.flags.h || command === "help") {
|
|
724
|
+
console.log(HELP);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
let runFn = COMMANDS[command];
|
|
728
|
+
let root;
|
|
729
|
+
if (runFn) {
|
|
730
|
+
const targetArg = command !== "add" ? args.positionals[1] : void 0;
|
|
731
|
+
if (targetArg !== void 0) {
|
|
732
|
+
const quick = resolveQuickRoot(targetArg);
|
|
733
|
+
if (!quick) {
|
|
734
|
+
log.error(`No such file or directory: "${targetArg}"`);
|
|
735
|
+
process.exitCode = 1;
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
root = quick;
|
|
739
|
+
} else {
|
|
740
|
+
root = resolveRoot(args.flags);
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
const quick = resolveQuickRoot(command);
|
|
744
|
+
if (!quick) {
|
|
745
|
+
log.error(`Unknown command "${command}".`);
|
|
746
|
+
console.log(HELP);
|
|
747
|
+
process.exitCode = 1;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
runFn = dev;
|
|
751
|
+
root = quick;
|
|
752
|
+
log.brand(`quick dev \u2192 ${path2.relative(process.cwd(), root) || "."}`);
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
const code = await runFn(root, args);
|
|
756
|
+
process.exitCode = code;
|
|
757
|
+
} catch (err) {
|
|
758
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
759
|
+
if (process.env.VANTAGE_DEBUG && err instanceof Error) console.error(err.stack);
|
|
760
|
+
process.exitCode = 1;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
void main();
|
|
764
|
+
//# sourceMappingURL=cli.js.map
|
|
765
|
+
//# sourceMappingURL=cli.js.map
|