@squadbase/vantage 0.2.3 → 0.3.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/README.md +1 -1
- package/dist/add-DWNDA57L.js +6 -0
- package/dist/add-DWNDA57L.js.map +1 -0
- package/dist/build-QP5BL3K7.js +108 -0
- package/dist/build-QP5BL3K7.js.map +1 -0
- package/dist/check-QPFERQLO.js +38 -0
- package/dist/check-QPFERQLO.js.map +1 -0
- package/dist/chunk-2KRCGXBY.js +56 -0
- package/dist/chunk-2KRCGXBY.js.map +1 -0
- package/dist/chunk-BAM33YZ6.js +202 -0
- package/dist/chunk-BAM33YZ6.js.map +1 -0
- package/dist/chunk-C4MQRWXS.js +81 -0
- package/dist/chunk-C4MQRWXS.js.map +1 -0
- package/dist/chunk-D4Y5RWJA.js +189 -0
- package/dist/chunk-D4Y5RWJA.js.map +1 -0
- package/dist/chunk-OFFJ3OL5.js +121 -0
- package/dist/chunk-OFFJ3OL5.js.map +1 -0
- package/dist/chunk-PSEYFYAP.js +276 -0
- package/dist/chunk-PSEYFYAP.js.map +1 -0
- package/dist/chunk-R2YYVCGS.js +345 -0
- package/dist/chunk-R2YYVCGS.js.map +1 -0
- package/dist/chunk-RA6Z3OX3.js +199 -0
- package/dist/chunk-RA6Z3OX3.js.map +1 -0
- package/dist/chunk-UTEKU3LG.js +267 -0
- package/dist/chunk-UTEKU3LG.js.map +1 -0
- package/dist/chunk-VXAV2435.js +142 -0
- package/dist/chunk-VXAV2435.js.map +1 -0
- package/dist/cli.js +24 -1604
- package/dist/cli.js.map +1 -1
- package/dist/dev-NX3PMYCT.js +55 -0
- package/dist/dev-NX3PMYCT.js.map +1 -0
- package/dist/docs-M5ZUI25V.js +114 -0
- package/dist/docs-M5ZUI25V.js.map +1 -0
- package/dist/doctor-N6JMEWKT.js +58 -0
- package/dist/doctor-N6JMEWKT.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/preview-CLALOEFE.js +49 -0
- package/dist/preview-CLALOEFE.js.map +1 -0
- package/dist/routes-YI25MKPF.js +119 -0
- package/dist/routes-YI25MKPF.js.map +1 -0
- package/dist/search-5TYAIQ7C.js +325 -0
- package/dist/search-5TYAIQ7C.js.map +1 -0
- package/dist/server/node.d.ts +1 -12
- package/dist/types-De3mo_hv.d.ts +59 -0
- package/dist/upgrade-3P5MBGE6.js +29 -0
- package/dist/upgrade-3P5MBGE6.js.map +1 -0
- package/dist/vite/index.d.ts +13 -0
- package/dist/vite/index.js +5 -1
- package/docs/en/changelog.md +82 -0
- package/docs/en/cli-reference.md +34 -4
- package/docs/en/components.md +2 -1
- package/docs/en/getting-started.md +6 -3
- package/docs/en/routing.md +27 -1
- package/docs/en/ui-and-theming.md +4 -2
- package/docs/ja/changelog.md +74 -0
- package/docs/ja/cli-reference.md +34 -4
- package/docs/ja/components.md +3 -2
- package/docs/ja/getting-started.md +6 -3
- package/docs/ja/routing.md +26 -1
- package/docs/ja/ui-and-theming.md +4 -2
- package/package.json +7 -4
- package/skills/vantage-add-feature/SKILL.md +14 -5
- package/skills/vantage-app/SKILL.md +9 -5
- package/templates/AGENTS.md +50 -6
- package/dist/chunk-3QKPNWKJ.js +0 -762
- package/dist/chunk-3QKPNWKJ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,1598 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import './chunk-
|
|
4
|
-
import '
|
|
5
|
-
import
|
|
6
|
-
import pc3 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 { fileURLToPath } from 'url';
|
|
12
|
-
import { spawn } from 'child_process';
|
|
2
|
+
import { parseArgs, frameworkVersion, resolveQuickRoot, log, resolveRoot } from './chunk-OFFJ3OL5.js';
|
|
3
|
+
import './chunk-C4MQRWXS.js';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import pc from 'picocolors';
|
|
13
6
|
|
|
14
7
|
// @squadbase/vantage — generated build. Do not edit.
|
|
15
|
-
var
|
|
16
|
-
"json",
|
|
17
|
-
"help",
|
|
18
|
-
"force",
|
|
19
|
-
"verbose",
|
|
20
|
-
"version",
|
|
21
|
-
"all",
|
|
22
|
-
"list",
|
|
23
|
-
"regex",
|
|
24
|
-
"pages",
|
|
25
|
-
"apis",
|
|
26
|
-
"detail",
|
|
27
|
-
"h",
|
|
28
|
-
"v"
|
|
29
|
-
]);
|
|
30
|
-
function parseArgs(argv) {
|
|
31
|
-
const positionals = [];
|
|
32
|
-
const flags = {};
|
|
33
|
-
for (let i = 0; i < argv.length; i++) {
|
|
34
|
-
const arg = argv[i];
|
|
35
|
-
if (arg.startsWith("--")) {
|
|
36
|
-
const eq = arg.indexOf("=");
|
|
37
|
-
if (eq !== -1) {
|
|
38
|
-
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const key = arg.slice(2);
|
|
42
|
-
if (BOOLEAN_FLAGS.has(key)) {
|
|
43
|
-
flags[key] = true;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const next = argv[i + 1];
|
|
47
|
-
if (next !== void 0 && !next.startsWith("-")) {
|
|
48
|
-
flags[key] = next;
|
|
49
|
-
i++;
|
|
50
|
-
} else {
|
|
51
|
-
flags[key] = true;
|
|
52
|
-
}
|
|
53
|
-
} else if (arg.length > 1 && arg.startsWith("-") && !/^-\d/.test(arg)) {
|
|
54
|
-
for (const ch of arg.slice(1)) flags[ch] = true;
|
|
55
|
-
} else {
|
|
56
|
-
positionals.push(arg);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return { positionals, flags };
|
|
60
|
-
}
|
|
61
|
-
function applyApiBaseUrlFlag(args) {
|
|
62
|
-
const value = args.flags["api-base-url"];
|
|
63
|
-
if (typeof value === "string" && value.length) {
|
|
64
|
-
process.env.PUBLIC_API_BASE_URL = value;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function parsePort(value, fallback) {
|
|
68
|
-
if (typeof value === "string") {
|
|
69
|
-
const n = Number(value);
|
|
70
|
-
if (Number.isInteger(n) && n > 0 && n < 65536) return n;
|
|
71
|
-
}
|
|
72
|
-
return fallback;
|
|
73
|
-
}
|
|
74
|
-
function resolveRoot(flags) {
|
|
75
|
-
const raw = typeof flags.root === "string" ? flags.root : process.cwd();
|
|
76
|
-
return path2.resolve(raw);
|
|
77
|
-
}
|
|
78
|
-
function resolveQuickRoot(target) {
|
|
79
|
-
try {
|
|
80
|
-
const abs = path2.resolve(target);
|
|
81
|
-
const stat = fs2.statSync(abs);
|
|
82
|
-
if (stat.isDirectory()) return abs;
|
|
83
|
-
if (stat.isFile() && PAGE_EXTENSIONS.some((ext) => abs.endsWith(ext))) {
|
|
84
|
-
return path2.dirname(abs);
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
var log = {
|
|
91
|
-
info: (msg) => console.log(msg),
|
|
92
|
-
step: (msg) => console.log(pc3.cyan("\u2192 ") + msg),
|
|
93
|
-
ok: (msg) => console.log(pc3.green("\u2713 ") + msg),
|
|
94
|
-
warn: (msg) => console.log(pc3.yellow("! ") + msg),
|
|
95
|
-
error: (msg) => console.error(pc3.red("\u2717 ") + msg),
|
|
96
|
-
brand: (msg) => console.log(pc3.bold(pc3.magenta("vantage ")) + msg)
|
|
97
|
-
};
|
|
98
|
-
function printDiagnostics(diags) {
|
|
99
|
-
for (const d of diags) {
|
|
100
|
-
const badge = d.severity === "error" ? pc3.red(pc3.bold(" ERROR ")) : pc3.yellow(pc3.bold(" WARN "));
|
|
101
|
-
console.log(`${badge} ${pc3.dim(d.code)} ${d.message}`);
|
|
102
|
-
if (d.files?.length) console.log(` ${pc3.dim("files:")} ${d.files.join(", ")}`);
|
|
103
|
-
if (d.fix) console.log(` ${pc3.dim("fix:")} ${d.fix}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function printJson(payload) {
|
|
107
|
-
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
108
|
-
}
|
|
109
|
-
function frameworkVersion() {
|
|
110
|
-
try {
|
|
111
|
-
const url = new URL("../package.json", import.meta.url);
|
|
112
|
-
const pkg = JSON.parse(fs2.readFileSync(url, "utf8"));
|
|
113
|
-
return pkg.version;
|
|
114
|
-
} catch {
|
|
115
|
-
return "0.0.0";
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// src/cli/add.ts
|
|
120
|
-
var PKG = "@squadbase/vantage";
|
|
121
|
-
function toPascalCase(segment) {
|
|
122
|
-
const cleaned = segment.replace(/\[|\]|\./g, "").replace(/\.\.\./g, "");
|
|
123
|
-
const words = cleaned.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
124
|
-
const pascal = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
125
|
-
return /^[A-Za-z]/.test(pascal) ? pascal : "Page";
|
|
126
|
-
}
|
|
127
|
-
function pageTemplate(name) {
|
|
128
|
-
const last = name.split("/").pop() ?? name;
|
|
129
|
-
const component = last === "index" ? toPascalCase(name.split("/").slice(-2)[0] ?? "Home") : toPascalCase(last);
|
|
130
|
-
const title = last.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
131
|
-
return `import { definePage } from "${PKG}"
|
|
132
|
-
|
|
133
|
-
export const page = definePage({ title: ${JSON.stringify(title)} })
|
|
134
|
-
|
|
135
|
-
export default function ${component}() {
|
|
136
|
-
return (
|
|
137
|
-
<main className="p-6">
|
|
138
|
-
<h1 className="text-2xl font-semibold">${title}</h1>
|
|
139
|
-
</main>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
`;
|
|
143
|
-
}
|
|
144
|
-
function apiTemplate() {
|
|
145
|
-
return `import type { ApiContext } from "${PKG}/server"
|
|
146
|
-
|
|
147
|
-
export async function GET({ request, params }: ApiContext) {
|
|
148
|
-
return Response.json({ ok: true, params })
|
|
149
|
-
}
|
|
150
|
-
`;
|
|
151
|
-
}
|
|
152
|
-
function resolvePackageDir(root, sub) {
|
|
153
|
-
try {
|
|
154
|
-
const require2 = createRequire(path2.join(root, "noop.js"));
|
|
155
|
-
const pkgJson = require2.resolve(`${PKG}/package.json`);
|
|
156
|
-
return path2.join(path2.dirname(pkgJson), sub);
|
|
157
|
-
} catch {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
function resolveRegistryDir(root) {
|
|
162
|
-
return resolvePackageDir(root, "registry");
|
|
163
|
-
}
|
|
164
|
-
var SKILL_FILE = "SKILL.md";
|
|
165
|
-
var SKILL_SCAN_SKIP = /* @__PURE__ */ new Set([
|
|
166
|
-
"node_modules",
|
|
167
|
-
".git",
|
|
168
|
-
".vantage",
|
|
169
|
-
"dist",
|
|
170
|
-
"build",
|
|
171
|
-
"out",
|
|
172
|
-
"coverage",
|
|
173
|
-
".next",
|
|
174
|
-
".turbo",
|
|
175
|
-
".cache",
|
|
176
|
-
".vercel"
|
|
177
|
-
]);
|
|
178
|
-
var SKILL_SCAN_DEPTH = 4;
|
|
179
|
-
function listBundledSkills(skillsDir) {
|
|
180
|
-
try {
|
|
181
|
-
return fs2.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && fs2.existsSync(path2.join(skillsDir, e.name, SKILL_FILE))).map((e) => e.name).sort();
|
|
182
|
-
} catch {
|
|
183
|
-
return [];
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function displayPath(root, target) {
|
|
187
|
-
const rel2 = path2.relative(root, target);
|
|
188
|
-
return !rel2 ? "." : rel2.startsWith("..") ? target : rel2;
|
|
189
|
-
}
|
|
190
|
-
function findPlacedSkills(root, names, skillsDir) {
|
|
191
|
-
const wanted = new Set(names);
|
|
192
|
-
const found = /* @__PURE__ */ new Map();
|
|
193
|
-
const isSource = (p) => !path2.relative(skillsDir, p).startsWith("..");
|
|
194
|
-
const walk = (dir, depth) => {
|
|
195
|
-
let entries;
|
|
196
|
-
try {
|
|
197
|
-
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
198
|
-
} catch {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
for (const entry of entries) {
|
|
202
|
-
if (!entry.isDirectory() || SKILL_SCAN_SKIP.has(entry.name)) continue;
|
|
203
|
-
const abs = path2.join(dir, entry.name);
|
|
204
|
-
if (wanted.has(entry.name) && fs2.existsSync(path2.join(abs, SKILL_FILE))) {
|
|
205
|
-
if (!isSource(abs)) found.set(entry.name, [...found.get(entry.name) ?? [], abs]);
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
if (depth < SKILL_SCAN_DEPTH) walk(abs, depth + 1);
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
walk(root, 1);
|
|
212
|
-
for (const list of found.values()) list.sort();
|
|
213
|
-
return found;
|
|
214
|
-
}
|
|
215
|
-
function writeSkill(root, skillsDir, name, dest, verb) {
|
|
216
|
-
fs2.mkdirSync(path2.dirname(dest), { recursive: true });
|
|
217
|
-
fs2.cpSync(path2.join(skillsDir, name), dest, { recursive: true, force: true });
|
|
218
|
-
log.ok(`${verb} skill ${displayPath(root, dest)}/`);
|
|
219
|
-
}
|
|
220
|
-
function printSkillList(root, available, placed) {
|
|
221
|
-
const width = Math.max(...available.map((n) => n.length));
|
|
222
|
-
log.info("Bundled skills:");
|
|
223
|
-
for (const name of available) {
|
|
224
|
-
const where = placed.get(name);
|
|
225
|
-
const status = where?.length ? where.map((p) => displayPath(root, p) + "/").join(", ") : "not placed";
|
|
226
|
-
log.info(` ${name.padEnd(width)} ${status}`);
|
|
227
|
-
}
|
|
228
|
-
const missing = available.filter((n) => !placed.get(n)?.length);
|
|
229
|
-
if (missing.length) {
|
|
230
|
-
log.info("");
|
|
231
|
-
log.info("Place the missing ones with: vantage add skill --all [--dir <path>]");
|
|
232
|
-
}
|
|
233
|
-
return 0;
|
|
234
|
-
}
|
|
235
|
-
function addSkill(root, name, all, force, dir) {
|
|
236
|
-
const skillsDir = resolvePackageDir(root, "skills");
|
|
237
|
-
if (!skillsDir) {
|
|
238
|
-
log.error("Could not resolve the Vantage skills directory.");
|
|
239
|
-
return 1;
|
|
240
|
-
}
|
|
241
|
-
const available = listBundledSkills(skillsDir);
|
|
242
|
-
if (available.length === 0) {
|
|
243
|
-
log.error("No skills are bundled with this version of Vantage.");
|
|
244
|
-
return 1;
|
|
245
|
-
}
|
|
246
|
-
const placed = findPlacedSkills(root, available, skillsDir);
|
|
247
|
-
if (!all && !name) return printSkillList(root, available, placed);
|
|
248
|
-
const destDir = path2.resolve(root, dir ?? ".");
|
|
249
|
-
const targets = all ? available : [name];
|
|
250
|
-
let failed = 0;
|
|
251
|
-
for (const target of targets) {
|
|
252
|
-
if (!available.includes(target)) {
|
|
253
|
-
log.error(`Unknown skill "${target}". Available: ${available.join(", ")}`);
|
|
254
|
-
failed++;
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
const existing = placed.get(target) ?? [];
|
|
258
|
-
if (existing.length > 0 && !force) {
|
|
259
|
-
const where = existing.map((p) => displayPath(root, p) + "/").join(", ");
|
|
260
|
-
log.info(`Skill ${target} is already placed at ${where} \u2014 skipped (--force to refresh).`);
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
const inDestDir = existing.filter((p) => path2.dirname(p) === destDir);
|
|
264
|
-
const refresh = dir === void 0 ? existing : inDestDir;
|
|
265
|
-
if (refresh.length > 0) {
|
|
266
|
-
for (const dest of refresh) writeSkill(root, skillsDir, target, dest, "Updated");
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (existing.length > 0) {
|
|
270
|
-
const where = existing.map((p) => displayPath(root, p) + "/").join(", ");
|
|
271
|
-
log.warn(`Skill ${target} is also placed at ${where} \u2014 left untouched.`);
|
|
272
|
-
}
|
|
273
|
-
writeSkill(root, skillsDir, target, path2.join(destDir, target), "Added");
|
|
274
|
-
}
|
|
275
|
-
return failed === 0 ? 0 : 1;
|
|
276
|
-
}
|
|
277
|
-
var AGENTS_FILE = "AGENTS.md";
|
|
278
|
-
function bundledAgentsPath(root) {
|
|
279
|
-
const dir = resolvePackageDir(root, "templates");
|
|
280
|
-
if (!dir) return null;
|
|
281
|
-
const src = path2.join(dir, AGENTS_FILE);
|
|
282
|
-
return fs2.existsSync(src) ? src : null;
|
|
283
|
-
}
|
|
284
|
-
function addAgents(root, force) {
|
|
285
|
-
const src = bundledAgentsPath(root);
|
|
286
|
-
if (!src) {
|
|
287
|
-
log.error("AGENTS.md is not bundled with this version of Vantage.");
|
|
288
|
-
return 1;
|
|
289
|
-
}
|
|
290
|
-
const dest = path2.join(root, AGENTS_FILE);
|
|
291
|
-
if (fs2.existsSync(dest) && !force) {
|
|
292
|
-
log.error(`${AGENTS_FILE} already exists. Use --force to overwrite.`);
|
|
293
|
-
return 1;
|
|
294
|
-
}
|
|
295
|
-
fs2.copyFileSync(src, dest);
|
|
296
|
-
log.ok(`Added ${path2.relative(root, dest) || AGENTS_FILE}`);
|
|
297
|
-
return 0;
|
|
298
|
-
}
|
|
299
|
-
function syncAgentsFile(root) {
|
|
300
|
-
const dest = path2.join(root, AGENTS_FILE);
|
|
301
|
-
if (!fs2.existsSync(dest)) return "absent";
|
|
302
|
-
const src = bundledAgentsPath(root);
|
|
303
|
-
if (!src) return "unchanged";
|
|
304
|
-
const next = fs2.readFileSync(src, "utf8");
|
|
305
|
-
if (fs2.readFileSync(dest, "utf8") === next) return "unchanged";
|
|
306
|
-
fs2.writeFileSync(dest, next);
|
|
307
|
-
return "updated";
|
|
308
|
-
}
|
|
309
|
-
function copyFromRegistry(root, kind, name, force) {
|
|
310
|
-
const registry = resolveRegistryDir(root);
|
|
311
|
-
if (!registry) {
|
|
312
|
-
log.error("Could not resolve the Vantage registry.");
|
|
313
|
-
return 1;
|
|
314
|
-
}
|
|
315
|
-
const sub = kind === "ui" ? "ui" : "blocks";
|
|
316
|
-
const src = path2.join(registry, sub, `${name}.tsx`);
|
|
317
|
-
if (!fs2.existsSync(src)) {
|
|
318
|
-
const available = fs2.existsSync(path2.join(registry, sub)) ? fs2.readdirSync(path2.join(registry, sub)).map((f) => f.replace(/\.tsx$/, "")) : [];
|
|
319
|
-
log.error(`Unknown ${kind} "${name}". Available: ${available.join(", ") || "(none)"}`);
|
|
320
|
-
return 1;
|
|
321
|
-
}
|
|
322
|
-
const destDir = path2.join(root, "components", kind === "ui" ? "ui" : "blocks");
|
|
323
|
-
const dest = path2.join(destDir, `${name}.tsx`);
|
|
324
|
-
if (fs2.existsSync(dest) && !force) {
|
|
325
|
-
log.error(`${path2.relative(root, dest)} already exists. Use --force to overwrite.`);
|
|
326
|
-
return 1;
|
|
327
|
-
}
|
|
328
|
-
fs2.mkdirSync(destDir, { recursive: true });
|
|
329
|
-
fs2.copyFileSync(src, dest);
|
|
330
|
-
log.ok(`Added ${path2.relative(root, dest)}`);
|
|
331
|
-
return 0;
|
|
332
|
-
}
|
|
333
|
-
async function add(root, args) {
|
|
334
|
-
const kind = args.positionals[1];
|
|
335
|
-
const name = args.positionals[2];
|
|
336
|
-
const force = Boolean(args.flags.force);
|
|
337
|
-
const all = Boolean(args.flags.all);
|
|
338
|
-
if (kind === "skill") {
|
|
339
|
-
const dir = typeof args.flags.dir === "string" ? args.flags.dir : void 0;
|
|
340
|
-
return addSkill(root, name, all, force, dir);
|
|
341
|
-
}
|
|
342
|
-
if (kind === "agents") return addAgents(root, force);
|
|
343
|
-
if (!kind || !name) {
|
|
344
|
-
log.error("Usage: vantage add <page|api|ui|block|skill|agents> <name>");
|
|
345
|
-
return 1;
|
|
346
|
-
}
|
|
347
|
-
if (kind === "page") {
|
|
348
|
-
const file = path2.join(root, name.endsWith(".tsx") ? name : `${name}.tsx`);
|
|
349
|
-
if (fs2.existsSync(file) && !force) {
|
|
350
|
-
log.error(`${path2.relative(root, file)} already exists. Use --force to overwrite.`);
|
|
351
|
-
return 1;
|
|
352
|
-
}
|
|
353
|
-
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
354
|
-
fs2.writeFileSync(file, pageTemplate(name));
|
|
355
|
-
log.ok(`Added page ${path2.relative(root, file)}`);
|
|
356
|
-
return 0;
|
|
357
|
-
}
|
|
358
|
-
if (kind === "api") {
|
|
359
|
-
const rel2 = name.startsWith("server/api/") ? name : path2.join("server", "api", name);
|
|
360
|
-
const file = path2.join(root, rel2.endsWith(".ts") ? rel2 : `${rel2}.ts`);
|
|
361
|
-
if (fs2.existsSync(file) && !force) {
|
|
362
|
-
log.error(`${path2.relative(root, file)} already exists. Use --force to overwrite.`);
|
|
363
|
-
return 1;
|
|
364
|
-
}
|
|
365
|
-
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
366
|
-
fs2.writeFileSync(file, apiTemplate());
|
|
367
|
-
log.ok(`Added API ${path2.relative(root, file)}`);
|
|
368
|
-
return 0;
|
|
369
|
-
}
|
|
370
|
-
if (kind === "ui") return copyFromRegistry(root, "ui", name, force);
|
|
371
|
-
if (kind === "block") return copyFromRegistry(root, "block", name, force);
|
|
372
|
-
log.error(`Unknown add target "${kind}". Use one of: page, api, ui, block, skill, agents.`);
|
|
373
|
-
return 1;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// src/core/source.ts
|
|
377
|
-
function stripComments(src) {
|
|
378
|
-
return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`\\])\/\/[^\n]*/g, "$1");
|
|
379
|
-
}
|
|
380
|
-
var OPENERS = { "(": ")", "[": "]", "{": "}" };
|
|
381
|
-
function readBracketBody(src, openIndex) {
|
|
382
|
-
const open = src[openIndex];
|
|
383
|
-
if (!open || !OPENERS[open]) return null;
|
|
384
|
-
let depth = 0;
|
|
385
|
-
let quote = null;
|
|
386
|
-
for (let i = openIndex; i < src.length; i++) {
|
|
387
|
-
const c = src[i];
|
|
388
|
-
if (quote) {
|
|
389
|
-
if (c === "\\") i++;
|
|
390
|
-
else if (c === quote) quote = null;
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
394
|
-
quote = c;
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
if (OPENERS[c]) depth++;
|
|
398
|
-
else if (c === ")" || c === "]" || c === "}") {
|
|
399
|
-
if (--depth === 0) return src.slice(openIndex + 1, i);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
function splitTopLevel(body) {
|
|
405
|
-
const parts = [];
|
|
406
|
-
let depth = 0;
|
|
407
|
-
let quote = null;
|
|
408
|
-
let start = 0;
|
|
409
|
-
for (let i = 0; i < body.length; i++) {
|
|
410
|
-
const c = body[i];
|
|
411
|
-
if (quote) {
|
|
412
|
-
if (c === "\\") i++;
|
|
413
|
-
else if (c === quote) quote = null;
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
417
|
-
quote = c;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
if (OPENERS[c]) depth++;
|
|
421
|
-
else if (c === ")" || c === "]" || c === "}") depth--;
|
|
422
|
-
else if (c === "," && depth === 0) {
|
|
423
|
-
parts.push(body.slice(start, i));
|
|
424
|
-
start = i + 1;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
parts.push(body.slice(start));
|
|
428
|
-
return parts.map((p) => p.trim()).filter(Boolean);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// src/core/route-spec.ts
|
|
432
|
-
function detectApiMethods(src) {
|
|
433
|
-
return API_METHODS.filter(
|
|
434
|
-
(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)
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
function methodRegions(src) {
|
|
438
|
-
const re = new RegExp(
|
|
439
|
-
`export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+(${API_METHODS.join("|")})\\b`,
|
|
440
|
-
"g"
|
|
441
|
-
);
|
|
442
|
-
const decls = [];
|
|
443
|
-
let m;
|
|
444
|
-
while (m = re.exec(src)) decls.push({ method: m[1], index: m.index });
|
|
445
|
-
const regions = /* @__PURE__ */ new Map();
|
|
446
|
-
decls.forEach((d, i) => {
|
|
447
|
-
regions.set(d.method, src.slice(d.index, decls[i + 1]?.index ?? src.length));
|
|
448
|
-
});
|
|
449
|
-
for (const method of detectApiMethods(src)) {
|
|
450
|
-
if (!regions.has(method)) regions.set(method, src);
|
|
451
|
-
}
|
|
452
|
-
return regions;
|
|
453
|
-
}
|
|
454
|
-
function objectKeys(expr) {
|
|
455
|
-
const trimmed = expr.trim();
|
|
456
|
-
if (!trimmed.startsWith("{")) return void 0;
|
|
457
|
-
const body = readBracketBody(trimmed, 0);
|
|
458
|
-
if (body == null) return void 0;
|
|
459
|
-
const keys = [];
|
|
460
|
-
for (const part of splitTopLevel(body)) {
|
|
461
|
-
if (part.startsWith("...")) {
|
|
462
|
-
keys.push("\u2026");
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
const name = part.match(/^["'`]?([A-Za-z0-9_$]+)/);
|
|
466
|
-
if (name) keys.push(name[1]);
|
|
467
|
-
}
|
|
468
|
-
return keys.length ? keys : void 0;
|
|
469
|
-
}
|
|
470
|
-
function initStatus(init) {
|
|
471
|
-
const m = init?.match(/\bstatus\s*:\s*(\d{3})\b/);
|
|
472
|
-
return m ? Number(m[1]) : void 0;
|
|
473
|
-
}
|
|
474
|
-
function initContentType(init) {
|
|
475
|
-
const m = init?.match(/["']?content-type["']?\s*:\s*["']([^"';]+)/i);
|
|
476
|
-
return m ? m[1].trim() : void 0;
|
|
477
|
-
}
|
|
478
|
-
function extractResponses(region) {
|
|
479
|
-
const re = /\bnew\s+Response\s*\(|\bResponse\s*\.\s*json\s*\(|(?<![.\w$])json\s*\(/g;
|
|
480
|
-
const out = [];
|
|
481
|
-
const seen = /* @__PURE__ */ new Set();
|
|
482
|
-
let m;
|
|
483
|
-
while (m = re.exec(region)) {
|
|
484
|
-
const isJson = !m[0].startsWith("new");
|
|
485
|
-
const args = readBracketBody(region, m.index + m[0].length - 1);
|
|
486
|
-
if (args == null) continue;
|
|
487
|
-
const [payload, init] = splitTopLevel(args);
|
|
488
|
-
const contentType = isJson ? "application/json" : initContentType(init) ?? (payload?.startsWith("JSON.stringify") ? "application/json" : void 0);
|
|
489
|
-
const keys = isJson && payload ? objectKeys(payload) : void 0;
|
|
490
|
-
const spec = {
|
|
491
|
-
status: initStatus(init) ?? 200,
|
|
492
|
-
...contentType ? { contentType } : {},
|
|
493
|
-
...keys ? { keys } : {}
|
|
494
|
-
};
|
|
495
|
-
const key = JSON.stringify(spec);
|
|
496
|
-
if (seen.has(key)) continue;
|
|
497
|
-
seen.add(key);
|
|
498
|
-
out.push(spec);
|
|
499
|
-
}
|
|
500
|
-
return out;
|
|
501
|
-
}
|
|
502
|
-
function extractErrors(region) {
|
|
503
|
-
const re = /\bHttpError\s*\(\s*(\d{3})\s*(?:,\s*(?:(["'`])((?:\\.|(?!\2)[\s\S])*)\2))?/g;
|
|
504
|
-
const out = [];
|
|
505
|
-
const seen = /* @__PURE__ */ new Set();
|
|
506
|
-
let m;
|
|
507
|
-
while (m = re.exec(region)) {
|
|
508
|
-
const message = m[3]?.replace(/\\(.)/g, "$1");
|
|
509
|
-
const spec = { status: Number(m[1]), ...message ? { message } : {} };
|
|
510
|
-
const key = JSON.stringify(spec);
|
|
511
|
-
if (seen.has(key)) continue;
|
|
512
|
-
seen.add(key);
|
|
513
|
-
out.push(spec);
|
|
514
|
-
}
|
|
515
|
-
return out;
|
|
516
|
-
}
|
|
517
|
-
function extractQuery(region) {
|
|
518
|
-
const re = /searchParams\s*\.\s*(?:get|getAll|has)\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
519
|
-
const out = /* @__PURE__ */ new Set();
|
|
520
|
-
let m;
|
|
521
|
-
while (m = re.exec(region)) out.add(m[1]);
|
|
522
|
-
return [...out];
|
|
523
|
-
}
|
|
524
|
-
function extractBody(region) {
|
|
525
|
-
const read = region.match(/\b(?:request|req)\s*\.\s*(json|formData|text|arrayBuffer|blob)\s*\(/);
|
|
526
|
-
if (!read) return void 0;
|
|
527
|
-
const method = read[1];
|
|
528
|
-
const format = method === "json" || method === "formData" || method === "text" ? method : "binary";
|
|
529
|
-
const destructured = region.match(
|
|
530
|
-
/(?:const|let|var)\s*(\{[\s\S]*?\})\s*=\s*(?:await\s+)?[\w.]*\.json\s*\(/
|
|
531
|
-
);
|
|
532
|
-
const keys = format === "json" && destructured ? objectKeys(destructured[1]) : void 0;
|
|
533
|
-
return { format, ...keys ? { keys } : {} };
|
|
534
|
-
}
|
|
535
|
-
function toParams(segments) {
|
|
536
|
-
const out = [];
|
|
537
|
-
for (const s of segments) {
|
|
538
|
-
if (s.kind === "static") continue;
|
|
539
|
-
out.push({ name: s.name, kind: s.kind });
|
|
540
|
-
}
|
|
541
|
-
return out;
|
|
542
|
-
}
|
|
543
|
-
function extractPageSpec(page) {
|
|
544
|
-
return { params: toParams(page.segments), ...extractPageMeta(page.absFile) };
|
|
545
|
-
}
|
|
546
|
-
function extractApiSpec(api) {
|
|
547
|
-
const params = toParams(api.segments);
|
|
548
|
-
let src;
|
|
549
|
-
try {
|
|
550
|
-
src = stripComments(fs2.readFileSync(api.absFile, "utf8"));
|
|
551
|
-
} catch {
|
|
552
|
-
return { params, methods: [] };
|
|
553
|
-
}
|
|
554
|
-
const regions = methodRegions(src);
|
|
555
|
-
const methods = API_METHODS.filter((m) => regions.has(m)).map((method) => {
|
|
556
|
-
const region = regions.get(method);
|
|
557
|
-
const body = extractBody(region);
|
|
558
|
-
return {
|
|
559
|
-
method,
|
|
560
|
-
query: extractQuery(region),
|
|
561
|
-
...body ? { body } : {},
|
|
562
|
-
responses: extractResponses(region),
|
|
563
|
-
errors: extractErrors(region)
|
|
564
|
-
};
|
|
565
|
-
});
|
|
566
|
-
return { params, methods };
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// src/core/diagnostics.ts
|
|
570
|
-
var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["MODE", "DEV", "PROD", "SSR", "BASE_URL", "PUBLIC_"]);
|
|
571
|
-
function hasDefaultExport(src) {
|
|
572
|
-
return /\bexport\s+default\b/.test(src) || /\bexport\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(src) || /\bexport\s*\{[^}]*\bdefault\b[^}]*\}\s*from\b/.test(src);
|
|
573
|
-
}
|
|
574
|
-
function collectClientFiles(root) {
|
|
575
|
-
const out = [];
|
|
576
|
-
const skipDirs = /* @__PURE__ */ new Set(["node_modules", ".vantage", "dist", "server", "public"]);
|
|
577
|
-
const walk = (dir) => {
|
|
578
|
-
let entries;
|
|
579
|
-
try {
|
|
580
|
-
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
581
|
-
} catch {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
for (const entry of entries) {
|
|
585
|
-
if (entry.name.startsWith(".")) continue;
|
|
586
|
-
const abs = path2.join(dir, entry.name);
|
|
587
|
-
if (entry.isDirectory()) {
|
|
588
|
-
if (skipDirs.has(entry.name)) continue;
|
|
589
|
-
walk(abs);
|
|
590
|
-
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
591
|
-
out.push(abs);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
walk(root);
|
|
596
|
-
return out;
|
|
597
|
-
}
|
|
598
|
-
function rel(root, abs) {
|
|
599
|
-
return path2.relative(root, abs).split(path2.sep).join("/");
|
|
600
|
-
}
|
|
601
|
-
function checkForbiddenFiles(root) {
|
|
602
|
-
const diags = [];
|
|
603
|
-
let entries = [];
|
|
604
|
-
try {
|
|
605
|
-
entries = fs2.readdirSync(root);
|
|
606
|
-
} catch {
|
|
607
|
-
return diags;
|
|
608
|
-
}
|
|
609
|
-
for (const name of entries) {
|
|
610
|
-
for (const rule of FORBIDDEN_FILES) {
|
|
611
|
-
if (rule.pattern.test(name)) {
|
|
612
|
-
diags.push({
|
|
613
|
-
code: "FORBIDDEN_FILE",
|
|
614
|
-
severity: "error",
|
|
615
|
-
message: `${rule.label} file "${name}" is not allowed in a Vantage project.`,
|
|
616
|
-
files: [name],
|
|
617
|
-
fix: rule.remedy
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
return diags;
|
|
623
|
-
}
|
|
624
|
-
function checkRouteConflicts(model) {
|
|
625
|
-
const byPath = /* @__PURE__ */ new Map();
|
|
626
|
-
for (const page of model.pages) {
|
|
627
|
-
const list = byPath.get(page.routePath) ?? [];
|
|
628
|
-
list.push(page.file);
|
|
629
|
-
byPath.set(page.routePath, list);
|
|
630
|
-
}
|
|
631
|
-
const diags = [];
|
|
632
|
-
for (const [routePath, files] of byPath) {
|
|
633
|
-
if (files.length > 1) {
|
|
634
|
-
diags.push({
|
|
635
|
-
code: "ROUTE_CONFLICT",
|
|
636
|
-
severity: "error",
|
|
637
|
-
message: `${files.length} files resolve to ${routePath}`,
|
|
638
|
-
files,
|
|
639
|
-
fix: "Rename or remove one route file so each route is unique."
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return diags;
|
|
644
|
-
}
|
|
645
|
-
function checkPageExports(root, model) {
|
|
646
|
-
const diags = [];
|
|
647
|
-
const pageFiles = [
|
|
648
|
-
...model.pages.map((p) => p.absFile),
|
|
649
|
-
...model.layouts.map((l) => l.absFile),
|
|
650
|
-
model.notFound && path2.join(root, model.notFound),
|
|
651
|
-
model.error && path2.join(root, model.error)
|
|
652
|
-
].filter((x) => Boolean(x));
|
|
653
|
-
for (const abs of pageFiles) {
|
|
654
|
-
let src;
|
|
655
|
-
try {
|
|
656
|
-
src = stripComments(fs2.readFileSync(abs, "utf8"));
|
|
657
|
-
} catch {
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
if (!hasDefaultExport(src)) {
|
|
661
|
-
diags.push({
|
|
662
|
-
code: "MISSING_DEFAULT_EXPORT",
|
|
663
|
-
severity: "error",
|
|
664
|
-
message: `Page module has no default export.`,
|
|
665
|
-
files: [rel(root, abs)],
|
|
666
|
-
fix: "A page/layout must `export default` a React component."
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
return diags;
|
|
671
|
-
}
|
|
672
|
-
function checkApiExports(root, model) {
|
|
673
|
-
const diags = [];
|
|
674
|
-
for (const api of model.apis) {
|
|
675
|
-
let src;
|
|
676
|
-
try {
|
|
677
|
-
src = stripComments(fs2.readFileSync(api.absFile, "utf8"));
|
|
678
|
-
} catch {
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
const exported = detectApiMethods(src);
|
|
682
|
-
if (exported.length === 0) {
|
|
683
|
-
const lower = ["get", "post", "put", "patch", "delete", "options"].filter(
|
|
684
|
-
(m) => new RegExp(`export\\s+(async\\s+)?function\\s+${m}\\b`).test(src)
|
|
685
|
-
);
|
|
686
|
-
diags.push({
|
|
687
|
-
code: "INVALID_API_EXPORT",
|
|
688
|
-
severity: "error",
|
|
689
|
-
message: `API module exports no HTTP method handler${lower.length ? ` (found lowercase "${lower.join(", ")}")` : ""}.`,
|
|
690
|
-
files: [api.file],
|
|
691
|
-
fix: `Export one of: ${API_METHODS.join(", ")} (uppercase).`
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
return diags;
|
|
696
|
-
}
|
|
697
|
-
function checkClientServerBoundary(root) {
|
|
698
|
-
const diags = [];
|
|
699
|
-
const importRe = /(?:import|export)[^'"]*?from\s+["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
700
|
-
for (const abs of collectClientFiles(root)) {
|
|
701
|
-
let src;
|
|
702
|
-
try {
|
|
703
|
-
src = stripComments(fs2.readFileSync(abs, "utf8"));
|
|
704
|
-
} catch {
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
let m;
|
|
708
|
-
importRe.lastIndex = 0;
|
|
709
|
-
while (m = importRe.exec(src)) {
|
|
710
|
-
const spec = m[1] ?? m[2];
|
|
711
|
-
if (!spec) continue;
|
|
712
|
-
const isServer = spec === "server" || spec.startsWith("server/") || /(^|\/)\.\.?\/server(\/|$)/.test(spec) || /\/server\//.test(spec);
|
|
713
|
-
if (isServer) {
|
|
714
|
-
diags.push({
|
|
715
|
-
code: "CLIENT_IMPORTS_SERVER",
|
|
716
|
-
severity: "error",
|
|
717
|
-
message: `Client module imports from server/ ("${spec}").`,
|
|
718
|
-
files: [rel(root, abs)],
|
|
719
|
-
fix: "server/ code must never be imported by the client. Move shared code to lib/."
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
return diags;
|
|
725
|
-
}
|
|
726
|
-
function checkPublicEnv(root) {
|
|
727
|
-
const diags = [];
|
|
728
|
-
const envRe = /import\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
729
|
-
for (const abs of collectClientFiles(root)) {
|
|
730
|
-
let src;
|
|
731
|
-
try {
|
|
732
|
-
src = stripComments(fs2.readFileSync(abs, "utf8"));
|
|
733
|
-
} catch {
|
|
734
|
-
continue;
|
|
735
|
-
}
|
|
736
|
-
let m;
|
|
737
|
-
envRe.lastIndex = 0;
|
|
738
|
-
const bad = /* @__PURE__ */ new Set();
|
|
739
|
-
while (m = envRe.exec(src)) {
|
|
740
|
-
const key = m[1];
|
|
741
|
-
if (ALLOWED_ENV_KEYS.has(key)) continue;
|
|
742
|
-
if (key.startsWith(PUBLIC_ENV_PREFIX)) continue;
|
|
743
|
-
bad.add(key);
|
|
744
|
-
}
|
|
745
|
-
for (const key of bad) {
|
|
746
|
-
diags.push({
|
|
747
|
-
code: "PUBLIC_ENV_MISUSE",
|
|
748
|
-
severity: "warning",
|
|
749
|
-
message: `Client reads non-public env "import.meta.env.${key}".`,
|
|
750
|
-
files: [rel(root, abs)],
|
|
751
|
-
fix: `Only ${PUBLIC_ENV_PREFIX}* vars reach the client. Read secrets via ApiContext.env in server/.`
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return diags;
|
|
756
|
-
}
|
|
757
|
-
function runDiagnostics(root, model) {
|
|
758
|
-
return [
|
|
759
|
-
...checkForbiddenFiles(root),
|
|
760
|
-
...checkRouteConflicts(model),
|
|
761
|
-
...checkPageExports(root, model),
|
|
762
|
-
...checkApiExports(root, model),
|
|
763
|
-
...checkClientServerBoundary(root),
|
|
764
|
-
...checkPublicEnv(root)
|
|
765
|
-
];
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
// src/cli/build.ts
|
|
769
|
-
var SERVER_ENTRY = `// AUTO-GENERATED by @squadbase/vantage.
|
|
770
|
-
import path from "node:path"
|
|
771
|
-
import { fileURLToPath } from "node:url"
|
|
772
|
-
import { startNodeServer } from "@squadbase/vantage/server/node"
|
|
773
|
-
import { apiRoutes } from "./server-routes.gen"
|
|
774
|
-
|
|
775
|
-
const clientDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../client")
|
|
776
|
-
startNodeServer({ apiRoutes, clientDir })
|
|
777
|
-
`;
|
|
778
|
-
async function build(root, args) {
|
|
779
|
-
const model = scanProject(root);
|
|
780
|
-
const errors = runDiagnostics(root, model).filter((d) => d.severity === "error");
|
|
781
|
-
if (errors.length) {
|
|
782
|
-
printDiagnostics(errors);
|
|
783
|
-
log.error("Fix the errors above before building.");
|
|
784
|
-
return 1;
|
|
785
|
-
}
|
|
786
|
-
const mode = "production";
|
|
787
|
-
applyApiBaseUrlFlag(args);
|
|
788
|
-
const env = { ...process.env, ...loadEnv(mode, root, "") };
|
|
789
|
-
const quiet = Boolean(args.flags.json);
|
|
790
|
-
if (!quiet) log.step("Building client\u2026");
|
|
791
|
-
await build$1({
|
|
792
|
-
configFile: false,
|
|
793
|
-
root,
|
|
794
|
-
mode,
|
|
795
|
-
plugins: [vantagePlugin({ root, env })],
|
|
796
|
-
logLevel: quiet ? "silent" : args.flags.verbose ? "info" : "warn"
|
|
797
|
-
});
|
|
798
|
-
relocateClientHtml(root);
|
|
799
|
-
if (model.hasServer) {
|
|
800
|
-
if (!quiet) log.step("Building server\u2026");
|
|
801
|
-
await buildServer(root);
|
|
802
|
-
}
|
|
803
|
-
writeDeployManifest(root, model);
|
|
804
|
-
if (!quiet) log.ok(`Build complete \u2192 ${path2.relative(process.cwd(), path2.join(root, "dist")) || "dist"}/`);
|
|
805
|
-
if (args.flags.json) {
|
|
806
|
-
process.stdout.write(
|
|
807
|
-
JSON.stringify({
|
|
808
|
-
ok: true,
|
|
809
|
-
mode: model.hasServer ? "fullstack" : "spa",
|
|
810
|
-
output: "dist",
|
|
811
|
-
pages: model.pages.length,
|
|
812
|
-
apis: model.apis.length
|
|
813
|
-
}) + "\n"
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
return 0;
|
|
817
|
-
}
|
|
818
|
-
function relocateClientHtml(root) {
|
|
819
|
-
const clientDir = path2.join(root, "dist", "client");
|
|
820
|
-
const nested = path2.join(clientDir, VANTAGE_DIR, "index.html");
|
|
821
|
-
const target = path2.join(clientDir, "index.html");
|
|
822
|
-
if (fs2.existsSync(nested)) {
|
|
823
|
-
fs2.mkdirSync(clientDir, { recursive: true });
|
|
824
|
-
fs2.renameSync(nested, target);
|
|
825
|
-
fs2.rmSync(path2.join(clientDir, VANTAGE_DIR), { recursive: true, force: true });
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
async function buildServer(root) {
|
|
829
|
-
const vantageDir = path2.join(root, VANTAGE_DIR);
|
|
830
|
-
const entry = path2.join(vantageDir, "server-entry.ts");
|
|
831
|
-
fs2.writeFileSync(entry, SERVER_ENTRY);
|
|
832
|
-
await build$2({
|
|
833
|
-
entryPoints: [entry],
|
|
834
|
-
outfile: path2.join(root, "dist", "server", "index.mjs"),
|
|
835
|
-
bundle: true,
|
|
836
|
-
platform: "node",
|
|
837
|
-
format: "esm",
|
|
838
|
-
target: "node20",
|
|
839
|
-
logLevel: "warning",
|
|
840
|
-
banner: { js: "// @squadbase/vantage server bundle" }
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
function writeDeployManifest(root, model) {
|
|
844
|
-
const dist = path2.join(root, "dist");
|
|
845
|
-
fs2.mkdirSync(dist, { recursive: true });
|
|
846
|
-
const manifest = {
|
|
847
|
-
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
848
|
-
mode: model.hasServer ? "fullstack" : "spa",
|
|
849
|
-
client: "./client",
|
|
850
|
-
...model.hasServer ? { server: "./server/index.mjs" } : {},
|
|
851
|
-
spaFallback: "./client/index.html",
|
|
852
|
-
runtime: "node"
|
|
853
|
-
};
|
|
854
|
-
fs2.writeFileSync(path2.join(dist, "vantage-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// src/cli/check.ts
|
|
858
|
-
async function check(root, args) {
|
|
859
|
-
const model = scanProject(root);
|
|
860
|
-
const diags = runDiagnostics(root, model);
|
|
861
|
-
const errors = diags.filter((d) => d.severity === "error");
|
|
862
|
-
const warnings = diags.filter((d) => d.severity === "warning");
|
|
863
|
-
if (args.flags.json) {
|
|
864
|
-
printJson({
|
|
865
|
-
ok: errors.length === 0,
|
|
866
|
-
errorCount: errors.length,
|
|
867
|
-
warningCount: warnings.length,
|
|
868
|
-
diagnostics: diags
|
|
869
|
-
});
|
|
870
|
-
return errors.length ? 1 : 0;
|
|
871
|
-
}
|
|
872
|
-
if (diags.length === 0) {
|
|
873
|
-
log.ok("No problems found.");
|
|
874
|
-
return 0;
|
|
875
|
-
}
|
|
876
|
-
printDiagnostics(diags);
|
|
877
|
-
console.log("");
|
|
878
|
-
if (errors.length) log.error(`${errors.length} error(s), ${warnings.length} warning(s).`);
|
|
879
|
-
else log.warn(`${warnings.length} warning(s).`);
|
|
880
|
-
return errors.length ? 1 : 0;
|
|
881
|
-
}
|
|
882
|
-
async function dev(root, args) {
|
|
883
|
-
const model = scanProject(root);
|
|
884
|
-
const errors = runDiagnostics(root, model).filter((d) => d.severity === "error");
|
|
885
|
-
if (errors.length) {
|
|
886
|
-
printDiagnostics(errors);
|
|
887
|
-
log.error("Fix the errors above before starting the dev server.");
|
|
888
|
-
return 1;
|
|
889
|
-
}
|
|
890
|
-
const mode = "development";
|
|
891
|
-
applyApiBaseUrlFlag(args);
|
|
892
|
-
const env = { ...process.env, ...loadEnv(mode, root, "") };
|
|
893
|
-
const port = parsePort(args.flags.port, 5173);
|
|
894
|
-
const server = await createServer({
|
|
895
|
-
configFile: false,
|
|
896
|
-
root,
|
|
897
|
-
mode,
|
|
898
|
-
plugins: [vantagePlugin({ root, env })],
|
|
899
|
-
server: { port },
|
|
900
|
-
clearScreen: false
|
|
901
|
-
});
|
|
902
|
-
await server.listen();
|
|
903
|
-
log.brand("dev server ready");
|
|
904
|
-
server.printUrls();
|
|
905
|
-
if (model.hasServer) log.info(" server/api enabled");
|
|
906
|
-
const shutdown = async () => {
|
|
907
|
-
await server.close();
|
|
908
|
-
process.exit(0);
|
|
909
|
-
};
|
|
910
|
-
process.on("SIGINT", shutdown);
|
|
911
|
-
process.on("SIGTERM", shutdown);
|
|
912
|
-
return new Promise(() => {
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
var SUPPORTED_SCHEMA_VERSION = 2;
|
|
916
|
-
var DOC_LANGS = ["ja", "en"];
|
|
917
|
-
var DEFAULT_DOC_LANG = "ja";
|
|
918
|
-
function isDocLang(value) {
|
|
919
|
-
return typeof value === "string" && DOC_LANGS.includes(value);
|
|
920
|
-
}
|
|
921
|
-
function docsDir() {
|
|
922
|
-
return fileURLToPath(new URL("../docs/", import.meta.url));
|
|
923
|
-
}
|
|
924
|
-
function loadCatalog() {
|
|
925
|
-
try {
|
|
926
|
-
const raw = fs2.readFileSync(path2.join(docsDir(), "index.json"), "utf8");
|
|
927
|
-
const parsed = JSON.parse(raw);
|
|
928
|
-
if (parsed.schemaVersion !== SUPPORTED_SCHEMA_VERSION) return null;
|
|
929
|
-
return Array.isArray(parsed.docs) ? parsed.docs : null;
|
|
930
|
-
} catch {
|
|
931
|
-
return null;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
function docsForLang(catalog, lang) {
|
|
935
|
-
return catalog.filter((doc) => doc.lang === lang);
|
|
936
|
-
}
|
|
937
|
-
function readDoc(entry) {
|
|
938
|
-
return fs2.readFileSync(path2.join(docsDir(), entry.file), "utf8");
|
|
939
|
-
}
|
|
940
|
-
function resolveDoc(entries, query) {
|
|
941
|
-
const needle = query.toLowerCase().replace(/^\/+|\/+$/g, "").replace(/\.mdx?$/, "");
|
|
942
|
-
const exact = entries.filter((doc) => doc.slug.toLowerCase() === needle);
|
|
943
|
-
if (exact.length === 1) return { kind: "found", doc: exact[0] };
|
|
944
|
-
const byLeaf = entries.filter((doc) => (doc.slug.split("/").pop() ?? "").toLowerCase() === needle);
|
|
945
|
-
if (byLeaf.length === 1) return { kind: "found", doc: byLeaf[0] };
|
|
946
|
-
if (byLeaf.length > 1) return { kind: "ambiguous", matches: byLeaf };
|
|
947
|
-
const partial = entries.filter(
|
|
948
|
-
(doc) => doc.slug.toLowerCase().includes(needle) || doc.title.toLowerCase().includes(needle)
|
|
949
|
-
);
|
|
950
|
-
return { kind: "missing", suggestions: partial };
|
|
951
|
-
}
|
|
952
|
-
function groupBySection(entries) {
|
|
953
|
-
const groups = [];
|
|
954
|
-
for (const doc of entries) {
|
|
955
|
-
const last = groups[groups.length - 1];
|
|
956
|
-
if (last && last.section === doc.section) last.docs.push(doc);
|
|
957
|
-
else groups.push({ section: doc.section, title: doc.sectionTitle, docs: [doc] });
|
|
958
|
-
}
|
|
959
|
-
return groups;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// src/cli/docs.ts
|
|
963
|
-
function paint(markdown) {
|
|
964
|
-
if (!process.stdout.isTTY) return markdown;
|
|
965
|
-
let inFence = false;
|
|
966
|
-
return markdown.split("\n").map((line) => {
|
|
967
|
-
if (/^\s*(```|~~~)/.test(line)) {
|
|
968
|
-
inFence = !inFence;
|
|
969
|
-
return pc3.dim(line);
|
|
970
|
-
}
|
|
971
|
-
if (inFence) return line;
|
|
972
|
-
if (/^#{1,6}\s/.test(line)) return pc3.bold(pc3.magenta(line));
|
|
973
|
-
if (/^>\s?/.test(line)) return pc3.dim(line);
|
|
974
|
-
return line;
|
|
975
|
-
}).join("\n");
|
|
976
|
-
}
|
|
977
|
-
function printList(entries) {
|
|
978
|
-
const width = Math.max(...entries.map((doc) => doc.slug.length), 0);
|
|
979
|
-
for (const group of groupBySection(entries)) {
|
|
980
|
-
console.log("");
|
|
981
|
-
console.log(pc3.bold(group.title));
|
|
982
|
-
for (const doc of group.docs) {
|
|
983
|
-
const slug = doc.slug.padEnd(width);
|
|
984
|
-
console.log(` ${pc3.cyan(slug)} ${doc.description || doc.title}`);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
console.log("");
|
|
988
|
-
console.log(pc3.dim("vantage docs <name> show one page --json machine-readable"));
|
|
989
|
-
console.log(pc3.dim("vantage docs --all show every page --lang ja | en"));
|
|
990
|
-
}
|
|
991
|
-
function toJson(doc, content) {
|
|
992
|
-
return {
|
|
993
|
-
slug: doc.slug,
|
|
994
|
-
lang: doc.lang,
|
|
995
|
-
title: doc.title,
|
|
996
|
-
description: doc.description,
|
|
997
|
-
section: doc.section,
|
|
998
|
-
sectionTitle: doc.sectionTitle,
|
|
999
|
-
content
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
async function docs(_root, args) {
|
|
1003
|
-
const catalog = loadCatalog();
|
|
1004
|
-
if (!catalog) {
|
|
1005
|
-
log.error("The bundled documentation is missing. Reinstall @squadbase/vantage, or run `pnpm build` in the framework repo.");
|
|
1006
|
-
return 1;
|
|
1007
|
-
}
|
|
1008
|
-
const langFlag = args.flags.lang;
|
|
1009
|
-
if (langFlag !== void 0 && !isDocLang(langFlag)) {
|
|
1010
|
-
log.error(`Unknown --lang "${String(langFlag)}". Available: ${DOC_LANGS.join(", ")}`);
|
|
1011
|
-
return 1;
|
|
1012
|
-
}
|
|
1013
|
-
const lang = isDocLang(langFlag) ? langFlag : DEFAULT_DOC_LANG;
|
|
1014
|
-
const entries = docsForLang(catalog, lang);
|
|
1015
|
-
if (entries.length === 0) {
|
|
1016
|
-
log.error(`No documentation bundled for "${lang}".`);
|
|
1017
|
-
return 1;
|
|
1018
|
-
}
|
|
1019
|
-
const json = Boolean(args.flags.json);
|
|
1020
|
-
const name = args.positionals[1];
|
|
1021
|
-
if (args.flags.all) {
|
|
1022
|
-
const pages = entries.map((doc) => ({ doc, content: readDoc(doc) }));
|
|
1023
|
-
if (json) {
|
|
1024
|
-
printJson({ lang, docs: pages.map(({ doc, content: content2 }) => toJson(doc, content2)) });
|
|
1025
|
-
return 0;
|
|
1026
|
-
}
|
|
1027
|
-
process.stdout.write(pages.map(({ content: content2 }) => content2.trimEnd()).join("\n\n---\n\n") + "\n");
|
|
1028
|
-
return 0;
|
|
1029
|
-
}
|
|
1030
|
-
if (!name || args.flags.list) {
|
|
1031
|
-
if (json) {
|
|
1032
|
-
printJson({
|
|
1033
|
-
lang,
|
|
1034
|
-
docs: entries.map(({ slug, title, description, section, sectionTitle }) => ({
|
|
1035
|
-
slug,
|
|
1036
|
-
title,
|
|
1037
|
-
description,
|
|
1038
|
-
section,
|
|
1039
|
-
sectionTitle
|
|
1040
|
-
}))
|
|
1041
|
-
});
|
|
1042
|
-
return 0;
|
|
1043
|
-
}
|
|
1044
|
-
printList(entries);
|
|
1045
|
-
return 0;
|
|
1046
|
-
}
|
|
1047
|
-
const found = resolveDoc(entries, name);
|
|
1048
|
-
if (found.kind === "ambiguous") {
|
|
1049
|
-
log.error(`"${name}" matches several pages: ${found.matches.map((d) => d.slug).join(", ")}`);
|
|
1050
|
-
return 1;
|
|
1051
|
-
}
|
|
1052
|
-
if (found.kind === "missing") {
|
|
1053
|
-
const suggestions = found.suggestions.map((d) => d.slug);
|
|
1054
|
-
log.error(
|
|
1055
|
-
suggestions.length ? `No page named "${name}". Did you mean: ${suggestions.slice(0, 8).join(", ")}?` : `No page named "${name}". Run \`vantage docs\` to list every page.`
|
|
1056
|
-
);
|
|
1057
|
-
return 1;
|
|
1058
|
-
}
|
|
1059
|
-
const content = readDoc(found.doc);
|
|
1060
|
-
if (json) {
|
|
1061
|
-
printJson(toJson(found.doc, content));
|
|
1062
|
-
return 0;
|
|
1063
|
-
}
|
|
1064
|
-
process.stdout.write(paint(content.trimEnd()) + "\n");
|
|
1065
|
-
return 0;
|
|
1066
|
-
}
|
|
1067
|
-
async function doctor(root, args) {
|
|
1068
|
-
const facets = [];
|
|
1069
|
-
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
1070
|
-
facets.push({
|
|
1071
|
-
name: "node",
|
|
1072
|
-
ok: nodeMajor >= 20,
|
|
1073
|
-
detail: `v${process.versions.node}${nodeMajor >= 20 ? "" : " (requires >= 20)"}`
|
|
1074
|
-
});
|
|
1075
|
-
const pkgPath = path2.join(root, "package.json");
|
|
1076
|
-
facets.push({ name: "package.json", ok: fs2.existsSync(pkgPath), detail: fs2.existsSync(pkgPath) ? "found" : "missing" });
|
|
1077
|
-
const lockfiles = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
|
|
1078
|
-
const lock = lockfiles.find((f) => fs2.existsSync(path2.join(root, f)) || fs2.existsSync(path2.join(root, "..", f)) || fs2.existsSync(path2.join(root, "..", "..", f)));
|
|
1079
|
-
facets.push({ name: "lockfile", ok: Boolean(lock), detail: lock ?? "none found" });
|
|
1080
|
-
let installedVantage = "not installed";
|
|
1081
|
-
let vantageOk = false;
|
|
1082
|
-
try {
|
|
1083
|
-
const require2 = createRequire(path2.join(root, "noop.js"));
|
|
1084
|
-
const p = require2.resolve("@squadbase/vantage/package.json");
|
|
1085
|
-
installedVantage = JSON.parse(fs2.readFileSync(p, "utf8")).version;
|
|
1086
|
-
vantageOk = true;
|
|
1087
|
-
} catch {
|
|
1088
|
-
}
|
|
1089
|
-
facets.push({ name: "@squadbase/vantage", ok: vantageOk, detail: installedVantage });
|
|
1090
|
-
const model = scanProject(root);
|
|
1091
|
-
const errors = runDiagnostics(root, model).filter((d) => d.severity === "error");
|
|
1092
|
-
facets.push({
|
|
1093
|
-
name: "project",
|
|
1094
|
-
ok: errors.length === 0,
|
|
1095
|
-
detail: errors.length === 0 ? "no blocking errors" : `${errors.length} error(s) (run \`vantage check\`)`
|
|
1096
|
-
});
|
|
1097
|
-
const allOk = facets.every((f) => f.ok);
|
|
1098
|
-
if (args.flags.json) {
|
|
1099
|
-
printJson({ ok: allOk, cli: frameworkVersion(), facets });
|
|
1100
|
-
return allOk ? 0 : 1;
|
|
1101
|
-
}
|
|
1102
|
-
log.brand(`doctor (cli v${frameworkVersion()})`);
|
|
1103
|
-
for (const f of facets) {
|
|
1104
|
-
const line = `${f.name.padEnd(22)} ${f.detail}`;
|
|
1105
|
-
if (f.ok) log.ok(line);
|
|
1106
|
-
else log.error(line);
|
|
1107
|
-
}
|
|
1108
|
-
return allOk ? 0 : 1;
|
|
1109
|
-
}
|
|
1110
|
-
async function preview(root, args) {
|
|
1111
|
-
const manifestPath = path2.join(root, "dist", "vantage-manifest.json");
|
|
1112
|
-
if (!fs2.existsSync(manifestPath)) {
|
|
1113
|
-
log.error("No build found. Run `vantage build` first.");
|
|
1114
|
-
return 1;
|
|
1115
|
-
}
|
|
1116
|
-
const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
|
|
1117
|
-
const port = parsePort(args.flags.port, 4173);
|
|
1118
|
-
if (manifest.mode === "fullstack") {
|
|
1119
|
-
const serverPath = path2.join(root, "dist", "server", "index.mjs");
|
|
1120
|
-
if (!fs2.existsSync(serverPath)) {
|
|
1121
|
-
log.error("Server bundle missing. Re-run `vantage build`.");
|
|
1122
|
-
return 1;
|
|
1123
|
-
}
|
|
1124
|
-
log.brand(`preview (fullstack) \u2192 http://localhost:${port}`);
|
|
1125
|
-
const child = spawn(process.execPath, [serverPath], {
|
|
1126
|
-
stdio: "inherit",
|
|
1127
|
-
env: { ...process.env, PORT: String(port) }
|
|
1128
|
-
});
|
|
1129
|
-
return new Promise((resolve) => {
|
|
1130
|
-
child.on("exit", (code) => resolve(code ?? 0));
|
|
1131
|
-
const stop = () => child.kill("SIGINT");
|
|
1132
|
-
process.on("SIGINT", stop);
|
|
1133
|
-
process.on("SIGTERM", stop);
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
const { preview: vitePreview } = await import('vite');
|
|
1137
|
-
const server = await vitePreview({
|
|
1138
|
-
configFile: false,
|
|
1139
|
-
root,
|
|
1140
|
-
build: { outDir: path2.join(root, "dist", "client") },
|
|
1141
|
-
preview: { port }
|
|
1142
|
-
});
|
|
1143
|
-
log.brand("preview (spa)");
|
|
1144
|
-
server.printUrls();
|
|
1145
|
-
return new Promise(() => {
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
var ROUTE_COLUMN = 28;
|
|
1149
|
-
var LABEL_COLUMN = 9;
|
|
1150
|
-
function detailLine(indent, label, value) {
|
|
1151
|
-
console.log(`${indent}${pc3.dim(label.padEnd(LABEL_COLUMN))}${value}`);
|
|
1152
|
-
}
|
|
1153
|
-
function formatParams(params) {
|
|
1154
|
-
return params.map((p) => p.kind === "catchall" ? `*${p.name}` : `:${p.name}`).join(", ");
|
|
1155
|
-
}
|
|
1156
|
-
function formatKeys(keys) {
|
|
1157
|
-
return keys?.length ? ` { ${keys.join(", ")} }` : "";
|
|
1158
|
-
}
|
|
1159
|
-
function printPageSpec(spec) {
|
|
1160
|
-
const indent = " ";
|
|
1161
|
-
if (spec.title) detailLine(indent, "title", spec.title);
|
|
1162
|
-
if (spec.navLabel) detailLine(indent, "nav", spec.navLabel);
|
|
1163
|
-
if (spec.description) detailLine(indent, "desc", spec.description);
|
|
1164
|
-
if (spec.params.length) detailLine(indent, "params", formatParams(spec.params));
|
|
1165
|
-
}
|
|
1166
|
-
function printApiMethodSpec(method, params) {
|
|
1167
|
-
const indent = " ";
|
|
1168
|
-
console.log(` ${pc3.bold(method.method)}`);
|
|
1169
|
-
if (params.length) detailLine(indent, "path", formatParams(params));
|
|
1170
|
-
if (method.query.length) detailLine(indent, "query", method.query.join(", "));
|
|
1171
|
-
if (method.body) detailLine(indent, "body", method.body.format + formatKeys(method.body.keys));
|
|
1172
|
-
for (const res of method.responses) {
|
|
1173
|
-
const parts = [String(res.status), res.contentType].filter(Boolean).join(" ");
|
|
1174
|
-
detailLine(indent, "response", parts + formatKeys(res.keys));
|
|
1175
|
-
}
|
|
1176
|
-
for (const err of method.errors) {
|
|
1177
|
-
detailLine(indent, "error", `${err.status}${err.message ? ` "${err.message}"` : ""}`);
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
function printPages(model, detail) {
|
|
1181
|
-
console.log(pc3.bold("\nPages"));
|
|
1182
|
-
if (model.pages.length === 0) console.log(pc3.dim(" (none)"));
|
|
1183
|
-
for (const p of model.pages) {
|
|
1184
|
-
console.log(` ${pc3.green(p.routePath.padEnd(ROUTE_COLUMN))} ${pc3.dim(p.file)}`);
|
|
1185
|
-
if (detail) printPageSpec(extractPageSpec(p));
|
|
1186
|
-
}
|
|
1187
|
-
if (model.layouts.length || model.notFound || model.error) {
|
|
1188
|
-
console.log(pc3.bold("\nSpecial"));
|
|
1189
|
-
for (const l of model.layouts) {
|
|
1190
|
-
const label = "layout " + (l.dir === "" ? "/" : "/" + l.dir);
|
|
1191
|
-
console.log(` ${pc3.cyan(label.padEnd(ROUTE_COLUMN))} ${pc3.dim(l.file)}`);
|
|
1192
|
-
}
|
|
1193
|
-
if (model.notFound) console.log(` ${pc3.cyan("404".padEnd(ROUTE_COLUMN))} ${pc3.dim(model.notFound)}`);
|
|
1194
|
-
if (model.error) console.log(` ${pc3.cyan("error".padEnd(ROUTE_COLUMN))} ${pc3.dim(model.error)}`);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
function printApis(model, detail) {
|
|
1198
|
-
console.log(pc3.bold("\nAPI"));
|
|
1199
|
-
if (!model.hasServer) {
|
|
1200
|
-
console.log(pc3.dim(" (none \u2014 this project has no server/ directory)"));
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
if (model.apis.length === 0) console.log(pc3.dim(" (none)"));
|
|
1204
|
-
for (const a of model.apis) {
|
|
1205
|
-
console.log(` ${pc3.yellow(a.routePath.padEnd(ROUTE_COLUMN))} ${pc3.dim(a.file)}`);
|
|
1206
|
-
if (!detail) continue;
|
|
1207
|
-
const spec = extractApiSpec(a);
|
|
1208
|
-
if (spec.methods.length === 0) console.log(` ${pc3.dim("(no HTTP method handler exported)")}`);
|
|
1209
|
-
for (const method of spec.methods) printApiMethodSpec(method, spec.params);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
function pageJson(p, detail) {
|
|
1213
|
-
return {
|
|
1214
|
-
route: p.routePath,
|
|
1215
|
-
file: p.file,
|
|
1216
|
-
dynamic: p.dynamic,
|
|
1217
|
-
...detail ? { spec: extractPageSpec(p) } : {}
|
|
1218
|
-
};
|
|
1219
|
-
}
|
|
1220
|
-
function apiJson(a, detail) {
|
|
1221
|
-
return {
|
|
1222
|
-
route: a.routePath,
|
|
1223
|
-
file: a.file,
|
|
1224
|
-
params: a.params,
|
|
1225
|
-
...detail ? { spec: extractApiSpec(a) } : {}
|
|
1226
|
-
};
|
|
1227
|
-
}
|
|
1228
|
-
async function routes(root, args) {
|
|
1229
|
-
const model = scanProject(root);
|
|
1230
|
-
const onlyPages = Boolean(args.flags.pages);
|
|
1231
|
-
const onlyApis = Boolean(args.flags.apis);
|
|
1232
|
-
const showPages = onlyPages || !onlyApis;
|
|
1233
|
-
const showApis = onlyApis || !onlyPages;
|
|
1234
|
-
const detail = Boolean(args.flags.detail);
|
|
1235
|
-
if (args.flags.json) {
|
|
1236
|
-
const payload = {};
|
|
1237
|
-
if (showPages) {
|
|
1238
|
-
payload.pages = model.pages.map((p) => pageJson(p, detail));
|
|
1239
|
-
payload.layouts = model.layouts.map((l) => ({ dir: l.dir === "" ? "/" : l.dir, file: l.file }));
|
|
1240
|
-
payload.notFound = model.notFound;
|
|
1241
|
-
payload.error = model.error;
|
|
1242
|
-
}
|
|
1243
|
-
if (showApis) {
|
|
1244
|
-
payload.apis = model.apis.map((a) => apiJson(a, detail));
|
|
1245
|
-
payload.hasServer = model.hasServer;
|
|
1246
|
-
}
|
|
1247
|
-
printJson(payload);
|
|
1248
|
-
return 0;
|
|
1249
|
-
}
|
|
1250
|
-
if (showPages) printPages(model, detail);
|
|
1251
|
-
if (showApis && (model.hasServer || onlyApis)) printApis(model, detail);
|
|
1252
|
-
console.log("");
|
|
1253
|
-
return 0;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
// src/cli/search-index.ts
|
|
1257
|
-
var FIELD_WEIGHTS = { slug: 3, title: 3, description: 2, body: 1 };
|
|
1258
|
-
var K1 = 1.2;
|
|
1259
|
-
var B = 0.75;
|
|
1260
|
-
var TOKEN_RE = /[a-z0-9]+|[-ヿ㐀-䶿一-鿿]+/g;
|
|
1261
|
-
var LATIN_RE = /^[a-z0-9]+$/;
|
|
1262
|
-
var TOKEN_RE_ANYCASE = /[a-zA-Z0-9]+|[-ヿ㐀-䶿一-鿿]+/g;
|
|
1263
|
-
var CJK_RE = /[-ヿ㐀-䶿一-鿿]/;
|
|
1264
|
-
var SCRIPT_RE = /[ぁ-ゟ]+|[゠-ヿ]+|[㐀-䶿一-鿿]+/g;
|
|
1265
|
-
var HIRAGANA_RE = /^[ぁ-ゟ]$/;
|
|
1266
|
-
function singular(word) {
|
|
1267
|
-
if (word.length >= 5 && word.endsWith("ies")) return `${word.slice(0, -3)}y`;
|
|
1268
|
-
if (word.length >= 4 && word.endsWith("s") && !/(ss|us|is)$/.test(word)) return word.slice(0, -1);
|
|
1269
|
-
return word;
|
|
1270
|
-
}
|
|
1271
|
-
function tokenize(text) {
|
|
1272
|
-
const normalized = text.normalize("NFKC").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
|
|
1273
|
-
const tokens = [];
|
|
1274
|
-
for (const match of normalized.matchAll(TOKEN_RE)) {
|
|
1275
|
-
const run = match[0];
|
|
1276
|
-
if (LATIN_RE.test(run)) {
|
|
1277
|
-
tokens.push(singular(run));
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
for (const segment of run.match(SCRIPT_RE) ?? []) {
|
|
1281
|
-
if (segment.length === 1 && HIRAGANA_RE.test(segment)) continue;
|
|
1282
|
-
if (segment.length <= 2) {
|
|
1283
|
-
tokens.push(segment);
|
|
1284
|
-
continue;
|
|
1285
|
-
}
|
|
1286
|
-
tokens.push(segment);
|
|
1287
|
-
for (let i = 0; i < segment.length - 1; i++) tokens.push(segment.slice(i, i + 2));
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
return tokens;
|
|
1291
|
-
}
|
|
1292
|
-
function queryPhrases(query) {
|
|
1293
|
-
const phrases = [];
|
|
1294
|
-
for (const match of query.normalize("NFKC").matchAll(TOKEN_RE_ANYCASE)) {
|
|
1295
|
-
const run = match[0];
|
|
1296
|
-
if (!CJK_RE.test(run)) {
|
|
1297
|
-
phrases.push(run);
|
|
1298
|
-
continue;
|
|
1299
|
-
}
|
|
1300
|
-
for (const segment of run.match(SCRIPT_RE) ?? []) {
|
|
1301
|
-
if (segment.length === 1 && HIRAGANA_RE.test(segment)) continue;
|
|
1302
|
-
phrases.push(segment);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
return [...new Set(phrases)];
|
|
1306
|
-
}
|
|
1307
|
-
function buildIndex(entries) {
|
|
1308
|
-
const docs2 = entries.map((entry) => {
|
|
1309
|
-
const body = readDoc(entry);
|
|
1310
|
-
const tf = /* @__PURE__ */ new Map();
|
|
1311
|
-
let length = 0;
|
|
1312
|
-
const add2 = (text, weight) => {
|
|
1313
|
-
for (const token of tokenize(text)) {
|
|
1314
|
-
tf.set(token, (tf.get(token) ?? 0) + weight);
|
|
1315
|
-
length += weight;
|
|
1316
|
-
}
|
|
1317
|
-
};
|
|
1318
|
-
add2(entry.slug, FIELD_WEIGHTS.slug);
|
|
1319
|
-
add2(entry.title, FIELD_WEIGHTS.title);
|
|
1320
|
-
add2(entry.description, FIELD_WEIGHTS.description);
|
|
1321
|
-
add2(body, FIELD_WEIGHTS.body);
|
|
1322
|
-
return { entry, body, tf, length };
|
|
1323
|
-
});
|
|
1324
|
-
const df = /* @__PURE__ */ new Map();
|
|
1325
|
-
for (const doc of docs2) {
|
|
1326
|
-
for (const term of doc.tf.keys()) df.set(term, (df.get(term) ?? 0) + 1);
|
|
1327
|
-
}
|
|
1328
|
-
const total = docs2.length;
|
|
1329
|
-
const avgLength = total ? docs2.reduce((sum, doc) => sum + doc.length, 0) / total : 1;
|
|
1330
|
-
return {
|
|
1331
|
-
search(query) {
|
|
1332
|
-
const terms = [...new Set(tokenize(query))];
|
|
1333
|
-
if (terms.length === 0) return [];
|
|
1334
|
-
const hits = [];
|
|
1335
|
-
for (const doc of docs2) {
|
|
1336
|
-
let score = 0;
|
|
1337
|
-
for (const term of terms) {
|
|
1338
|
-
const tf = doc.tf.get(term);
|
|
1339
|
-
if (!tf) continue;
|
|
1340
|
-
const n = df.get(term) ?? 0;
|
|
1341
|
-
const idf = Math.log(1 + (total - n + 0.5) / (n + 0.5));
|
|
1342
|
-
const norm = 1 - B + B * doc.length / avgLength;
|
|
1343
|
-
score += idf * (tf * (K1 + 1) / (tf + K1 * norm));
|
|
1344
|
-
}
|
|
1345
|
-
if (score > 0) hits.push({ entry: doc.entry, score, terms, body: doc.body });
|
|
1346
|
-
}
|
|
1347
|
-
hits.sort((a, b) => b.score - a.score || a.entry.slug.localeCompare(b.entry.slug));
|
|
1348
|
-
return hits;
|
|
1349
|
-
}
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
var SNIPPET_LENGTH = 150;
|
|
1353
|
-
function snippet(body, terms, length = SNIPPET_LENGTH) {
|
|
1354
|
-
let best = "";
|
|
1355
|
-
let bestHits = 0;
|
|
1356
|
-
let bestIndex = 0;
|
|
1357
|
-
let bestIsHeading = true;
|
|
1358
|
-
for (const raw of body.split("\n").slice(1)) {
|
|
1359
|
-
const line = raw.replace(/^\s*[-*]\s+/, "").replace(/\s+/g, " ").trim();
|
|
1360
|
-
if (!line || /^(```|~~~|\|\s*-{2,})/.test(line)) continue;
|
|
1361
|
-
const haystack = line.toLowerCase();
|
|
1362
|
-
const heading = line.startsWith("#");
|
|
1363
|
-
let hits = 0;
|
|
1364
|
-
let first = -1;
|
|
1365
|
-
for (const term of terms) {
|
|
1366
|
-
const at = haystack.indexOf(term);
|
|
1367
|
-
if (at === -1) continue;
|
|
1368
|
-
hits++;
|
|
1369
|
-
if (first === -1 || at < first) first = at;
|
|
1370
|
-
}
|
|
1371
|
-
if (hits > bestHits || hits === bestHits && hits > 0 && bestIsHeading && !heading) {
|
|
1372
|
-
best = line;
|
|
1373
|
-
bestHits = hits;
|
|
1374
|
-
bestIndex = first;
|
|
1375
|
-
bestIsHeading = heading;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
if (!bestHits) return "";
|
|
1379
|
-
if (best.length <= length) return best;
|
|
1380
|
-
const start = Math.max(0, bestIndex - Math.floor(length / 4));
|
|
1381
|
-
const text = best.slice(start, start + length).trim();
|
|
1382
|
-
return `${start > 0 ? "\u2026" : ""}${text}${start + length < best.length ? "\u2026" : ""}`;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
// src/cli/search.ts
|
|
1386
|
-
var DEFAULT_LIMIT = 10;
|
|
1387
|
-
var MAX_REGEX_LINES = 3;
|
|
1388
|
-
function parseLimit(value, fallback) {
|
|
1389
|
-
if (typeof value === "string") {
|
|
1390
|
-
const n = Number(value);
|
|
1391
|
-
if (Number.isInteger(n) && n > 0) return n;
|
|
1392
|
-
}
|
|
1393
|
-
return fallback;
|
|
1394
|
-
}
|
|
1395
|
-
function escapeRegExp(value) {
|
|
1396
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1397
|
-
}
|
|
1398
|
-
function paintMatches(text, re) {
|
|
1399
|
-
if (!process.stdout.isTTY) return text;
|
|
1400
|
-
let out = "";
|
|
1401
|
-
let last = 0;
|
|
1402
|
-
for (const match of text.matchAll(re)) {
|
|
1403
|
-
const at = match.index ?? 0;
|
|
1404
|
-
if (!match[0]) continue;
|
|
1405
|
-
out += pc3.dim(text.slice(last, at)) + pc3.yellow(match[0]);
|
|
1406
|
-
last = at + match[0].length;
|
|
1407
|
-
}
|
|
1408
|
-
return out + pc3.dim(text.slice(last));
|
|
1409
|
-
}
|
|
1410
|
-
function paintTerms(text, words) {
|
|
1411
|
-
if (!process.stdout.isTTY) return text;
|
|
1412
|
-
if (words.length === 0) return pc3.dim(text);
|
|
1413
|
-
return paintMatches(text, new RegExp(words.map(escapeRegExp).join("|"), "gi"));
|
|
1414
|
-
}
|
|
1415
|
-
function header(count, query, lang, mode) {
|
|
1416
|
-
const label = `${count} result${count === 1 ? "" : "s"} for "${query}"`;
|
|
1417
|
-
console.log("");
|
|
1418
|
-
console.log(pc3.bold(label) + pc3.dim(` ${lang} \xB7 ${mode}`));
|
|
1419
|
-
console.log("");
|
|
1420
|
-
}
|
|
1421
|
-
function footer(total, shown, lines) {
|
|
1422
|
-
if (total > shown) {
|
|
1423
|
-
console.log("");
|
|
1424
|
-
console.log(pc3.dim(` ${total - shown} more \u2014 raise --limit to see them`));
|
|
1425
|
-
}
|
|
1426
|
-
console.log("");
|
|
1427
|
-
for (const line of lines) console.log(pc3.dim(line));
|
|
1428
|
-
}
|
|
1429
|
-
function noResults(query) {
|
|
1430
|
-
log.warn(`Nothing matches "${query}".`);
|
|
1431
|
-
console.log(pc3.dim(" Try fewer or more general words, or run `vantage docs --list` to browse every page."));
|
|
1432
|
-
}
|
|
1433
|
-
function meta(entry) {
|
|
1434
|
-
return {
|
|
1435
|
-
slug: entry.slug,
|
|
1436
|
-
title: entry.title,
|
|
1437
|
-
description: entry.description,
|
|
1438
|
-
section: entry.section,
|
|
1439
|
-
sectionTitle: entry.sectionTitle,
|
|
1440
|
-
command: `vantage docs ${entry.slug}`
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
function runRanked(entries, query, lang, limit, json) {
|
|
1444
|
-
const hits = buildIndex(entries).search(query);
|
|
1445
|
-
const shown = hits.slice(0, limit);
|
|
1446
|
-
const passage = (hit) => snippet(hit.body, hit.terms);
|
|
1447
|
-
if (json) {
|
|
1448
|
-
printJson({
|
|
1449
|
-
query,
|
|
1450
|
-
mode: "bm25",
|
|
1451
|
-
lang,
|
|
1452
|
-
count: hits.length,
|
|
1453
|
-
results: shown.map((hit) => ({
|
|
1454
|
-
...meta(hit.entry),
|
|
1455
|
-
score: Math.round(hit.score * 1e3) / 1e3,
|
|
1456
|
-
snippet: passage(hit)
|
|
1457
|
-
}))
|
|
1458
|
-
});
|
|
1459
|
-
return 0;
|
|
1460
|
-
}
|
|
1461
|
-
if (hits.length === 0) {
|
|
1462
|
-
noResults(query);
|
|
1463
|
-
return 0;
|
|
1464
|
-
}
|
|
1465
|
-
const words = queryPhrases(query);
|
|
1466
|
-
const width = Math.max(...shown.map((hit) => hit.entry.slug.length));
|
|
1467
|
-
header(hits.length, query, lang, "bm25");
|
|
1468
|
-
for (const hit of shown) {
|
|
1469
|
-
const title = `${hit.entry.title}${pc3.dim(` \xB7 ${hit.entry.sectionTitle}`)}`;
|
|
1470
|
-
console.log(` ${pc3.cyan(hit.entry.slug.padEnd(width))} ${title}`);
|
|
1471
|
-
const text = passage(hit);
|
|
1472
|
-
if (text) console.log(` ${" ".repeat(width)} ${paintTerms(text, words)}`);
|
|
1473
|
-
}
|
|
1474
|
-
footer(hits.length, shown.length, [
|
|
1475
|
-
"vantage docs <name> open one result --regex treat the query as a regular expression",
|
|
1476
|
-
"vantage search --json machine-readable --limit max results (default 10)"
|
|
1477
|
-
]);
|
|
1478
|
-
return 0;
|
|
1479
|
-
}
|
|
1480
|
-
function runRegex(entries, query, lang, limit, json) {
|
|
1481
|
-
let scan;
|
|
1482
|
-
let probe;
|
|
1483
|
-
try {
|
|
1484
|
-
scan = new RegExp(query, "gi");
|
|
1485
|
-
probe = new RegExp(query, "i");
|
|
1486
|
-
} catch (err) {
|
|
1487
|
-
log.error(err instanceof Error ? err.message : `Invalid regular expression: ${String(err)}`);
|
|
1488
|
-
return 1;
|
|
1489
|
-
}
|
|
1490
|
-
const results = [];
|
|
1491
|
-
for (const entry of entries) {
|
|
1492
|
-
const lines = readDoc(entry).split("\n");
|
|
1493
|
-
const matches = [];
|
|
1494
|
-
let matchCount = 0;
|
|
1495
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1496
|
-
const found = [...lines[i].matchAll(scan)];
|
|
1497
|
-
if (found.length === 0) continue;
|
|
1498
|
-
matchCount += found.length;
|
|
1499
|
-
if (matches.length < MAX_REGEX_LINES) {
|
|
1500
|
-
matches.push({ line: i + 1, text: lines[i].replace(/\s+/g, " ").trim() });
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
const inMeta = probe.test(entry.slug) || probe.test(entry.title) || probe.test(entry.description);
|
|
1504
|
-
if (matchCount === 0 && !inMeta) continue;
|
|
1505
|
-
results.push({ entry, matchCount, matches });
|
|
1506
|
-
}
|
|
1507
|
-
results.sort((a, b) => b.matchCount - a.matchCount);
|
|
1508
|
-
const shown = results.slice(0, limit);
|
|
1509
|
-
if (json) {
|
|
1510
|
-
printJson({
|
|
1511
|
-
query,
|
|
1512
|
-
mode: "regex",
|
|
1513
|
-
lang,
|
|
1514
|
-
count: results.length,
|
|
1515
|
-
results: shown.map((result) => ({
|
|
1516
|
-
...meta(result.entry),
|
|
1517
|
-
matchCount: result.matchCount,
|
|
1518
|
-
matches: result.matches
|
|
1519
|
-
}))
|
|
1520
|
-
});
|
|
1521
|
-
return 0;
|
|
1522
|
-
}
|
|
1523
|
-
if (results.length === 0) {
|
|
1524
|
-
noResults(query);
|
|
1525
|
-
return 0;
|
|
1526
|
-
}
|
|
1527
|
-
header(results.length, query, lang, "regex");
|
|
1528
|
-
for (const result of shown) {
|
|
1529
|
-
const count = pc3.dim(
|
|
1530
|
-
result.matchCount ? ` ${result.matchCount} match${result.matchCount === 1 ? "" : "es"}` : " name match"
|
|
1531
|
-
);
|
|
1532
|
-
console.log(` ${pc3.cyan(result.entry.slug)} ${result.entry.title}${count}`);
|
|
1533
|
-
for (const match of result.matches) {
|
|
1534
|
-
console.log(` ${pc3.dim(String(match.line).padStart(4))} ${paintMatches(match.text, scan)}`);
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
footer(results.length, shown.length, [
|
|
1538
|
-
"vantage docs <name> open one result --limit max results (default 10)",
|
|
1539
|
-
"vantage search --json machine-readable --lang ja | en"
|
|
1540
|
-
]);
|
|
1541
|
-
return 0;
|
|
1542
|
-
}
|
|
1543
|
-
async function search(_root, args) {
|
|
1544
|
-
const query = args.positionals.slice(1).join(" ").trim();
|
|
1545
|
-
if (!query) {
|
|
1546
|
-
log.error("Usage: vantage search <query> [--regex] [--limit <n>] [--lang ja|en] [--json]");
|
|
1547
|
-
return 1;
|
|
1548
|
-
}
|
|
1549
|
-
const catalog = loadCatalog();
|
|
1550
|
-
if (!catalog) {
|
|
1551
|
-
log.error("The bundled documentation is missing. Reinstall @squadbase/vantage, or run `pnpm build` in the framework repo.");
|
|
1552
|
-
return 1;
|
|
1553
|
-
}
|
|
1554
|
-
const langFlag = args.flags.lang;
|
|
1555
|
-
if (langFlag !== void 0 && !isDocLang(langFlag)) {
|
|
1556
|
-
log.error(`Unknown --lang "${String(langFlag)}". Available: ${DOC_LANGS.join(", ")}`);
|
|
1557
|
-
return 1;
|
|
1558
|
-
}
|
|
1559
|
-
const lang = isDocLang(langFlag) ? langFlag : DEFAULT_DOC_LANG;
|
|
1560
|
-
const entries = docsForLang(catalog, lang);
|
|
1561
|
-
if (entries.length === 0) {
|
|
1562
|
-
log.error(`No documentation bundled for "${lang}".`);
|
|
1563
|
-
return 1;
|
|
1564
|
-
}
|
|
1565
|
-
const searchable = entries.filter((entry) => entry.searchable !== false);
|
|
1566
|
-
const limit = parseLimit(args.flags.limit, DEFAULT_LIMIT);
|
|
1567
|
-
const json = Boolean(args.flags.json);
|
|
1568
|
-
return args.flags.regex ? runRegex(searchable, query, lang, limit, json) : runRanked(searchable, query, lang, limit, json);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
// src/cli/upgrade.ts
|
|
1572
|
-
async function upgrade(root, args) {
|
|
1573
|
-
const version = frameworkVersion();
|
|
1574
|
-
const model = scanProject(root);
|
|
1575
|
-
generateArtifacts({ root, model });
|
|
1576
|
-
const agents = syncAgentsFile(root);
|
|
1577
|
-
if (args.flags.json) {
|
|
1578
|
-
printJson({ ok: true, version, transforms: [], regenerated: true, agentsSynced: agents === "updated" });
|
|
1579
|
-
return 0;
|
|
1580
|
-
}
|
|
1581
|
-
log.brand(`upgrade \u2192 v${version}`);
|
|
1582
|
-
log.ok("Regenerated .vantage artifacts.");
|
|
1583
|
-
if (agents === "updated") log.ok("Synced AGENTS.md from the framework.");
|
|
1584
|
-
log.info("No breaking code transforms required for this version.");
|
|
1585
|
-
return 0;
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
// src/cli.ts
|
|
1589
|
-
var HELP = `${pc3.bold(pc3.magenta("vantage"))} \u2014 data dashboard framework for Squadbase
|
|
8
|
+
var HELP = `${pc.bold(pc.magenta("vantage"))} \u2014 data dashboard framework for Squadbase
|
|
1590
9
|
|
|
1591
|
-
${
|
|
10
|
+
${pc.bold("Usage")}
|
|
1592
11
|
vantage <command> [path] [options]
|
|
1593
12
|
vantage <path> Quick dev shortcut (folder or entry file)
|
|
1594
13
|
|
|
1595
|
-
${
|
|
14
|
+
${pc.bold("Commands")}
|
|
1596
15
|
dev Start the dev server (HMR + optional API)
|
|
1597
16
|
build Build client and optional server into dist/
|
|
1598
17
|
preview Run the production build locally
|
|
@@ -1605,7 +24,7 @@ ${pc3.bold("Commands")}
|
|
|
1605
24
|
doctor Environment and installation health checks
|
|
1606
25
|
upgrade Regenerate artifacts, sync AGENTS.md / apply migrations
|
|
1607
26
|
|
|
1608
|
-
${
|
|
27
|
+
${pc.bold("Options")}
|
|
1609
28
|
--json Machine-readable output (check, routes, doctor, build, docs, search)
|
|
1610
29
|
--port <n> Port for dev/preview
|
|
1611
30
|
--root <dir> Project root (default: cwd)
|
|
@@ -1623,16 +42,16 @@ ${pc3.bold("Options")}
|
|
|
1623
42
|
-h, --help Print this help
|
|
1624
43
|
`;
|
|
1625
44
|
var COMMANDS = {
|
|
1626
|
-
dev,
|
|
1627
|
-
build,
|
|
1628
|
-
preview,
|
|
1629
|
-
check,
|
|
1630
|
-
routes,
|
|
1631
|
-
add,
|
|
1632
|
-
docs,
|
|
1633
|
-
search,
|
|
1634
|
-
doctor,
|
|
1635
|
-
upgrade
|
|
45
|
+
dev: () => import('./dev-NX3PMYCT.js').then((m) => m.dev),
|
|
46
|
+
build: () => import('./build-QP5BL3K7.js').then((m) => m.build),
|
|
47
|
+
preview: () => import('./preview-CLALOEFE.js').then((m) => m.preview),
|
|
48
|
+
check: () => import('./check-QPFERQLO.js').then((m) => m.check),
|
|
49
|
+
routes: () => import('./routes-YI25MKPF.js').then((m) => m.routes),
|
|
50
|
+
add: () => import('./add-DWNDA57L.js').then((m) => m.add),
|
|
51
|
+
docs: () => import('./docs-M5ZUI25V.js').then((m) => m.docs),
|
|
52
|
+
search: () => import('./search-5TYAIQ7C.js').then((m) => m.search),
|
|
53
|
+
doctor: () => import('./doctor-N6JMEWKT.js').then((m) => m.doctor),
|
|
54
|
+
upgrade: () => import('./upgrade-3P5MBGE6.js').then((m) => m.upgrade)
|
|
1636
55
|
};
|
|
1637
56
|
var NO_ROOT_POSITIONAL = /* @__PURE__ */ new Set(["add", "docs", "search"]);
|
|
1638
57
|
async function main() {
|
|
@@ -1646,9 +65,9 @@ async function main() {
|
|
|
1646
65
|
console.log(HELP);
|
|
1647
66
|
return;
|
|
1648
67
|
}
|
|
1649
|
-
let
|
|
68
|
+
let load = COMMANDS[command];
|
|
1650
69
|
let root;
|
|
1651
|
-
if (
|
|
70
|
+
if (load) {
|
|
1652
71
|
const targetArg = !NO_ROOT_POSITIONAL.has(command) ? args.positionals[1] : void 0;
|
|
1653
72
|
if (targetArg !== void 0) {
|
|
1654
73
|
const quick = resolveQuickRoot(targetArg);
|
|
@@ -1669,12 +88,13 @@ async function main() {
|
|
|
1669
88
|
process.exitCode = 1;
|
|
1670
89
|
return;
|
|
1671
90
|
}
|
|
1672
|
-
|
|
91
|
+
load = COMMANDS.dev;
|
|
1673
92
|
root = quick;
|
|
1674
|
-
log.brand(`quick dev \u2192 ${
|
|
93
|
+
log.brand(`quick dev \u2192 ${path.relative(process.cwd(), root) || "."}`);
|
|
1675
94
|
}
|
|
1676
95
|
try {
|
|
1677
|
-
const
|
|
96
|
+
const run = await load();
|
|
97
|
+
const code = await run(root, args);
|
|
1678
98
|
process.exitCode = code;
|
|
1679
99
|
} catch (err) {
|
|
1680
100
|
log.error(err instanceof Error ? err.message : String(err));
|