@fragno-dev/cli 0.1.23 → 0.2.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @fragno-dev/cli@0.1.23 build /home/runner/work/fragno/fragno/apps/fragno-cli
2
+ > @fragno-dev/cli@0.2.1 build /home/runner/work/fragno/fragno/apps/fragno-cli
3
3
  > tsdown
4
4
 
5
5
  ℹ tsdown v0.15.12 powered by rolldown v1.0.0-beta.45
@@ -10,9 +10,9 @@
10
10
  ℹ Build start
11
11
  ℹ Granting execute permission to dist/cli.d.ts
12
12
  ℹ Granting execute permission to dist/cli.js
13
- ℹ dist/cli.js 37.36 kB │ gzip: 9.31 kB
14
- ℹ dist/cli.js.map 75.92 kB │ gzip: 18.59 kB
15
- ℹ dist/cli.d.ts.map  0.83 kB │ gzip: 0.38 kB
16
- ℹ dist/cli.d.ts  2.01 kB │ gzip: 0.49 kB
17
- ℹ 4 files, total: 116.12 kB
18
- ✔ Build complete in 11494ms
13
+ ℹ dist/cli.js 42.56 kB │ gzip: 10.64 kB
14
+ ℹ dist/cli.js.map 86.93 kB │ gzip: 21.13 kB
15
+ ℹ dist/cli.d.ts.map  1.04 kB │ gzip: 0.47 kB
16
+ ℹ dist/cli.d.ts  2.39 kB │ gzip: 0.52 kB
17
+ ℹ 4 files, total: 132.92 kB
18
+ ✔ Build complete in 11857ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # @fragno-dev/cli
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 54541d0: feat: add `serve` command to start a local HTTP server for fragments
8
+
9
+ ## 0.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 8e9b6cd: feat(db,cli): add SqlAdapter and explicit schema output formats
14
+
15
+ BREAKING CHANGE: The database adapter API now requires SqlAdapter with explicit schema output
16
+ formats.
17
+
18
+ ### Patch Changes
19
+
20
+ - 15e3263: feat(db): require schema names and support namespace-aware SQL naming
21
+ - Updated dependencies [f569301]
22
+ - Updated dependencies [dbbbf60]
23
+ - Updated dependencies [3e07799]
24
+ - Updated dependencies [20a98f8]
25
+ - Updated dependencies [1902f30]
26
+ - Updated dependencies [15e3263]
27
+ - Updated dependencies [208cb8e]
28
+ - Updated dependencies [33f671b]
29
+ - Updated dependencies [fc803fc]
30
+ - Updated dependencies [0628c1f]
31
+ - Updated dependencies [7e1eb47]
32
+ - Updated dependencies [301e2f8]
33
+ - Updated dependencies [5f6f90e]
34
+ - Updated dependencies [1dc4e7f]
35
+ - Updated dependencies [2eafef4]
36
+ - Updated dependencies [3c9fbac]
37
+ - Updated dependencies [a5ead11]
38
+ - Updated dependencies [7d7b2b9]
39
+ - Updated dependencies [c4d4cc6]
40
+ - Updated dependencies [d4baad3]
41
+ - Updated dependencies [548bf37]
42
+ - Updated dependencies [a79e90d]
43
+ - Updated dependencies [3041732]
44
+ - Updated dependencies [7e179d1]
45
+ - Updated dependencies [0013fa6]
46
+ - Updated dependencies [7c60341]
47
+ - Updated dependencies [afb06a4]
48
+ - Updated dependencies [53e5f97]
49
+ - Updated dependencies [8e9b6cd]
50
+ - Updated dependencies [c5fd7b3]
51
+ - Updated dependencies [69b9a79]
52
+ - Updated dependencies [5cef16e]
53
+ - @fragno-dev/core@0.2.0
54
+ - @fragno-dev/db@0.3.0
55
+ - @fragno-dev/corpus@0.0.7
56
+
3
57
  ## 0.1.23
4
58
 
5
59
  ### Patch Changes
package/dist/cli.d.ts CHANGED
@@ -4,6 +4,10 @@ import "@fragno-dev/corpus";
4
4
 
5
5
  //#region src/commands/db/generate.d.ts
