@fragno-dev/cli 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +44 -0
- package/dist/cli.d.ts +52 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +573 -51
- package/dist/cli.js.map +1 -1
- package/package.json +6 -3
- package/src/cli.ts +95 -76
- package/src/commands/corpus.ts +581 -0
- package/src/commands/search.ts +105 -0
- package/src/utils/format-search-results.ts +121 -0
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { cli, define
|
|
2
|
+
import { cli, define } from "gunshi";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
-
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { executeMigrations, generateMigrationsOrSchema } 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/api/fragment-instantiation";
|
|
9
9
|
import { loadConfig } from "c12";
|
|
10
|
+
import { getAllSubjects, getSubject, getSubjectChildren, getSubjectParent, getSubjects } from "@fragno-dev/corpus";
|
|
11
|
+
import { marked } from "marked";
|
|
12
|
+
import { markedTerminal } from "marked-terminal";
|
|
13
|
+
import { stripVTControlCharacters } from "node:util";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
10
16
|
|
|
11
17
|
//#region src/utils/find-fragno-databases.ts
|
|
12
18
|
async function importFragmentFile(path) {
|
|
@@ -258,76 +264,592 @@ const infoCommand = define({
|
|
|
258
264
|
}
|
|
259
265
|
});
|
|
260
266
|
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/utils/format-search-results.ts
|
|
269
|
+
/**
|
|
270
|
+
* Merge search results by URL, grouping sections and content under each URL (without hash)
|
|
271
|
+
*/
|
|
272
|
+
function mergeResultsByUrl(results, baseUrl) {
|
|
273
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
274
|
+
for (const result of results) {
|
|
275
|
+
const baseUrlWithoutHash = result.url.split("#")[0];
|
|
276
|
+
const existing = mergedMap.get(baseUrlWithoutHash);
|
|
277
|
+
if (existing) existing.sections.push({
|
|
278
|
+
content: result.content,
|
|
279
|
+
type: result.type
|
|
280
|
+
});
|
|
281
|
+
else {
|
|
282
|
+
const urlWithMd = `${baseUrlWithoutHash}.md`;
|
|
283
|
+
const fullUrl = `https://${baseUrl}${baseUrlWithoutHash}`;
|
|
284
|
+
const fullUrlWithMd = `https://${baseUrl}${urlWithMd}`;
|
|
285
|
+
mergedMap.set(baseUrlWithoutHash, {
|
|
286
|
+
url: baseUrlWithoutHash,
|
|
287
|
+
urlWithMd,
|
|
288
|
+
fullUrl,
|
|
289
|
+
fullUrlWithMd,
|
|
290
|
+
title: result.type === "page" ? result.content : void 0,
|
|
291
|
+
breadcrumbs: result.breadcrumbs,
|
|
292
|
+
type: result.type,
|
|
293
|
+
sections: [{
|
|
294
|
+
content: result.content,
|
|
295
|
+
type: result.type
|
|
296
|
+
}]
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return Array.from(mergedMap.values());
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Format merged results as markdown
|
|
304
|
+
*/
|
|
305
|
+
function formatAsMarkdown(mergedResults) {
|
|
306
|
+
const lines = [];
|
|
307
|
+
for (const result of mergedResults) {
|
|
308
|
+
const title = result.title || result.sections[0]?.content || "Untitled";
|
|
309
|
+
lines.push(`## Page: '${title}'`);
|
|
310
|
+
if (result.breadcrumbs && result.breadcrumbs.length > 0) {
|
|
311
|
+
lines.push(" " + result.breadcrumbs.join(" > "));
|
|
312
|
+
lines.push("");
|
|
313
|
+
}
|
|
314
|
+
lines.push("URLs:");
|
|
315
|
+
lines.push(` - ${result.fullUrl}`);
|
|
316
|
+
lines.push(` - ${result.fullUrlWithMd}`);
|
|
317
|
+
lines.push("");
|
|
318
|
+
if (result.sections.length > 1) {
|
|
319
|
+
lines.push("Relevant sections:");
|
|
320
|
+
for (let i = 0; i < result.sections.length; i++) {
|
|
321
|
+
const section = result.sections[i];
|
|
322
|
+
if (i === 0 && result.type === "page" && section.content === result.title) continue;
|
|
323
|
+
lines.push(` - ${section.content}`);
|
|
324
|
+
}
|
|
325
|
+
lines.push("");
|
|
326
|
+
}
|
|
327
|
+
lines.push("---");
|
|
328
|
+
lines.push("");
|
|
329
|
+
}
|
|
330
|
+
return lines.join("\n");
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Format merged results as JSON
|
|
334
|
+
*/
|
|
335
|
+
function formatAsJson(mergedResults) {
|
|
336
|
+
return JSON.stringify(mergedResults, null, 2);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/commands/search.ts
|
|
341
|
+
const searchCommand = define({
|
|
342
|
+
name: "search",
|
|
343
|
+
description: "Search the Fragno documentation",
|
|
344
|
+
args: {
|
|
345
|
+
limit: {
|
|
346
|
+
type: "number",
|
|
347
|
+
description: "Maximum number of results to show",
|
|
348
|
+
default: 10
|
|
349
|
+
},
|
|
350
|
+
json: {
|
|
351
|
+
type: "boolean",
|
|
352
|
+
description: "Output results in JSON format",
|
|
353
|
+
default: false
|
|
354
|
+
},
|
|
355
|
+
markdown: {
|
|
356
|
+
type: "boolean",
|
|
357
|
+
description: "Output results in Markdown format (default)",
|
|
358
|
+
default: true
|
|
359
|
+
},
|
|
360
|
+
"base-url": {
|
|
361
|
+
type: "string",
|
|
362
|
+
description: "Base URL for the documentation site",
|
|
363
|
+
default: "fragno.dev"
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
run: async (ctx) => {
|
|
367
|
+
const query = ctx.positionals.join(" ");
|
|
368
|
+
if (!query || query.trim().length === 0) throw new Error("Please provide a search query");
|
|
369
|
+
const jsonMode = ctx.values.json;
|
|
370
|
+
const baseUrl = ctx.values["base-url"];
|
|
371
|
+
if (!jsonMode) console.log(`Searching for: "${query}"\n`);
|
|
372
|
+
try {
|
|
373
|
+
const encodedQuery = encodeURIComponent(query);
|
|
374
|
+
const response = await fetch(`https://${baseUrl}/api/search?query=${encodedQuery}`);
|
|
375
|
+
if (!response.ok) throw new Error(`API request failed with status ${response.status}`);
|
|
376
|
+
const results = await response.json();
|
|
377
|
+
const limit = ctx.values.limit;
|
|
378
|
+
const limitedResults = results.slice(0, limit);
|
|
379
|
+
if (limitedResults.length === 0) {
|
|
380
|
+
if (jsonMode) console.log("[]");
|
|
381
|
+
else console.log("No results found.");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const mergedResults = mergeResultsByUrl(limitedResults, baseUrl);
|
|
385
|
+
if (jsonMode) console.log(formatAsJson(mergedResults));
|
|
386
|
+
else {
|
|
387
|
+
console.log(`Found ${results.length} result${results.length === 1 ? "" : "s"}${results.length > limit ? ` (showing ${limit})` : ""}\n`);
|
|
388
|
+
console.log(formatAsMarkdown(mergedResults));
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
if (error instanceof Error) throw new Error(`Search failed: ${error.message}`);
|
|
392
|
+
throw new Error("Search failed: An unknown error occurred");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/commands/corpus.ts
|
|
399
|
+
marked.use(markedTerminal());
|
|
400
|
+
/**
|
|
401
|
+
* Build markdown content for multiple subjects
|
|
402
|
+
*/
|
|
403
|
+
function buildSubjectsMarkdown(subjects) {
|
|
404
|
+
let fullMarkdown = "";
|
|
405
|
+
for (const subject of subjects) {
|
|
406
|
+
fullMarkdown += `# ${subject.title}\n\n`;
|
|
407
|
+
if (subject.description) fullMarkdown += `${subject.description}\n\n`;
|
|
408
|
+
if (subject.imports) fullMarkdown += `### Imports\n\n\`\`\`typescript\n${subject.imports}\n\`\`\`\n\n`;
|
|
409
|
+
if (subject.prelude.length > 0) {
|
|
410
|
+
fullMarkdown += `### Prelude\n\n`;
|
|
411
|
+
for (const block of subject.prelude) fullMarkdown += `\`\`\`typescript\n${block.code}\n\`\`\`\n\n`;
|
|
412
|
+
}
|
|
413
|
+
for (const section of subject.sections) fullMarkdown += `## ${section.heading}\n\n${section.content}\n\n`;
|
|
414
|
+
}
|
|
415
|
+
return fullMarkdown;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Add line numbers to content
|
|
419
|
+
*/
|
|
420
|
+
function addLineNumbers(content, startFrom = 1) {
|
|
421
|
+
const lines = content.split("\n");
|
|
422
|
+
const maxDigits = String(startFrom + lines.length - 1).length;
|
|
423
|
+
return lines.map((line, index) => {
|
|
424
|
+
const lineNum = startFrom + index;
|
|
425
|
+
return `${String(lineNum).padStart(maxDigits, " ")}│ ${line}`;
|
|
426
|
+
}).join("\n");
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Filter content by line range
|
|
430
|
+
*/
|
|
431
|
+
function filterByLineRange(content, startLine, endLine) {
|
|
432
|
+
const lines = content.split("\n");
|
|
433
|
+
const start = Math.max(0, startLine - 1);
|
|
434
|
+
const end = Math.min(lines.length, endLine);
|
|
435
|
+
return lines.slice(start, end).join("\n");
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Extract headings and code block information with line numbers
|
|
439
|
+
*/
|
|
440
|
+
function extractHeadingsAndBlocks(subjects) {
|
|
441
|
+
let output = "";
|
|
442
|
+
let currentLine = 1;
|
|
443
|
+
let lastOutputLine = 0;
|
|
444
|
+
const addGapIfNeeded = () => {
|
|
445
|
+
if (lastOutputLine > 0 && currentLine > lastOutputLine + 1) output += ` │\n`;
|
|
446
|
+
};
|
|
447
|
+
output += "Use --start N --end N flags to show specific line ranges\n\n";
|
|
448
|
+
for (const subject of subjects) {
|
|
449
|
+
addGapIfNeeded();
|
|
450
|
+
output += `${currentLine.toString().padStart(4, " ")}│ # ${subject.title}\n`;
|
|
451
|
+
lastOutputLine = currentLine;
|
|
452
|
+
currentLine += 1;
|
|
453
|
+
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
454
|
+
lastOutputLine = currentLine;
|
|
455
|
+
currentLine += 1;
|
|
456
|
+
if (subject.description) {
|
|
457
|
+
const descLines = subject.description.split("\n");
|
|
458
|
+
for (const line of descLines) {
|
|
459
|
+
output += `${currentLine.toString().padStart(4, " ")}│ ${line}\n`;
|
|
460
|
+
lastOutputLine = currentLine;
|
|
461
|
+
currentLine += 1;
|
|
462
|
+
}
|
|
463
|
+
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
464
|
+
lastOutputLine = currentLine;
|
|
465
|
+
currentLine += 1;
|
|
466
|
+
}
|
|
467
|
+
if (subject.imports) {
|
|
468
|
+
addGapIfNeeded();
|
|
469
|
+
output += `${currentLine.toString().padStart(4, " ")}│ ### Imports\n`;
|
|
470
|
+
lastOutputLine = currentLine;
|
|
471
|
+
currentLine += 1;
|
|
472
|
+
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
473
|
+
lastOutputLine = currentLine;
|
|
474
|
+
currentLine += 1;
|
|
475
|
+
output += `${currentLine.toString().padStart(4, " ")}│ \`\`\`typescript\n`;
|
|
476
|
+
lastOutputLine = currentLine;
|
|
477
|
+
currentLine += 1;
|
|
478
|
+
const importLines = subject.imports.split("\n");
|
|
479
|
+
for (const line of importLines) {
|
|
480
|
+
output += `${currentLine.toString().padStart(4, " ")}│ ${line}\n`;
|
|
481
|
+
lastOutputLine = currentLine;
|
|
482
|
+
currentLine += 1;
|
|
483
|
+
}
|
|
484
|
+
output += `${currentLine.toString().padStart(4, " ")}│ \`\`\`\n`;
|
|
485
|
+
lastOutputLine = currentLine;
|
|
486
|
+
currentLine += 1;
|
|
487
|
+
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
488
|
+
lastOutputLine = currentLine;
|
|
489
|
+
currentLine += 1;
|
|
490
|
+
}
|
|
491
|
+
if (subject.prelude.length > 0) {
|
|
492
|
+
addGapIfNeeded();
|
|
493
|
+
output += `${currentLine.toString().padStart(4, " ")}│ ### Prelude\n`;
|
|
494
|
+
lastOutputLine = currentLine;
|
|
495
|
+
currentLine += 1;
|
|
496
|
+
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
497
|
+
lastOutputLine = currentLine;
|
|
498
|
+
currentLine += 1;
|
|
499
|
+
for (const block of subject.prelude) {
|
|
500
|
+
const id = block.id || "(no-id)";
|
|
501
|
+
const blockStartLine = currentLine + 1;
|
|
502
|
+
const codeLines = block.code.split("\n").length;
|
|
503
|
+
const blockEndLine = currentLine + 1 + codeLines;
|
|
504
|
+
output += `${currentLine.toString().padStart(4, " ")}│ - id: \`${id}\`, L${blockStartLine}-${blockEndLine}\n`;
|
|
505
|
+
lastOutputLine = currentLine;
|
|
506
|
+
currentLine += codeLines + 3;
|
|
507
|
+
}
|
|
508
|
+
lastOutputLine = currentLine - 1;
|
|
509
|
+
}
|
|
510
|
+
const sectionToExamples = /* @__PURE__ */ new Map();
|
|
511
|
+
for (const example of subject.examples) for (const section of subject.sections) if (section.content.includes(example.code.substring(0, Math.min(50, example.code.length)))) {
|
|
512
|
+
if (!sectionToExamples.has(section.heading)) sectionToExamples.set(section.heading, []);
|
|
513
|
+
sectionToExamples.get(section.heading).push(example);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
for (const section of subject.sections) {
|
|
517
|
+
addGapIfNeeded();
|
|
518
|
+
output += `${currentLine.toString().padStart(4, " ")}│ ## ${section.heading}\n`;
|
|
519
|
+
lastOutputLine = currentLine;
|
|
520
|
+
currentLine += 1;
|
|
521
|
+
const examples = sectionToExamples.get(section.heading) || [];
|
|
522
|
+
if (examples.length > 0) {
|
|
523
|
+
const sectionStartLine = currentLine;
|
|
524
|
+
const lines = section.content.split("\n");
|
|
525
|
+
for (const example of examples) {
|
|
526
|
+
const id = example.id || "(no-id)";
|
|
527
|
+
let blockStartLine = sectionStartLine;
|
|
528
|
+
let blockEndLine = sectionStartLine;
|
|
529
|
+
let foundBlock = false;
|
|
530
|
+
for (let i = 0; i < lines.length; i++) if (lines[i].trim().startsWith("```") && true) {
|
|
531
|
+
const codeStart = i + 1;
|
|
532
|
+
let matches = true;
|
|
533
|
+
const exampleLines = example.code.split("\n");
|
|
534
|
+
for (let j = 0; j < Math.min(3, exampleLines.length); j++) if (lines[codeStart + j]?.trim() !== exampleLines[j]?.trim()) {
|
|
535
|
+
matches = false;
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
if (matches) {
|
|
539
|
+
blockStartLine = sectionStartLine + i + 1;
|
|
540
|
+
blockEndLine = sectionStartLine + i + exampleLines.length;
|
|
541
|
+
foundBlock = true;
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (foundBlock) output += `${currentLine.toString().padStart(4, " ")}│ - id: \`${id}\`, L${blockStartLine}-${blockEndLine}\n`;
|
|
546
|
+
else output += `${currentLine.toString().padStart(4, " ")}│ - id: \`${id}\`\n`;
|
|
547
|
+
lastOutputLine = currentLine;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const sectionLines = section.content.split("\n");
|
|
551
|
+
for (const _line of sectionLines) currentLine += 1;
|
|
552
|
+
currentLine += 1;
|
|
553
|
+
lastOutputLine = currentLine - 1;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return output;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Print subjects with the given options
|
|
560
|
+
*/
|
|
561
|
+
async function printSubjects(subjects, options) {
|
|
562
|
+
if (options.headingsOnly) {
|
|
563
|
+
const headingsOutput = extractHeadingsAndBlocks(subjects);
|
|
564
|
+
console.log(headingsOutput);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const markdown = buildSubjectsMarkdown(subjects);
|
|
568
|
+
let output = await marked.parse(markdown);
|
|
569
|
+
const startLine = options.startLine ?? 1;
|
|
570
|
+
if (options.startLine !== void 0 || options.endLine !== void 0) {
|
|
571
|
+
const end = options.endLine ?? output.split("\n").length;
|
|
572
|
+
output = filterByLineRange(output, startLine, end);
|
|
573
|
+
}
|
|
574
|
+
if (options.showLineNumbers) output = addLineNumbers(output, startLine);
|
|
575
|
+
console.log(output);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Find and print code blocks by ID
|
|
579
|
+
*/
|
|
580
|
+
async function printCodeBlockById(id, topics, showLineNumbers) {
|
|
581
|
+
const subjects = topics.length > 0 ? getSubject(...topics) : getAllSubjects();
|
|
582
|
+
const matches = [];
|
|
583
|
+
for (const subject of subjects) {
|
|
584
|
+
const fullMarkdown = buildSubjectsMarkdown([subject]);
|
|
585
|
+
const renderedLines = (await marked.parse(fullMarkdown)).split("\n");
|
|
586
|
+
for (const block of subject.prelude) if (block.id === id) {
|
|
587
|
+
let startLine;
|
|
588
|
+
let endLine;
|
|
589
|
+
const codeLines = block.code.split("\n");
|
|
590
|
+
const firstCodeLine = codeLines[0].trim();
|
|
591
|
+
for (let i = 0; i < renderedLines.length; i++) if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {
|
|
592
|
+
startLine = i + 1;
|
|
593
|
+
endLine = i + codeLines.length;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
matches.push({
|
|
597
|
+
subjectId: subject.id,
|
|
598
|
+
subjectTitle: subject.title,
|
|
599
|
+
section: "Prelude",
|
|
600
|
+
code: block.code,
|
|
601
|
+
type: "prelude",
|
|
602
|
+
startLine,
|
|
603
|
+
endLine
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
for (const example of subject.examples) if (example.id === id) {
|
|
607
|
+
let sectionName = "Unknown Section";
|
|
608
|
+
let startLine;
|
|
609
|
+
let endLine;
|
|
610
|
+
for (const section of subject.sections) if (section.content.includes(example.code.substring(0, Math.min(50, example.code.length)))) {
|
|
611
|
+
sectionName = section.heading;
|
|
612
|
+
const codeLines = example.code.split("\n");
|
|
613
|
+
const firstCodeLine = codeLines[0].trim();
|
|
614
|
+
for (let i = 0; i < renderedLines.length; i++) if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {
|
|
615
|
+
startLine = i + 1;
|
|
616
|
+
endLine = i + codeLines.length;
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
matches.push({
|
|
622
|
+
subjectId: subject.id,
|
|
623
|
+
subjectTitle: subject.title,
|
|
624
|
+
section: sectionName,
|
|
625
|
+
code: example.code,
|
|
626
|
+
type: "example",
|
|
627
|
+
startLine,
|
|
628
|
+
endLine
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (matches.length === 0) {
|
|
633
|
+
console.error(`Error: No code block found with id "${id}"`);
|
|
634
|
+
if (topics.length > 0) console.error(`Searched in topics: ${topics.join(", ")}`);
|
|
635
|
+
else console.error("Searched in all available topics");
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
for (let i = 0; i < matches.length; i++) {
|
|
639
|
+
const match = matches[i];
|
|
640
|
+
if (matches.length > 1 && i > 0) console.log("\n---\n");
|
|
641
|
+
let matchMarkdown = `# ${match.subjectTitle}\n\n`;
|
|
642
|
+
matchMarkdown += `## ${match.section}\n\n`;
|
|
643
|
+
if (showLineNumbers && match.startLine && match.endLine) console.log(`Lines ${match.startLine}-${match.endLine} (use with --start/--end)\n`);
|
|
644
|
+
matchMarkdown += `\`\`\`typescript\n${match.code}\n\`\`\`\n`;
|
|
645
|
+
const rendered = await marked.parse(matchMarkdown);
|
|
646
|
+
console.log(rendered);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Print information about the corpus command
|
|
651
|
+
*/
|
|
652
|
+
function printCorpusHelp() {
|
|
653
|
+
console.log("Fragno Corpus - Code examples and documentation (similar to LLMs.txt");
|
|
654
|
+
console.log("");
|
|
655
|
+
console.log("Usage: fragno-cli corpus [options] [topic...]");
|
|
656
|
+
console.log("");
|
|
657
|
+
console.log("Options:");
|
|
658
|
+
console.log(" -n, --no-line-numbers Hide line numbers (shown by default)");
|
|
659
|
+
console.log(" -s, --start N Starting line number to display from");
|
|
660
|
+
console.log(" -e, --end N Ending line number to display to");
|
|
661
|
+
console.log(" --headings Show only headings and code block IDs");
|
|
662
|
+
console.log(" --id <id> Retrieve a specific code block by ID");
|
|
663
|
+
console.log("");
|
|
664
|
+
console.log("Examples:");
|
|
665
|
+
console.log(" fragno-cli corpus # List all available topics");
|
|
666
|
+
console.log(" fragno-cli corpus defining-routes # Show route definition examples");
|
|
667
|
+
console.log(" fragno-cli corpus --headings database-querying");
|
|
668
|
+
console.log(" # Show structure overview");
|
|
669
|
+
console.log(" fragno-cli corpus --start 10 --end 50 database-querying");
|
|
670
|
+
console.log(" # Show specific lines");
|
|
671
|
+
console.log(" fragno-cli corpus --id create-user # Get code block by ID");
|
|
672
|
+
console.log(" fragno-cli corpus database-adapters kysely-adapter");
|
|
673
|
+
console.log(" # Show multiple topics");
|
|
674
|
+
console.log("");
|
|
675
|
+
console.log("Available topics:");
|
|
676
|
+
const subjects = getSubjects();
|
|
677
|
+
const rootSubjects = [];
|
|
678
|
+
const subjectMap = new Map(subjects.map((s) => [s.id, s]));
|
|
679
|
+
for (const subject of subjects) if (!getSubjectParent(subject.id)) {
|
|
680
|
+
const children = getSubjectChildren(subject.id);
|
|
681
|
+
rootSubjects.push({
|
|
682
|
+
id: subject.id,
|
|
683
|
+
title: subject.title,
|
|
684
|
+
children: children.map((childId) => ({
|
|
685
|
+
id: childId,
|
|
686
|
+
title: subjectMap.get(childId)?.title || childId
|
|
687
|
+
}))
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
for (const root of rootSubjects) {
|
|
691
|
+
console.log(` ${root.id.padEnd(30)} ${root.title}`);
|
|
692
|
+
for (let i = 0; i < root.children.length; i++) {
|
|
693
|
+
const child = root.children[i];
|
|
694
|
+
const connector = i === root.children.length - 1 ? "└─" : "├─";
|
|
695
|
+
console.log(` ${connector} ${child.id.padEnd(26)} ${child.title}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const corpusCommand = define({
|
|
700
|
+
name: "corpus",
|
|
701
|
+
description: "View code examples and documentation for Fragno",
|
|
702
|
+
args: {
|
|
703
|
+
"no-line-numbers": {
|
|
704
|
+
type: "boolean",
|
|
705
|
+
short: "n",
|
|
706
|
+
description: "Hide line numbers (line numbers are shown by default)"
|
|
707
|
+
},
|
|
708
|
+
start: {
|
|
709
|
+
type: "number",
|
|
710
|
+
short: "s",
|
|
711
|
+
description: "Starting line number (1-based) to display from"
|
|
712
|
+
},
|
|
713
|
+
end: {
|
|
714
|
+
type: "number",
|
|
715
|
+
short: "e",
|
|
716
|
+
description: "Ending line number (1-based) to display to"
|
|
717
|
+
},
|
|
718
|
+
headings: {
|
|
719
|
+
type: "boolean",
|
|
720
|
+
description: "Show only section headings and code block IDs with line numbers"
|
|
721
|
+
},
|
|
722
|
+
id: {
|
|
723
|
+
type: "string",
|
|
724
|
+
description: "Retrieve a specific code block by ID"
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
run: async (ctx) => {
|
|
728
|
+
const topics = ctx.positionals;
|
|
729
|
+
const showLineNumbers = !(ctx.values["no-line-numbers"] ?? false);
|
|
730
|
+
const startLine = ctx.values.start;
|
|
731
|
+
const endLine = ctx.values.end;
|
|
732
|
+
const headingsOnly = ctx.values.headings ?? false;
|
|
733
|
+
const codeBlockId = ctx.values.id;
|
|
734
|
+
if (codeBlockId) {
|
|
735
|
+
await printCodeBlockById(codeBlockId, topics, showLineNumbers);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (topics.length === 0) {
|
|
739
|
+
printCorpusHelp();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (startLine !== void 0 && endLine !== void 0 && startLine > endLine) {
|
|
743
|
+
console.error("Error: --start must be less than or equal to --end");
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
await printSubjects(getSubject(...topics), {
|
|
748
|
+
showLineNumbers,
|
|
749
|
+
startLine,
|
|
750
|
+
endLine,
|
|
751
|
+
headingsOnly
|
|
752
|
+
});
|
|
753
|
+
} catch (error) {
|
|
754
|
+
console.error("Error loading topics:", error instanceof Error ? error.message : error);
|
|
755
|
+
console.log("\nRun 'fragno-cli corpus' to see available topics.");
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
|
|
261
761
|
//#endregion
|
|
262
762
|
//#region src/cli.ts
|
|
763
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
764
|
+
const version = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8")).version;
|
|
263
765
|
const dbSubCommands = /* @__PURE__ */ new Map();
|
|
264
766
|
dbSubCommands.set("generate", generateCommand);
|
|
265
767
|
dbSubCommands.set("migrate", migrateCommand);
|
|
266
768
|
dbSubCommands.set("info", infoCommand);
|
|
267
|
-
function printDbHelp() {
|
|
268
|
-
console.log("Database management commands for Fragno");
|
|
269
|
-
console.log("");
|
|
270
|
-
console.log("Usage: fragno-cli db <command> [options]");
|
|
271
|
-
console.log("");
|
|
272
|
-
console.log("Commands:");
|
|
273
|
-
console.log(" generate Generate schema files from FragnoDatabase definitions");
|
|
274
|
-
console.log(" migrate Run database migrations");
|
|
275
|
-
console.log(" info Display database information and migration status");
|
|
276
|
-
console.log("");
|
|
277
|
-
console.log("Run 'fragno-cli db <command> --help' for more information.");
|
|
278
|
-
}
|
|
279
769
|
const dbCommand = define({
|
|
280
770
|
name: "db",
|
|
281
|
-
description: "Database management commands"
|
|
282
|
-
run: printDbHelp
|
|
771
|
+
description: "Database management commands"
|
|
283
772
|
});
|
|
284
|
-
const rootSubCommands = /* @__PURE__ */ new Map();
|
|
285
|
-
rootSubCommands.set("db", dbCommand);
|
|
286
773
|
const mainCommand = define({
|
|
287
774
|
name: "fragno-cli",
|
|
288
|
-
description: "
|
|
289
|
-
run: () => {
|
|
290
|
-
console.log("Fragno CLI - Tools for working with Fragno fragments");
|
|
291
|
-
console.log("");
|
|
292
|
-
console.log("Usage: fragno-cli <command> [options]");
|
|
293
|
-
console.log("");
|
|
294
|
-
console.log("Commands:");
|
|
295
|
-
console.log(" db Database management commands");
|
|
296
|
-
console.log("");
|
|
297
|
-
console.log("Run 'fragno-cli <command> --help' for more information.");
|
|
298
|
-
}
|
|
775
|
+
description: "Tools for working with Fragno fragments"
|
|
299
776
|
});
|
|
300
777
|
if (import.meta.main) try {
|
|
301
778
|
const args = process.argv.slice(2);
|
|
302
|
-
if (args[0] === "
|
|
779
|
+
if (args[0] === "search") await cli(args.slice(1), searchCommand, {
|
|
780
|
+
name: "fragno-cli search",
|
|
781
|
+
version
|
|
782
|
+
});
|
|
783
|
+
else if (args[0] === "corpus") await cli(args.slice(1), corpusCommand, {
|
|
784
|
+
name: "fragno-cli corpus",
|
|
785
|
+
version
|
|
786
|
+
});
|
|
787
|
+
else if (args[0] === "db") {
|
|
303
788
|
const subCommandName = args[1];
|
|
304
|
-
if (subCommandName === "--help" || subCommandName === "-h") {
|
|
305
|
-
|
|
306
|
-
process.exit(0);
|
|
307
|
-
}
|
|
308
|
-
const subCommand = dbSubCommands.get(subCommandName);
|
|
309
|
-
if (!subCommand) {
|
|
310
|
-
console.error(`Unknown command: ${subCommandName}`);
|
|
789
|
+
if (!subCommandName || subCommandName === "--help" || subCommandName === "-h") {
|
|
790
|
+
console.log("Database management commands");
|
|
311
791
|
console.log("");
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
792
|
+
console.log("USAGE:");
|
|
793
|
+
console.log(" fragno-cli db <COMMAND>");
|
|
794
|
+
console.log("");
|
|
795
|
+
console.log("COMMANDS:");
|
|
796
|
+
console.log(" generate Generate schema files from FragnoDatabase definitions");
|
|
797
|
+
console.log(" migrate Run database migrations");
|
|
798
|
+
console.log(" info Display database information and migration status");
|
|
799
|
+
console.log("");
|
|
800
|
+
console.log("For more info, run any command with the `--help` flag:");
|
|
801
|
+
console.log(" fragno-cli db generate --help");
|
|
802
|
+
console.log(" fragno-cli db migrate --help");
|
|
803
|
+
console.log(" fragno-cli db info --help");
|
|
804
|
+
console.log("");
|
|
805
|
+
console.log("OPTIONS:");
|
|
806
|
+
console.log(" -h, --help Display this help message");
|
|
807
|
+
console.log(" -v, --version Display this version");
|
|
808
|
+
} else if (subCommandName === "--version" || subCommandName === "-v") console.log(version);
|
|
809
|
+
else {
|
|
810
|
+
const subCommand = dbSubCommands.get(subCommandName);
|
|
811
|
+
if (!subCommand) {
|
|
812
|
+
console.error(`Unknown command: ${subCommandName}`);
|
|
813
|
+
console.log("");
|
|
814
|
+
console.log("Run 'fragno-cli db --help' for available commands.");
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
await cli(args.slice(2), subCommand, {
|
|
818
|
+
name: `fragno-cli db ${subCommandName}`,
|
|
819
|
+
version
|
|
820
|
+
});
|
|
321
821
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
822
|
+
} else if (!args.length || args[0] === "--help" || args[0] === "-h") {
|
|
823
|
+
console.log("Tools for working with Fragno");
|
|
824
|
+
console.log("");
|
|
825
|
+
console.log("USAGE:");
|
|
826
|
+
console.log(" fragno-cli <COMMAND>");
|
|
827
|
+
console.log("");
|
|
828
|
+
console.log("COMMANDS:");
|
|
829
|
+
console.log(" db Database management commands");
|
|
830
|
+
console.log(" search Search the Fragno documentation");
|
|
831
|
+
console.log(" corpus View code examples and documentation for Fragno");
|
|
832
|
+
console.log("");
|
|
833
|
+
console.log("For more info, run any command with the `--help` flag:");
|
|
834
|
+
console.log(" fragno-cli db --help");
|
|
835
|
+
console.log(" fragno-cli search --help");
|
|
836
|
+
console.log(" fragno-cli corpus --help");
|
|
837
|
+
console.log("");
|
|
838
|
+
console.log("OPTIONS:");
|
|
839
|
+
console.log(" -h, --help Display this help message");
|
|
840
|
+
console.log(" -v, --version Display this version");
|
|
841
|
+
} else if (args[0] === "--version" || args[0] === "-v") console.log(version);
|
|
842
|
+
else {
|
|
843
|
+
console.error(`Unknown command: ${args[0]}`);
|
|
844
|
+
console.log("");
|
|
845
|
+
console.log("Run 'fragno-cli --help' for available commands.");
|
|
846
|
+
process.exit(1);
|
|
847
|
+
}
|
|
326
848
|
} catch (error) {
|
|
327
849
|
console.error("Error:", error instanceof Error ? error.message : error);
|
|
328
850
|
process.exit(1);
|
|
329
851
|
}
|
|
330
852
|
|
|
331
853
|
//#endregion
|
|
332
|
-
export { dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand };
|
|
854
|
+
export { corpusCommand, dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand, searchCommand };
|
|
333
855
|
//# sourceMappingURL=cli.js.map
|