@squadbase/vantage 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
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-UHZ7XSAN.js';
2
+ import { PAGE_EXTENSIONS, scanProject, vantagePlugin, generateArtifacts, VANTAGE_DIR, MANIFEST_SCHEMA_VERSION, FORBIDDEN_FILES, API_METHODS, PUBLIC_ENV_PREFIX, extractPageMeta } from './chunk-3QKPNWKJ.js';
3
3
  import './chunk-46QI6GFC.js';
4
4
  import './chunk-B7LLBNLV.js';
5
5
  import path2 from 'path';
@@ -12,7 +12,21 @@ import { fileURLToPath } from 'url';
12
12
  import { spawn } from 'child_process';
13
13
 
14
14
  // @squadbase/vantage — generated build. Do not edit.
15
- var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "force", "verbose", "version", "all", "list", "regex", "h", "v"]);
15
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
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
+ ]);
16
30
  function parseArgs(argv) {
17
31
  const positionals = [];
18
32
  const flags = {};
@@ -358,10 +372,202 @@ async function add(root, args) {
358
372
  log.error(`Unknown add target "${kind}". Use one of: page, api, ui, block, skill, agents.`);
359
373
  return 1;
360
374
  }
361
- var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["MODE", "DEV", "PROD", "SSR", "BASE_URL", "PUBLIC_"]);
375
+
376
+ // src/core/source.ts
362
377
  function stripComments(src) {
363
378
  return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`\\])\/\/[^\n]*/g, "$1");
364
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_"]);
365
571
  function hasDefaultExport(src) {
366
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);
367
573
  }
@@ -472,9 +678,7 @@ function checkApiExports(root, model) {
472
678
  } catch {
473
679
  continue;
474
680
  }
475
- const exported = API_METHODS.filter(
476
- (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)
477
- );
681
+ const exported = detectApiMethods(src);
478
682
  if (exported.length === 0) {
479
683
  const lower = ["get", "post", "put", "patch", "delete", "options"].filter(
480
684
  (m) => new RegExp(`export\\s+(async\\s+)?function\\s+${m}\\b`).test(src)
@@ -941,39 +1145,110 @@ async function preview(root, args) {
941
1145
  return new Promise(() => {
942
1146
  });
943
1147
  }
944
- async function routes(root, args) {
945
- const model = scanProject(root);
946
- if (args.flags.json) {
947
- printJson({
948
- pages: model.pages.map((p) => ({ route: p.routePath, file: p.file, dynamic: p.dynamic })),
949
- layouts: model.layouts.map((l) => ({ dir: l.dir === "" ? "/" : l.dir, file: l.file })),
950
- notFound: model.notFound,
951
- error: model.error,
952
- apis: model.apis.map((a) => ({ route: a.routePath, file: a.file, params: a.params })),
953
- hasServer: model.hasServer
954
- });
955
- return 0;
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}"` : ""}`);
956
1178
  }
1179
+ }
1180
+ function printPages(model, detail) {
957
1181
  console.log(pc3.bold("\nPages"));
958
1182
  if (model.pages.length === 0) console.log(pc3.dim(" (none)"));
959
1183
  for (const p of model.pages) {
960
- console.log(` ${pc3.green(p.routePath.padEnd(28))} ${pc3.dim(p.file)}`);
1184
+ console.log(` ${pc3.green(p.routePath.padEnd(ROUTE_COLUMN))} ${pc3.dim(p.file)}`);
1185
+ if (detail) printPageSpec(extractPageSpec(p));
961
1186
  }
962
1187
  if (model.layouts.length || model.notFound || model.error) {
963
1188
  console.log(pc3.bold("\nSpecial"));
964
1189
  for (const l of model.layouts) {
965
- console.log(` ${pc3.cyan(("layout " + (l.dir === "" ? "/" : "/" + l.dir)).padEnd(28))} ${pc3.dim(l.file)}`);
1190
+ const label = "layout " + (l.dir === "" ? "/" : "/" + l.dir);
1191
+ console.log(` ${pc3.cyan(label.padEnd(ROUTE_COLUMN))} ${pc3.dim(l.file)}`);
966
1192
  }
967
- if (model.notFound) console.log(` ${pc3.cyan("404".padEnd(28))} ${pc3.dim(model.notFound)}`);
968
- if (model.error) console.log(` ${pc3.cyan("error".padEnd(28))} ${pc3.dim(model.error)}`);
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)}`);
969
1195
  }
970
- if (model.hasServer) {
971
- console.log(pc3.bold("\nAPI"));
972
- if (model.apis.length === 0) console.log(pc3.dim(" (none)"));
973
- for (const a of model.apis) {
974
- console.log(` ${pc3.yellow(a.routePath.padEnd(28))} ${pc3.dim(a.file)}`);
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;
975
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;
976
1249
  }
1250
+ if (showPages) printPages(model, detail);
1251
+ if (showApis && (model.hasServer || onlyApis)) printApis(model, detail);
977
1252
  console.log("");
978
1253
  return 0;
979
1254
  }
@@ -1322,7 +1597,7 @@ ${pc3.bold("Commands")}
1322
1597
  build Build client and optional server into dist/
1323
1598
  preview Run the production build locally
1324
1599
  check Static verification (routes, boundaries, forbidden files)
1325
- routes Show page and API URL map
1600
+ routes Show page and API URL map (--pages | --apis | --detail)
1326
1601
  add <kind> <name> Scaffold a page | api | ui | block | skill | agents
1327
1602
  (add skill with no name lists the skills and where they sit)
1328
1603
  docs [name] Show the bundled guide and component reference
@@ -1335,6 +1610,9 @@ ${pc3.bold("Options")}
1335
1610
  --port <n> Port for dev/preview
1336
1611
  --root <dir> Project root (default: cwd)
1337
1612
  --force Overwrite when scaffolding
1613
+ --pages Show only page routes (routes)
1614
+ --apis Show only API routes (routes)
1615
+ --detail Add each route's static spec (routes)
1338
1616
  --list List pages without showing one (docs)
1339
1617
  --lang <ja|en> Documentation language (docs, search, default: ja)
1340
1618
  --regex Treat the query as a regular expression (search)