6
6
  declare const generateCommand: gunshi4.Command<{
7
+ format: {
8
+ type: "string";
9
+ description: string;
10
+ };
7
11
  output: {
8
12
  type: "string";
9
13
  short: string;
@@ -87,10 +91,26 @@ declare const corpusCommand: gunshi4.Command<{
87
91
  };
88
92
  }>;
89
93
  //#endregion
94
+ //#region src/commands/serve.d.ts
95
+ declare const serveCommand: gunshi4.Command<{
96
+ port: {
97
+ type: "number";
98
+ short: string;
99
+ description: string;
100
+ default: number;
101
+ };
102
+ host: {
103
+ type: "string";
104
+ short: string;
105
+ description: string;
106
+ default: string;
107
+ };
108
+ }>;
109
+ //#endregion
90
110
  //#region src/cli.d.ts
91
111
  declare const dbCommand: gunshi4.Command<gunshi4.Args>;
92
112
  declare const mainCommand: gunshi4.Command<gunshi4.Args>;
93
113
  declare function run(): Promise<void>;
94
114
  //#endregion
95
- export { corpusCommand, dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand, run, searchCommand };
115
+ export { corpusCommand, dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand, run, searchCommand, serveCommand };
96
116
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","names":[],"sources":["../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/commands/search.ts","../src/commands/corpus.ts","../src/cli.ts"],"sourcesContent":[],"mappings":";;;;;cAOa,iBAyGX,OAAA,CAzG0B;;;;;EAAf,CAAA;;;;ICFA,WAAA,EAqEX,MAAA;;;;ICtEW,KAAA,EAAA,MA6GX;;;;IC7FW,IAAA,EAAA,QAoFX;;;;ACsaF,CAAA,CAAA;;;cHzgBa,gBAqEX,OAAA,CArEyB;;;cCDd,aA6GX,OAAA,CA7GsB;;;cCgBX,eAoFX,OAAA,CApFwB;;;;;EHbb,CAAA;;;;ICFA,OAAA,EAAA,KAqEX;;;;ICtEW,WA6GX,EAAA,MAAA;;;;IC7FW,IAAA,EAAA,QAoFX;;;;ACsaF,CAAA,CAAA;;;AD1fa,cC0fA,aDtaX,EC+gBA,OAAA,CAzGwB,OD1fA,CAAA;;;;IC0fb,WAAA,EAyGX,MAAA;;;;IChmBW,KAAA,EAAA,MAGX;IAGW,WAGX,EAAA,MAAA;EAEoB,CAAA;;;;;;;;;;;;;;;;;;;;;cAXT,WAAS,OAAA,CAAA,QAGpB,OAAA,CAHoB,IAAA;cAMT,aAAW,OAAA,CAAA,QAGtB,OAAA,CAHsB,IAAA;iBAKF,GAAA,CAAA,GAAG"}
1
+ {"version":3,"file":"cli.d.ts","names":[],"sources":["../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/commands/search.ts","../src/commands/corpus.ts","../src/commands/serve.ts","../src/cli.ts"],"sourcesContent":[],"mappings":";;;;;cAOa,iBAqHX,OAAA,CArH0B;;;;;EAAf,MAAA,EAAA;;;;ECFA,CAAA;;;;ICDA,WAmIX,EAAA,MAAA;;;;ICnHW,KAAA,EAAA,MAoFX;;;;ICsaW,IAAA,EAAA,QAyGX;;;;AC/mBF,CAAA,CAAA;;;cJHa,gBAqEX,OAAA,CArEyB;;;cCDd,aAmIX,OAAA,CAnIsB;;;cCgBX,eAoFX,OAAA,CApFwB;;;;;EHbb,CAAA;;;;ICFA,OAAA,EAAA,KAqEX;;;;ICtEW,WAmIX,EAAA,MAAA;;;;ICnHW,IAAA,EAAA,QAoFX;;;;ACsaF,CAAA,CAAA;;;AD1fa,cC0fA,aDtaX,EC+gBA,OAAA,CAzGwB,OD1fA,CAAA;;;;IC0fb,WAAA,EAyGX,MAAA;;;;IC/mBW,KAAA,EAAA,MAoHX;;;;ICpGW,IAAA,EAAA,QAGX;IAGW,KAAA,EAAA,MAGX;IAEoB,WAAG,EAAA,MAAA;;;;;;;;;;;;;;;;;cD3BZ,cAoHX,OAAA,CApHuB;;;;;ILDZ,OAAA,EAAA,MAqHX;;;;ICvHW,KAAA,EAAA,MAqEX;;;;ACtEF,CAAA,CAAA;;;cIoBa,WAAS,OAAA,CAAA,QAGpB,OAAA,CAHoB,IAAA;cAMT,aAAW,OAAA,CAAA,QAGtB,OAAA,CAHsB,IAAA;ALzBX,iBK8BS,GAAA,CAAA,CLuCpB,EKvCuB,OLuCvB,CAAA,IArEyB,CAAA"}
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { cli, define } from "gunshi";
3
3
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import { dirname, join, relative, resolve } from "node:path";
5
- import { executeMigrations, generateMigrationsOrSchema } from "@fragno-dev/db/generation-engine";
5
+ import { executeMigrations, generateSchemaArtifacts } from "@fragno-dev/db/generation-engine";
6
6
  import { FragnoDatabase, isFragnoDatabase } from "@fragno-dev/db";
7
7
  import { fragnoDatabaseAdapterNameFakeSymbol, fragnoDatabaseAdapterVersionFakeSymbol } from "@fragno-dev/db/adapters";
8
8
  import { instantiatedFragmentFakeSymbol } from "@fragno-dev/core/internal/symbols";
@@ -13,6 +13,8 @@ import { getAllSubjectIdsInOrder, getAllSubjects, getCategoryTitle, getSubject,
13
13
  import { marked } from "marked";
14
14
  import { markedTerminal } from "marked-terminal";
15
15
  import { stripVTControlCharacters } from "node:util";
16
+ import { createServer } from "node:http";
17
+ import { toNodeHandler } from "@fragno-dev/node";
16
18
  import { fileURLToPath } from "node:url";
17
19
 
18
20
  //#region src/utils/load-config.ts
@@ -153,6 +155,14 @@ function isNewFragnoInstantiatedFragment(value) {
153
155
  return typeof value === "object" && value !== null && instantiatedFragmentFakeSymbol in value && value[instantiatedFragmentFakeSymbol] === instantiatedFragmentFakeSymbol;
154
156
  }
155
157
  /**
158
+ * Finds all instantiated Fragno fragments in a module's exports.
159
+ */
160
+ function findFragnoFragments(targetModule) {
161
+ const fragments = [];
162
+ for (const [_key, value] of Object.entries(targetModule)) if (isNewFragnoInstantiatedFragment(value)) fragments.push(value);
163
+ return fragments;
164
+ }
165
+ /**
156
166
  * Finds all FragnoDatabase instances in a module, including those embedded
157
167
  * in instantiated fragments.
158
168
  */
@@ -184,8 +194,12 @@ function findFragnoDatabases(targetModule) {
184
194
  //#region src/commands/db/generate.ts
185
195
  const generateCommand = define({
186
196
  name: "generate",
187
- description: "Generate schema files from FragnoDatabase definitions",
197
+ description: "Generate SQL migrations or schema outputs from FragnoDatabase definitions",
188
198
  args: {
199
+ format: {
200
+ type: "string",
201
+ description: "Output format: sql (migrations), drizzle (schema), prisma (schema)"
202
+ },
189
203
  output: {
190
204
  type: "string",
191
205
  short: "o",
@@ -210,15 +224,24 @@ const generateCommand = define({
210
224
  run: async (ctx) => {
211
225
  const targets = ctx.positionals;
212
226
  const output = ctx.values.output;
227
+ const format = ctx.values.format ?? "sql";
213
228
  const toVersion = ctx.values.to;
214
229
  const fromVersion = ctx.values.from;
215
230
  const prefix = ctx.values.prefix;
216
- const { databases: allFragnoDatabases, adapter } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
217
- if (!adapter.createSchemaGenerator && !adapter.prepareMigrations) throw new Error("The adapter does not support schema generation. Please use an adapter that implements either createSchemaGenerator or prepareMigrations.");
218
- console.log("Generating schema...");
231
+ const { databases: allFragnoDatabases } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
232
+ const allowedFormats = [
233
+ "sql",
234
+ "drizzle",
235
+ "prisma"
236
+ ];
237
+ if (!allowedFormats.includes(format)) throw new Error(`Unsupported format '${format}'. Use one of: ${allowedFormats.join(", ")}.`);
238
+ if (format !== "sql" && (toVersion !== void 0 || fromVersion !== void 0)) throw new Error("--from and --to are only supported for SQL migration output.");
239
+ if (format === "sql") console.log("Generating SQL migrations...");
240
+ else console.log(`Generating ${format} schema output...`);
219
241
  let results;
220
242
  try {
221
- results = await generateMigrationsOrSchema(allFragnoDatabases, {
243
+ results = await generateSchemaArtifacts(allFragnoDatabases, {
244
+ format,
222
245
  path: output,
223
246
  toVersion,
224
247
  fromVersion
@@ -241,10 +264,13 @@ const generateCommand = define({
241
264
  }
242
265
  console.log(`✓ Generated: ${finalOutputPath}`);
243
266
  }
244
- console.log(`\n✓ Schema generated successfully!`);
267
+ console.log(`\n✓ Output generated successfully!`);
245
268
  console.log(` Files generated: ${results.length}`);
246
269
  console.log(` Fragments:`);
247
- for (const db of allFragnoDatabases) console.log(` - ${db.namespace} (version ${db.schema.version})`);
270
+ for (const db of allFragnoDatabases) {
271
+ const namespaceLabel = db.namespace ?? "(none)";
272
+ console.log(` - ${db.schema.name} [${namespaceLabel}] (version ${db.schema.version})`);
273
+ }
248
274
  }
249
275
  });
250
276
 
@@ -252,7 +278,7 @@ const generateCommand = define({
252
278
  //#region src/commands/db/migrate.ts
253
279
  const migrateCommand = define({
254
280
  name: "migrate",
255
- description: "Run database migrations for all fragments to their latest versions",
281
+ description: "Run SQL database migrations for all fragments to their latest versions",
256
282
  args: {},
257
283
  run: async (ctx) => {
258
284
  const targets = ctx.positionals;
@@ -301,36 +327,48 @@ const infoCommand = define({
301
327
  if (targets.length === 0) throw new Error("At least one target file path is required");
302
328
  const { databases: allFragnoDatabases } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
303
329
  const dbInfos = await Promise.all(allFragnoDatabases.map(async (fragnoDb) => {
330
+ const adapterMetadata = fragnoDb.adapter.adapterMetadata;
331
+ const databaseType = adapterMetadata?.databaseType;
332
+ const sqliteProfile = adapterMetadata?.sqliteProfile;
333
+ const namespaceKey = fragnoDb.namespace ?? fragnoDb.schema.name;
304
334
  const info = {
305
- namespace: fragnoDb.namespace,
335
+ namespace: fragnoDb.namespace ?? "(none)",
306
336
  schemaVersion: fragnoDb.schema.version,
307
- migrationSupport: !!fragnoDb.adapter.prepareMigrations
337
+ migrationSupport: !!fragnoDb.adapter.prepareMigrations,
338
+ databaseType,
339
+ sqliteProfile: databaseType === "sqlite" ? sqliteProfile : void 0
308
340
  };
309
341
  if (fragnoDb.adapter.prepareMigrations) {
310
- info.currentVersion = await fragnoDb.adapter.getSchemaVersion(fragnoDb.namespace);
342
+ info.currentVersion = await fragnoDb.adapter.getSchemaVersion(namespaceKey);
311
343
  if (info.schemaVersion.toString() !== info.currentVersion) info.status = `Migrations pending`;
312
344
  else info.status = "Up to date";
313
345
  } else info.status = "Schema only";
314
346
  return info;
315
347
  }));
348
+ const showDatabaseType = dbInfos.some((info) => !!info.databaseType);
349
+ const showSqliteProfile = dbInfos.some((info) => info.databaseType === "sqlite");
316
350
  const hasMigrationSupport = dbInfos.some((info) => info.migrationSupport);
317
351
  console.log("");
318
352
  console.log(`Database Information:`);
319
353
  console.log("");
320
354
  const namespaceHeader = "Namespace";
321
355
  const versionHeader = "Schema";
356
+ const databaseHeader = "DB";
357
+ const profileHeader = "SQLite";
322
358
  const currentHeader = "Current";
323
359
  const statusHeader = "Status";
324
360
  const maxNamespaceLen = Math.max(9, ...dbInfos.map((info) => info.namespace.length));
325
361
  const namespaceWidth = Math.max(maxNamespaceLen + 2, 20);
326
362
  const versionWidth = 8;
363
+ const databaseWidth = 8;
364
+ const profileWidth = 10;
327
365
  const currentWidth = 9;
328
366
  const statusWidth = 25;
329
- console.log(namespaceHeader.padEnd(namespaceWidth) + versionHeader.padEnd(versionWidth) + (hasMigrationSupport ? currentHeader.padEnd(currentWidth) : "") + statusHeader);
330
- console.log("-".repeat(namespaceWidth) + "-".repeat(versionWidth) + (hasMigrationSupport ? "-".repeat(currentWidth) : "") + "-".repeat(statusWidth));
367
+ console.log(namespaceHeader.padEnd(namespaceWidth) + versionHeader.padEnd(versionWidth) + (showDatabaseType ? databaseHeader.padEnd(databaseWidth) : "") + (showSqliteProfile ? profileHeader.padEnd(profileWidth) : "") + (hasMigrationSupport ? currentHeader.padEnd(currentWidth) : "") + statusHeader);
368
+ console.log("-".repeat(namespaceWidth) + "-".repeat(versionWidth) + (showDatabaseType ? "-".repeat(databaseWidth) : "") + (showSqliteProfile ? "-".repeat(profileWidth) : "") + (hasMigrationSupport ? "-".repeat(currentWidth) : "") + "-".repeat(statusWidth));
331
369
  for (const info of dbInfos) {
332
370
  const currentVersionStr = info.currentVersion !== void 0 ? String(info.currentVersion) : "-";
333
- console.log(info.namespace.padEnd(namespaceWidth) + String(info.schemaVersion).padEnd(versionWidth) + (hasMigrationSupport ? currentVersionStr.padEnd(currentWidth) : "") + (info.status || "-"));
371
+ console.log(info.namespace.padEnd(namespaceWidth) + String(info.schemaVersion).padEnd(versionWidth) + (showDatabaseType ? (info.databaseType ?? "-").padEnd(databaseWidth) : "") + (showSqliteProfile ? (info.sqliteProfile ?? "-").padEnd(profileWidth) : "") + (hasMigrationSupport ? currentVersionStr.padEnd(currentWidth) : "") + (info.status || "-"));
334
372
  }
335
373
  console.log("");
336
374
  if (!hasMigrationSupport) {
@@ -867,6 +905,85 @@ const corpusCommand = define({
867
905
  }
868
906
  });
869
907
 
908
+ //#endregion
909
+ //#region src/commands/serve.ts
910
+ const serveCommand = define({
911
+ name: "serve",
912
+ description: "Start a local HTTP server to serve one or more Fragno fragments",
913
+ args: {
914
+ port: {
915
+ type: "number",
916
+ short: "p",
917
+ description: "Port to listen on",
918
+ default: 8080
919
+ },
920
+ host: {
921
+ type: "string",
922
+ short: "H",
923
+ description: "Host to bind to",
924
+ default: "localhost"
925
+ }
926
+ },
927
+ run: async (ctx) => {
928
+ const targets = ctx.positionals;
929
+ const port = ctx.values.port ?? 8080;
930
+ const host = ctx.values.host ?? "localhost";
931
+ const cwd = process.cwd();
932
+ if (targets.length === 0) throw new Error("No fragment files specified.\n\nUsage: fragno-cli serve <fragment-file> [fragment-file...]\n\nExample: fragno-cli serve ./src/my-fragment.ts");
933
+ const targetPaths = targets.map((target) => resolve(cwd, target));
934
+ const allFragments = [];
935
+ for (const targetPath of targetPaths) {
936
+ const relativePath = relative(cwd, targetPath);
937
+ const fragments = findFragnoFragments(await loadConfig$1(targetPath));
938
+ if (fragments.length === 0) {
939
+ console.warn(`Warning: No instantiated fragments found in ${relativePath}.\nMake sure you export an instantiated fragment (e.g., the return value of createMyFragment()).\n`);
940
+ continue;
941
+ }
942
+ allFragments.push(...fragments);
943
+ console.log(` Found ${fragments.length} fragment(s) in ${relativePath}: ${fragments.map((f) => f.name).join(", ")}`);
944
+ }
945
+ if (allFragments.length === 0) throw new Error("No instantiated fragments found in any of the specified files.\nMake sure your files export instantiated fragments.");
946
+ const handlers = allFragments.map((fragment) => ({
947
+ mountRoute: fragment.mountRoute,
948
+ handler: toNodeHandler(fragment.handler.bind(fragment)),
949
+ fragment
950
+ }));
951
+ const server = createServer((req, res) => {
952
+ const url = req.url ?? "";
953
+ for (const { mountRoute, handler } of handlers) if (url.startsWith(mountRoute)) return handler(req, res);
954
+ res.statusCode = 404;
955
+ res.setHeader("Content-Type", "application/json");
956
+ res.end(JSON.stringify({
957
+ error: "Not Found",
958
+ availableRoutes: handlers.map((h) => h.mountRoute)
959
+ }));
960
+ });
961
+ server.listen(port, host, () => {
962
+ const hostStr = addressToString(server);
963
+ console.log(`\nFragno server is running on: ${hostStr}\n`);
964
+ for (const { fragment } of handlers) {
965
+ console.log(`Fragment: ${fragment.name}`);
966
+ console.log(` Mount: ${hostStr}${fragment.mountRoute}`);
967
+ const routes = fragment.routes;
968
+ if (routes.length > 0) {
969
+ console.log(" Routes:");
970
+ for (const route of routes) console.log(` ${route.method} ${fragment.mountRoute}${route.path}`);
971
+ }
972
+ console.log("");
973
+ }
974
+ });
975
+ }
976
+ });
977
+ function addressToString(server, protocol = "http") {
978
+ const addr = server.address();
979
+ if (!addr) throw new Error("Address invalid");
980
+ if (typeof addr === "string") return addr;
981
+ let host = addr.address;
982
+ if (host === "::" || host === "0.0.0.0") host = "localhost";
983
+ if (addr.family === "IPv6" && host !== "localhost") host = `[${host}]`;
984
+ return `${protocol}://${host}:${addr.port}`;
985
+ }
986
+
870
987
  //#endregion
871
988
  //#region src/cli.ts
872
989
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -894,6 +1011,10 @@ async function run() {
894
1011
  name: "fragno-cli corpus",
895
1012
  version
896
1013
  });
1014
+ else if (args[0] === "serve") await cli(args.slice(1), serveCommand, {
1015
+ name: "fragno-cli serve",
1016
+ version
1017
+ });
897
1018
  else if (args[0] === "db") {
898
1019
  const subCommandName = args[1];
899
1020
  if (!subCommandName || subCommandName === "--help" || subCommandName === "-h") {
@@ -936,11 +1057,13 @@ async function run() {
936
1057
  console.log(" fragno-cli <COMMAND>");
937
1058
  console.log("");
938
1059
  console.log("COMMANDS:");
1060
+ console.log(" serve Start a local HTTP server to serve fragments");
939
1061
  console.log(" db Database management commands");
940
1062
  console.log(" search Search the Fragno documentation");
941
1063
  console.log(" corpus View code examples and documentation for Fragno");
942
1064
  console.log("");
943
1065
  console.log("For more info, run any command with the `--help` flag:");
1066
+ console.log(" fragno-cli serve --help");
944
1067
  console.log(" fragno-cli db --help");
945
1068
  console.log(" fragno-cli search --help");
946
1069
  console.log(" fragno-cli corpus --help");
@@ -963,5 +1086,5 @@ async function run() {
963
1086
  if (import.meta.main) await run();
964
1087
 
965
1088
  //#endregion
966
- export { corpusCommand, dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand, run, searchCommand };
1089
+ export { corpusCommand, dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand, run, searchCommand, serveCommand };
967
1090
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":["loadConfig","c12LoadConfig","loadConfig","allDatabases: FragnoDatabase<AnySchema>[]","adapter: DatabaseAdapter | undefined","firstAdapterFile: string | undefined","fragnoDatabases: FragnoDatabase<AnySchema>[]","results: { schema: string; path: string; namespace: string }[]","results: ExecuteMigrationResult[]","info: {\n namespace: string;\n schemaVersion: number;\n migrationSupport: boolean;\n currentVersion?: string;\n pendingVersions?: string;\n status?: string;\n }","lines: string[]","matches: CodeBlockMatch[]","startLine: number | undefined","endLine: number | undefined"],"sources":["../src/utils/load-config.ts","../src/utils/find-fragno-databases.ts","../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/utils/format-search-results.ts","../src/commands/search.ts","../src/commands/corpus.ts","../src/cli.ts"],"sourcesContent":["import { loadConfig as c12LoadConfig } from \"c12\";\nimport { readFile, access } from \"node:fs/promises\";\nimport { dirname, resolve, join } from \"node:path\";\nimport { constants } from \"node:fs\";\n\n/**\n * Checks if a file exists using async API.\n */\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Walks up the directory tree from the target path to find a tsconfig.json file.\n */\nasync function findTsconfig(startPath: string): Promise<string | null> {\n let currentDir = dirname(startPath);\n const root = resolve(\"/\");\n\n while (currentDir !== root) {\n const tsconfigPath = join(currentDir, \"tsconfig.json\");\n if (await fileExists(tsconfigPath)) {\n return tsconfigPath;\n }\n currentDir = dirname(currentDir);\n }\n\n return null;\n}\n\n/**\n * Strips comments from JSONC (JSON with Comments) content.\n */\nexport function stripJsonComments(jsonc: string): string {\n // Remove single-line comments (// ...)\n let result = jsonc.replace(/\\/\\/[^\\n]*/g, \"\");\n\n // Remove multi-line comments (/* ... */)\n result = result.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n\n return result;\n}\n\n/**\n * Converts TypeScript path aliases to jiti alias format.\n * Strips trailing '*' from aliases and paths, and resolves paths relative to baseUrl.\n */\nexport function convertTsconfigPathsToJitiAlias(\n tsconfigPaths: Record<string, string[]>,\n baseUrlResolved: string,\n): Record<string, string> {\n return Object.fromEntries(\n Object.entries(tsconfigPaths).map(([_alias, paths]) => {\n const pathsArray = paths as string[];\n // trim '*' if present and resolve the actual path\n const aliasKey = _alias.endsWith(\"*\") ? _alias.slice(0, -1) : _alias;\n const pathValue = pathsArray[0].endsWith(\"*\") ? pathsArray[0].slice(0, -1) : pathsArray[0];\n return [aliasKey, resolve(baseUrlResolved, pathValue)];\n }),\n );\n}\n\n/**\n * Resolves tsconfig path aliases for use with jiti.\n */\nasync function resolveTsconfigAliases(targetPath: string): Promise<Record<string, string>> {\n const tsconfigPath = await findTsconfig(targetPath);\n\n if (!tsconfigPath) {\n return {};\n }\n\n try {\n const tsconfigContent = await readFile(tsconfigPath, \"utf-8\");\n // Strip comments to handle JSONC format\n const jsonContent = stripJsonComments(tsconfigContent);\n const tsconfig = JSON.parse(jsonContent);\n const tsconfigPaths = tsconfig?.compilerOptions?.paths;\n\n if (!tsconfigPaths || typeof tsconfigPaths !== \"object\") {\n return {};\n }\n\n const tsconfigDir = dirname(tsconfigPath);\n const baseUrl = tsconfig?.compilerOptions?.baseUrl || \".\";\n const baseUrlResolved = resolve(tsconfigDir, baseUrl);\n\n // Convert tsconfig paths to jiti alias format\n return convertTsconfigPathsToJitiAlias(tsconfigPaths, baseUrlResolved);\n } catch (error) {\n console.warn(`Warning: Failed to parse tsconfig at ${tsconfigPath}:`, error);\n return {};\n }\n}\n\n/**\n * Loads a config file using c12 with automatic tsconfig path alias resolution.\n */\nexport async function loadConfig(path: string): Promise<Record<string, unknown>> {\n const alias = await resolveTsconfigAliases(path);\n\n const { config } = await c12LoadConfig({\n configFile: path,\n jitiOptions: {\n alias,\n },\n });\n\n return config as Record<string, unknown>;\n}\n","import { isFragnoDatabase, type DatabaseAdapter, FragnoDatabase } from \"@fragno-dev/db\";\nimport {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"@fragno-dev/db/adapters\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport { instantiatedFragmentFakeSymbol } from \"@fragno-dev/core/internal/symbols\";\nimport { type FragnoInstantiatedFragment } from \"@fragno-dev/core\";\nimport { loadConfig } from \"./load-config\";\nimport { relative } from \"node:path\";\n\nexport async function importFragmentFile(path: string): Promise<Record<string, unknown>> {\n // Enable dry run mode for database schema extraction\n process.env[\"FRAGNO_INIT_DRY_RUN\"] = \"true\";\n\n try {\n const config = await loadConfig(path);\n\n const databases = findFragnoDatabases(config);\n const adapterNames = databases.map(\n (db) =>\n `${db.adapter[fragnoDatabaseAdapterNameFakeSymbol]}@${db.adapter[fragnoDatabaseAdapterVersionFakeSymbol]}`,\n );\n const uniqueAdapterNames = [...new Set(adapterNames)];\n\n if (uniqueAdapterNames.length > 1) {\n throw new Error(\n `All Fragno databases must use the same adapter name and version. ` +\n `Found mismatch: (${adapterNames.join(\", \")})`,\n );\n }\n\n return {\n adapter: databases[0].adapter,\n databases,\n };\n } finally {\n // Clean up after loading\n delete process.env[\"FRAGNO_INIT_DRY_RUN\"];\n }\n}\n\n/**\n * Imports multiple fragment files and validates they all use the same adapter.\n * Returns the combined databases from all files.\n */\nexport async function importFragmentFiles(paths: string[]): Promise<{\n adapter: DatabaseAdapter;\n databases: FragnoDatabase<AnySchema>[];\n}> {\n // De-duplicate paths (in case same file was specified multiple times)\n const uniquePaths = Array.from(new Set(paths));\n\n if (uniquePaths.length === 0) {\n throw new Error(\"No fragment files provided\");\n }\n\n const allDatabases: FragnoDatabase<AnySchema>[] = [];\n let adapter: DatabaseAdapter | undefined;\n let firstAdapterFile: string | undefined;\n const cwd = process.cwd();\n\n for (const path of uniquePaths) {\n const relativePath = relative(cwd, path);\n\n try {\n const result = await importFragmentFile(path);\n const databases = result[\"databases\"] as FragnoDatabase<AnySchema>[];\n const fileAdapter = result[\"adapter\"] as DatabaseAdapter;\n\n if (databases.length === 0) {\n console.warn(\n `Warning: No FragnoDatabase instances found in ${relativePath}.\\n` +\n `Make sure you export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n continue;\n }\n\n // Set the adapter from the first file with databases\n if (!adapter) {\n adapter = fileAdapter;\n firstAdapterFile = relativePath;\n }\n\n // Validate all files use the same adapter name and version\n const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n const fileAdapterName = fileAdapter[fragnoDatabaseAdapterNameFakeSymbol];\n const fileAdapterVersion = fileAdapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n if (firstAdapterName !== fileAdapterName || firstAdapterVersion !== fileAdapterVersion) {\n const firstAdapterInfo = `${firstAdapterName}@${firstAdapterVersion}`;\n const fileAdapterInfo = `${fileAdapterName}@${fileAdapterVersion}`;\n\n throw new Error(\n `All fragments must use the same database adapter. Mixed adapters found:\\n` +\n ` - ${firstAdapterFile}: ${firstAdapterInfo}\\n` +\n ` - ${relativePath}: ${fileAdapterInfo}\\n\\n` +\n `Make sure all fragments use the same adapter name and version.`,\n );\n }\n\n allDatabases.push(...databases);\n console.log(` Found ${databases.length} database(s) in ${relativePath}`);\n } catch (error) {\n throw new Error(\n `Failed to import fragment file ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n if (allDatabases.length === 0) {\n throw new Error(\n `No FragnoDatabase instances found in any of the target files.\\n` +\n `Make sure your files export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n }\n\n if (!adapter) {\n throw new Error(\"No adapter found in any of the fragment files\");\n }\n\n return {\n adapter,\n databases: allDatabases,\n };\n}\n\nfunction isNewFragnoInstantiatedFragment(\n value: unknown,\n): value is FragnoInstantiatedFragment<\n [],\n unknown,\n Record<string, unknown>,\n Record<string, unknown>,\n Record<string, unknown>,\n unknown,\n Record<string, unknown>\n> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n instantiatedFragmentFakeSymbol in value &&\n value[instantiatedFragmentFakeSymbol] === instantiatedFragmentFakeSymbol\n );\n}\n\n/**\n * Finds all FragnoDatabase instances in a module, including those embedded\n * in instantiated fragments.\n */\nexport function findFragnoDatabases(\n targetModule: Record<string, unknown>,\n): FragnoDatabase<AnySchema>[] {\n const fragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const [_key, value] of Object.entries(targetModule)) {\n if (isFragnoDatabase(value)) {\n fragnoDatabases.push(value);\n } else if (isNewFragnoInstantiatedFragment(value)) {\n // Handle new fragment API\n const internal = value.$internal;\n const deps = internal.deps as Record<string, unknown>;\n const options = internal.options as Record<string, unknown>;\n\n // Check if this is a database fragment by looking for implicit database dependencies\n if (!deps[\"db\"] || !deps[\"schema\"]) {\n continue;\n }\n\n const schema = deps[\"schema\"] as AnySchema;\n const namespace = deps[\"namespace\"] as string;\n const databaseAdapter = options[\"databaseAdapter\"] as DatabaseAdapter | undefined;\n\n if (!databaseAdapter) {\n console.warn(\n `Warning: Fragment '${value.name}' appears to be a database fragment but no databaseAdapter found in options.`,\n );\n continue;\n }\n\n fragnoDatabases.push(\n new FragnoDatabase({\n namespace,\n schema,\n adapter: databaseAdapter,\n }),\n );\n }\n }\n\n return fragnoDatabases;\n}\n","import { writeFile, mkdir } from \"node:fs/promises\";\nimport { resolve, dirname } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { generateMigrationsOrSchema } from \"@fragno-dev/db/generation-engine\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\n\n// Define the db generate command with type safety\nexport const generateCommand = define({\n name: \"generate\",\n description: \"Generate schema files from FragnoDatabase definitions\",\n args: {\n output: {\n type: \"string\",\n short: \"o\",\n description:\n \"Output path: for single file, exact file path; for multiple files, output directory (default: current directory)\",\n },\n from: {\n type: \"number\",\n short: \"f\",\n description: \"Source version to generate migration from (default: current database version)\",\n },\n to: {\n type: \"number\",\n short: \"t\",\n description: \"Target version to generate migration to (default: latest schema version)\",\n },\n prefix: {\n type: \"string\",\n short: \"p\",\n description: \"String to prepend to the generated file (e.g., '/* eslint-disable */')\",\n },\n },\n run: async (ctx) => {\n // With `define()` and `multiple: true`, targets is properly typed as string[]\n const targets = ctx.positionals;\n const output = ctx.values.output;\n const toVersion = ctx.values.to;\n const fromVersion = ctx.values.from;\n const prefix = ctx.values.prefix;\n\n // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files and validate they use the same adapter\n const { databases: allFragnoDatabases, adapter } = await importFragmentFiles(targetPaths);\n\n // Check if adapter supports any form of schema generation\n if (!adapter.createSchemaGenerator && !adapter.prepareMigrations) {\n throw new Error(\n `The adapter does not support schema generation. ` +\n `Please use an adapter that implements either createSchemaGenerator or prepareMigrations.`,\n );\n }\n\n // Generate schema for all fragments\n console.log(\"Generating schema...\");\n\n let results: { schema: string; path: string; namespace: string }[];\n try {\n results = await generateMigrationsOrSchema(allFragnoDatabases, {\n path: output,\n toVersion,\n fromVersion,\n });\n } catch (error) {\n throw new Error(\n `Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Write all generated files\n for (const result of results) {\n // For single file: use output as exact file path\n // For multiple files: use output as base directory\n const finalOutputPath =\n output && results.length === 1\n ? resolve(process.cwd(), output)\n : output\n ? resolve(process.cwd(), output, result.path)\n : resolve(process.cwd(), result.path);\n\n // Ensure parent directory exists\n const parentDir = dirname(finalOutputPath);\n try {\n await mkdir(parentDir, { recursive: true });\n } catch (error) {\n throw new Error(\n `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Write schema to file\n try {\n const content = prefix ? `${prefix}\\n${result.schema}` : result.schema;\n await writeFile(finalOutputPath, content, { encoding: \"utf-8\" });\n } catch (error) {\n throw new Error(\n `Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n console.log(`✓ Generated: ${finalOutputPath}`);\n }\n\n console.log(`\\n✓ Schema generated successfully!`);\n console.log(` Files generated: ${results.length}`);\n console.log(` Fragments:`);\n for (const db of allFragnoDatabases) {\n console.log(` - ${db.namespace} (version ${db.schema.version})`);\n }\n },\n});\n","import { resolve } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\nimport { executeMigrations, type ExecuteMigrationResult } from \"@fragno-dev/db/generation-engine\";\n\nexport const migrateCommand = define({\n name: \"migrate\",\n description: \"Run database migrations for all fragments to their latest versions\",\n args: {},\n run: async (ctx) => {\n const targets = ctx.positionals;\n\n if (targets.length === 0) {\n throw new Error(\"At least one target file path is required\");\n }\n\n // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files and validate they use the same adapter\n const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);\n\n console.log(\"\\nMigrating all fragments to their latest versions...\\n\");\n\n let results: ExecuteMigrationResult[];\n try {\n results = await executeMigrations(allFragnoDatabases);\n } catch (error) {\n throw new Error(\n `Migration failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Display progress for each result\n for (const result of results) {\n console.log(`Fragment: ${result.namespace}`);\n console.log(` Current version: ${result.fromVersion}`);\n console.log(` Target version: ${result.toVersion}`);\n\n if (result.didMigrate) {\n console.log(` ✓ Migration completed: v${result.fromVersion} → v${result.toVersion}\\n`);\n } else {\n console.log(` ✓ Already at latest version. No migration needed.\\n`);\n }\n }\n\n // Summary\n console.log(\"═══════════════════════════════════════\");\n console.log(\"Migration Summary\");\n console.log(\"═══════════════════════════════════════\");\n\n const migrated = results.filter((r) => r.didMigrate);\n const skipped = results.filter((r) => !r.didMigrate);\n\n if (migrated.length > 0) {\n console.log(`\\n✓ Migrated ${migrated.length} fragment(s):`);\n for (const r of migrated) {\n console.log(` - ${r.namespace}: v${r.fromVersion} → v${r.toVersion}`);\n }\n }\n\n if (skipped.length > 0) {\n console.log(`\\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);\n for (const r of skipped) {\n console.log(` - ${r.namespace}: v${r.toVersion}`);\n }\n }\n\n for (const db of allFragnoDatabases) {\n await db.adapter.close();\n }\n\n console.log(\"\\n✓ All migrations completed successfully\");\n },\n});\n","import { resolve } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\n\nexport const infoCommand = define({\n name: \"info\",\n description: \"Display database information and migration status\",\n args: {},\n run: async (ctx) => {\n const targets = ctx.positionals;\n\n if (targets.length === 0) {\n throw new Error(\"At least one target file path is required\");\n }\n\n // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files\n const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);\n\n // Collect database information\n const dbInfos = await Promise.all(\n allFragnoDatabases.map(async (fragnoDb) => {\n const info: {\n namespace: string;\n schemaVersion: number;\n migrationSupport: boolean;\n currentVersion?: string;\n pendingVersions?: string;\n status?: string;\n } = {\n namespace: fragnoDb.namespace,\n schemaVersion: fragnoDb.schema.version,\n migrationSupport: !!fragnoDb.adapter.prepareMigrations,\n };\n\n // Get current database version if migrations are supported\n if (fragnoDb.adapter.prepareMigrations) {\n const currentVersion = await fragnoDb.adapter.getSchemaVersion(fragnoDb.namespace);\n info.currentVersion = currentVersion;\n // info.pendingVersions = fragnoDb.schema.version - currentVersion;\n\n if (info.schemaVersion.toString() !== info.currentVersion) {\n info.status = `Migrations pending`;\n } else {\n info.status = \"Up to date\";\n }\n } else {\n info.status = \"Schema only\";\n }\n\n return info;\n }),\n );\n\n // Determine if any database supports migrations\n const hasMigrationSupport = dbInfos.some((info) => info.migrationSupport);\n\n // Print compact table\n console.log(\"\");\n console.log(`Database Information:`);\n console.log(\"\");\n\n // Table header\n const namespaceHeader = \"Namespace\";\n const versionHeader = \"Schema\";\n const currentHeader = \"Current\";\n const statusHeader = \"Status\";\n\n const maxNamespaceLen = Math.max(\n namespaceHeader.length,\n ...dbInfos.map((info) => info.namespace.length),\n );\n const namespaceWidth = Math.max(maxNamespaceLen + 2, 20);\n const versionWidth = 8;\n const currentWidth = 9;\n const statusWidth = 25;\n\n // Print table\n console.log(\n namespaceHeader.padEnd(namespaceWidth) +\n versionHeader.padEnd(versionWidth) +\n (hasMigrationSupport ? currentHeader.padEnd(currentWidth) : \"\") +\n statusHeader,\n );\n console.log(\n \"-\".repeat(namespaceWidth) +\n \"-\".repeat(versionWidth) +\n (hasMigrationSupport ? \"-\".repeat(currentWidth) : \"\") +\n \"-\".repeat(statusWidth),\n );\n\n for (const info of dbInfos) {\n const currentVersionStr =\n info.currentVersion !== undefined ? String(info.currentVersion) : \"-\";\n console.log(\n info.namespace.padEnd(namespaceWidth) +\n String(info.schemaVersion).padEnd(versionWidth) +\n (hasMigrationSupport ? currentVersionStr.padEnd(currentWidth) : \"\") +\n (info.status || \"-\"),\n );\n }\n\n // Print help text\n console.log(\"\");\n if (!hasMigrationSupport) {\n console.log(\"Note: These adapters do not support migrations.\");\n console.log(\"Use 'fragno-cli db generate' to generate schema files.\");\n } else {\n console.log(\"Run 'fragno-cli db migrate <target>' to apply pending migrations.\");\n }\n },\n});\n","interface SearchResult {\n id: string;\n type: \"page\" | \"heading\" | \"text\";\n content: string;\n breadcrumbs?: string[];\n contentWithHighlights?: Array<{\n type: string;\n content: string;\n styles?: { highlight?: boolean };\n }>;\n url: string;\n}\n\ninterface MergedResult {\n url: string;\n urlWithMd: string;\n fullUrl: string;\n fullUrlWithMd: string;\n title?: string;\n breadcrumbs?: string[];\n type: \"page\" | \"heading\" | \"text\";\n sections: Array<{\n content: string;\n type: \"page\" | \"heading\" | \"text\";\n }>;\n}\n\n/**\n * Merge search results by URL, grouping sections and content under each URL (without hash)\n */\nexport function mergeResultsByUrl(results: SearchResult[], baseUrl: string): MergedResult[] {\n const mergedMap = new Map<string, MergedResult>();\n\n for (const result of results) {\n // Strip hash to get base URL for merging\n const baseUrlWithoutHash = result.url.split(\"#\")[0];\n const existing = mergedMap.get(baseUrlWithoutHash);\n\n if (existing) {\n // Add this result as a section\n existing.sections.push({\n content: result.content,\n type: result.type,\n });\n } else {\n // Create new merged result\n const urlWithMd = `${baseUrlWithoutHash}.md`;\n\n const fullUrl = `https://${baseUrl}${baseUrlWithoutHash}`;\n const fullUrlWithMd = `https://${baseUrl}${urlWithMd}`;\n\n mergedMap.set(baseUrlWithoutHash, {\n url: baseUrlWithoutHash,\n urlWithMd,\n fullUrl,\n fullUrlWithMd,\n title: result.type === \"page\" ? result.content : undefined,\n breadcrumbs: result.breadcrumbs,\n type: result.type,\n sections: [\n {\n content: result.content,\n type: result.type,\n },\n ],\n });\n }\n }\n\n return Array.from(mergedMap.values());\n}\n\n/**\n * Format merged results as markdown\n */\nexport function formatAsMarkdown(mergedResults: MergedResult[]): string {\n const lines: string[] = [];\n\n for (const result of mergedResults) {\n // Title (use first section content if it's a page, or just use content)\n const title = result.title || result.sections[0]?.content || \"Untitled\";\n lines.push(`## Page: '${title}'`);\n // Breadcrumbs\n if (result.breadcrumbs && result.breadcrumbs.length > 0) {\n lines.push(\" \" + result.breadcrumbs.join(\" > \"));\n lines.push(\"\");\n }\n\n // Both URLs\n lines.push(\"URLs:\");\n lines.push(` - ${result.fullUrl}`);\n lines.push(` - ${result.fullUrlWithMd}`);\n lines.push(\"\");\n\n // Show all sections found on this page\n if (result.sections.length > 1) {\n lines.push(\"Relevant sections:\");\n for (let i = 0; i < result.sections.length; i++) {\n const section = result.sections[i];\n // Skip the first section if it's just the page title repeated\n if (i === 0 && result.type === \"page\" && section.content === result.title) {\n continue;\n }\n lines.push(` - ${section.content}`);\n }\n lines.push(\"\");\n }\n\n lines.push(\"---\");\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format merged results as JSON\n */\nexport function formatAsJson(mergedResults: MergedResult[]): string {\n return JSON.stringify(mergedResults, null, 2);\n}\n","import { define } from \"gunshi\";\nimport {\n mergeResultsByUrl,\n formatAsMarkdown,\n formatAsJson,\n} from \"../utils/format-search-results.js\";\n\ninterface SearchResult {\n id: string;\n type: \"page\" | \"heading\" | \"text\";\n content: string;\n breadcrumbs?: string[];\n contentWithHighlights?: Array<{\n type: string;\n content: string;\n styles?: { highlight?: boolean };\n }>;\n url: string;\n}\n\nexport const searchCommand = define({\n name: \"search\",\n description: \"Search the Fragno documentation\",\n args: {\n limit: {\n type: \"number\",\n description: \"Maximum number of results to show\",\n default: 10,\n },\n json: {\n type: \"boolean\",\n description: \"Output results in JSON format\",\n default: false,\n },\n markdown: {\n type: \"boolean\",\n description: \"Output results in Markdown format (default)\",\n default: true,\n },\n \"base-url\": {\n type: \"string\",\n description: \"Base URL for the documentation site\",\n default: \"fragno.dev\",\n },\n },\n run: async (ctx) => {\n const query = ctx.positionals.join(\" \");\n\n if (!query || query.trim().length === 0) {\n throw new Error(\"Please provide a search query\");\n }\n\n // Determine output mode\n const jsonMode = ctx.values.json as boolean;\n const baseUrl = ctx.values[\"base-url\"] as string;\n\n if (!jsonMode) {\n console.log(`Searching for: \"${query}\"\\n`);\n }\n\n try {\n // Make request to the docs search API\n const encodedQuery = encodeURIComponent(query);\n const response = await fetch(`https://${baseUrl}/api/search?query=${encodedQuery}`);\n\n if (!response.ok) {\n throw new Error(`API request failed with status ${response.status}`);\n }\n\n const results = (await response.json()) as SearchResult[];\n\n // Apply limit\n const limit = ctx.values.limit as number;\n const limitedResults = results.slice(0, limit);\n\n if (limitedResults.length === 0) {\n if (jsonMode) {\n console.log(\"[]\");\n } else {\n console.log(\"No results found.\");\n }\n return;\n }\n\n // Merge results by URL\n const mergedResults = mergeResultsByUrl(limitedResults, baseUrl);\n\n // Output based on mode\n if (jsonMode) {\n console.log(formatAsJson(mergedResults));\n } else {\n // Markdown mode (default)\n console.log(\n `Found ${results.length} result${results.length === 1 ? \"\" : \"s\"}${results.length > limit ? ` (showing ${limit})` : \"\"}\\n`,\n );\n console.log(formatAsMarkdown(mergedResults));\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Search failed: ${error.message}`);\n }\n throw new Error(\"Search failed: An unknown error occurred\");\n }\n },\n});\n","import { define } from \"gunshi\";\nimport {\n getSubjects,\n getSubject,\n getAllSubjects,\n getSubjectParent,\n getSubjectChildren,\n getAllSubjectIdsInOrder,\n isCategory,\n getCategoryTitle,\n} from \"@fragno-dev/corpus\";\nimport type { Subject, Example } from \"@fragno-dev/corpus\";\nimport { marked } from \"marked\";\n// @ts-expect-error - marked-terminal types are outdated for v7\nimport { markedTerminal } from \"marked-terminal\";\nimport { stripVTControlCharacters } from \"node:util\";\n\n// Always configure marked to use terminal renderer\nmarked.use(markedTerminal());\n\ninterface PrintOptions {\n showLineNumbers: boolean;\n startLine?: number;\n endLine?: number;\n headingsOnly: boolean;\n}\n\n/**\n * Build markdown content for multiple subjects\n */\nexport function buildSubjectsMarkdown(subjects: Subject[]): string {\n let fullMarkdown = \"\";\n\n for (const subject of subjects) {\n fullMarkdown += `# ${subject.title}\\n\\n`;\n\n if (subject.description) {\n fullMarkdown += `${subject.description}\\n\\n`;\n }\n\n // Add imports block if present\n if (subject.imports) {\n fullMarkdown += `### Imports\\n\\n\\`\\`\\`typescript\\n${subject.imports}\\n\\`\\`\\`\\n\\n`;\n }\n\n // Add prelude blocks if present\n if (subject.prelude.length > 0) {\n fullMarkdown += `### Prelude\\n\\n`;\n for (const block of subject.prelude) {\n // Don't include the directive in the displayed code fence\n fullMarkdown += `\\`\\`\\`typescript\\n${block.code}\\n\\`\\`\\`\\n\\n`;\n }\n }\n\n // Add all sections\n for (const section of subject.sections) {\n fullMarkdown += `## ${section.heading}\\n\\n${section.content}\\n\\n`;\n }\n }\n\n return fullMarkdown;\n}\n\n/**\n * Add line numbers to content\n */\nexport function addLineNumbers(content: string, startFrom: number = 1): string {\n const lines = content.split(\"\\n\");\n const maxDigits = String(startFrom + lines.length - 1).length;\n\n return lines\n .map((line, index) => {\n const lineNum = startFrom + index;\n const paddedNum = String(lineNum).padStart(maxDigits, \" \");\n return `${paddedNum}│ ${line}`;\n })\n .join(\"\\n\");\n}\n\n/**\n * Filter content by line range\n */\nexport function filterByLineRange(content: string, startLine: number, endLine: number): string {\n const lines = content.split(\"\\n\");\n // Convert to 0-based index\n const start = Math.max(0, startLine - 1);\n const end = Math.min(lines.length, endLine);\n return lines.slice(start, end).join(\"\\n\");\n}\n\n/**\n * Extract headings and code block information with line numbers\n */\nexport function extractHeadingsAndBlocks(subjects: Subject[]): string {\n let output = \"\";\n let currentLine = 1;\n let lastOutputLine = 0;\n\n // Helper to add a gap indicator if we skipped lines\n const addGapIfNeeded = () => {\n if (lastOutputLine > 0 && currentLine > lastOutputLine + 1) {\n output += ` │\\n`;\n }\n };\n\n // Add instruction header\n output += \"Use --start N --end N flags to show specific line ranges\\n\\n\";\n\n for (const subject of subjects) {\n // Title\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ # ${subject.title}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n // Empty line after title - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n // Description - show full text\n if (subject.description) {\n const descLines = subject.description.split(\"\\n\");\n for (const line of descLines) {\n output += `${currentLine.toString().padStart(4, \" \")}│ ${line}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n // Empty line after description - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n\n // Imports block - show full code\n if (subject.imports) {\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ ### Imports\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n // Empty line after heading - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n output += `${currentLine.toString().padStart(4, \" \")}│ \\`\\`\\`typescript\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n const importLines = subject.imports.split(\"\\n\");\n for (const line of importLines) {\n output += `${currentLine.toString().padStart(4, \" \")}│ ${line}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n output += `${currentLine.toString().padStart(4, \" \")}│ \\`\\`\\`\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n // Empty line after code block - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n\n // Prelude blocks - show as list\n if (subject.prelude.length > 0) {\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ ### Prelude\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n // Empty line after heading\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n for (const block of subject.prelude) {\n const id = block.id || \"(no-id)\";\n const blockStartLine = currentLine + 1; // +1 for opening ```\n const codeLines = block.code.split(\"\\n\").length;\n const blockEndLine = currentLine + 1 + codeLines; // opening ``` + code lines\n output += `${currentLine.toString().padStart(4, \" \")}│ - id: \\`${id}\\`, L${blockStartLine}-${blockEndLine}\\n`;\n lastOutputLine = currentLine;\n currentLine += codeLines + 3; // opening ```, code, closing ```, blank line\n }\n // Update lastOutputLine to current position to avoid gap indicator\n lastOutputLine = currentLine - 1;\n }\n\n // Sections - show headings and any example IDs that belong to them\n const sectionToExamples = new Map<string, Example[]>();\n\n // Group examples by their rough section (based on heading appearance in explanations)\n for (const example of subject.examples) {\n // Try to match the example to a section based on context\n // For now, we'll list all example IDs under the sections where they appear\n for (const section of subject.sections) {\n // Check if the section contains references to this example\n if (\n section.content.includes(example.code.substring(0, Math.min(50, example.code.length)))\n ) {\n if (!sectionToExamples.has(section.heading)) {\n sectionToExamples.set(section.heading, []);\n }\n sectionToExamples.get(section.heading)!.push(example);\n break;\n }\n }\n }\n\n for (const section of subject.sections) {\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ ## ${section.heading}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n // Show code block IDs as a list if any examples match this section\n const examples = sectionToExamples.get(section.heading) || [];\n if (examples.length > 0) {\n // We need to parse the section content to find where each example appears\n const sectionStartLine = currentLine;\n const lines = section.content.split(\"\\n\");\n\n for (const example of examples) {\n const id = example.id || \"(no-id)\";\n // Find the code block in section content\n let blockStartLine = sectionStartLine;\n let blockEndLine = sectionStartLine;\n let inCodeBlock = false;\n let foundBlock = false;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().startsWith(\"```\") && !inCodeBlock) {\n // Check if next lines match the example\n const codeStart = i + 1;\n let matches = true;\n const exampleLines = example.code.split(\"\\n\");\n for (let j = 0; j < Math.min(3, exampleLines.length); j++) {\n if (lines[codeStart + j]?.trim() !== exampleLines[j]?.trim()) {\n matches = false;\n break;\n }\n }\n if (matches) {\n blockStartLine = sectionStartLine + i + 1; // +1 to skip opening ```\n blockEndLine = sectionStartLine + i + exampleLines.length;\n foundBlock = true;\n break;\n }\n }\n }\n\n if (foundBlock) {\n output += `${currentLine.toString().padStart(4, \" \")}│ - id: \\`${id}\\`, L${blockStartLine}-${blockEndLine}\\n`;\n } else {\n output += `${currentLine.toString().padStart(4, \" \")}│ - id: \\`${id}\\`\\n`;\n }\n lastOutputLine = currentLine;\n }\n }\n\n // Count lines\n const sectionLines = section.content.split(\"\\n\");\n for (const _line of sectionLines) {\n currentLine += 1;\n }\n currentLine += 1; // blank line after section\n // Update lastOutputLine to current position to avoid gap indicator\n lastOutputLine = currentLine - 1;\n }\n }\n\n return output;\n}\n\n/**\n * Print subjects with the given options\n */\nasync function printSubjects(subjects: Subject[], options: PrintOptions): Promise<void> {\n if (options.headingsOnly) {\n // Show only headings and code block IDs\n const headingsOutput = extractHeadingsAndBlocks(subjects);\n console.log(headingsOutput);\n return;\n }\n\n // Build the full markdown content\n const markdown = buildSubjectsMarkdown(subjects);\n\n // Render markdown to terminal for nice formatting\n let output = await marked.parse(markdown);\n\n // Apply line range filter if specified (after rendering)\n const startLine = options.startLine ?? 1;\n if (options.startLine !== undefined || options.endLine !== undefined) {\n const end = options.endLine ?? output.split(\"\\n\").length;\n output = filterByLineRange(output, startLine, end);\n }\n\n // Add line numbers after rendering (if requested)\n // Line numbers correspond to the rendered output that agents interact with\n if (options.showLineNumbers) {\n output = addLineNumbers(output, startLine);\n }\n\n console.log(output);\n}\n\n/**\n * Find and print code blocks by ID\n */\nasync function printCodeBlockById(\n id: string,\n topics: string[],\n showLineNumbers: boolean,\n): Promise<void> {\n // If topics are specified, search only those; otherwise search all subjects\n const subjects = topics.length > 0 ? getSubject(...topics) : getAllSubjects();\n\n interface CodeBlockMatch {\n subjectId: string;\n subjectTitle: string;\n section: string;\n code: string;\n type: \"prelude\" | \"example\";\n startLine?: number;\n endLine?: number;\n }\n\n const matches: CodeBlockMatch[] = [];\n\n for (const subject of subjects) {\n // Build the rendered markdown to get correct line numbers (matching --start/--end behavior)\n const fullMarkdown = buildSubjectsMarkdown([subject]);\n const renderedOutput = await marked.parse(fullMarkdown);\n const renderedLines = renderedOutput.split(\"\\n\");\n\n // Search in prelude blocks\n for (const block of subject.prelude) {\n if (block.id === id) {\n // Find line numbers in the rendered output\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n // Search for the prelude code in the rendered output\n const codeLines = block.code.split(\"\\n\");\n const firstCodeLine = codeLines[0].trim();\n\n for (let i = 0; i < renderedLines.length; i++) {\n // Strip ANSI codes before comparing\n if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {\n // Found the start of the code\n startLine = i + 1; // 1-based line numbers\n endLine = i + codeLines.length;\n break;\n }\n }\n\n matches.push({\n subjectId: subject.id,\n subjectTitle: subject.title,\n section: \"Prelude\",\n code: block.code,\n type: \"prelude\",\n startLine,\n endLine,\n });\n }\n }\n\n // Search in examples\n for (const example of subject.examples) {\n if (example.id === id) {\n // Try to find which section this example belongs to\n let sectionName = \"Unknown Section\";\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n for (const section of subject.sections) {\n if (\n section.content.includes(example.code.substring(0, Math.min(50, example.code.length)))\n ) {\n sectionName = section.heading;\n\n // Find line numbers in the rendered output\n const codeLines = example.code.split(\"\\n\");\n const firstCodeLine = codeLines[0].trim();\n\n for (let i = 0; i < renderedLines.length; i++) {\n // Strip ANSI codes before comparing\n if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {\n // Found the start of the code\n startLine = i + 1; // 1-based line numbers\n endLine = i + codeLines.length;\n break;\n }\n }\n break;\n }\n }\n\n matches.push({\n subjectId: subject.id,\n subjectTitle: subject.title,\n section: sectionName,\n code: example.code,\n type: \"example\",\n startLine,\n endLine,\n });\n }\n }\n }\n\n if (matches.length === 0) {\n console.error(`Error: No code block found with id \"${id}\"`);\n if (topics.length > 0) {\n console.error(`Searched in topics: ${topics.join(\", \")}`);\n } else {\n console.error(\"Searched in all available topics\");\n }\n process.exit(1);\n }\n\n // Build markdown output\n for (let i = 0; i < matches.length; i++) {\n const match = matches[i];\n\n if (matches.length > 1 && i > 0) {\n console.log(\"\\n---\\n\");\n }\n\n // Build markdown for this match\n let matchMarkdown = `# ${match.subjectTitle}\\n\\n`;\n matchMarkdown += `## ${match.section}\\n\\n`;\n\n // Add line number info if available and requested (as plain text, not in markdown)\n if (showLineNumbers && match.startLine && match.endLine) {\n console.log(`Lines ${match.startLine}-${match.endLine} (use with --start/--end)\\n`);\n }\n\n matchMarkdown += `\\`\\`\\`typescript\\n${match.code}\\n\\`\\`\\`\\n`;\n\n // Render the markdown\n const rendered = await marked.parse(matchMarkdown);\n console.log(rendered);\n }\n}\n\n/**\n * Print only the topic tree\n */\nfunction printTopicTree(): void {\n const subjects = getSubjects();\n const subjectMap = new Map(subjects.map((s) => [s.id, s]));\n\n // Helper function to get title for any subject ID (including categories)\n function getTitle(subjectId: string): string {\n if (isCategory(subjectId)) {\n return getCategoryTitle(subjectId);\n }\n const subject = subjectMap.get(subjectId);\n return subject ? subject.title : subjectId;\n }\n\n // Helper function to recursively display tree\n function displayNode(subjectId: string, indent: string, isLast: boolean, isRoot: boolean): void {\n const title = getTitle(subjectId);\n\n if (isRoot) {\n console.log(` ${subjectId.padEnd(30)} ${title}`);\n } else {\n const connector = isLast ? \"└─\" : \"├─\";\n console.log(`${indent}${connector} ${subjectId.padEnd(26)} ${title}`);\n }\n\n const children = getSubjectChildren(subjectId);\n if (children.length > 0) {\n const childIndent = isRoot ? \" \" : indent + (isLast ? \" \" : \"│ \");\n for (let i = 0; i < children.length; i++) {\n displayNode(children[i], childIndent, i === children.length - 1, false);\n }\n }\n }\n\n // Get all root subject IDs (including categories)\n const allIds = getAllSubjectIdsInOrder();\n const rootIds = allIds.filter((id) => !getSubjectParent(id));\n\n // Display root subjects\n for (const subjectId of rootIds) {\n displayNode(subjectId, \"\", false, true);\n }\n}\n\n/**\n * Print information about the corpus command\n */\nfunction printCorpusHelp(): void {\n console.log(\"Fragno Corpus - Code examples and documentation (similar to LLMs.txt\");\n console.log(\"\");\n console.log(\"Usage: fragno-cli corpus [options] [topic...]\");\n console.log(\"\");\n console.log(\"Options:\");\n console.log(\" -n, --no-line-numbers Hide line numbers (shown by default)\");\n console.log(\" -s, --start N Starting line number to display from\");\n console.log(\" -e, --end N Ending line number to display to\");\n console.log(\" --headings Show only headings and code block IDs\");\n console.log(\" --id <id> Retrieve a specific code block by ID\");\n console.log(\" --tree Show only the topic tree\");\n console.log(\"\");\n console.log(\"Examples:\");\n console.log(\" fragno-cli corpus # List all available topics\");\n console.log(\" fragno-cli corpus --tree # Show only the topic tree\");\n console.log(\" fragno-cli corpus defining-routes # Show route definition examples\");\n console.log(\" fragno-cli corpus --headings database-querying\");\n console.log(\" # Show structure overview\");\n console.log(\" fragno-cli corpus --start 10 --end 50 database-querying\");\n console.log(\" # Show specific lines\");\n console.log(\" fragno-cli corpus --id create-user # Get code block by ID\");\n console.log(\" fragno-cli corpus database-adapters kysely-adapter\");\n console.log(\" # Show multiple topics\");\n console.log(\"\");\n console.log(\"Available topics:\");\n\n printTopicTree();\n}\n\nexport const corpusCommand = define({\n name: \"corpus\",\n description: \"View code examples and documentation for Fragno\",\n args: {\n \"no-line-numbers\": {\n type: \"boolean\",\n short: \"n\",\n description: \"Hide line numbers (line numbers are shown by default)\",\n },\n start: {\n type: \"number\",\n short: \"s\",\n description: \"Starting line number (1-based) to display from\",\n },\n end: {\n type: \"number\",\n short: \"e\",\n description: \"Ending line number (1-based) to display to\",\n },\n headings: {\n type: \"boolean\",\n description: \"Show only section headings and code block IDs with line numbers\",\n },\n id: {\n type: \"string\",\n description: \"Retrieve a specific code block by ID\",\n },\n tree: {\n type: \"boolean\",\n description: \"Show only the topic tree (without help text)\",\n },\n },\n run: async (ctx) => {\n const topics = ctx.positionals;\n const showLineNumbers = !(ctx.values[\"no-line-numbers\"] ?? false);\n const startLine = ctx.values.start;\n const endLine = ctx.values.end;\n const headingsOnly = ctx.values.headings ?? false;\n const codeBlockId = ctx.values.id;\n const treeOnly = ctx.values.tree ?? false;\n\n // Handle --id flag\n if (codeBlockId) {\n await printCodeBlockById(codeBlockId, topics, showLineNumbers);\n return;\n }\n\n // Handle --tree flag\n if (treeOnly) {\n printTopicTree();\n return;\n }\n\n // No topics provided - show help\n if (topics.length === 0) {\n printCorpusHelp();\n return;\n }\n\n // Validate line range\n if (startLine !== undefined && endLine !== undefined && startLine > endLine) {\n console.error(\"Error: --start must be less than or equal to --end\");\n process.exit(1);\n }\n\n // Load and display requested topics\n try {\n const subjects = getSubject(...topics);\n\n await printSubjects(subjects, {\n showLineNumbers,\n startLine,\n endLine,\n headingsOnly,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"ENOENT\")) {\n // Extract the subject name from the error message or use the topics array\n const missingTopics = topics.filter((topic) => {\n try {\n getSubject(topic);\n return false;\n } catch {\n return true;\n }\n });\n\n if (missingTopics.length === 1) {\n console.error(`Error: Subject '${missingTopics[0]}' not found.`);\n } else if (missingTopics.length > 1) {\n console.error(\n `Error: Subjects not found: ${missingTopics.map((t) => `'${t}'`).join(\", \")}`,\n );\n } else {\n console.error(\"Error: One or more subjects not found.\");\n }\n console.log(\"\\nAvailable topics:\");\n printTopicTree();\n } else {\n console.error(\"Error loading topics:\", error instanceof Error ? error.message : error);\n console.log(\"\\nRun 'fragno-cli corpus' to see available topics.\");\n }\n process.exit(1);\n }\n },\n});\n","#!/usr/bin/env node\n\nimport { cli, define } from \"gunshi\";\nimport { generateCommand } from \"./commands/db/generate.js\";\nimport { migrateCommand } from \"./commands/db/migrate.js\";\nimport { infoCommand } from \"./commands/db/info.js\";\nimport { searchCommand } from \"./commands/search.js\";\nimport { corpusCommand } from \"./commands/corpus.js\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../package.json\"), \"utf-8\"));\nconst version = packageJson.version;\n\n// Create a Map of db sub-commands\nconst dbSubCommands = new Map();\ndbSubCommands.set(\"generate\", generateCommand);\ndbSubCommands.set(\"migrate\", migrateCommand);\ndbSubCommands.set(\"info\", infoCommand);\n\n// Define the db command with nested subcommands\nexport const dbCommand = define({\n name: \"db\",\n description: \"Database management commands\",\n});\n\n// Define the main command\nexport const mainCommand = define({\n name: \"fragno-cli\",\n description: \"Tools for working with Fragno fragments\",\n});\n\nexport async function run() {\n try {\n const args = process.argv.slice(2);\n\n // Manual routing for top-level commands\n if (args[0] === \"search\") {\n // Run search command directly\n await cli(args.slice(1), searchCommand, {\n name: \"fragno-cli search\",\n version,\n });\n } else if (args[0] === \"corpus\") {\n // Run corpus command directly\n await cli(args.slice(1), corpusCommand, {\n name: \"fragno-cli corpus\",\n version,\n });\n } else if (args[0] === \"db\") {\n // Handle db subcommands\n const subCommandName = args[1];\n\n if (!subCommandName || subCommandName === \"--help\" || subCommandName === \"-h\") {\n // Show db help with subcommands\n console.log(\"Database management commands\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-cli db <COMMAND>\");\n console.log(\"\");\n console.log(\"COMMANDS:\");\n console.log(\n \" generate Generate schema files from FragnoDatabase definitions\",\n );\n console.log(\" migrate Run database migrations\");\n console.log(\" info Display database information and migration status\");\n console.log(\"\");\n console.log(\"For more info, run any command with the `--help` flag:\");\n console.log(\" fragno-cli db generate --help\");\n console.log(\" fragno-cli db migrate --help\");\n console.log(\" fragno-cli db info --help\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -h, --help Display this help message\");\n console.log(\" -v, --version Display this version\");\n } else if (subCommandName === \"--version\" || subCommandName === \"-v\") {\n console.log(version);\n } else {\n // Route to specific db subcommand\n const subCommand = dbSubCommands.get(subCommandName);\n\n if (!subCommand) {\n console.error(`Unknown command: ${subCommandName}`);\n console.log(\"\");\n console.log(\"Run 'fragno-cli db --help' for available commands.\");\n process.exit(1);\n }\n\n // Run the subcommand\n await cli(args.slice(2), subCommand, {\n name: `fragno-cli db ${subCommandName}`,\n version,\n });\n }\n } else if (!args.length || args[0] === \"--help\" || args[0] === \"-h\") {\n // Show main help\n console.log(\"Tools for working with Fragno\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-cli <COMMAND>\");\n console.log(\"\");\n console.log(\"COMMANDS:\");\n console.log(\" db Database management commands\");\n console.log(\" search Search the Fragno documentation\");\n console.log(\" corpus View code examples and documentation for Fragno\");\n console.log(\"\");\n console.log(\"For more info, run any command with the `--help` flag:\");\n console.log(\" fragno-cli db --help\");\n console.log(\" fragno-cli search --help\");\n console.log(\" fragno-cli corpus --help\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -h, --help Display this help message\");\n console.log(\" -v, --version Display this version\");\n } else if (args[0] === \"--version\" || args[0] === \"-v\") {\n console.log(version);\n } else {\n // Unknown command\n console.error(`Unknown command: ${args[0]}`);\n console.log(\"\");\n console.log(\"Run 'fragno-cli --help' for available commands.\");\n process.exit(1);\n }\n } catch (error) {\n console.error(error);\n process.exit(1);\n }\n}\n\nif (import.meta.main) {\n await run();\n}\n\nexport { generateCommand, migrateCommand, infoCommand, searchCommand, corpusCommand };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAQA,eAAe,WAAW,MAAgC;AACxD,KAAI;AACF,QAAM,OAAO,MAAM,UAAU,KAAK;AAClC,SAAO;SACD;AACN,SAAO;;;;;;AAOX,eAAe,aAAa,WAA2C;CACrE,IAAI,aAAa,QAAQ,UAAU;CACnC,MAAM,OAAO,QAAQ,IAAI;AAEzB,QAAO,eAAe,MAAM;EAC1B,MAAM,eAAe,KAAK,YAAY,gBAAgB;AACtD,MAAI,MAAM,WAAW,aAAa,CAChC,QAAO;AAET,eAAa,QAAQ,WAAW;;AAGlC,QAAO;;;;;AAMT,SAAgB,kBAAkB,OAAuB;CAEvD,IAAI,SAAS,MAAM,QAAQ,eAAe,GAAG;AAG7C,UAAS,OAAO,QAAQ,qBAAqB,GAAG;AAEhD,QAAO;;;;;;AAOT,SAAgB,gCACd,eACA,iBACwB;AACxB,QAAO,OAAO,YACZ,OAAO,QAAQ,cAAc,CAAC,KAAK,CAAC,QAAQ,WAAW;EACrD,MAAM,aAAa;AAInB,SAAO,CAFU,OAAO,SAAS,IAAI,GAAG,OAAO,MAAM,GAAG,GAAG,GAAG,QAE5C,QAAQ,iBADR,WAAW,GAAG,SAAS,IAAI,GAAG,WAAW,GAAG,MAAM,GAAG,GAAG,GAAG,WAAW,GACnC,CAAC;GACtD,CACH;;;;;AAMH,eAAe,uBAAuB,YAAqD;CACzF,MAAM,eAAe,MAAM,aAAa,WAAW;AAEnD,KAAI,CAAC,aACH,QAAO,EAAE;AAGX,KAAI;EAGF,MAAM,cAAc,kBAFI,MAAM,SAAS,cAAc,QAAQ,CAEP;EACtD,MAAM,WAAW,KAAK,MAAM,YAAY;EACxC,MAAM,gBAAgB,UAAU,iBAAiB;AAEjD,MAAI,CAAC,iBAAiB,OAAO,kBAAkB,SAC7C,QAAO,EAAE;AAQX,SAAO,gCAAgC,eAHf,QAFJ,QAAQ,aAAa,EACzB,UAAU,iBAAiB,WAAW,IACD,CAGiB;UAC/D,OAAO;AACd,UAAQ,KAAK,wCAAwC,aAAa,IAAI,MAAM;AAC5E,SAAO,EAAE;;;;;;AAOb,eAAsBA,aAAW,MAAgD;CAG/E,MAAM,EAAE,WAAW,MAAMC,WAAc;EACrC,YAAY;EACZ,aAAa,EACX,OALU,MAAM,uBAAuB,KAAK,EAM7C;EACF,CAAC;AAEF,QAAO;;;;;ACtGT,eAAsB,mBAAmB,MAAgD;AAEvF,SAAQ,IAAI,yBAAyB;AAErC,KAAI;EAGF,MAAM,YAAY,oBAFH,MAAMC,aAAW,KAAK,CAEQ;EAC7C,MAAM,eAAe,UAAU,KAC5B,OACC,GAAG,GAAG,QAAQ,qCAAqC,GAAG,GAAG,QAAQ,0CACpE;AAGD,MAF2B,CAAC,GAAG,IAAI,IAAI,aAAa,CAAC,CAE9B,SAAS,EAC9B,OAAM,IAAI,MACR,qFACsB,aAAa,KAAK,KAAK,CAAC,GAC/C;AAGH,SAAO;GACL,SAAS,UAAU,GAAG;GACtB;GACD;WACO;AAER,SAAO,QAAQ,IAAI;;;;;;;AAQvB,eAAsB,oBAAoB,OAGvC;CAED,MAAM,cAAc,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC;AAE9C,KAAI,YAAY,WAAW,EACzB,OAAM,IAAI,MAAM,6BAA6B;CAG/C,MAAMC,eAA4C,EAAE;CACpD,IAAIC;CACJ,IAAIC;CACJ,MAAM,MAAM,QAAQ,KAAK;AAEzB,MAAK,MAAM,QAAQ,aAAa;EAC9B,MAAM,eAAe,SAAS,KAAK,KAAK;AAExC,MAAI;GACF,MAAM,SAAS,MAAM,mBAAmB,KAAK;GAC7C,MAAM,YAAY,OAAO;GACzB,MAAM,cAAc,OAAO;AAE3B,OAAI,UAAU,WAAW,GAAG;AAC1B,YAAQ,KACN,iDAAiD,aAAa,gKAI/D;AACD;;AAIF,OAAI,CAAC,SAAS;AACZ,cAAU;AACV,uBAAmB;;GAIrB,MAAM,mBAAmB,QAAQ;GACjC,MAAM,sBAAsB,QAAQ;GACpC,MAAM,kBAAkB,YAAY;GACpC,MAAM,qBAAqB,YAAY;AAEvC,OAAI,qBAAqB,mBAAmB,wBAAwB,oBAAoB;IACtF,MAAM,mBAAmB,GAAG,iBAAiB,GAAG;IAChD,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;AAE9C,UAAM,IAAI,MACR,gFACS,iBAAiB,IAAI,iBAAiB,QACtC,aAAa,IAAI,gBAAgB,oEAE3C;;AAGH,gBAAa,KAAK,GAAG,UAAU;AAC/B,WAAQ,IAAI,WAAW,UAAU,OAAO,kBAAkB,eAAe;WAClE,OAAO;AACd,SAAM,IAAI,MACR,kCAAkC,aAAa,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAC1G;;;AAIL,KAAI,aAAa,WAAW,EAC1B,OAAM,IAAI,MACR,oOAID;AAGH,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,gDAAgD;AAGlE,QAAO;EACL;EACA,WAAW;EACZ;;AAGH,SAAS,gCACP,OASA;AACA,QACE,OAAO,UAAU,YACjB,UAAU,QACV,kCAAkC,SAClC,MAAM,oCAAoC;;;;;;AAQ9C,SAAgB,oBACd,cAC6B;CAC7B,MAAMC,kBAA+C,EAAE;AAEvD,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,aAAa,CACtD,KAAI,iBAAiB,MAAM,CACzB,iBAAgB,KAAK,MAAM;UAClB,gCAAgC,MAAM,EAAE;EAEjD,MAAM,WAAW,MAAM;EACvB,MAAM,OAAO,SAAS;EACtB,MAAM,UAAU,SAAS;AAGzB,MAAI,CAAC,KAAK,SAAS,CAAC,KAAK,UACvB;EAGF,MAAM,SAAS,KAAK;EACpB,MAAM,YAAY,KAAK;EACvB,MAAM,kBAAkB,QAAQ;AAEhC,MAAI,CAAC,iBAAiB;AACpB,WAAQ,KACN,sBAAsB,MAAM,KAAK,8EAClC;AACD;;AAGF,kBAAgB,KACd,IAAI,eAAe;GACjB;GACA;GACA,SAAS;GACV,CAAC,CACH;;AAIL,QAAO;;;;;AC5LT,MAAa,kBAAkB,OAAO;CACpC,MAAM;CACN,aAAa;CACb,MAAM;EACJ,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aACE;GACH;EACD,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,IAAI;GACF,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,KAAK,OAAO,QAAQ;EAElB,MAAM,UAAU,IAAI;EACpB,MAAM,SAAS,IAAI,OAAO;EAC1B,MAAM,YAAY,IAAI,OAAO;EAC7B,MAAM,cAAc,IAAI,OAAO;EAC/B,MAAM,SAAS,IAAI,OAAO;EAM1B,MAAM,EAAE,WAAW,oBAAoB,YAAY,MAAM,oBAHrC,QAAQ,KAAK,WAAW,QAAQ,QAAQ,KAAK,EAAE,OAAO,CAAC,CAGc;AAGzF,MAAI,CAAC,QAAQ,yBAAyB,CAAC,QAAQ,kBAC7C,OAAM,IAAI,MACR,2IAED;AAIH,UAAQ,IAAI,uBAAuB;EAEnC,IAAIC;AACJ,MAAI;AACF,aAAU,MAAM,2BAA2B,oBAAoB;IAC7D,MAAM;IACN;IACA;IACD,CAAC;WACK,OAAO;AACd,SAAM,IAAI,MACR,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACrF;;AAIH,OAAK,MAAM,UAAU,SAAS;GAG5B,MAAM,kBACJ,UAAU,QAAQ,WAAW,IACzB,QAAQ,QAAQ,KAAK,EAAE,OAAO,GAC9B,SACE,QAAQ,QAAQ,KAAK,EAAE,QAAQ,OAAO,KAAK,GAC3C,QAAQ,QAAQ,KAAK,EAAE,OAAO,KAAK;GAG3C,MAAM,YAAY,QAAQ,gBAAgB;AAC1C,OAAI;AACF,UAAM,MAAM,WAAW,EAAE,WAAW,MAAM,CAAC;YACpC,OAAO;AACd,UAAM,IAAI,MACR,+BAA+B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACtF;;AAIH,OAAI;AAEF,UAAM,UAAU,iBADA,SAAS,GAAG,OAAO,IAAI,OAAO,WAAW,OAAO,QACtB,EAAE,UAAU,SAAS,CAAC;YACzD,OAAO;AACd,UAAM,IAAI,MACR,gCAAgC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACvF;;AAGH,WAAQ,IAAI,gBAAgB,kBAAkB;;AAGhD,UAAQ,IAAI,qCAAqC;AACjD,UAAQ,IAAI,sBAAsB,QAAQ,SAAS;AACnD,UAAQ,IAAI,eAAe;AAC3B,OAAK,MAAM,MAAM,mBACf,SAAQ,IAAI,SAAS,GAAG,UAAU,YAAY,GAAG,OAAO,QAAQ,GAAG;;CAGxE,CAAC;;;;AC3GF,MAAa,iBAAiB,OAAO;CACnC,MAAM;CACN,aAAa;CACb,MAAM,EAAE;CACR,KAAK,OAAO,QAAQ;EAClB,MAAM,UAAU,IAAI;AAEpB,MAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,4CAA4C;EAO9D,MAAM,EAAE,WAAW,uBAAuB,MAAM,oBAH5B,QAAQ,KAAK,WAAW,QAAQ,QAAQ,KAAK,EAAE,OAAO,CAAC,CAGK;AAEhF,UAAQ,IAAI,0DAA0D;EAEtE,IAAIC;AACJ,MAAI;AACF,aAAU,MAAM,kBAAkB,mBAAmB;WAC9C,OAAO;AACd,SAAM,IAAI,MACR,qBAAqB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAC5E;;AAIH,OAAK,MAAM,UAAU,SAAS;AAC5B,WAAQ,IAAI,aAAa,OAAO,YAAY;AAC5C,WAAQ,IAAI,sBAAsB,OAAO,cAAc;AACvD,WAAQ,IAAI,qBAAqB,OAAO,YAAY;AAEpD,OAAI,OAAO,WACT,SAAQ,IAAI,6BAA6B,OAAO,YAAY,MAAM,OAAO,UAAU,IAAI;OAEvF,SAAQ,IAAI,wDAAwD;;AAKxE,UAAQ,IAAI,0CAA0C;AACtD,UAAQ,IAAI,oBAAoB;AAChC,UAAQ,IAAI,0CAA0C;EAEtD,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,WAAW;EACpD,MAAM,UAAU,QAAQ,QAAQ,MAAM,CAAC,EAAE,WAAW;AAEpD,MAAI,SAAS,SAAS,GAAG;AACvB,WAAQ,IAAI,gBAAgB,SAAS,OAAO,eAAe;AAC3D,QAAK,MAAM,KAAK,SACd,SAAQ,IAAI,OAAO,EAAE,UAAU,KAAK,EAAE,YAAY,MAAM,EAAE,YAAY;;AAI1E,MAAI,QAAQ,SAAS,GAAG;AACtB,WAAQ,IAAI,eAAe,QAAQ,OAAO,oCAAoC;AAC9E,QAAK,MAAM,KAAK,QACd,SAAQ,IAAI,OAAO,EAAE,UAAU,KAAK,EAAE,YAAY;;AAItD,OAAK,MAAM,MAAM,mBACf,OAAM,GAAG,QAAQ,OAAO;AAG1B,UAAQ,IAAI,4CAA4C;;CAE3D,CAAC;;;;ACtEF,MAAa,cAAc,OAAO;CAChC,MAAM;CACN,aAAa;CACb,MAAM,EAAE;CACR,KAAK,OAAO,QAAQ;EAClB,MAAM,UAAU,IAAI;AAEpB,MAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,4CAA4C;EAO9D,MAAM,EAAE,WAAW,uBAAuB,MAAM,oBAH5B,QAAQ,KAAK,WAAW,QAAQ,QAAQ,KAAK,EAAE,OAAO,CAAC,CAGK;EAGhF,MAAM,UAAU,MAAM,QAAQ,IAC5B,mBAAmB,IAAI,OAAO,aAAa;GACzC,MAAMC,OAOF;IACF,WAAW,SAAS;IACpB,eAAe,SAAS,OAAO;IAC/B,kBAAkB,CAAC,CAAC,SAAS,QAAQ;IACtC;AAGD,OAAI,SAAS,QAAQ,mBAAmB;AAEtC,SAAK,iBADkB,MAAM,SAAS,QAAQ,iBAAiB,SAAS,UAAU;AAIlF,QAAI,KAAK,cAAc,UAAU,KAAK,KAAK,eACzC,MAAK,SAAS;QAEd,MAAK,SAAS;SAGhB,MAAK,SAAS;AAGhB,UAAO;IACP,CACH;EAGD,MAAM,sBAAsB,QAAQ,MAAM,SAAS,KAAK,iBAAiB;AAGzE,UAAQ,IAAI,GAAG;AACf,UAAQ,IAAI,wBAAwB;AACpC,UAAQ,IAAI,GAAG;EAGf,MAAM,kBAAkB;EACxB,MAAM,gBAAgB;EACtB,MAAM,gBAAgB;EACtB,MAAM,eAAe;EAErB,MAAM,kBAAkB,KAAK,IAC3B,GACA,GAAG,QAAQ,KAAK,SAAS,KAAK,UAAU,OAAO,CAChD;EACD,MAAM,iBAAiB,KAAK,IAAI,kBAAkB,GAAG,GAAG;EACxD,MAAM,eAAe;EACrB,MAAM,eAAe;EACrB,MAAM,cAAc;AAGpB,UAAQ,IACN,gBAAgB,OAAO,eAAe,GACpC,cAAc,OAAO,aAAa,IACjC,sBAAsB,cAAc,OAAO,aAAa,GAAG,MAC5D,aACH;AACD,UAAQ,IACN,IAAI,OAAO,eAAe,GACxB,IAAI,OAAO,aAAa,IACvB,sBAAsB,IAAI,OAAO,aAAa,GAAG,MAClD,IAAI,OAAO,YAAY,CAC1B;AAED,OAAK,MAAM,QAAQ,SAAS;GAC1B,MAAM,oBACJ,KAAK,mBAAmB,SAAY,OAAO,KAAK,eAAe,GAAG;AACpE,WAAQ,IACN,KAAK,UAAU,OAAO,eAAe,GACnC,OAAO,KAAK,cAAc,CAAC,OAAO,aAAa,IAC9C,sBAAsB,kBAAkB,OAAO,aAAa,GAAG,OAC/D,KAAK,UAAU,KACnB;;AAIH,UAAQ,IAAI,GAAG;AACf,MAAI,CAAC,qBAAqB;AACxB,WAAQ,IAAI,kDAAkD;AAC9D,WAAQ,IAAI,yDAAyD;QAErE,SAAQ,IAAI,oEAAoE;;CAGrF,CAAC;;;;;;;ACnFF,SAAgB,kBAAkB,SAAyB,SAAiC;CAC1F,MAAM,4BAAY,IAAI,KAA2B;AAEjD,MAAK,MAAM,UAAU,SAAS;EAE5B,MAAM,qBAAqB,OAAO,IAAI,MAAM,IAAI,CAAC;EACjD,MAAM,WAAW,UAAU,IAAI,mBAAmB;AAElD,MAAI,SAEF,UAAS,SAAS,KAAK;GACrB,SAAS,OAAO;GAChB,MAAM,OAAO;GACd,CAAC;OACG;GAEL,MAAM,YAAY,GAAG,mBAAmB;GAExC,MAAM,UAAU,WAAW,UAAU;GACrC,MAAM,gBAAgB,WAAW,UAAU;AAE3C,aAAU,IAAI,oBAAoB;IAChC,KAAK;IACL;IACA;IACA;IACA,OAAO,OAAO,SAAS,SAAS,OAAO,UAAU;IACjD,aAAa,OAAO;IACpB,MAAM,OAAO;IACb,UAAU,CACR;KACE,SAAS,OAAO;KAChB,MAAM,OAAO;KACd,CACF;IACF,CAAC;;;AAIN,QAAO,MAAM,KAAK,UAAU,QAAQ,CAAC;;;;;AAMvC,SAAgB,iBAAiB,eAAuC;CACtE,MAAMC,QAAkB,EAAE;AAE1B,MAAK,MAAM,UAAU,eAAe;EAElC,MAAM,QAAQ,OAAO,SAAS,OAAO,SAAS,IAAI,WAAW;AAC7D,QAAM,KAAK,aAAa,MAAM,GAAG;AAEjC,MAAI,OAAO,eAAe,OAAO,YAAY,SAAS,GAAG;AACvD,SAAM,KAAK,QAAQ,OAAO,YAAY,KAAK,MAAM,CAAC;AAClD,SAAM,KAAK,GAAG;;AAIhB,QAAM,KAAK,QAAQ;AACnB,QAAM,KAAK,OAAO,OAAO,UAAU;AACnC,QAAM,KAAK,OAAO,OAAO,gBAAgB;AACzC,QAAM,KAAK,GAAG;AAGd,MAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,SAAM,KAAK,qBAAqB;AAChC,QAAK,IAAI,IAAI,GAAG,IAAI,OAAO,SAAS,QAAQ,KAAK;IAC/C,MAAM,UAAU,OAAO,SAAS;AAEhC,QAAI,MAAM,KAAK,OAAO,SAAS,UAAU,QAAQ,YAAY,OAAO,MAClE;AAEF,UAAM,KAAK,OAAO,QAAQ,UAAU;;AAEtC,SAAM,KAAK,GAAG;;AAGhB,QAAM,KAAK,MAAM;AACjB,QAAM,KAAK,GAAG;;AAGhB,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAgB,aAAa,eAAuC;AAClE,QAAO,KAAK,UAAU,eAAe,MAAM,EAAE;;;;;ACnG/C,MAAa,gBAAgB,OAAO;CAClC,MAAM;CACN,aAAa;CACb,MAAM;EACJ,OAAO;GACL,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,UAAU;GACR,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,YAAY;GACV,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACF;CACD,KAAK,OAAO,QAAQ;EAClB,MAAM,QAAQ,IAAI,YAAY,KAAK,IAAI;AAEvC,MAAI,CAAC,SAAS,MAAM,MAAM,CAAC,WAAW,EACpC,OAAM,IAAI,MAAM,gCAAgC;EAIlD,MAAM,WAAW,IAAI,OAAO;EAC5B,MAAM,UAAU,IAAI,OAAO;AAE3B,MAAI,CAAC,SACH,SAAQ,IAAI,mBAAmB,MAAM,KAAK;AAG5C,MAAI;GAEF,MAAM,eAAe,mBAAmB,MAAM;GAC9C,MAAM,WAAW,MAAM,MAAM,WAAW,QAAQ,oBAAoB,eAAe;AAEnF,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,kCAAkC,SAAS,SAAS;GAGtE,MAAM,UAAW,MAAM,SAAS,MAAM;GAGtC,MAAM,QAAQ,IAAI,OAAO;GACzB,MAAM,iBAAiB,QAAQ,MAAM,GAAG,MAAM;AAE9C,OAAI,eAAe,WAAW,GAAG;AAC/B,QAAI,SACF,SAAQ,IAAI,KAAK;QAEjB,SAAQ,IAAI,oBAAoB;AAElC;;GAIF,MAAM,gBAAgB,kBAAkB,gBAAgB,QAAQ;AAGhE,OAAI,SACF,SAAQ,IAAI,aAAa,cAAc,CAAC;QACnC;AAEL,YAAQ,IACN,SAAS,QAAQ,OAAO,SAAS,QAAQ,WAAW,IAAI,KAAK,MAAM,QAAQ,SAAS,QAAQ,aAAa,MAAM,KAAK,GAAG,IACxH;AACD,YAAQ,IAAI,iBAAiB,cAAc,CAAC;;WAEvC,OAAO;AACd,OAAI,iBAAiB,MACnB,OAAM,IAAI,MAAM,kBAAkB,MAAM,UAAU;AAEpD,SAAM,IAAI,MAAM,2CAA2C;;;CAGhE,CAAC;;;;ACtFF,OAAO,IAAI,gBAAgB,CAAC;;;;AAY5B,SAAgB,sBAAsB,UAA6B;CACjE,IAAI,eAAe;AAEnB,MAAK,MAAM,WAAW,UAAU;AAC9B,kBAAgB,KAAK,QAAQ,MAAM;AAEnC,MAAI,QAAQ,YACV,iBAAgB,GAAG,QAAQ,YAAY;AAIzC,MAAI,QAAQ,QACV,iBAAgB,oCAAoC,QAAQ,QAAQ;AAItE,MAAI,QAAQ,QAAQ,SAAS,GAAG;AAC9B,mBAAgB;AAChB,QAAK,MAAM,SAAS,QAAQ,QAE1B,iBAAgB,qBAAqB,MAAM,KAAK;;AAKpD,OAAK,MAAM,WAAW,QAAQ,SAC5B,iBAAgB,MAAM,QAAQ,QAAQ,MAAM,QAAQ,QAAQ;;AAIhE,QAAO;;;;;AAMT,SAAgB,eAAe,SAAiB,YAAoB,GAAW;CAC7E,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,MAAM,YAAY,OAAO,YAAY,MAAM,SAAS,EAAE,CAAC;AAEvD,QAAO,MACJ,KAAK,MAAM,UAAU;EACpB,MAAM,UAAU,YAAY;AAE5B,SAAO,GADW,OAAO,QAAQ,CAAC,SAAS,WAAW,IAAI,CACtC,IAAI;GACxB,CACD,KAAK,KAAK;;;;;AAMf,SAAgB,kBAAkB,SAAiB,WAAmB,SAAyB;CAC7F,MAAM,QAAQ,QAAQ,MAAM,KAAK;CAEjC,MAAM,QAAQ,KAAK,IAAI,GAAG,YAAY,EAAE;CACxC,MAAM,MAAM,KAAK,IAAI,MAAM,QAAQ,QAAQ;AAC3C,QAAO,MAAM,MAAM,OAAO,IAAI,CAAC,KAAK,KAAK;;;;;AAM3C,SAAgB,yBAAyB,UAA6B;CACpE,IAAI,SAAS;CACb,IAAI,cAAc;CAClB,IAAI,iBAAiB;CAGrB,MAAM,uBAAuB;AAC3B,MAAI,iBAAiB,KAAK,cAAc,iBAAiB,EACvD,WAAU;;AAKd,WAAU;AAEV,MAAK,MAAM,WAAW,UAAU;AAE9B,kBAAgB;AAChB,YAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,QAAQ,MAAM;AACzE,mBAAiB;AACjB,iBAAe;AAGf,YAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,mBAAiB;AACjB,iBAAe;AAGf,MAAI,QAAQ,aAAa;GACvB,MAAM,YAAY,QAAQ,YAAY,MAAM,KAAK;AACjD,QAAK,MAAM,QAAQ,WAAW;AAC5B,cAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,KAAK;AAC9D,qBAAiB;AACjB,mBAAe;;AAGjB,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;;AAIjB,MAAI,QAAQ,SAAS;AACnB,mBAAgB;AAChB,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;AAEf,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;AACf,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;GACf,MAAM,cAAc,QAAQ,QAAQ,MAAM,KAAK;AAC/C,QAAK,MAAM,QAAQ,aAAa;AAC9B,cAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,KAAK;AAC9D,qBAAiB;AACjB,mBAAe;;AAEjB,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;AAEf,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;;AAIjB,MAAI,QAAQ,QAAQ,SAAS,GAAG;AAC9B,mBAAgB;AAChB,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;AAEf,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;AACrD,oBAAiB;AACjB,kBAAe;AAEf,QAAK,MAAM,SAAS,QAAQ,SAAS;IACnC,MAAM,KAAK,MAAM,MAAM;IACvB,MAAM,iBAAiB,cAAc;IACrC,MAAM,YAAY,MAAM,KAAK,MAAM,KAAK,CAAC;IACzC,MAAM,eAAe,cAAc,IAAI;AACvC,cAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,cAAc,GAAG,OAAO,eAAe,GAAG,aAAa;AAC5G,qBAAiB;AACjB,mBAAe,YAAY;;AAG7B,oBAAiB,cAAc;;EAIjC,MAAM,oCAAoB,IAAI,KAAwB;AAGtD,OAAK,MAAM,WAAW,QAAQ,SAG5B,MAAK,MAAM,WAAW,QAAQ,SAE5B,KACE,QAAQ,QAAQ,SAAS,QAAQ,KAAK,UAAU,GAAG,KAAK,IAAI,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,EACtF;AACA,OAAI,CAAC,kBAAkB,IAAI,QAAQ,QAAQ,CACzC,mBAAkB,IAAI,QAAQ,SAAS,EAAE,CAAC;AAE5C,qBAAkB,IAAI,QAAQ,QAAQ,CAAE,KAAK,QAAQ;AACrD;;AAKN,OAAK,MAAM,WAAW,QAAQ,UAAU;AACtC,mBAAgB;AAChB,aAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,QAAQ,QAAQ;AAC5E,oBAAiB;AACjB,kBAAe;GAGf,MAAM,WAAW,kBAAkB,IAAI,QAAQ,QAAQ,IAAI,EAAE;AAC7D,OAAI,SAAS,SAAS,GAAG;IAEvB,MAAM,mBAAmB;IACzB,MAAM,QAAQ,QAAQ,QAAQ,MAAM,KAAK;AAEzC,SAAK,MAAM,WAAW,UAAU;KAC9B,MAAM,KAAK,QAAQ,MAAM;KAEzB,IAAI,iBAAiB;KACrB,IAAI,eAAe;KAEnB,IAAI,aAAa;AAEjB,UAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IAEhC,KADa,MAAM,GACV,MAAM,CAAC,WAAW,MAAM,IAAI,MAAc;MAEjD,MAAM,YAAY,IAAI;MACtB,IAAI,UAAU;MACd,MAAM,eAAe,QAAQ,KAAK,MAAM,KAAK;AAC7C,WAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,aAAa,OAAO,EAAE,IACpD,KAAI,MAAM,YAAY,IAAI,MAAM,KAAK,aAAa,IAAI,MAAM,EAAE;AAC5D,iBAAU;AACV;;AAGJ,UAAI,SAAS;AACX,wBAAiB,mBAAmB,IAAI;AACxC,sBAAe,mBAAmB,IAAI,aAAa;AACnD,oBAAa;AACb;;;AAKN,SAAI,WACF,WAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,cAAc,GAAG,OAAO,eAAe,GAAG,aAAa;SAE5G,WAAU,GAAG,YAAY,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,cAAc,GAAG;AAExE,sBAAiB;;;GAKrB,MAAM,eAAe,QAAQ,QAAQ,MAAM,KAAK;AAChD,QAAK,MAAM,SAAS,aAClB,gBAAe;AAEjB,kBAAe;AAEf,oBAAiB,cAAc;;;AAInC,QAAO;;;;;AAMT,eAAe,cAAc,UAAqB,SAAsC;AACtF,KAAI,QAAQ,cAAc;EAExB,MAAM,iBAAiB,yBAAyB,SAAS;AACzD,UAAQ,IAAI,eAAe;AAC3B;;CAIF,MAAM,WAAW,sBAAsB,SAAS;CAGhD,IAAI,SAAS,MAAM,OAAO,MAAM,SAAS;CAGzC,MAAM,YAAY,QAAQ,aAAa;AACvC,KAAI,QAAQ,cAAc,UAAa,QAAQ,YAAY,QAAW;EACpE,MAAM,MAAM,QAAQ,WAAW,OAAO,MAAM,KAAK,CAAC;AAClD,WAAS,kBAAkB,QAAQ,WAAW,IAAI;;AAKpD,KAAI,QAAQ,gBACV,UAAS,eAAe,QAAQ,UAAU;AAG5C,SAAQ,IAAI,OAAO;;;;;AAMrB,eAAe,mBACb,IACA,QACA,iBACe;CAEf,MAAM,WAAW,OAAO,SAAS,IAAI,WAAW,GAAG,OAAO,GAAG,gBAAgB;CAY7E,MAAMC,UAA4B,EAAE;AAEpC,MAAK,MAAM,WAAW,UAAU;EAE9B,MAAM,eAAe,sBAAsB,CAAC,QAAQ,CAAC;EAErD,MAAM,iBADiB,MAAM,OAAO,MAAM,aAAa,EAClB,MAAM,KAAK;AAGhD,OAAK,MAAM,SAAS,QAAQ,QAC1B,KAAI,MAAM,OAAO,IAAI;GAEnB,IAAIC;GACJ,IAAIC;GAGJ,MAAM,YAAY,MAAM,KAAK,MAAM,KAAK;GACxC,MAAM,gBAAgB,UAAU,GAAG,MAAM;AAEzC,QAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,IAExC,KAAI,yBAAyB,cAAc,GAAG,CAAC,MAAM,KAAK,eAAe;AAEvE,gBAAY,IAAI;AAChB,cAAU,IAAI,UAAU;AACxB;;AAIJ,WAAQ,KAAK;IACX,WAAW,QAAQ;IACnB,cAAc,QAAQ;IACtB,SAAS;IACT,MAAM,MAAM;IACZ,MAAM;IACN;IACA;IACD,CAAC;;AAKN,OAAK,MAAM,WAAW,QAAQ,SAC5B,KAAI,QAAQ,OAAO,IAAI;GAErB,IAAI,cAAc;GAClB,IAAID;GACJ,IAAIC;AAEJ,QAAK,MAAM,WAAW,QAAQ,SAC5B,KACE,QAAQ,QAAQ,SAAS,QAAQ,KAAK,UAAU,GAAG,KAAK,IAAI,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,EACtF;AACA,kBAAc,QAAQ;IAGtB,MAAM,YAAY,QAAQ,KAAK,MAAM,KAAK;IAC1C,MAAM,gBAAgB,UAAU,GAAG,MAAM;AAEzC,SAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,IAExC,KAAI,yBAAyB,cAAc,GAAG,CAAC,MAAM,KAAK,eAAe;AAEvE,iBAAY,IAAI;AAChB,eAAU,IAAI,UAAU;AACxB;;AAGJ;;AAIJ,WAAQ,KAAK;IACX,WAAW,QAAQ;IACnB,cAAc,QAAQ;IACtB,SAAS;IACT,MAAM,QAAQ;IACd,MAAM;IACN;IACA;IACD,CAAC;;;AAKR,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,MAAM,uCAAuC,GAAG,GAAG;AAC3D,MAAI,OAAO,SAAS,EAClB,SAAQ,MAAM,uBAAuB,OAAO,KAAK,KAAK,GAAG;MAEzD,SAAQ,MAAM,mCAAmC;AAEnD,UAAQ,KAAK,EAAE;;AAIjB,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,QAAQ,QAAQ;AAEtB,MAAI,QAAQ,SAAS,KAAK,IAAI,EAC5B,SAAQ,IAAI,UAAU;EAIxB,IAAI,gBAAgB,KAAK,MAAM,aAAa;AAC5C,mBAAiB,MAAM,MAAM,QAAQ;AAGrC,MAAI,mBAAmB,MAAM,aAAa,MAAM,QAC9C,SAAQ,IAAI,SAAS,MAAM,UAAU,GAAG,MAAM,QAAQ,6BAA6B;AAGrF,mBAAiB,qBAAqB,MAAM,KAAK;EAGjD,MAAM,WAAW,MAAM,OAAO,MAAM,cAAc;AAClD,UAAQ,IAAI,SAAS;;;;;;AAOzB,SAAS,iBAAuB;CAC9B,MAAM,WAAW,aAAa;CAC9B,MAAM,aAAa,IAAI,IAAI,SAAS,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;CAG1D,SAAS,SAAS,WAA2B;AAC3C,MAAI,WAAW,UAAU,CACvB,QAAO,iBAAiB,UAAU;EAEpC,MAAM,UAAU,WAAW,IAAI,UAAU;AACzC,SAAO,UAAU,QAAQ,QAAQ;;CAInC,SAAS,YAAY,WAAmB,QAAgB,QAAiB,QAAuB;EAC9F,MAAM,QAAQ,SAAS,UAAU;AAEjC,MAAI,OACF,SAAQ,IAAI,KAAK,UAAU,OAAO,GAAG,CAAC,GAAG,QAAQ;OAC5C;GACL,MAAM,YAAY,SAAS,OAAO;AAClC,WAAQ,IAAI,GAAG,SAAS,UAAU,GAAG,UAAU,OAAO,GAAG,CAAC,GAAG,QAAQ;;EAGvE,MAAM,WAAW,mBAAmB,UAAU;AAC9C,MAAI,SAAS,SAAS,GAAG;GACvB,MAAM,cAAc,SAAS,SAAS,UAAU,SAAS,QAAQ;AACjE,QAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IACnC,aAAY,SAAS,IAAI,aAAa,MAAM,SAAS,SAAS,GAAG,MAAM;;;CAO7E,MAAM,UADS,yBAAyB,CACjB,QAAQ,OAAO,CAAC,iBAAiB,GAAG,CAAC;AAG5D,MAAK,MAAM,aAAa,QACtB,aAAY,WAAW,IAAI,OAAO,KAAK;;;;;AAO3C,SAAS,kBAAwB;AAC/B,SAAQ,IAAI,uEAAuE;AACnF,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,gDAAgD;AAC5D,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,WAAW;AACvB,SAAQ,IAAI,oEAAoE;AAChF,SAAQ,IAAI,oEAAoE;AAChF,SAAQ,IAAI,gEAAgE;AAC5E,SAAQ,IAAI,qEAAqE;AACjF,SAAQ,IAAI,oEAAoE;AAChF,SAAQ,IAAI,wDAAwD;AACpE,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,YAAY;AACxB,SAAQ,IAAI,4EAA4E;AACxF,SAAQ,IAAI,2EAA2E;AACvF,SAAQ,IAAI,iFAAiF;AAC7F,SAAQ,IAAI,mDAAmD;AAC/D,SAAQ,IAAI,0EAA0E;AACtF,SAAQ,IAAI,4DAA4D;AACxE,SAAQ,IAAI,sEAAsE;AAClF,SAAQ,IAAI,uEAAuE;AACnF,SAAQ,IAAI,uDAAuD;AACnE,SAAQ,IAAI,uEAAuE;AACnF,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,oBAAoB;AAEhC,iBAAgB;;AAGlB,MAAa,gBAAgB,OAAO;CAClC,MAAM;CACN,aAAa;CACb,MAAM;EACJ,mBAAmB;GACjB,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,KAAK;GACH,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,UAAU;GACR,MAAM;GACN,aAAa;GACd;EACD,IAAI;GACF,MAAM;GACN,aAAa;GACd;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACd;EACF;CACD,KAAK,OAAO,QAAQ;EAClB,MAAM,SAAS,IAAI;EACnB,MAAM,kBAAkB,EAAE,IAAI,OAAO,sBAAsB;EAC3D,MAAM,YAAY,IAAI,OAAO;EAC7B,MAAM,UAAU,IAAI,OAAO;EAC3B,MAAM,eAAe,IAAI,OAAO,YAAY;EAC5C,MAAM,cAAc,IAAI,OAAO;EAC/B,MAAM,WAAW,IAAI,OAAO,QAAQ;AAGpC,MAAI,aAAa;AACf,SAAM,mBAAmB,aAAa,QAAQ,gBAAgB;AAC9D;;AAIF,MAAI,UAAU;AACZ,mBAAgB;AAChB;;AAIF,MAAI,OAAO,WAAW,GAAG;AACvB,oBAAiB;AACjB;;AAIF,MAAI,cAAc,UAAa,YAAY,UAAa,YAAY,SAAS;AAC3E,WAAQ,MAAM,qDAAqD;AACnE,WAAQ,KAAK,EAAE;;AAIjB,MAAI;AAGF,SAAM,cAFW,WAAW,GAAG,OAAO,EAER;IAC5B;IACA;IACA;IACA;IACD,CAAC;WACK,OAAO;AACd,OAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,SAAS,EAAE;IAE9D,MAAM,gBAAgB,OAAO,QAAQ,UAAU;AAC7C,SAAI;AACF,iBAAW,MAAM;AACjB,aAAO;aACD;AACN,aAAO;;MAET;AAEF,QAAI,cAAc,WAAW,EAC3B,SAAQ,MAAM,mBAAmB,cAAc,GAAG,cAAc;aACvD,cAAc,SAAS,EAChC,SAAQ,MACN,8BAA8B,cAAc,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,KAAK,GAC5E;QAED,SAAQ,MAAM,yCAAyC;AAEzD,YAAQ,IAAI,sBAAsB;AAClC,oBAAgB;UACX;AACL,YAAQ,MAAM,yBAAyB,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AACtF,YAAQ,IAAI,qDAAqD;;AAEnE,WAAQ,KAAK,EAAE;;;CAGpB,CAAC;;;;AC3mBF,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,MAAM,UADc,KAAK,MAAM,aAAa,KAAK,WAAW,kBAAkB,EAAE,QAAQ,CAAC,CAC7D;AAG5B,MAAM,gCAAgB,IAAI,KAAK;AAC/B,cAAc,IAAI,YAAY,gBAAgB;AAC9C,cAAc,IAAI,WAAW,eAAe;AAC5C,cAAc,IAAI,QAAQ,YAAY;AAGtC,MAAa,YAAY,OAAO;CAC9B,MAAM;CACN,aAAa;CACd,CAAC;AAGF,MAAa,cAAc,OAAO;CAChC,MAAM;CACN,aAAa;CACd,CAAC;AAEF,eAAsB,MAAM;AAC1B,KAAI;EACF,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAGlC,MAAI,KAAK,OAAO,SAEd,OAAM,IAAI,KAAK,MAAM,EAAE,EAAE,eAAe;GACtC,MAAM;GACN;GACD,CAAC;WACO,KAAK,OAAO,SAErB,OAAM,IAAI,KAAK,MAAM,EAAE,EAAE,eAAe;GACtC,MAAM;GACN;GACD,CAAC;WACO,KAAK,OAAO,MAAM;GAE3B,MAAM,iBAAiB,KAAK;AAE5B,OAAI,CAAC,kBAAkB,mBAAmB,YAAY,mBAAmB,MAAM;AAE7E,YAAQ,IAAI,+BAA+B;AAC3C,YAAQ,IAAI,GAAG;AACf,YAAQ,IAAI,SAAS;AACrB,YAAQ,IAAI,4BAA4B;AACxC,YAAQ,IAAI,GAAG;AACf,YAAQ,IAAI,YAAY;AACxB,YAAQ,IACN,gFACD;AACD,YAAQ,IAAI,kDAAkD;AAC9D,YAAQ,IAAI,4EAA4E;AACxF,YAAQ,IAAI,GAAG;AACf,YAAQ,IAAI,yDAAyD;AACrE,YAAQ,IAAI,kCAAkC;AAC9C,YAAQ,IAAI,iCAAiC;AAC7C,YAAQ,IAAI,8BAA8B;AAC1C,YAAQ,IAAI,GAAG;AACf,YAAQ,IAAI,WAAW;AACvB,YAAQ,IAAI,qDAAqD;AACjE,YAAQ,IAAI,gDAAgD;cACnD,mBAAmB,eAAe,mBAAmB,KAC9D,SAAQ,IAAI,QAAQ;QACf;IAEL,MAAM,aAAa,cAAc,IAAI,eAAe;AAEpD,QAAI,CAAC,YAAY;AACf,aAAQ,MAAM,oBAAoB,iBAAiB;AACnD,aAAQ,IAAI,GAAG;AACf,aAAQ,IAAI,qDAAqD;AACjE,aAAQ,KAAK,EAAE;;AAIjB,UAAM,IAAI,KAAK,MAAM,EAAE,EAAE,YAAY;KACnC,MAAM,iBAAiB;KACvB;KACD,CAAC;;aAEK,CAAC,KAAK,UAAU,KAAK,OAAO,YAAY,KAAK,OAAO,MAAM;AAEnE,WAAQ,IAAI,gCAAgC;AAC5C,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,SAAS;AACrB,WAAQ,IAAI,yBAAyB;AACrC,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,YAAY;AACxB,WAAQ,IAAI,uDAAuD;AACnE,WAAQ,IAAI,0DAA0D;AACtE,WAAQ,IAAI,0EAA0E;AACtF,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,yDAAyD;AACrE,WAAQ,IAAI,yBAAyB;AACrC,WAAQ,IAAI,6BAA6B;AACzC,WAAQ,IAAI,6BAA6B;AACzC,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,WAAW;AACvB,WAAQ,IAAI,qDAAqD;AACjE,WAAQ,IAAI,gDAAgD;aACnD,KAAK,OAAO,eAAe,KAAK,OAAO,KAChD,SAAQ,IAAI,QAAQ;OACf;AAEL,WAAQ,MAAM,oBAAoB,KAAK,KAAK;AAC5C,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,kDAAkD;AAC9D,WAAQ,KAAK,EAAE;;UAEV,OAAO;AACd,UAAQ,MAAM,MAAM;AACpB,UAAQ,KAAK,EAAE;;;AAInB,IAAI,OAAO,KAAK,KACd,OAAM,KAAK"}
1
+ {"version":3,"file":"cli.js","names":["loadConfig","c12LoadConfig","loadConfig","allDatabases: FragnoDatabase<AnySchema>[]","adapter: DatabaseAdapter | undefined","firstAdapterFile: string | undefined","fragments: FragnoInstantiatedFragment<\n [],\n unknown,\n Record<string, unknown>,\n Record<string, unknown>,\n Record<string, unknown>,\n unknown,\n Record<string, unknown>\n >[]","fragnoDatabases: FragnoDatabase<AnySchema>[]","results: { schema: string; path: string; namespace: string | null }[]","results: ExecuteMigrationResult[]","info: {\n namespace: string;\n schemaVersion: number;\n migrationSupport: boolean;\n databaseType?: string;\n sqliteProfile?: string;\n currentVersion?: string;\n pendingVersions?: string;\n status?: string;\n }","lines: string[]","matches: CodeBlockMatch[]","startLine: number | undefined","endLine: number | undefined","allFragments: FragnoInstantiatedFragment<\n [],\n unknown,\n Record<string, unknown>,\n Record<string, unknown>,\n Record<string, unknown>,\n unknown,\n Record<string, unknown>\n >[]","loadConfig"],"sources":["../src/utils/load-config.ts","../src/utils/find-fragno-databases.ts","../src/commands/db/generate.ts","../src/commands/db/migrate.ts","../src/commands/db/info.ts","../src/utils/format-search-results.ts","../src/commands/search.ts","../src/commands/corpus.ts","../src/commands/serve.ts","../src/cli.ts"],"sourcesContent":["import { loadConfig as c12LoadConfig } from \"c12\";\nimport { readFile, access } from \"node:fs/promises\";\nimport { dirname, resolve, join } from \"node:path\";\nimport { constants } from \"node:fs\";\n\n/**\n * Checks if a file exists using async API.\n */\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Walks up the directory tree from the target path to find a tsconfig.json file.\n */\nasync function findTsconfig(startPath: string): Promise<string | null> {\n let currentDir = dirname(startPath);\n const root = resolve(\"/\");\n\n while (currentDir !== root) {\n const tsconfigPath = join(currentDir, \"tsconfig.json\");\n if (await fileExists(tsconfigPath)) {\n return tsconfigPath;\n }\n currentDir = dirname(currentDir);\n }\n\n return null;\n}\n\n/**\n * Strips comments from JSONC (JSON with Comments) content.\n */\nexport function stripJsonComments(jsonc: string): string {\n // Remove single-line comments (// ...)\n let result = jsonc.replace(/\\/\\/[^\\n]*/g, \"\");\n\n // Remove multi-line comments (/* ... */)\n result = result.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n\n return result;\n}\n\n/**\n * Converts TypeScript path aliases to jiti alias format.\n * Strips trailing '*' from aliases and paths, and resolves paths relative to baseUrl.\n */\nexport function convertTsconfigPathsToJitiAlias(\n tsconfigPaths: Record<string, string[]>,\n baseUrlResolved: string,\n): Record<string, string> {\n return Object.fromEntries(\n Object.entries(tsconfigPaths).map(([_alias, paths]) => {\n const pathsArray = paths as string[];\n // trim '*' if present and resolve the actual path\n const aliasKey = _alias.endsWith(\"*\") ? _alias.slice(0, -1) : _alias;\n const pathValue = pathsArray[0].endsWith(\"*\") ? pathsArray[0].slice(0, -1) : pathsArray[0];\n return [aliasKey, resolve(baseUrlResolved, pathValue)];\n }),\n );\n}\n\n/**\n * Resolves tsconfig path aliases for use with jiti.\n */\nasync function resolveTsconfigAliases(targetPath: string): Promise<Record<string, string>> {\n const tsconfigPath = await findTsconfig(targetPath);\n\n if (!tsconfigPath) {\n return {};\n }\n\n try {\n const tsconfigContent = await readFile(tsconfigPath, \"utf-8\");\n // Strip comments to handle JSONC format\n const jsonContent = stripJsonComments(tsconfigContent);\n const tsconfig = JSON.parse(jsonContent);\n const tsconfigPaths = tsconfig?.compilerOptions?.paths;\n\n if (!tsconfigPaths || typeof tsconfigPaths !== \"object\") {\n return {};\n }\n\n const tsconfigDir = dirname(tsconfigPath);\n const baseUrl = tsconfig?.compilerOptions?.baseUrl || \".\";\n const baseUrlResolved = resolve(tsconfigDir, baseUrl);\n\n // Convert tsconfig paths to jiti alias format\n return convertTsconfigPathsToJitiAlias(tsconfigPaths, baseUrlResolved);\n } catch (error) {\n console.warn(`Warning: Failed to parse tsconfig at ${tsconfigPath}:`, error);\n return {};\n }\n}\n\n/**\n * Loads a config file using c12 with automatic tsconfig path alias resolution.\n */\nexport async function loadConfig(path: string): Promise<Record<string, unknown>> {\n const alias = await resolveTsconfigAliases(path);\n\n const { config } = await c12LoadConfig({\n configFile: path,\n jitiOptions: {\n alias,\n },\n });\n\n return config as Record<string, unknown>;\n}\n","import { isFragnoDatabase, type DatabaseAdapter, FragnoDatabase } from \"@fragno-dev/db\";\nimport {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"@fragno-dev/db/adapters\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport { instantiatedFragmentFakeSymbol } from \"@fragno-dev/core/internal/symbols\";\nimport { type FragnoInstantiatedFragment } from \"@fragno-dev/core\";\nimport { loadConfig } from \"./load-config\";\nimport { relative } from \"node:path\";\n\nexport async function importFragmentFile(path: string): Promise<Record<string, unknown>> {\n // Enable dry run mode for database schema extraction\n process.env[\"FRAGNO_INIT_DRY_RUN\"] = \"true\";\n\n try {\n const config = await loadConfig(path);\n\n const databases = findFragnoDatabases(config);\n const adapterNames = databases.map(\n (db) =>\n `${db.adapter[fragnoDatabaseAdapterNameFakeSymbol]}@${db.adapter[fragnoDatabaseAdapterVersionFakeSymbol]}`,\n );\n const uniqueAdapterNames = [...new Set(adapterNames)];\n\n if (uniqueAdapterNames.length > 1) {\n throw new Error(\n `All Fragno databases must use the same adapter name and version. ` +\n `Found mismatch: (${adapterNames.join(\", \")})`,\n );\n }\n\n return {\n adapter: databases[0].adapter,\n databases,\n };\n } finally {\n // Clean up after loading\n delete process.env[\"FRAGNO_INIT_DRY_RUN\"];\n }\n}\n\n/**\n * Imports multiple fragment files and validates they all use the same adapter.\n * Returns the combined databases from all files.\n */\nexport async function importFragmentFiles(paths: string[]): Promise<{\n adapter: DatabaseAdapter;\n databases: FragnoDatabase<AnySchema>[];\n}> {\n // De-duplicate paths (in case same file was specified multiple times)\n const uniquePaths = Array.from(new Set(paths));\n\n if (uniquePaths.length === 0) {\n throw new Error(\"No fragment files provided\");\n }\n\n const allDatabases: FragnoDatabase<AnySchema>[] = [];\n let adapter: DatabaseAdapter | undefined;\n let firstAdapterFile: string | undefined;\n const cwd = process.cwd();\n\n for (const path of uniquePaths) {\n const relativePath = relative(cwd, path);\n\n try {\n const result = await importFragmentFile(path);\n const databases = result[\"databases\"] as FragnoDatabase<AnySchema>[];\n const fileAdapter = result[\"adapter\"] as DatabaseAdapter;\n\n if (databases.length === 0) {\n console.warn(\n `Warning: No FragnoDatabase instances found in ${relativePath}.\\n` +\n `Make sure you export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n continue;\n }\n\n // Set the adapter from the first file with databases\n if (!adapter) {\n adapter = fileAdapter;\n firstAdapterFile = relativePath;\n }\n\n // Validate all files use the same adapter name and version\n const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n const fileAdapterName = fileAdapter[fragnoDatabaseAdapterNameFakeSymbol];\n const fileAdapterVersion = fileAdapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n if (firstAdapterName !== fileAdapterName || firstAdapterVersion !== fileAdapterVersion) {\n const firstAdapterInfo = `${firstAdapterName}@${firstAdapterVersion}`;\n const fileAdapterInfo = `${fileAdapterName}@${fileAdapterVersion}`;\n\n throw new Error(\n `All fragments must use the same database adapter. Mixed adapters found:\\n` +\n ` - ${firstAdapterFile}: ${firstAdapterInfo}\\n` +\n ` - ${relativePath}: ${fileAdapterInfo}\\n\\n` +\n `Make sure all fragments use the same adapter name and version.`,\n );\n }\n\n allDatabases.push(...databases);\n console.log(` Found ${databases.length} database(s) in ${relativePath}`);\n } catch (error) {\n throw new Error(\n `Failed to import fragment file ${relativePath}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n if (allDatabases.length === 0) {\n throw new Error(\n `No FragnoDatabase instances found in any of the target files.\\n` +\n `Make sure your files export either:\\n` +\n ` - A FragnoDatabase instance created with .create(adapter)\\n` +\n ` - An instantiated fragment with embedded database definition\\n`,\n );\n }\n\n if (!adapter) {\n throw new Error(\"No adapter found in any of the fragment files\");\n }\n\n return {\n adapter,\n databases: allDatabases,\n };\n}\n\nfunction isNewFragnoInstantiatedFragment(\n value: unknown,\n): value is FragnoInstantiatedFragment<\n [],\n unknown,\n Record<string, unknown>,\n Record<string, unknown>,\n Record<string, unknown>,\n unknown,\n Record<string, unknown>\n> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n instantiatedFragmentFakeSymbol in value &&\n value[instantiatedFragmentFakeSymbol] === instantiatedFragmentFakeSymbol\n );\n}\n\n/**\n * Finds all instantiated Fragno fragments in a module's exports.\n */\nexport function findFragnoFragments(\n targetModule: Record<string, unknown>,\n): FragnoInstantiatedFragment<\n [],\n unknown,\n Record<string, unknown>,\n Record<string, unknown>,\n Record<string, unknown>,\n unknown,\n Record<string, unknown>\n>[] {\n const fragments: FragnoInstantiatedFragment<\n [],\n unknown,\n Record<string, unknown>,\n Record<string, unknown>,\n Record<string, unknown>,\n unknown,\n Record<string, unknown>\n >[] = [];\n\n for (const [_key, value] of Object.entries(targetModule)) {\n if (isNewFragnoInstantiatedFragment(value)) {\n fragments.push(value);\n }\n }\n\n return fragments;\n}\n\n/**\n * Finds all FragnoDatabase instances in a module, including those embedded\n * in instantiated fragments.\n */\nexport function findFragnoDatabases(\n targetModule: Record<string, unknown>,\n): FragnoDatabase<AnySchema>[] {\n const fragnoDatabases: FragnoDatabase<AnySchema>[] = [];\n\n for (const [_key, value] of Object.entries(targetModule)) {\n if (isFragnoDatabase(value)) {\n fragnoDatabases.push(value);\n } else if (isNewFragnoInstantiatedFragment(value)) {\n // Handle new fragment API\n const internal = value.$internal;\n const deps = internal.deps as Record<string, unknown>;\n const options = internal.options as Record<string, unknown>;\n\n // Check if this is a database fragment by looking for implicit database dependencies\n if (!deps[\"db\"] || !deps[\"schema\"]) {\n continue;\n }\n\n const schema = deps[\"schema\"] as AnySchema;\n const namespace = deps[\"namespace\"] as string;\n const databaseAdapter = options[\"databaseAdapter\"] as DatabaseAdapter | undefined;\n\n if (!databaseAdapter) {\n console.warn(\n `Warning: Fragment '${value.name}' appears to be a database fragment but no databaseAdapter found in options.`,\n );\n continue;\n }\n\n fragnoDatabases.push(\n new FragnoDatabase({\n namespace,\n schema,\n adapter: databaseAdapter,\n }),\n );\n }\n }\n\n return fragnoDatabases;\n}\n","import { writeFile, mkdir } from \"node:fs/promises\";\nimport { resolve, dirname } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { generateSchemaArtifacts } from \"@fragno-dev/db/generation-engine\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\n\n// Define the db generate command with type safety\nexport const generateCommand = define({\n name: \"generate\",\n description: \"Generate SQL migrations or schema outputs from FragnoDatabase definitions\",\n args: {\n format: {\n type: \"string\",\n description: \"Output format: sql (migrations), drizzle (schema), prisma (schema)\",\n },\n output: {\n type: \"string\",\n short: \"o\",\n description:\n \"Output path: for single file, exact file path; for multiple files, output directory (default: current directory)\",\n },\n from: {\n type: \"number\",\n short: \"f\",\n description: \"Source version to generate migration from (default: current database version)\",\n },\n to: {\n type: \"number\",\n short: \"t\",\n description: \"Target version to generate migration to (default: latest schema version)\",\n },\n prefix: {\n type: \"string\",\n short: \"p\",\n description: \"String to prepend to the generated file (e.g., '/* eslint-disable */')\",\n },\n },\n run: async (ctx) => {\n // With `define()` and `multiple: true`, targets is properly typed as string[]\n const targets = ctx.positionals;\n const output = ctx.values.output;\n const format = ctx.values.format ?? \"sql\";\n const toVersion = ctx.values.to;\n const fromVersion = ctx.values.from;\n const prefix = ctx.values.prefix;\n\n // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files and validate they use the same adapter\n const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);\n\n const allowedFormats = [\"sql\", \"drizzle\", \"prisma\"] as const;\n if (!allowedFormats.includes(format as (typeof allowedFormats)[number])) {\n throw new Error(`Unsupported format '${format}'. Use one of: ${allowedFormats.join(\", \")}.`);\n }\n\n if (format !== \"sql\" && (toVersion !== undefined || fromVersion !== undefined)) {\n throw new Error(\"--from and --to are only supported for SQL migration output.\");\n }\n\n // Generate schema for all fragments\n if (format === \"sql\") {\n console.log(\"Generating SQL migrations...\");\n } else {\n console.log(`Generating ${format} schema output...`);\n }\n\n let results: { schema: string; path: string; namespace: string | null }[];\n try {\n results = await generateSchemaArtifacts(allFragnoDatabases, {\n format: format as \"sql\" | \"drizzle\" | \"prisma\",\n path: output,\n toVersion,\n fromVersion,\n });\n } catch (error) {\n throw new Error(\n `Failed to generate schema: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Write all generated files\n for (const result of results) {\n // For single file: use output as exact file path\n // For multiple files: use output as base directory\n const finalOutputPath =\n output && results.length === 1\n ? resolve(process.cwd(), output)\n : output\n ? resolve(process.cwd(), output, result.path)\n : resolve(process.cwd(), result.path);\n\n // Ensure parent directory exists\n const parentDir = dirname(finalOutputPath);\n try {\n await mkdir(parentDir, { recursive: true });\n } catch (error) {\n throw new Error(\n `Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Write schema to file\n try {\n const content = prefix ? `${prefix}\\n${result.schema}` : result.schema;\n await writeFile(finalOutputPath, content, { encoding: \"utf-8\" });\n } catch (error) {\n throw new Error(\n `Failed to write schema file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n console.log(`✓ Generated: ${finalOutputPath}`);\n }\n\n console.log(`\\n✓ Output generated successfully!`);\n console.log(` Files generated: ${results.length}`);\n console.log(` Fragments:`);\n for (const db of allFragnoDatabases) {\n const namespaceLabel = db.namespace ?? \"(none)\";\n console.log(` - ${db.schema.name} [${namespaceLabel}] (version ${db.schema.version})`);\n }\n },\n});\n","import { resolve } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\nimport { executeMigrations, type ExecuteMigrationResult } from \"@fragno-dev/db/generation-engine\";\n\nexport const migrateCommand = define({\n name: \"migrate\",\n description: \"Run SQL database migrations for all fragments to their latest versions\",\n args: {},\n run: async (ctx) => {\n const targets = ctx.positionals;\n\n if (targets.length === 0) {\n throw new Error(\"At least one target file path is required\");\n }\n\n // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files and validate they use the same adapter\n const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);\n\n console.log(\"\\nMigrating all fragments to their latest versions...\\n\");\n\n let results: ExecuteMigrationResult[];\n try {\n results = await executeMigrations(allFragnoDatabases);\n } catch (error) {\n throw new Error(\n `Migration failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Display progress for each result\n for (const result of results) {\n console.log(`Fragment: ${result.namespace}`);\n console.log(` Current version: ${result.fromVersion}`);\n console.log(` Target version: ${result.toVersion}`);\n\n if (result.didMigrate) {\n console.log(` ✓ Migration completed: v${result.fromVersion} → v${result.toVersion}\\n`);\n } else {\n console.log(` ✓ Already at latest version. No migration needed.\\n`);\n }\n }\n\n // Summary\n console.log(\"═══════════════════════════════════════\");\n console.log(\"Migration Summary\");\n console.log(\"═══════════════════════════════════════\");\n\n const migrated = results.filter((r) => r.didMigrate);\n const skipped = results.filter((r) => !r.didMigrate);\n\n if (migrated.length > 0) {\n console.log(`\\n✓ Migrated ${migrated.length} fragment(s):`);\n for (const r of migrated) {\n console.log(` - ${r.namespace}: v${r.fromVersion} → v${r.toVersion}`);\n }\n }\n\n if (skipped.length > 0) {\n console.log(`\\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);\n for (const r of skipped) {\n console.log(` - ${r.namespace}: v${r.toVersion}`);\n }\n }\n\n for (const db of allFragnoDatabases) {\n await db.adapter.close();\n }\n\n console.log(\"\\n✓ All migrations completed successfully\");\n },\n});\n","import { resolve } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { importFragmentFiles } from \"../../utils/find-fragno-databases\";\n\nexport const infoCommand = define({\n name: \"info\",\n description: \"Display database information and migration status\",\n args: {},\n run: async (ctx) => {\n const targets = ctx.positionals;\n\n if (targets.length === 0) {\n throw new Error(\"At least one target file path is required\");\n }\n\n // Resolve all target paths\n const targetPaths = targets.map((target) => resolve(process.cwd(), target));\n\n // Import all fragment files\n const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);\n\n // Collect database information\n const dbInfos = await Promise.all(\n allFragnoDatabases.map(async (fragnoDb) => {\n const adapterMetadata = fragnoDb.adapter.adapterMetadata;\n const databaseType = adapterMetadata?.databaseType;\n const sqliteProfile = adapterMetadata?.sqliteProfile;\n const namespaceKey = fragnoDb.namespace ?? fragnoDb.schema.name;\n const displayNamespace = fragnoDb.namespace ?? \"(none)\";\n const info: {\n namespace: string;\n schemaVersion: number;\n migrationSupport: boolean;\n databaseType?: string;\n sqliteProfile?: string;\n currentVersion?: string;\n pendingVersions?: string;\n status?: string;\n } = {\n namespace: displayNamespace,\n schemaVersion: fragnoDb.schema.version,\n migrationSupport: !!fragnoDb.adapter.prepareMigrations,\n databaseType,\n sqliteProfile: databaseType === \"sqlite\" ? sqliteProfile : undefined,\n };\n\n // Get current database version if migrations are supported\n if (fragnoDb.adapter.prepareMigrations) {\n const currentVersion = await fragnoDb.adapter.getSchemaVersion(namespaceKey);\n info.currentVersion = currentVersion;\n // info.pendingVersions = fragnoDb.schema.version - currentVersion;\n\n if (info.schemaVersion.toString() !== info.currentVersion) {\n info.status = `Migrations pending`;\n } else {\n info.status = \"Up to date\";\n }\n } else {\n info.status = \"Schema only\";\n }\n\n return info;\n }),\n );\n\n const showDatabaseType = dbInfos.some((info) => !!info.databaseType);\n const showSqliteProfile = dbInfos.some((info) => info.databaseType === \"sqlite\");\n\n // Determine if any database supports migrations\n const hasMigrationSupport = dbInfos.some((info) => info.migrationSupport);\n\n // Print compact table\n console.log(\"\");\n console.log(`Database Information:`);\n console.log(\"\");\n\n // Table header\n const namespaceHeader = \"Namespace\";\n const versionHeader = \"Schema\";\n const databaseHeader = \"DB\";\n const profileHeader = \"SQLite\";\n const currentHeader = \"Current\";\n const statusHeader = \"Status\";\n\n const maxNamespaceLen = Math.max(\n namespaceHeader.length,\n ...dbInfos.map((info) => info.namespace.length),\n );\n const namespaceWidth = Math.max(maxNamespaceLen + 2, 20);\n const versionWidth = 8;\n const databaseWidth = 8;\n const profileWidth = 10;\n const currentWidth = 9;\n const statusWidth = 25;\n\n // Print table\n console.log(\n namespaceHeader.padEnd(namespaceWidth) +\n versionHeader.padEnd(versionWidth) +\n (showDatabaseType ? databaseHeader.padEnd(databaseWidth) : \"\") +\n (showSqliteProfile ? profileHeader.padEnd(profileWidth) : \"\") +\n (hasMigrationSupport ? currentHeader.padEnd(currentWidth) : \"\") +\n statusHeader,\n );\n console.log(\n \"-\".repeat(namespaceWidth) +\n \"-\".repeat(versionWidth) +\n (showDatabaseType ? \"-\".repeat(databaseWidth) : \"\") +\n (showSqliteProfile ? \"-\".repeat(profileWidth) : \"\") +\n (hasMigrationSupport ? \"-\".repeat(currentWidth) : \"\") +\n \"-\".repeat(statusWidth),\n );\n\n for (const info of dbInfos) {\n const currentVersionStr =\n info.currentVersion !== undefined ? String(info.currentVersion) : \"-\";\n console.log(\n info.namespace.padEnd(namespaceWidth) +\n String(info.schemaVersion).padEnd(versionWidth) +\n (showDatabaseType ? (info.databaseType ?? \"-\").padEnd(databaseWidth) : \"\") +\n (showSqliteProfile ? (info.sqliteProfile ?? \"-\").padEnd(profileWidth) : \"\") +\n (hasMigrationSupport ? currentVersionStr.padEnd(currentWidth) : \"\") +\n (info.status || \"-\"),\n );\n }\n\n // Print help text\n console.log(\"\");\n if (!hasMigrationSupport) {\n console.log(\"Note: These adapters do not support migrations.\");\n console.log(\"Use 'fragno-cli db generate' to generate schema files.\");\n } else {\n console.log(\"Run 'fragno-cli db migrate <target>' to apply pending migrations.\");\n }\n },\n});\n","interface SearchResult {\n id: string;\n type: \"page\" | \"heading\" | \"text\";\n content: string;\n breadcrumbs?: string[];\n contentWithHighlights?: Array<{\n type: string;\n content: string;\n styles?: { highlight?: boolean };\n }>;\n url: string;\n}\n\ninterface MergedResult {\n url: string;\n urlWithMd: string;\n fullUrl: string;\n fullUrlWithMd: string;\n title?: string;\n breadcrumbs?: string[];\n type: \"page\" | \"heading\" | \"text\";\n sections: Array<{\n content: string;\n type: \"page\" | \"heading\" | \"text\";\n }>;\n}\n\n/**\n * Merge search results by URL, grouping sections and content under each URL (without hash)\n */\nexport function mergeResultsByUrl(results: SearchResult[], baseUrl: string): MergedResult[] {\n const mergedMap = new Map<string, MergedResult>();\n\n for (const result of results) {\n // Strip hash to get base URL for merging\n const baseUrlWithoutHash = result.url.split(\"#\")[0];\n const existing = mergedMap.get(baseUrlWithoutHash);\n\n if (existing) {\n // Add this result as a section\n existing.sections.push({\n content: result.content,\n type: result.type,\n });\n } else {\n // Create new merged result\n const urlWithMd = `${baseUrlWithoutHash}.md`;\n\n const fullUrl = `https://${baseUrl}${baseUrlWithoutHash}`;\n const fullUrlWithMd = `https://${baseUrl}${urlWithMd}`;\n\n mergedMap.set(baseUrlWithoutHash, {\n url: baseUrlWithoutHash,\n urlWithMd,\n fullUrl,\n fullUrlWithMd,\n title: result.type === \"page\" ? result.content : undefined,\n breadcrumbs: result.breadcrumbs,\n type: result.type,\n sections: [\n {\n content: result.content,\n type: result.type,\n },\n ],\n });\n }\n }\n\n return Array.from(mergedMap.values());\n}\n\n/**\n * Format merged results as markdown\n */\nexport function formatAsMarkdown(mergedResults: MergedResult[]): string {\n const lines: string[] = [];\n\n for (const result of mergedResults) {\n // Title (use first section content if it's a page, or just use content)\n const title = result.title || result.sections[0]?.content || \"Untitled\";\n lines.push(`## Page: '${title}'`);\n // Breadcrumbs\n if (result.breadcrumbs && result.breadcrumbs.length > 0) {\n lines.push(\" \" + result.breadcrumbs.join(\" > \"));\n lines.push(\"\");\n }\n\n // Both URLs\n lines.push(\"URLs:\");\n lines.push(` - ${result.fullUrl}`);\n lines.push(` - ${result.fullUrlWithMd}`);\n lines.push(\"\");\n\n // Show all sections found on this page\n if (result.sections.length > 1) {\n lines.push(\"Relevant sections:\");\n for (let i = 0; i < result.sections.length; i++) {\n const section = result.sections[i];\n // Skip the first section if it's just the page title repeated\n if (i === 0 && result.type === \"page\" && section.content === result.title) {\n continue;\n }\n lines.push(` - ${section.content}`);\n }\n lines.push(\"\");\n }\n\n lines.push(\"---\");\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format merged results as JSON\n */\nexport function formatAsJson(mergedResults: MergedResult[]): string {\n return JSON.stringify(mergedResults, null, 2);\n}\n","import { define } from \"gunshi\";\nimport {\n mergeResultsByUrl,\n formatAsMarkdown,\n formatAsJson,\n} from \"../utils/format-search-results.js\";\n\ninterface SearchResult {\n id: string;\n type: \"page\" | \"heading\" | \"text\";\n content: string;\n breadcrumbs?: string[];\n contentWithHighlights?: Array<{\n type: string;\n content: string;\n styles?: { highlight?: boolean };\n }>;\n url: string;\n}\n\nexport const searchCommand = define({\n name: \"search\",\n description: \"Search the Fragno documentation\",\n args: {\n limit: {\n type: \"number\",\n description: \"Maximum number of results to show\",\n default: 10,\n },\n json: {\n type: \"boolean\",\n description: \"Output results in JSON format\",\n default: false,\n },\n markdown: {\n type: \"boolean\",\n description: \"Output results in Markdown format (default)\",\n default: true,\n },\n \"base-url\": {\n type: \"string\",\n description: \"Base URL for the documentation site\",\n default: \"fragno.dev\",\n },\n },\n run: async (ctx) => {\n const query = ctx.positionals.join(\" \");\n\n if (!query || query.trim().length === 0) {\n throw new Error(\"Please provide a search query\");\n }\n\n // Determine output mode\n const jsonMode = ctx.values.json as boolean;\n const baseUrl = ctx.values[\"base-url\"] as string;\n\n if (!jsonMode) {\n console.log(`Searching for: \"${query}\"\\n`);\n }\n\n try {\n // Make request to the docs search API\n const encodedQuery = encodeURIComponent(query);\n const response = await fetch(`https://${baseUrl}/api/search?query=${encodedQuery}`);\n\n if (!response.ok) {\n throw new Error(`API request failed with status ${response.status}`);\n }\n\n const results = (await response.json()) as SearchResult[];\n\n // Apply limit\n const limit = ctx.values.limit as number;\n const limitedResults = results.slice(0, limit);\n\n if (limitedResults.length === 0) {\n if (jsonMode) {\n console.log(\"[]\");\n } else {\n console.log(\"No results found.\");\n }\n return;\n }\n\n // Merge results by URL\n const mergedResults = mergeResultsByUrl(limitedResults, baseUrl);\n\n // Output based on mode\n if (jsonMode) {\n console.log(formatAsJson(mergedResults));\n } else {\n // Markdown mode (default)\n console.log(\n `Found ${results.length} result${results.length === 1 ? \"\" : \"s\"}${results.length > limit ? ` (showing ${limit})` : \"\"}\\n`,\n );\n console.log(formatAsMarkdown(mergedResults));\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Search failed: ${error.message}`);\n }\n throw new Error(\"Search failed: An unknown error occurred\");\n }\n },\n});\n","import { define } from \"gunshi\";\nimport {\n getSubjects,\n getSubject,\n getAllSubjects,\n getSubjectParent,\n getSubjectChildren,\n getAllSubjectIdsInOrder,\n isCategory,\n getCategoryTitle,\n} from \"@fragno-dev/corpus\";\nimport type { Subject, Example } from \"@fragno-dev/corpus\";\nimport { marked } from \"marked\";\n// @ts-expect-error - marked-terminal types are outdated for v7\nimport { markedTerminal } from \"marked-terminal\";\nimport { stripVTControlCharacters } from \"node:util\";\n\n// Always configure marked to use terminal renderer\nmarked.use(markedTerminal());\n\ninterface PrintOptions {\n showLineNumbers: boolean;\n startLine?: number;\n endLine?: number;\n headingsOnly: boolean;\n}\n\n/**\n * Build markdown content for multiple subjects\n */\nexport function buildSubjectsMarkdown(subjects: Subject[]): string {\n let fullMarkdown = \"\";\n\n for (const subject of subjects) {\n fullMarkdown += `# ${subject.title}\\n\\n`;\n\n if (subject.description) {\n fullMarkdown += `${subject.description}\\n\\n`;\n }\n\n // Add imports block if present\n if (subject.imports) {\n fullMarkdown += `### Imports\\n\\n\\`\\`\\`typescript\\n${subject.imports}\\n\\`\\`\\`\\n\\n`;\n }\n\n // Add prelude blocks if present\n if (subject.prelude.length > 0) {\n fullMarkdown += `### Prelude\\n\\n`;\n for (const block of subject.prelude) {\n // Don't include the directive in the displayed code fence\n fullMarkdown += `\\`\\`\\`typescript\\n${block.code}\\n\\`\\`\\`\\n\\n`;\n }\n }\n\n // Add all sections\n for (const section of subject.sections) {\n fullMarkdown += `## ${section.heading}\\n\\n${section.content}\\n\\n`;\n }\n }\n\n return fullMarkdown;\n}\n\n/**\n * Add line numbers to content\n */\nexport function addLineNumbers(content: string, startFrom: number = 1): string {\n const lines = content.split(\"\\n\");\n const maxDigits = String(startFrom + lines.length - 1).length;\n\n return lines\n .map((line, index) => {\n const lineNum = startFrom + index;\n const paddedNum = String(lineNum).padStart(maxDigits, \" \");\n return `${paddedNum}│ ${line}`;\n })\n .join(\"\\n\");\n}\n\n/**\n * Filter content by line range\n */\nexport function filterByLineRange(content: string, startLine: number, endLine: number): string {\n const lines = content.split(\"\\n\");\n // Convert to 0-based index\n const start = Math.max(0, startLine - 1);\n const end = Math.min(lines.length, endLine);\n return lines.slice(start, end).join(\"\\n\");\n}\n\n/**\n * Extract headings and code block information with line numbers\n */\nexport function extractHeadingsAndBlocks(subjects: Subject[]): string {\n let output = \"\";\n let currentLine = 1;\n let lastOutputLine = 0;\n\n // Helper to add a gap indicator if we skipped lines\n const addGapIfNeeded = () => {\n if (lastOutputLine > 0 && currentLine > lastOutputLine + 1) {\n output += ` │\\n`;\n }\n };\n\n // Add instruction header\n output += \"Use --start N --end N flags to show specific line ranges\\n\\n\";\n\n for (const subject of subjects) {\n // Title\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ # ${subject.title}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n // Empty line after title - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n // Description - show full text\n if (subject.description) {\n const descLines = subject.description.split(\"\\n\");\n for (const line of descLines) {\n output += `${currentLine.toString().padStart(4, \" \")}│ ${line}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n // Empty line after description - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n\n // Imports block - show full code\n if (subject.imports) {\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ ### Imports\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n // Empty line after heading - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n output += `${currentLine.toString().padStart(4, \" \")}│ \\`\\`\\`typescript\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n const importLines = subject.imports.split(\"\\n\");\n for (const line of importLines) {\n output += `${currentLine.toString().padStart(4, \" \")}│ ${line}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n output += `${currentLine.toString().padStart(4, \" \")}│ \\`\\`\\`\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n // Empty line after code block - SHOW IT\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n }\n\n // Prelude blocks - show as list\n if (subject.prelude.length > 0) {\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ ### Prelude\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n // Empty line after heading\n output += `${currentLine.toString().padStart(4, \" \")}│\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n for (const block of subject.prelude) {\n const id = block.id || \"(no-id)\";\n const blockStartLine = currentLine + 1; // +1 for opening ```\n const codeLines = block.code.split(\"\\n\").length;\n const blockEndLine = currentLine + 1 + codeLines; // opening ``` + code lines\n output += `${currentLine.toString().padStart(4, \" \")}│ - id: \\`${id}\\`, L${blockStartLine}-${blockEndLine}\\n`;\n lastOutputLine = currentLine;\n currentLine += codeLines + 3; // opening ```, code, closing ```, blank line\n }\n // Update lastOutputLine to current position to avoid gap indicator\n lastOutputLine = currentLine - 1;\n }\n\n // Sections - show headings and any example IDs that belong to them\n const sectionToExamples = new Map<string, Example[]>();\n\n // Group examples by their rough section (based on heading appearance in explanations)\n for (const example of subject.examples) {\n // Try to match the example to a section based on context\n // For now, we'll list all example IDs under the sections where they appear\n for (const section of subject.sections) {\n // Check if the section contains references to this example\n if (\n section.content.includes(example.code.substring(0, Math.min(50, example.code.length)))\n ) {\n if (!sectionToExamples.has(section.heading)) {\n sectionToExamples.set(section.heading, []);\n }\n sectionToExamples.get(section.heading)!.push(example);\n break;\n }\n }\n }\n\n for (const section of subject.sections) {\n addGapIfNeeded();\n output += `${currentLine.toString().padStart(4, \" \")}│ ## ${section.heading}\\n`;\n lastOutputLine = currentLine;\n currentLine += 1;\n\n // Show code block IDs as a list if any examples match this section\n const examples = sectionToExamples.get(section.heading) || [];\n if (examples.length > 0) {\n // We need to parse the section content to find where each example appears\n const sectionStartLine = currentLine;\n const lines = section.content.split(\"\\n\");\n\n for (const example of examples) {\n const id = example.id || \"(no-id)\";\n // Find the code block in section content\n let blockStartLine = sectionStartLine;\n let blockEndLine = sectionStartLine;\n let inCodeBlock = false;\n let foundBlock = false;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().startsWith(\"```\") && !inCodeBlock) {\n // Check if next lines match the example\n const codeStart = i + 1;\n let matches = true;\n const exampleLines = example.code.split(\"\\n\");\n for (let j = 0; j < Math.min(3, exampleLines.length); j++) {\n if (lines[codeStart + j]?.trim() !== exampleLines[j]?.trim()) {\n matches = false;\n break;\n }\n }\n if (matches) {\n blockStartLine = sectionStartLine + i + 1; // +1 to skip opening ```\n blockEndLine = sectionStartLine + i + exampleLines.length;\n foundBlock = true;\n break;\n }\n }\n }\n\n if (foundBlock) {\n output += `${currentLine.toString().padStart(4, \" \")}│ - id: \\`${id}\\`, L${blockStartLine}-${blockEndLine}\\n`;\n } else {\n output += `${currentLine.toString().padStart(4, \" \")}│ - id: \\`${id}\\`\\n`;\n }\n lastOutputLine = currentLine;\n }\n }\n\n // Count lines\n const sectionLines = section.content.split(\"\\n\");\n for (const _line of sectionLines) {\n currentLine += 1;\n }\n currentLine += 1; // blank line after section\n // Update lastOutputLine to current position to avoid gap indicator\n lastOutputLine = currentLine - 1;\n }\n }\n\n return output;\n}\n\n/**\n * Print subjects with the given options\n */\nasync function printSubjects(subjects: Subject[], options: PrintOptions): Promise<void> {\n if (options.headingsOnly) {\n // Show only headings and code block IDs\n const headingsOutput = extractHeadingsAndBlocks(subjects);\n console.log(headingsOutput);\n return;\n }\n\n // Build the full markdown content\n const markdown = buildSubjectsMarkdown(subjects);\n\n // Render markdown to terminal for nice formatting\n let output = await marked.parse(markdown);\n\n // Apply line range filter if specified (after rendering)\n const startLine = options.startLine ?? 1;\n if (options.startLine !== undefined || options.endLine !== undefined) {\n const end = options.endLine ?? output.split(\"\\n\").length;\n output = filterByLineRange(output, startLine, end);\n }\n\n // Add line numbers after rendering (if requested)\n // Line numbers correspond to the rendered output that agents interact with\n if (options.showLineNumbers) {\n output = addLineNumbers(output, startLine);\n }\n\n console.log(output);\n}\n\n/**\n * Find and print code blocks by ID\n */\nasync function printCodeBlockById(\n id: string,\n topics: string[],\n showLineNumbers: boolean,\n): Promise<void> {\n // If topics are specified, search only those; otherwise search all subjects\n const subjects = topics.length > 0 ? getSubject(...topics) : getAllSubjects();\n\n interface CodeBlockMatch {\n subjectId: string;\n subjectTitle: string;\n section: string;\n code: string;\n type: \"prelude\" | \"example\";\n startLine?: number;\n endLine?: number;\n }\n\n const matches: CodeBlockMatch[] = [];\n\n for (const subject of subjects) {\n // Build the rendered markdown to get correct line numbers (matching --start/--end behavior)\n const fullMarkdown = buildSubjectsMarkdown([subject]);\n const renderedOutput = await marked.parse(fullMarkdown);\n const renderedLines = renderedOutput.split(\"\\n\");\n\n // Search in prelude blocks\n for (const block of subject.prelude) {\n if (block.id === id) {\n // Find line numbers in the rendered output\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n // Search for the prelude code in the rendered output\n const codeLines = block.code.split(\"\\n\");\n const firstCodeLine = codeLines[0].trim();\n\n for (let i = 0; i < renderedLines.length; i++) {\n // Strip ANSI codes before comparing\n if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {\n // Found the start of the code\n startLine = i + 1; // 1-based line numbers\n endLine = i + codeLines.length;\n break;\n }\n }\n\n matches.push({\n subjectId: subject.id,\n subjectTitle: subject.title,\n section: \"Prelude\",\n code: block.code,\n type: \"prelude\",\n startLine,\n endLine,\n });\n }\n }\n\n // Search in examples\n for (const example of subject.examples) {\n if (example.id === id) {\n // Try to find which section this example belongs to\n let sectionName = \"Unknown Section\";\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n for (const section of subject.sections) {\n if (\n section.content.includes(example.code.substring(0, Math.min(50, example.code.length)))\n ) {\n sectionName = section.heading;\n\n // Find line numbers in the rendered output\n const codeLines = example.code.split(\"\\n\");\n const firstCodeLine = codeLines[0].trim();\n\n for (let i = 0; i < renderedLines.length; i++) {\n // Strip ANSI codes before comparing\n if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {\n // Found the start of the code\n startLine = i + 1; // 1-based line numbers\n endLine = i + codeLines.length;\n break;\n }\n }\n break;\n }\n }\n\n matches.push({\n subjectId: subject.id,\n subjectTitle: subject.title,\n section: sectionName,\n code: example.code,\n type: \"example\",\n startLine,\n endLine,\n });\n }\n }\n }\n\n if (matches.length === 0) {\n console.error(`Error: No code block found with id \"${id}\"`);\n if (topics.length > 0) {\n console.error(`Searched in topics: ${topics.join(\", \")}`);\n } else {\n console.error(\"Searched in all available topics\");\n }\n process.exit(1);\n }\n\n // Build markdown output\n for (let i = 0; i < matches.length; i++) {\n const match = matches[i];\n\n if (matches.length > 1 && i > 0) {\n console.log(\"\\n---\\n\");\n }\n\n // Build markdown for this match\n let matchMarkdown = `# ${match.subjectTitle}\\n\\n`;\n matchMarkdown += `## ${match.section}\\n\\n`;\n\n // Add line number info if available and requested (as plain text, not in markdown)\n if (showLineNumbers && match.startLine && match.endLine) {\n console.log(`Lines ${match.startLine}-${match.endLine} (use with --start/--end)\\n`);\n }\n\n matchMarkdown += `\\`\\`\\`typescript\\n${match.code}\\n\\`\\`\\`\\n`;\n\n // Render the markdown\n const rendered = await marked.parse(matchMarkdown);\n console.log(rendered);\n }\n}\n\n/**\n * Print only the topic tree\n */\nfunction printTopicTree(): void {\n const subjects = getSubjects();\n const subjectMap = new Map(subjects.map((s) => [s.id, s]));\n\n // Helper function to get title for any subject ID (including categories)\n function getTitle(subjectId: string): string {\n if (isCategory(subjectId)) {\n return getCategoryTitle(subjectId);\n }\n const subject = subjectMap.get(subjectId);\n return subject ? subject.title : subjectId;\n }\n\n // Helper function to recursively display tree\n function displayNode(subjectId: string, indent: string, isLast: boolean, isRoot: boolean): void {\n const title = getTitle(subjectId);\n\n if (isRoot) {\n console.log(` ${subjectId.padEnd(30)} ${title}`);\n } else {\n const connector = isLast ? \"└─\" : \"├─\";\n console.log(`${indent}${connector} ${subjectId.padEnd(26)} ${title}`);\n }\n\n const children = getSubjectChildren(subjectId);\n if (children.length > 0) {\n const childIndent = isRoot ? \" \" : indent + (isLast ? \" \" : \"│ \");\n for (let i = 0; i < children.length; i++) {\n displayNode(children[i], childIndent, i === children.length - 1, false);\n }\n }\n }\n\n // Get all root subject IDs (including categories)\n const allIds = getAllSubjectIdsInOrder();\n const rootIds = allIds.filter((id) => !getSubjectParent(id));\n\n // Display root subjects\n for (const subjectId of rootIds) {\n displayNode(subjectId, \"\", false, true);\n }\n}\n\n/**\n * Print information about the corpus command\n */\nfunction printCorpusHelp(): void {\n console.log(\"Fragno Corpus - Code examples and documentation (similar to LLMs.txt\");\n console.log(\"\");\n console.log(\"Usage: fragno-cli corpus [options] [topic...]\");\n console.log(\"\");\n console.log(\"Options:\");\n console.log(\" -n, --no-line-numbers Hide line numbers (shown by default)\");\n console.log(\" -s, --start N Starting line number to display from\");\n console.log(\" -e, --end N Ending line number to display to\");\n console.log(\" --headings Show only headings and code block IDs\");\n console.log(\" --id <id> Retrieve a specific code block by ID\");\n console.log(\" --tree Show only the topic tree\");\n console.log(\"\");\n console.log(\"Examples:\");\n console.log(\" fragno-cli corpus # List all available topics\");\n console.log(\" fragno-cli corpus --tree # Show only the topic tree\");\n console.log(\" fragno-cli corpus defining-routes # Show route definition examples\");\n console.log(\" fragno-cli corpus --headings database-querying\");\n console.log(\" # Show structure overview\");\n console.log(\" fragno-cli corpus --start 10 --end 50 database-querying\");\n console.log(\" # Show specific lines\");\n console.log(\" fragno-cli corpus --id create-user # Get code block by ID\");\n console.log(\" fragno-cli corpus database-adapters kysely-adapter\");\n console.log(\" # Show multiple topics\");\n console.log(\"\");\n console.log(\"Available topics:\");\n\n printTopicTree();\n}\n\nexport const corpusCommand = define({\n name: \"corpus\",\n description: \"View code examples and documentation for Fragno\",\n args: {\n \"no-line-numbers\": {\n type: \"boolean\",\n short: \"n\",\n description: \"Hide line numbers (line numbers are shown by default)\",\n },\n start: {\n type: \"number\",\n short: \"s\",\n description: \"Starting line number (1-based) to display from\",\n },\n end: {\n type: \"number\",\n short: \"e\",\n description: \"Ending line number (1-based) to display to\",\n },\n headings: {\n type: \"boolean\",\n description: \"Show only section headings and code block IDs with line numbers\",\n },\n id: {\n type: \"string\",\n description: \"Retrieve a specific code block by ID\",\n },\n tree: {\n type: \"boolean\",\n description: \"Show only the topic tree (without help text)\",\n },\n },\n run: async (ctx) => {\n const topics = ctx.positionals;\n const showLineNumbers = !(ctx.values[\"no-line-numbers\"] ?? false);\n const startLine = ctx.values.start;\n const endLine = ctx.values.end;\n const headingsOnly = ctx.values.headings ?? false;\n const codeBlockId = ctx.values.id;\n const treeOnly = ctx.values.tree ?? false;\n\n // Handle --id flag\n if (codeBlockId) {\n await printCodeBlockById(codeBlockId, topics, showLineNumbers);\n return;\n }\n\n // Handle --tree flag\n if (treeOnly) {\n printTopicTree();\n return;\n }\n\n // No topics provided - show help\n if (topics.length === 0) {\n printCorpusHelp();\n return;\n }\n\n // Validate line range\n if (startLine !== undefined && endLine !== undefined && startLine > endLine) {\n console.error(\"Error: --start must be less than or equal to --end\");\n process.exit(1);\n }\n\n // Load and display requested topics\n try {\n const subjects = getSubject(...topics);\n\n await printSubjects(subjects, {\n showLineNumbers,\n startLine,\n endLine,\n headingsOnly,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"ENOENT\")) {\n // Extract the subject name from the error message or use the topics array\n const missingTopics = topics.filter((topic) => {\n try {\n getSubject(topic);\n return false;\n } catch {\n return true;\n }\n });\n\n if (missingTopics.length === 1) {\n console.error(`Error: Subject '${missingTopics[0]}' not found.`);\n } else if (missingTopics.length > 1) {\n console.error(\n `Error: Subjects not found: ${missingTopics.map((t) => `'${t}'`).join(\", \")}`,\n );\n } else {\n console.error(\"Error: One or more subjects not found.\");\n }\n console.log(\"\\nAvailable topics:\");\n printTopicTree();\n } else {\n console.error(\"Error loading topics:\", error instanceof Error ? error.message : error);\n console.log(\"\\nRun 'fragno-cli corpus' to see available topics.\");\n }\n process.exit(1);\n }\n },\n});\n","import { createServer, type Server } from \"node:http\";\nimport { resolve, relative } from \"node:path\";\nimport { define } from \"gunshi\";\nimport { toNodeHandler } from \"@fragno-dev/node\";\nimport type { FragnoInstantiatedFragment } from \"@fragno-dev/core\";\nimport { loadConfig } from \"../utils/load-config\";\nimport { findFragnoFragments } from \"../utils/find-fragno-databases\";\n\nexport const serveCommand = define({\n name: \"serve\",\n description: \"Start a local HTTP server to serve one or more Fragno fragments\",\n args: {\n port: {\n type: \"number\",\n short: \"p\",\n description: \"Port to listen on\",\n default: 8080,\n },\n host: {\n type: \"string\",\n short: \"H\",\n description: \"Host to bind to\",\n default: \"localhost\",\n },\n },\n run: async (ctx) => {\n const targets = ctx.positionals;\n const port = ctx.values.port ?? 8080;\n const host = ctx.values.host ?? \"localhost\";\n const cwd = process.cwd();\n\n if (targets.length === 0) {\n throw new Error(\n \"No fragment files specified.\\n\\n\" +\n \"Usage: fragno-cli serve <fragment-file> [fragment-file...]\\n\\n\" +\n \"Example: fragno-cli serve ./src/my-fragment.ts\",\n );\n }\n\n const targetPaths = targets.map((target) => resolve(cwd, target));\n\n // Import all fragment files and find instantiated fragments\n const allFragments: FragnoInstantiatedFragment<\n [],\n unknown,\n Record<string, unknown>,\n Record<string, unknown>,\n Record<string, unknown>,\n unknown,\n Record<string, unknown>\n >[] = [];\n\n for (const targetPath of targetPaths) {\n const relativePath = relative(cwd, targetPath);\n const config = await loadConfig(targetPath);\n const fragments = findFragnoFragments(config);\n\n if (fragments.length === 0) {\n console.warn(\n `Warning: No instantiated fragments found in ${relativePath}.\\n` +\n `Make sure you export an instantiated fragment (e.g., the return value of createMyFragment()).\\n`,\n );\n continue;\n }\n\n allFragments.push(...fragments);\n console.log(\n ` Found ${fragments.length} fragment(s) in ${relativePath}: ${fragments.map((f) => f.name).join(\", \")}`,\n );\n }\n\n if (allFragments.length === 0) {\n throw new Error(\n \"No instantiated fragments found in any of the specified files.\\n\" +\n \"Make sure your files export instantiated fragments.\",\n );\n }\n\n // Build handlers mapped by mountRoute\n const handlers = allFragments.map((fragment) => ({\n mountRoute: fragment.mountRoute,\n handler: toNodeHandler(fragment.handler.bind(fragment)),\n fragment,\n }));\n\n const server = createServer((req, res) => {\n const url = req.url ?? \"\";\n\n for (const { mountRoute, handler } of handlers) {\n if (url.startsWith(mountRoute)) {\n return handler(req, res);\n }\n }\n\n res.statusCode = 404;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n error: \"Not Found\",\n availableRoutes: handlers.map((h) => h.mountRoute),\n }),\n );\n });\n\n server.listen(port, host, () => {\n const hostStr = addressToString(server);\n console.log(`\\nFragno server is running on: ${hostStr}\\n`);\n\n for (const { fragment } of handlers) {\n console.log(`Fragment: ${fragment.name}`);\n console.log(` Mount: ${hostStr}${fragment.mountRoute}`);\n\n const routes = fragment.routes as unknown as { method: string; path: string }[];\n if (routes.length > 0) {\n console.log(\" Routes:\");\n for (const route of routes) {\n console.log(` ${route.method} ${fragment.mountRoute}${route.path}`);\n }\n }\n\n console.log(\"\");\n }\n });\n },\n});\n\nfunction addressToString(server: Server, protocol: \"http\" | \"https\" = \"http\"): string {\n const addr = server.address();\n if (!addr) {\n throw new Error(\"Address invalid\");\n }\n\n if (typeof addr === \"string\") {\n return addr;\n }\n\n let host = addr.address;\n\n if (host === \"::\" || host === \"0.0.0.0\") {\n host = \"localhost\";\n }\n\n if (addr.family === \"IPv6\" && host !== \"localhost\") {\n host = `[${host}]`;\n }\n\n return `${protocol}://${host}:${addr.port}`;\n}\n","#!/usr/bin/env node\n\nimport { cli, define } from \"gunshi\";\nimport { generateCommand } from \"./commands/db/generate.js\";\nimport { migrateCommand } from \"./commands/db/migrate.js\";\nimport { infoCommand } from \"./commands/db/info.js\";\nimport { searchCommand } from \"./commands/search.js\";\nimport { corpusCommand } from \"./commands/corpus.js\";\nimport { serveCommand } from \"./commands/serve.js\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../package.json\"), \"utf-8\"));\nconst version = packageJson.version;\n\n// Create a Map of db sub-commands\nconst dbSubCommands = new Map();\ndbSubCommands.set(\"generate\", generateCommand);\ndbSubCommands.set(\"migrate\", migrateCommand);\ndbSubCommands.set(\"info\", infoCommand);\n\n// Define the db command with nested subcommands\nexport const dbCommand = define({\n name: \"db\",\n description: \"Database management commands\",\n});\n\n// Define the main command\nexport const mainCommand = define({\n name: \"fragno-cli\",\n description: \"Tools for working with Fragno fragments\",\n});\n\nexport async function run() {\n try {\n const args = process.argv.slice(2);\n\n // Manual routing for top-level commands\n if (args[0] === \"search\") {\n // Run search command directly\n await cli(args.slice(1), searchCommand, {\n name: \"fragno-cli search\",\n version,\n });\n } else if (args[0] === \"corpus\") {\n // Run corpus command directly\n await cli(args.slice(1), corpusCommand, {\n name: \"fragno-cli corpus\",\n version,\n });\n } else if (args[0] === \"serve\") {\n // Run serve command directly\n await cli(args.slice(1), serveCommand, {\n name: \"fragno-cli serve\",\n version,\n });\n } else if (args[0] === \"db\") {\n // Handle db subcommands\n const subCommandName = args[1];\n\n if (!subCommandName || subCommandName === \"--help\" || subCommandName === \"-h\") {\n // Show db help with subcommands\n console.log(\"Database management commands\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-cli db <COMMAND>\");\n console.log(\"\");\n console.log(\"COMMANDS:\");\n console.log(\n \" generate Generate schema files from FragnoDatabase definitions\",\n );\n console.log(\" migrate Run database migrations\");\n console.log(\" info Display database information and migration status\");\n console.log(\"\");\n console.log(\"For more info, run any command with the `--help` flag:\");\n console.log(\" fragno-cli db generate --help\");\n console.log(\" fragno-cli db migrate --help\");\n console.log(\" fragno-cli db info --help\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -h, --help Display this help message\");\n console.log(\" -v, --version Display this version\");\n } else if (subCommandName === \"--version\" || subCommandName === \"-v\") {\n console.log(version);\n } else {\n // Route to specific db subcommand\n const subCommand = dbSubCommands.get(subCommandName);\n\n if (!subCommand) {\n console.error(`Unknown command: ${subCommandName}`);\n console.log(\"\");\n console.log(\"Run 'fragno-cli db --help' for available commands.\");\n process.exit(1);\n }\n\n // Run the subcommand\n await cli(args.slice(2), subCommand, {\n name: `fragno-cli db ${subCommandName}`,\n version,\n });\n }\n } else if (!args.length || args[0] === \"--help\" || args[0] === \"-h\") {\n // Show main help\n console.log(\"Tools for working with Fragno\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-cli <COMMAND>\");\n console.log(\"\");\n console.log(\"COMMANDS:\");\n console.log(\" serve Start a local HTTP server to serve fragments\");\n console.log(\" db Database management commands\");\n console.log(\" search Search the Fragno documentation\");\n console.log(\" corpus View code examples and documentation for Fragno\");\n console.log(\"\");\n console.log(\"For more info, run any command with the `--help` flag:\");\n console.log(\" fragno-cli serve --help\");\n console.log(\" fragno-cli db --help\");\n console.log(\" fragno-cli search --help\");\n console.log(\" fragno-cli corpus --help\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -h, --help Display this help message\");\n console.log(\" -v, --version Display this version\");\n } else if (args[0] === \"--version\" || args[0] === \"-v\") {\n console.log(version);\n } else {\n // Unknown command\n console.error(`Unknown command: ${args[0]}`);\n console.log(\"\");\n console.log(\"Run 'fragno-cli --help' for available commands.\");\n process.exit(1);\n }\n } catch (error) {\n console.error(error);\n process.exit(1);\n }\n}\n\nif (import.meta.main) {\n await run();\n}\n\nexport { generateCommand, migrateCommand, infoCommand, searchCommand, corpusCommand, serveCommand };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragno-dev/cli",
3
- "version": "0.1.23",
3
+ "version": "0.2.1",
4
4
  "exports": {
5
5
  ".": {
6
6
  "development": "./src/cli.ts",
@@ -16,20 +16,21 @@
16
16
  "node": ">=22"
17
17
  },
18
18
  "devDependencies": {
19
- "@types/node": "^22",
19
+ "@types/node": "^22.19.7",
20
20
  "@vitest/coverage-istanbul": "^3.2.4",
21
21
  "@fragno-private/typescript-config": "0.0.1",
22
22
  "@fragno-private/vitest-config": "0.0.0"
23
23
  },
24
24
  "dependencies": {
25
25
  "@clack/prompts": "^0.11.0",
26
- "c12": "^3.3.1",
26
+ "c12": "^3.3.3",
27
27
  "gunshi": "^0.26.3",
28
28
  "marked": "^15.0.12",
29
29
  "marked-terminal": "^7.3.0",
30
- "@fragno-dev/core": "0.1.11",
30
+ "@fragno-dev/core": "0.2.0",
31
31
  "@fragno-dev/corpus": "0.0.7",
32
- "@fragno-dev/db": "0.2.2"
32
+ "@fragno-dev/db": "0.3.0",
33
+ "@fragno-dev/node": "0.0.8"
33
34
  },
34
35
  "main": "./dist/cli.js",
35
36
  "module": "./dist/cli.js",
package/src/cli.ts CHANGED
@@ -6,6 +6,7 @@ import { migrateCommand } from "./commands/db/migrate.js";
6
6
  import { infoCommand } from "./commands/db/info.js";
7
7
  import { searchCommand } from "./commands/search.js";
8
8
  import { corpusCommand } from "./commands/corpus.js";
9
+ import { serveCommand } from "./commands/serve.js";
9
10
  import { readFileSync } from "node:fs";
10
11
  import { fileURLToPath } from "node:url";
11
12
  import { dirname, join } from "node:path";
@@ -49,6 +50,12 @@ export async function run() {
49
50
  name: "fragno-cli corpus",
50
51
  version,
51
52
  });
53
+ } else if (args[0] === "serve") {
54
+ // Run serve command directly
55
+ await cli(args.slice(1), serveCommand, {
56
+ name: "fragno-cli serve",
57
+ version,
58
+ });
52
59
  } else if (args[0] === "db") {
53
60
  // Handle db subcommands
54
61
  const subCommandName = args[1];
@@ -102,11 +109,13 @@ export async function run() {
102
109
  console.log(" fragno-cli <COMMAND>");
103
110
  console.log("");
104
111
  console.log("COMMANDS:");
112
+ console.log(" serve Start a local HTTP server to serve fragments");
105
113
  console.log(" db Database management commands");
106
114
  console.log(" search Search the Fragno documentation");
107
115
  console.log(" corpus View code examples and documentation for Fragno");
108
116
  console.log("");
109
117
  console.log("For more info, run any command with the `--help` flag:");
118
+ console.log(" fragno-cli serve --help");
110
119
  console.log(" fragno-cli db --help");
111
120
  console.log(" fragno-cli search --help");
112
121
  console.log(" fragno-cli corpus --help");
@@ -133,4 +142,4 @@ if (import.meta.main) {
133
142
  await run();
134
143
  }
135
144
 
136
- export { generateCommand, migrateCommand, infoCommand, searchCommand, corpusCommand };
145
+ export { generateCommand, migrateCommand, infoCommand, searchCommand, corpusCommand, serveCommand };
@@ -1,14 +1,18 @@
1
1
  import { writeFile, mkdir } from "node:fs/promises";
2
2
  import { resolve, dirname } from "node:path";
3
3
  import { define } from "gunshi";
4
- import { generateMigrationsOrSchema } from "@fragno-dev/db/generation-engine";
4
+ import { generateSchemaArtifacts } from "@fragno-dev/db/generation-engine";
5
5
  import { importFragmentFiles } from "../../utils/find-fragno-databases";
6
6
 
7
7
  // Define the db generate command with type safety
8
8
  export const generateCommand = define({
9
9
  name: "generate",
10
- description: "Generate schema files from FragnoDatabase definitions",
10
+ description: "Generate SQL migrations or schema outputs from FragnoDatabase definitions",
11
11
  args: {
12
+ format: {
13
+ type: "string",
14
+ description: "Output format: sql (migrations), drizzle (schema), prisma (schema)",
15
+ },
12
16
  output: {
13
17
  type: "string",
14
18
  short: "o",
@@ -35,6 +39,7 @@ export const generateCommand = define({
35
39
  // With `define()` and `multiple: true`, targets is properly typed as string[]
36
40
  const targets = ctx.positionals;
37
41
  const output = ctx.values.output;
42
+ const format = ctx.values.format ?? "sql";
38
43
  const toVersion = ctx.values.to;
39
44
  const fromVersion = ctx.values.from;
40
45
  const prefix = ctx.values.prefix;
@@ -43,22 +48,28 @@ export const generateCommand = define({
43
48
  const targetPaths = targets.map((target) => resolve(process.cwd(), target));
44
49
 
45
50
  // Import all fragment files and validate they use the same adapter
46
- const { databases: allFragnoDatabases, adapter } = await importFragmentFiles(targetPaths);
51
+ const { databases: allFragnoDatabases } = await importFragmentFiles(targetPaths);
47
52
 
48
- // Check if adapter supports any form of schema generation
49
- if (!adapter.createSchemaGenerator && !adapter.prepareMigrations) {
50
- throw new Error(
51
- `The adapter does not support schema generation. ` +
52
- `Please use an adapter that implements either createSchemaGenerator or prepareMigrations.`,
53
- );
53
+ const allowedFormats = ["sql", "drizzle", "prisma"] as const;
54
+ if (!allowedFormats.includes(format as (typeof allowedFormats)[number])) {
55
+ throw new Error(`Unsupported format '${format}'. Use one of: ${allowedFormats.join(", ")}.`);
56
+ }
57
+
58
+ if (format !== "sql" && (toVersion !== undefined || fromVersion !== undefined)) {
59
+ throw new Error("--from and --to are only supported for SQL migration output.");
54
60
  }
55
61
 
56
62
  // Generate schema for all fragments
57
- console.log("Generating schema...");
63
+ if (format === "sql") {
64
+ console.log("Generating SQL migrations...");
65
+ } else {
66
+ console.log(`Generating ${format} schema output...`);
67
+ }
58
68
 
59
- let results: { schema: string; path: string; namespace: string }[];
69
+ let results: { schema: string; path: string; namespace: string | null }[];
60
70
  try {
61
- results = await generateMigrationsOrSchema(allFragnoDatabases, {
71
+ results = await generateSchemaArtifacts(allFragnoDatabases, {
72
+ format: format as "sql" | "drizzle" | "prisma",
62
73
  path: output,
63
74
  toVersion,
64
75
  fromVersion,
@@ -103,11 +114,12 @@ export const generateCommand = define({
103
114
  console.log(`✓ Generated: ${finalOutputPath}`);
104
115
  }
105
116
 
106
- console.log(`\n✓ Schema generated successfully!`);
117
+ console.log(`\n✓ Output generated successfully!`);
107
118
  console.log(` Files generated: ${results.length}`);
108
119
  console.log(` Fragments:`);
109
120
  for (const db of allFragnoDatabases) {
110
- console.log(` - ${db.namespace} (version ${db.schema.version})`);
121
+ const namespaceLabel = db.namespace ?? "(none)";
122
+ console.log(` - ${db.schema.name} [${namespaceLabel}] (version ${db.schema.version})`);
111
123
  }
112
124
  },
113
125
  });
@@ -22,22 +22,31 @@ export const infoCommand = define({
22
22
  // Collect database information
23
23
  const dbInfos = await Promise.all(
24
24
  allFragnoDatabases.map(async (fragnoDb) => {
25
+ const adapterMetadata = fragnoDb.adapter.adapterMetadata;
26
+ const databaseType = adapterMetadata?.databaseType;
27
+ const sqliteProfile = adapterMetadata?.sqliteProfile;
28
+ const namespaceKey = fragnoDb.namespace ?? fragnoDb.schema.name;
29
+ const displayNamespace = fragnoDb.namespace ?? "(none)";
25
30
  const info: {
26
31
  namespace: string;
27
32
  schemaVersion: number;
28
33
  migrationSupport: boolean;
34
+ databaseType?: string;
35
+ sqliteProfile?: string;
29
36
  currentVersion?: string;
30
37
  pendingVersions?: string;
31
38
  status?: string;
32
39
  } = {
33
- namespace: fragnoDb.namespace,
40
+ namespace: displayNamespace,
34
41
  schemaVersion: fragnoDb.schema.version,
35
42
  migrationSupport: !!fragnoDb.adapter.prepareMigrations,
43
+ databaseType,
44
+ sqliteProfile: databaseType === "sqlite" ? sqliteProfile : undefined,
36
45
  };
37
46
 
38
47
  // Get current database version if migrations are supported
39
48
  if (fragnoDb.adapter.prepareMigrations) {
40
- const currentVersion = await fragnoDb.adapter.getSchemaVersion(fragnoDb.namespace);
49
+ const currentVersion = await fragnoDb.adapter.getSchemaVersion(namespaceKey);
41
50
  info.currentVersion = currentVersion;
42
51
  // info.pendingVersions = fragnoDb.schema.version - currentVersion;
43
52
 
@@ -54,6 +63,9 @@ export const infoCommand = define({
54
63
  }),
55
64
  );
56
65
 
66
+ const showDatabaseType = dbInfos.some((info) => !!info.databaseType);
67
+ const showSqliteProfile = dbInfos.some((info) => info.databaseType === "sqlite");
68
+
57
69
  // Determine if any database supports migrations
58
70
  const hasMigrationSupport = dbInfos.some((info) => info.migrationSupport);
59
71
 
@@ -65,6 +77,8 @@ export const infoCommand = define({
65
77
  // Table header
66
78
  const namespaceHeader = "Namespace";
67
79
  const versionHeader = "Schema";
80
+ const databaseHeader = "DB";
81
+ const profileHeader = "SQLite";
68
82
  const currentHeader = "Current";
69
83
  const statusHeader = "Status";
70
84
 
@@ -74,6 +88,8 @@ export const infoCommand = define({
74
88
  );
75
89
  const namespaceWidth = Math.max(maxNamespaceLen + 2, 20);
76
90
  const versionWidth = 8;
91
+ const databaseWidth = 8;
92
+ const profileWidth = 10;
77
93
  const currentWidth = 9;
78
94
  const statusWidth = 25;
79
95
 
@@ -81,12 +97,16 @@ export const infoCommand = define({
81
97
  console.log(
82
98
  namespaceHeader.padEnd(namespaceWidth) +
83
99
  versionHeader.padEnd(versionWidth) +
100
+ (showDatabaseType ? databaseHeader.padEnd(databaseWidth) : "") +
101
+ (showSqliteProfile ? profileHeader.padEnd(profileWidth) : "") +
84
102
  (hasMigrationSupport ? currentHeader.padEnd(currentWidth) : "") +
85
103
  statusHeader,
86
104
  );
87
105
  console.log(
88
106
  "-".repeat(namespaceWidth) +
89
107
  "-".repeat(versionWidth) +
108
+ (showDatabaseType ? "-".repeat(databaseWidth) : "") +
109
+ (showSqliteProfile ? "-".repeat(profileWidth) : "") +
90
110
  (hasMigrationSupport ? "-".repeat(currentWidth) : "") +
91
111
  "-".repeat(statusWidth),
92
112
  );
@@ -97,6 +117,8 @@ export const infoCommand = define({
97
117
  console.log(
98
118
  info.namespace.padEnd(namespaceWidth) +
99
119
  String(info.schemaVersion).padEnd(versionWidth) +
120
+ (showDatabaseType ? (info.databaseType ?? "-").padEnd(databaseWidth) : "") +
121
+ (showSqliteProfile ? (info.sqliteProfile ?? "-").padEnd(profileWidth) : "") +
100
122
  (hasMigrationSupport ? currentVersionStr.padEnd(currentWidth) : "") +
101
123
  (info.status || "-"),
102
124
  );
@@ -5,7 +5,7 @@ import { executeMigrations, type ExecuteMigrationResult } from "@fragno-dev/db/g
5
5
 
6
6
  export const migrateCommand = define({
7
7
  name: "migrate",
8
- description: "Run database migrations for all fragments to their latest versions",
8
+ description: "Run SQL database migrations for all fragments to their latest versions",
9
9
  args: {},
10
10
  run: async (ctx) => {
11
11
  const targets = ctx.positionals;
@@ -0,0 +1,148 @@
1
+ import { createServer, type Server } from "node:http";
2
+ import { resolve, relative } from "node:path";
3
+ import { define } from "gunshi";
4
+ import { toNodeHandler } from "@fragno-dev/node";
5
+ import type { FragnoInstantiatedFragment } from "@fragno-dev/core";
6
+ import { loadConfig } from "../utils/load-config";
7
+ import { findFragnoFragments } from "../utils/find-fragno-databases";
8
+
9
+ export const serveCommand = define({
10
+ name: "serve",
11
+ description: "Start a local HTTP server to serve one or more Fragno fragments",
12
+ args: {
13
+ port: {
14
+ type: "number",
15
+ short: "p",
16
+ description: "Port to listen on",
17
+ default: 8080,
18
+ },
19
+ host: {
20
+ type: "string",
21
+ short: "H",
22
+ description: "Host to bind to",
23
+ default: "localhost",
24
+ },
25
+ },
26
+ run: async (ctx) => {
27
+ const targets = ctx.positionals;
28
+ const port = ctx.values.port ?? 8080;
29
+ const host = ctx.values.host ?? "localhost";
30
+ const cwd = process.cwd();
31
+
32
+ if (targets.length === 0) {
33
+ throw new Error(
34
+ "No fragment files specified.\n\n" +
35
+ "Usage: fragno-cli serve <fragment-file> [fragment-file...]\n\n" +
36
+ "Example: fragno-cli serve ./src/my-fragment.ts",
37
+ );
38
+ }
39
+
40
+ const targetPaths = targets.map((target) => resolve(cwd, target));
41
+
42
+ // Import all fragment files and find instantiated fragments
43
+ const allFragments: FragnoInstantiatedFragment<
44
+ [],
45
+ unknown,
46
+ Record<string, unknown>,
47
+ Record<string, unknown>,
48
+ Record<string, unknown>,
49
+ unknown,
50
+ Record<string, unknown>
51
+ >[] = [];
52
+
53
+ for (const targetPath of targetPaths) {
54
+ const relativePath = relative(cwd, targetPath);
55
+ const config = await loadConfig(targetPath);
56
+ const fragments = findFragnoFragments(config);
57
+
58
+ if (fragments.length === 0) {
59
+ console.warn(
60
+ `Warning: No instantiated fragments found in ${relativePath}.\n` +
61
+ `Make sure you export an instantiated fragment (e.g., the return value of createMyFragment()).\n`,
62
+ );
63
+ continue;
64
+ }
65
+
66
+ allFragments.push(...fragments);
67
+ console.log(
68
+ ` Found ${fragments.length} fragment(s) in ${relativePath}: ${fragments.map((f) => f.name).join(", ")}`,
69
+ );
70
+ }
71
+
72
+ if (allFragments.length === 0) {
73
+ throw new Error(
74
+ "No instantiated fragments found in any of the specified files.\n" +
75
+ "Make sure your files export instantiated fragments.",
76
+ );
77
+ }
78
+
79
+ // Build handlers mapped by mountRoute
80
+ const handlers = allFragments.map((fragment) => ({
81
+ mountRoute: fragment.mountRoute,
82
+ handler: toNodeHandler(fragment.handler.bind(fragment)),
83
+ fragment,
84
+ }));
85
+
86
+ const server = createServer((req, res) => {
87
+ const url = req.url ?? "";
88
+
89
+ for (const { mountRoute, handler } of handlers) {
90
+ if (url.startsWith(mountRoute)) {
91
+ return handler(req, res);
92
+ }
93
+ }
94
+
95
+ res.statusCode = 404;
96
+ res.setHeader("Content-Type", "application/json");
97
+ res.end(
98
+ JSON.stringify({
99
+ error: "Not Found",
100
+ availableRoutes: handlers.map((h) => h.mountRoute),
101
+ }),
102
+ );
103
+ });
104
+
105
+ server.listen(port, host, () => {
106
+ const hostStr = addressToString(server);
107
+ console.log(`\nFragno server is running on: ${hostStr}\n`);
108
+
109
+ for (const { fragment } of handlers) {
110
+ console.log(`Fragment: ${fragment.name}`);
111
+ console.log(` Mount: ${hostStr}${fragment.mountRoute}`);
112
+
113
+ const routes = fragment.routes as unknown as { method: string; path: string }[];
114
+ if (routes.length > 0) {
115
+ console.log(" Routes:");
116
+ for (const route of routes) {
117
+ console.log(` ${route.method} ${fragment.mountRoute}${route.path}`);
118
+ }
119
+ }
120
+
121
+ console.log("");
122
+ }
123
+ });
124
+ },
125
+ });
126
+
127
+ function addressToString(server: Server, protocol: "http" | "https" = "http"): string {
128
+ const addr = server.address();
129
+ if (!addr) {
130
+ throw new Error("Address invalid");
131
+ }
132
+
133
+ if (typeof addr === "string") {
134
+ return addr;
135
+ }
136
+
137
+ let host = addr.address;
138
+
139
+ if (host === "::" || host === "0.0.0.0") {
140
+ host = "localhost";
141
+ }
142
+
143
+ if (addr.family === "IPv6" && host !== "localhost") {
144
+ host = `[${host}]`;
145
+ }
146
+
147
+ return `${protocol}://${host}:${addr.port}`;
148
+ }
@@ -149,6 +149,39 @@ function isNewFragnoInstantiatedFragment(
149
149
  );
150
150
  }
151
151
 
152
+ /**
153
+ * Finds all instantiated Fragno fragments in a module's exports.
154
+ */
155
+ export function findFragnoFragments(
156
+ targetModule: Record<string, unknown>,
157
+ ): FragnoInstantiatedFragment<
158
+ [],
159
+ unknown,
160
+ Record<string, unknown>,
161
+ Record<string, unknown>,
162
+ Record<string, unknown>,
163
+ unknown,
164
+ Record<string, unknown>
165
+ >[] {
166
+ const fragments: FragnoInstantiatedFragment<
167
+ [],
168
+ unknown,
169
+ Record<string, unknown>,
170
+ Record<string, unknown>,
171
+ Record<string, unknown>,
172
+ unknown,
173
+ Record<string, unknown>
174
+ >[] = [];
175
+
176
+ for (const [_key, value] of Object.entries(targetModule)) {
177
+ if (isNewFragnoInstantiatedFragment(value)) {
178
+ fragments.push(value);
179
+ }
180
+ }
181
+
182
+ return fragments;
183
+ }
184
+
152
185
  /**
153
186
  * Finds all FragnoDatabase instances in a module, including those embedded
154
187
  * in instantiated fragments.
package/vitest.config.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { defineConfig } from "vitest/config";
1
+ import { defineConfig, mergeConfig } from "vitest/config";
2
2
  import { baseConfig } from "@fragno-private/vitest-config";
3
3
 
4
- export default defineConfig(baseConfig);
4
+ export default defineConfig(
5
+ mergeConfig(baseConfig, {
6
+ test: {
7
+ coverage: {
8
+ enabled: false,
9
+ },
10
+ },
11
+ }),
12
+ );