@fragno-dev/cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +11 -7
- package/CHANGELOG.md +71 -0
- package/dist/cli.d.ts +4 -52
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +55 -719
- package/dist/cli.js.map +1 -1
- package/dist/find-fragno-databases-Depht1jV.js +184 -0
- package/dist/find-fragno-databases-Depht1jV.js.map +1 -0
- package/dist/serve-eh3Tpjhc.js +87 -0
- package/dist/serve-eh3Tpjhc.js.map +1 -0
- package/package.json +28 -30
- package/src/cli.ts +8 -15
- package/src/commands/db/generate.ts +3 -1
- package/src/commands/db/info.ts +2 -0
- package/src/commands/db/migrate.ts +3 -1
- package/src/commands/search.ts +1 -0
- package/src/commands/serve.ts +5 -2
- package/src/utils/find-fragno-databases.ts +11 -6
- package/src/utils/load-config.test.ts +42 -36
- package/src/utils/load-config.ts +5 -9
- package/tsconfig.json +1 -1
- package/vitest.config.ts +1 -0
- package/src/commands/corpus.test.ts +0 -1129
- package/src/commands/corpus.ts +0 -632
package/dist/cli.js
CHANGED
|
@@ -1,196 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { n as importFragmentFiles } from "./find-fragno-databases-Depht1jV.js";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
2
6
|
import { cli, define } from "gunshi";
|
|
3
|
-
import {
|
|
4
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
7
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
8
|
import { executeMigrations, generateSchemaArtifacts } from "@fragno-dev/db/generation-engine";
|
|
6
|
-
import { FragnoDatabase, isFragnoDatabase } from "@fragno-dev/db";
|
|
7
|
-
import { fragnoDatabaseAdapterNameFakeSymbol, fragnoDatabaseAdapterVersionFakeSymbol } from "@fragno-dev/db/adapters";
|
|
8
|
-
import { instantiatedFragmentFakeSymbol } from "@fragno-dev/core/internal/symbols";
|
|
9
|
-
import "@fragno-dev/core";
|
|
10
|
-
import { loadConfig } from "c12";
|
|
11
|
-
import { constants, readFileSync } from "node:fs";
|
|
12
|
-
import { getAllSubjectIdsInOrder, getAllSubjects, getCategoryTitle, getSubject, getSubjectChildren, getSubjectParent, getSubjects, isCategory } from "@fragno-dev/corpus";
|
|
13
|
-
import { marked } from "marked";
|
|
14
|
-
import { markedTerminal } from "marked-terminal";
|
|
15
|
-
import { stripVTControlCharacters } from "node:util";
|
|
16
|
-
import { createServer } from "node:http";
|
|
17
|
-
import { toNodeHandler } from "@fragno-dev/node";
|
|
18
|
-
import { fileURLToPath } from "node:url";
|
|
19
|
-
|
|
20
|
-
//#region src/utils/load-config.ts
|
|
21
|
-
/**
|
|
22
|
-
* Checks if a file exists using async API.
|
|
23
|
-
*/
|
|
24
|
-
async function fileExists(path) {
|
|
25
|
-
try {
|
|
26
|
-
await access(path, constants.F_OK);
|
|
27
|
-
return true;
|
|
28
|
-
} catch {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Walks up the directory tree from the target path to find a tsconfig.json file.
|
|
34
|
-
*/
|
|
35
|
-
async function findTsconfig(startPath) {
|
|
36
|
-
let currentDir = dirname(startPath);
|
|
37
|
-
const root = resolve("/");
|
|
38
|
-
while (currentDir !== root) {
|
|
39
|
-
const tsconfigPath = join(currentDir, "tsconfig.json");
|
|
40
|
-
if (await fileExists(tsconfigPath)) return tsconfigPath;
|
|
41
|
-
currentDir = dirname(currentDir);
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Strips comments from JSONC (JSON with Comments) content.
|
|
47
|
-
*/
|
|
48
|
-
function stripJsonComments(jsonc) {
|
|
49
|
-
let result = jsonc.replace(/\/\/[^\n]*/g, "");
|
|
50
|
-
result = result.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Converts TypeScript path aliases to jiti alias format.
|
|
55
|
-
* Strips trailing '*' from aliases and paths, and resolves paths relative to baseUrl.
|
|
56
|
-
*/
|
|
57
|
-
function convertTsconfigPathsToJitiAlias(tsconfigPaths, baseUrlResolved) {
|
|
58
|
-
return Object.fromEntries(Object.entries(tsconfigPaths).map(([_alias, paths]) => {
|
|
59
|
-
const pathsArray = paths;
|
|
60
|
-
return [_alias.endsWith("*") ? _alias.slice(0, -1) : _alias, resolve(baseUrlResolved, pathsArray[0].endsWith("*") ? pathsArray[0].slice(0, -1) : pathsArray[0])];
|
|
61
|
-
}));
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Resolves tsconfig path aliases for use with jiti.
|
|
65
|
-
*/
|
|
66
|
-
async function resolveTsconfigAliases(targetPath) {
|
|
67
|
-
const tsconfigPath = await findTsconfig(targetPath);
|
|
68
|
-
if (!tsconfigPath) return {};
|
|
69
|
-
try {
|
|
70
|
-
const jsonContent = stripJsonComments(await readFile(tsconfigPath, "utf-8"));
|
|
71
|
-
const tsconfig = JSON.parse(jsonContent);
|
|
72
|
-
const tsconfigPaths = tsconfig?.compilerOptions?.paths;
|
|
73
|
-
if (!tsconfigPaths || typeof tsconfigPaths !== "object") return {};
|
|
74
|
-
return convertTsconfigPathsToJitiAlias(tsconfigPaths, resolve(dirname(tsconfigPath), tsconfig?.compilerOptions?.baseUrl || "."));
|
|
75
|
-
} catch (error) {
|
|
76
|
-
console.warn(`Warning: Failed to parse tsconfig at ${tsconfigPath}:`, error);
|
|
77
|
-
return {};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Loads a config file using c12 with automatic tsconfig path alias resolution.
|
|
82
|
-
*/
|
|
83
|
-
async function loadConfig$1(path) {
|
|
84
|
-
const { config } = await loadConfig({
|
|
85
|
-
configFile: path,
|
|
86
|
-
jitiOptions: { alias: await resolveTsconfigAliases(path) }
|
|
87
|
-
});
|
|
88
|
-
return config;
|
|
89
|
-
}
|
|
90
9
|
|
|
91
|
-
//#endregion
|
|
92
|
-
//#region src/utils/find-fragno-databases.ts
|
|
93
|
-
async function importFragmentFile(path) {
|
|
94
|
-
process.env["FRAGNO_INIT_DRY_RUN"] = "true";
|
|
95
|
-
try {
|
|
96
|
-
const databases = findFragnoDatabases(await loadConfig$1(path));
|
|
97
|
-
const adapterNames = databases.map((db) => `${db.adapter[fragnoDatabaseAdapterNameFakeSymbol]}@${db.adapter[fragnoDatabaseAdapterVersionFakeSymbol]}`);
|
|
98
|
-
if ([...new Set(adapterNames)].length > 1) throw new Error(`All Fragno databases must use the same adapter name and version. Found mismatch: (${adapterNames.join(", ")})`);
|
|
99
|
-
return {
|
|
100
|
-
adapter: databases[0].adapter,
|
|
101
|
-
databases
|
|
102
|
-
};
|
|
103
|
-
} finally {
|
|
104
|
-
delete process.env["FRAGNO_INIT_DRY_RUN"];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Imports multiple fragment files and validates they all use the same adapter.
|
|
109
|
-
* Returns the combined databases from all files.
|
|
110
|
-
*/
|
|
111
|
-
async function importFragmentFiles(paths) {
|
|
112
|
-
const uniquePaths = Array.from(new Set(paths));
|
|
113
|
-
if (uniquePaths.length === 0) throw new Error("No fragment files provided");
|
|
114
|
-
const allDatabases = [];
|
|
115
|
-
let adapter;
|
|
116
|
-
let firstAdapterFile;
|
|
117
|
-
const cwd = process.cwd();
|
|
118
|
-
for (const path of uniquePaths) {
|
|
119
|
-
const relativePath = relative(cwd, path);
|
|
120
|
-
try {
|
|
121
|
-
const result = await importFragmentFile(path);
|
|
122
|
-
const databases = result["databases"];
|
|
123
|
-
const fileAdapter = result["adapter"];
|
|
124
|
-
if (databases.length === 0) {
|
|
125
|
-
console.warn(`Warning: No FragnoDatabase instances found in ${relativePath}.\nMake sure you export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n`);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (!adapter) {
|
|
129
|
-
adapter = fileAdapter;
|
|
130
|
-
firstAdapterFile = relativePath;
|
|
131
|
-
}
|
|
132
|
-
const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];
|
|
133
|
-
const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];
|
|
134
|
-
const fileAdapterName = fileAdapter[fragnoDatabaseAdapterNameFakeSymbol];
|
|
135
|
-
const fileAdapterVersion = fileAdapter[fragnoDatabaseAdapterVersionFakeSymbol];
|
|
136
|
-
if (firstAdapterName !== fileAdapterName || firstAdapterVersion !== fileAdapterVersion) {
|
|
137
|
-
const firstAdapterInfo = `${firstAdapterName}@${firstAdapterVersion}`;
|
|
138
|
-
const fileAdapterInfo = `${fileAdapterName}@${fileAdapterVersion}`;
|
|
139
|
-
throw new Error(`All fragments must use the same database adapter. Mixed adapters found:\n - ${firstAdapterFile}: ${firstAdapterInfo}\n - ${relativePath}: ${fileAdapterInfo}\n\nMake sure all fragments use the same adapter name and version.`);
|
|
140
|
-
}
|
|
141
|
-
allDatabases.push(...databases);
|
|
142
|
-
console.log(` Found ${databases.length} database(s) in ${relativePath}`);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
throw new Error(`Failed to import fragment file ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (allDatabases.length === 0) throw new Error("No FragnoDatabase instances found in any of the target files.\nMake sure your files export either:\n - A FragnoDatabase instance created with .create(adapter)\n - An instantiated fragment with embedded database definition\n");
|
|
148
|
-
if (!adapter) throw new Error("No adapter found in any of the fragment files");
|
|
149
|
-
return {
|
|
150
|
-
adapter,
|
|
151
|
-
databases: allDatabases
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
function isNewFragnoInstantiatedFragment(value) {
|
|
155
|
-
return typeof value === "object" && value !== null && instantiatedFragmentFakeSymbol in value && value[instantiatedFragmentFakeSymbol] === instantiatedFragmentFakeSymbol;
|
|
156
|
-
}
|
|
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
|
-
/**
|
|
166
|
-
* Finds all FragnoDatabase instances in a module, including those embedded
|
|
167
|
-
* in instantiated fragments.
|
|
168
|
-
*/
|
|
169
|
-
function findFragnoDatabases(targetModule) {
|
|
170
|
-
const fragnoDatabases = [];
|
|
171
|
-
for (const [_key, value] of Object.entries(targetModule)) if (isFragnoDatabase(value)) fragnoDatabases.push(value);
|
|
172
|
-
else if (isNewFragnoInstantiatedFragment(value)) {
|
|
173
|
-
const internal = value.$internal;
|
|
174
|
-
const deps = internal.deps;
|
|
175
|
-
const options = internal.options;
|
|
176
|
-
if (!deps["db"] || !deps["schema"]) continue;
|
|
177
|
-
const schema = deps["schema"];
|
|
178
|
-
const namespace = deps["namespace"];
|
|
179
|
-
const databaseAdapter = options["databaseAdapter"];
|
|
180
|
-
if (!databaseAdapter) {
|
|
181
|
-
console.warn(`Warning: Fragment '${value.name}' appears to be a database fragment but no databaseAdapter found in options.`);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
fragnoDatabases.push(new FragnoDatabase({
|
|
185
|
-
namespace,
|
|
186
|
-
schema,
|
|
187
|
-
adapter: databaseAdapter
|
|
188
|
-
}));
|
|
189
|
-
}
|
|
190
|
-
return fragnoDatabases;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
//#endregion
|
|
194
10
|
//#region src/commands/db/generate.ts
|
|
195
11
|
const generateCommand = define({
|
|
196
12
|
name: "generate",
|
|
@@ -274,48 +90,6 @@ const generateCommand = define({
|
|
|
274
90
|
}
|
|
275
91
|
});
|
|
276
92
|
|
|
277
|
-
//#endregion
|
|
278
|
-
//#region src/commands/db/migrate.ts
|
|
279
|
-
const migrateCommand = define({
|
|
280
|
-
name: "migrate",
|
|
281
|
-
description: "Run SQL database migrations for all fragments to their latest versions",
|
|
282
|
-
args: {},
|
|
283
|
-
run: async (ctx) => {
|
|
284
|
-
const targets = ctx.positionals;
|
|
285
|
-
if (targets.length === 0) throw new Error("At least one target file path is required");
|
|
286
|
-
const { databases: allFragnoDatabases } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
|
|
287
|
-
console.log("\nMigrating all fragments to their latest versions...\n");
|
|
288
|
-
let results;
|
|
289
|
-
try {
|
|
290
|
-
results = await executeMigrations(allFragnoDatabases);
|
|
291
|
-
} catch (error) {
|
|
292
|
-
throw new Error(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
293
|
-
}
|
|
294
|
-
for (const result of results) {
|
|
295
|
-
console.log(`Fragment: ${result.namespace}`);
|
|
296
|
-
console.log(` Current version: ${result.fromVersion}`);
|
|
297
|
-
console.log(` Target version: ${result.toVersion}`);
|
|
298
|
-
if (result.didMigrate) console.log(` ✓ Migration completed: v${result.fromVersion} → v${result.toVersion}\n`);
|
|
299
|
-
else console.log(` ✓ Already at latest version. No migration needed.\n`);
|
|
300
|
-
}
|
|
301
|
-
console.log("═══════════════════════════════════════");
|
|
302
|
-
console.log("Migration Summary");
|
|
303
|
-
console.log("═══════════════════════════════════════");
|
|
304
|
-
const migrated = results.filter((r) => r.didMigrate);
|
|
305
|
-
const skipped = results.filter((r) => !r.didMigrate);
|
|
306
|
-
if (migrated.length > 0) {
|
|
307
|
-
console.log(`\n✓ Migrated ${migrated.length} fragment(s):`);
|
|
308
|
-
for (const r of migrated) console.log(` - ${r.namespace}: v${r.fromVersion} → v${r.toVersion}`);
|
|
309
|
-
}
|
|
310
|
-
if (skipped.length > 0) {
|
|
311
|
-
console.log(`\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);
|
|
312
|
-
for (const r of skipped) console.log(` - ${r.namespace}: v${r.toVersion}`);
|
|
313
|
-
}
|
|
314
|
-
for (const db of allFragnoDatabases) await db.adapter.close();
|
|
315
|
-
console.log("\n✓ All migrations completed successfully");
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
|
|
319
93
|
//#endregion
|
|
320
94
|
//#region src/commands/db/info.ts
|
|
321
95
|
const infoCommand = define({
|
|
@@ -378,6 +152,48 @@ const infoCommand = define({
|
|
|
378
152
|
}
|
|
379
153
|
});
|
|
380
154
|
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/commands/db/migrate.ts
|
|
157
|
+
const migrateCommand = define({
|
|
158
|
+
name: "migrate",
|
|
159
|
+
description: "Run SQL database migrations for all fragments to their latest versions",
|
|
160
|
+
args: {},
|
|
161
|
+
run: async (ctx) => {
|
|
162
|
+
const targets = ctx.positionals;
|
|
163
|
+
if (targets.length === 0) throw new Error("At least one target file path is required");
|
|
164
|
+
const { databases: allFragnoDatabases } = await importFragmentFiles(targets.map((target) => resolve(process.cwd(), target)));
|
|
165
|
+
console.log("\nMigrating all fragments to their latest versions...\n");
|
|
166
|
+
let results;
|
|
167
|
+
try {
|
|
168
|
+
results = await executeMigrations(allFragnoDatabases);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
throw new Error(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
|
+
}
|
|
172
|
+
for (const result of results) {
|
|
173
|
+
console.log(`Fragment: ${result.namespace}`);
|
|
174
|
+
console.log(` Current version: ${result.fromVersion}`);
|
|
175
|
+
console.log(` Target version: ${result.toVersion}`);
|
|
176
|
+
if (result.didMigrate) console.log(` ✓ Migration completed: v${result.fromVersion} → v${result.toVersion}\n`);
|
|
177
|
+
else console.log(` ✓ Already at latest version. No migration needed.\n`);
|
|
178
|
+
}
|
|
179
|
+
console.log("═══════════════════════════════════════");
|
|
180
|
+
console.log("Migration Summary");
|
|
181
|
+
console.log("═══════════════════════════════════════");
|
|
182
|
+
const migrated = results.filter((r) => r.didMigrate);
|
|
183
|
+
const skipped = results.filter((r) => !r.didMigrate);
|
|
184
|
+
if (migrated.length > 0) {
|
|
185
|
+
console.log(`\n✓ Migrated ${migrated.length} fragment(s):`);
|
|
186
|
+
for (const r of migrated) console.log(` - ${r.namespace}: v${r.fromVersion} → v${r.toVersion}`);
|
|
187
|
+
}
|
|
188
|
+
if (skipped.length > 0) {
|
|
189
|
+
console.log(`\n○ Skipped ${skipped.length} fragment(s) (already up-to-date):`);
|
|
190
|
+
for (const r of skipped) console.log(` - ${r.namespace}: v${r.toVersion}`);
|
|
191
|
+
}
|
|
192
|
+
for (const db of allFragnoDatabases) await db.adapter.close();
|
|
193
|
+
console.log("\n✓ All migrations completed successfully");
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
381
197
|
//#endregion
|
|
382
198
|
//#region src/utils/format-search-results.ts
|
|
383
199
|
/**
|
|
@@ -508,482 +324,6 @@ const searchCommand = define({
|
|
|
508
324
|
}
|
|
509
325
|
});
|
|
510
326
|
|
|
511
|
-
//#endregion
|
|
512
|
-
//#region src/commands/corpus.ts
|
|
513
|
-
marked.use(markedTerminal());
|
|
514
|
-
/**
|
|
515
|
-
* Build markdown content for multiple subjects
|
|
516
|
-
*/
|
|
517
|
-
function buildSubjectsMarkdown(subjects) {
|
|
518
|
-
let fullMarkdown = "";
|
|
519
|
-
for (const subject of subjects) {
|
|
520
|
-
fullMarkdown += `# ${subject.title}\n\n`;
|
|
521
|
-
if (subject.description) fullMarkdown += `${subject.description}\n\n`;
|
|
522
|
-
if (subject.imports) fullMarkdown += `### Imports\n\n\`\`\`typescript\n${subject.imports}\n\`\`\`\n\n`;
|
|
523
|
-
if (subject.prelude.length > 0) {
|
|
524
|
-
fullMarkdown += `### Prelude\n\n`;
|
|
525
|
-
for (const block of subject.prelude) fullMarkdown += `\`\`\`typescript\n${block.code}\n\`\`\`\n\n`;
|
|
526
|
-
}
|
|
527
|
-
for (const section of subject.sections) fullMarkdown += `## ${section.heading}\n\n${section.content}\n\n`;
|
|
528
|
-
}
|
|
529
|
-
return fullMarkdown;
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Add line numbers to content
|
|
533
|
-
*/
|
|
534
|
-
function addLineNumbers(content, startFrom = 1) {
|
|
535
|
-
const lines = content.split("\n");
|
|
536
|
-
const maxDigits = String(startFrom + lines.length - 1).length;
|
|
537
|
-
return lines.map((line, index) => {
|
|
538
|
-
const lineNum = startFrom + index;
|
|
539
|
-
return `${String(lineNum).padStart(maxDigits, " ")}│ ${line}`;
|
|
540
|
-
}).join("\n");
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Filter content by line range
|
|
544
|
-
*/
|
|
545
|
-
function filterByLineRange(content, startLine, endLine) {
|
|
546
|
-
const lines = content.split("\n");
|
|
547
|
-
const start = Math.max(0, startLine - 1);
|
|
548
|
-
const end = Math.min(lines.length, endLine);
|
|
549
|
-
return lines.slice(start, end).join("\n");
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Extract headings and code block information with line numbers
|
|
553
|
-
*/
|
|
554
|
-
function extractHeadingsAndBlocks(subjects) {
|
|
555
|
-
let output = "";
|
|
556
|
-
let currentLine = 1;
|
|
557
|
-
let lastOutputLine = 0;
|
|
558
|
-
const addGapIfNeeded = () => {
|
|
559
|
-
if (lastOutputLine > 0 && currentLine > lastOutputLine + 1) output += ` │\n`;
|
|
560
|
-
};
|
|
561
|
-
output += "Use --start N --end N flags to show specific line ranges\n\n";
|
|
562
|
-
for (const subject of subjects) {
|
|
563
|
-
addGapIfNeeded();
|
|
564
|
-
output += `${currentLine.toString().padStart(4, " ")}│ # ${subject.title}\n`;
|
|
565
|
-
lastOutputLine = currentLine;
|
|
566
|
-
currentLine += 1;
|
|
567
|
-
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
568
|
-
lastOutputLine = currentLine;
|
|
569
|
-
currentLine += 1;
|
|
570
|
-
if (subject.description) {
|
|
571
|
-
const descLines = subject.description.split("\n");
|
|
572
|
-
for (const line of descLines) {
|
|
573
|
-
output += `${currentLine.toString().padStart(4, " ")}│ ${line}\n`;
|
|
574
|
-
lastOutputLine = currentLine;
|
|
575
|
-
currentLine += 1;
|
|
576
|
-
}
|
|
577
|
-
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
578
|
-
lastOutputLine = currentLine;
|
|
579
|
-
currentLine += 1;
|
|
580
|
-
}
|
|
581
|
-
if (subject.imports) {
|
|
582
|
-
addGapIfNeeded();
|
|
583
|
-
output += `${currentLine.toString().padStart(4, " ")}│ ### Imports\n`;
|
|
584
|
-
lastOutputLine = currentLine;
|
|
585
|
-
currentLine += 1;
|
|
586
|
-
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
587
|
-
lastOutputLine = currentLine;
|
|
588
|
-
currentLine += 1;
|
|
589
|
-
output += `${currentLine.toString().padStart(4, " ")}│ \`\`\`typescript\n`;
|
|
590
|
-
lastOutputLine = currentLine;
|
|
591
|
-
currentLine += 1;
|
|
592
|
-
const importLines = subject.imports.split("\n");
|
|
593
|
-
for (const line of importLines) {
|
|
594
|
-
output += `${currentLine.toString().padStart(4, " ")}│ ${line}\n`;
|
|
595
|
-
lastOutputLine = currentLine;
|
|
596
|
-
currentLine += 1;
|
|
597
|
-
}
|
|
598
|
-
output += `${currentLine.toString().padStart(4, " ")}│ \`\`\`\n`;
|
|
599
|
-
lastOutputLine = currentLine;
|
|
600
|
-
currentLine += 1;
|
|
601
|
-
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
602
|
-
lastOutputLine = currentLine;
|
|
603
|
-
currentLine += 1;
|
|
604
|
-
}
|
|
605
|
-
if (subject.prelude.length > 0) {
|
|
606
|
-
addGapIfNeeded();
|
|
607
|
-
output += `${currentLine.toString().padStart(4, " ")}│ ### Prelude\n`;
|
|
608
|
-
lastOutputLine = currentLine;
|
|
609
|
-
currentLine += 1;
|
|
610
|
-
output += `${currentLine.toString().padStart(4, " ")}│\n`;
|
|
611
|
-
lastOutputLine = currentLine;
|
|
612
|
-
currentLine += 1;
|
|
613
|
-
for (const block of subject.prelude) {
|
|
614
|
-
const id = block.id || "(no-id)";
|
|
615
|
-
const blockStartLine = currentLine + 1;
|
|
616
|
-
const codeLines = block.code.split("\n").length;
|
|
617
|
-
const blockEndLine = currentLine + 1 + codeLines;
|
|
618
|
-
output += `${currentLine.toString().padStart(4, " ")}│ - id: \`${id}\`, L${blockStartLine}-${blockEndLine}\n`;
|
|
619
|
-
lastOutputLine = currentLine;
|
|
620
|
-
currentLine += codeLines + 3;
|
|
621
|
-
}
|
|
622
|
-
lastOutputLine = currentLine - 1;
|
|
623
|
-
}
|
|
624
|
-
const sectionToExamples = /* @__PURE__ */ new Map();
|
|
625
|
-
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)))) {
|
|
626
|
-
if (!sectionToExamples.has(section.heading)) sectionToExamples.set(section.heading, []);
|
|
627
|
-
sectionToExamples.get(section.heading).push(example);
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
for (const section of subject.sections) {
|
|
631
|
-
addGapIfNeeded();
|
|
632
|
-
output += `${currentLine.toString().padStart(4, " ")}│ ## ${section.heading}\n`;
|
|
633
|
-
lastOutputLine = currentLine;
|
|
634
|
-
currentLine += 1;
|
|
635
|
-
const examples = sectionToExamples.get(section.heading) || [];
|
|
636
|
-
if (examples.length > 0) {
|
|
637
|
-
const sectionStartLine = currentLine;
|
|
638
|
-
const lines = section.content.split("\n");
|
|
639
|
-
for (const example of examples) {
|
|
640
|
-
const id = example.id || "(no-id)";
|
|
641
|
-
let blockStartLine = sectionStartLine;
|
|
642
|
-
let blockEndLine = sectionStartLine;
|
|
643
|
-
let foundBlock = false;
|
|
644
|
-
for (let i = 0; i < lines.length; i++) if (lines[i].trim().startsWith("```") && true) {
|
|
645
|
-
const codeStart = i + 1;
|
|
646
|
-
let matches = true;
|
|
647
|
-
const exampleLines = example.code.split("\n");
|
|
648
|
-
for (let j = 0; j < Math.min(3, exampleLines.length); j++) if (lines[codeStart + j]?.trim() !== exampleLines[j]?.trim()) {
|
|
649
|
-
matches = false;
|
|
650
|
-
break;
|
|
651
|
-
}
|
|
652
|
-
if (matches) {
|
|
653
|
-
blockStartLine = sectionStartLine + i + 1;
|
|
654
|
-
blockEndLine = sectionStartLine + i + exampleLines.length;
|
|
655
|
-
foundBlock = true;
|
|
656
|
-
break;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
if (foundBlock) output += `${currentLine.toString().padStart(4, " ")}│ - id: \`${id}\`, L${blockStartLine}-${blockEndLine}\n`;
|
|
660
|
-
else output += `${currentLine.toString().padStart(4, " ")}│ - id: \`${id}\`\n`;
|
|
661
|
-
lastOutputLine = currentLine;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
const sectionLines = section.content.split("\n");
|
|
665
|
-
for (const _line of sectionLines) currentLine += 1;
|
|
666
|
-
currentLine += 1;
|
|
667
|
-
lastOutputLine = currentLine - 1;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
return output;
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* Print subjects with the given options
|
|
674
|
-
*/
|
|
675
|
-
async function printSubjects(subjects, options) {
|
|
676
|
-
if (options.headingsOnly) {
|
|
677
|
-
const headingsOutput = extractHeadingsAndBlocks(subjects);
|
|
678
|
-
console.log(headingsOutput);
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
const markdown = buildSubjectsMarkdown(subjects);
|
|
682
|
-
let output = await marked.parse(markdown);
|
|
683
|
-
const startLine = options.startLine ?? 1;
|
|
684
|
-
if (options.startLine !== void 0 || options.endLine !== void 0) {
|
|
685
|
-
const end = options.endLine ?? output.split("\n").length;
|
|
686
|
-
output = filterByLineRange(output, startLine, end);
|
|
687
|
-
}
|
|
688
|
-
if (options.showLineNumbers) output = addLineNumbers(output, startLine);
|
|
689
|
-
console.log(output);
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Find and print code blocks by ID
|
|
693
|
-
*/
|
|
694
|
-
async function printCodeBlockById(id, topics, showLineNumbers) {
|
|
695
|
-
const subjects = topics.length > 0 ? getSubject(...topics) : getAllSubjects();
|
|
696
|
-
const matches = [];
|
|
697
|
-
for (const subject of subjects) {
|
|
698
|
-
const fullMarkdown = buildSubjectsMarkdown([subject]);
|
|
699
|
-
const renderedLines = (await marked.parse(fullMarkdown)).split("\n");
|
|
700
|
-
for (const block of subject.prelude) if (block.id === id) {
|
|
701
|
-
let startLine;
|
|
702
|
-
let endLine;
|
|
703
|
-
const codeLines = block.code.split("\n");
|
|
704
|
-
const firstCodeLine = codeLines[0].trim();
|
|
705
|
-
for (let i = 0; i < renderedLines.length; i++) if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {
|
|
706
|
-
startLine = i + 1;
|
|
707
|
-
endLine = i + codeLines.length;
|
|
708
|
-
break;
|
|
709
|
-
}
|
|
710
|
-
matches.push({
|
|
711
|
-
subjectId: subject.id,
|
|
712
|
-
subjectTitle: subject.title,
|
|
713
|
-
section: "Prelude",
|
|
714
|
-
code: block.code,
|
|
715
|
-
type: "prelude",
|
|
716
|
-
startLine,
|
|
717
|
-
endLine
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
for (const example of subject.examples) if (example.id === id) {
|
|
721
|
-
let sectionName = "Unknown Section";
|
|
722
|
-
let startLine;
|
|
723
|
-
let endLine;
|
|
724
|
-
for (const section of subject.sections) if (section.content.includes(example.code.substring(0, Math.min(50, example.code.length)))) {
|
|
725
|
-
sectionName = section.heading;
|
|
726
|
-
const codeLines = example.code.split("\n");
|
|
727
|
-
const firstCodeLine = codeLines[0].trim();
|
|
728
|
-
for (let i = 0; i < renderedLines.length; i++) if (stripVTControlCharacters(renderedLines[i]).trim() === firstCodeLine) {
|
|
729
|
-
startLine = i + 1;
|
|
730
|
-
endLine = i + codeLines.length;
|
|
731
|
-
break;
|
|
732
|
-
}
|
|
733
|
-
break;
|
|
734
|
-
}
|
|
735
|
-
matches.push({
|
|
736
|
-
subjectId: subject.id,
|
|
737
|
-
subjectTitle: subject.title,
|
|
738
|
-
section: sectionName,
|
|
739
|
-
code: example.code,
|
|
740
|
-
type: "example",
|
|
741
|
-
startLine,
|
|
742
|
-
endLine
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
if (matches.length === 0) {
|
|
747
|
-
console.error(`Error: No code block found with id "${id}"`);
|
|
748
|
-
if (topics.length > 0) console.error(`Searched in topics: ${topics.join(", ")}`);
|
|
749
|
-
else console.error("Searched in all available topics");
|
|
750
|
-
process.exit(1);
|
|
751
|
-
}
|
|
752
|
-
for (let i = 0; i < matches.length; i++) {
|
|
753
|
-
const match = matches[i];
|
|
754
|
-
if (matches.length > 1 && i > 0) console.log("\n---\n");
|
|
755
|
-
let matchMarkdown = `# ${match.subjectTitle}\n\n`;
|
|
756
|
-
matchMarkdown += `## ${match.section}\n\n`;
|
|
757
|
-
if (showLineNumbers && match.startLine && match.endLine) console.log(`Lines ${match.startLine}-${match.endLine} (use with --start/--end)\n`);
|
|
758
|
-
matchMarkdown += `\`\`\`typescript\n${match.code}\n\`\`\`\n`;
|
|
759
|
-
const rendered = await marked.parse(matchMarkdown);
|
|
760
|
-
console.log(rendered);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Print only the topic tree
|
|
765
|
-
*/
|
|
766
|
-
function printTopicTree() {
|
|
767
|
-
const subjects = getSubjects();
|
|
768
|
-
const subjectMap = new Map(subjects.map((s) => [s.id, s]));
|
|
769
|
-
function getTitle(subjectId) {
|
|
770
|
-
if (isCategory(subjectId)) return getCategoryTitle(subjectId);
|
|
771
|
-
const subject = subjectMap.get(subjectId);
|
|
772
|
-
return subject ? subject.title : subjectId;
|
|
773
|
-
}
|
|
774
|
-
function displayNode(subjectId, indent, isLast, isRoot) {
|
|
775
|
-
const title = getTitle(subjectId);
|
|
776
|
-
if (isRoot) console.log(` ${subjectId.padEnd(30)} ${title}`);
|
|
777
|
-
else {
|
|
778
|
-
const connector = isLast ? "└─" : "├─";
|
|
779
|
-
console.log(`${indent}${connector} ${subjectId.padEnd(26)} ${title}`);
|
|
780
|
-
}
|
|
781
|
-
const children = getSubjectChildren(subjectId);
|
|
782
|
-
if (children.length > 0) {
|
|
783
|
-
const childIndent = isRoot ? " " : indent + (isLast ? " " : "│ ");
|
|
784
|
-
for (let i = 0; i < children.length; i++) displayNode(children[i], childIndent, i === children.length - 1, false);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
const rootIds = getAllSubjectIdsInOrder().filter((id) => !getSubjectParent(id));
|
|
788
|
-
for (const subjectId of rootIds) displayNode(subjectId, "", false, true);
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Print information about the corpus command
|
|
792
|
-
*/
|
|
793
|
-
function printCorpusHelp() {
|
|
794
|
-
console.log("Fragno Corpus - Code examples and documentation (similar to LLMs.txt");
|
|
795
|
-
console.log("");
|
|
796
|
-
console.log("Usage: fragno-cli corpus [options] [topic...]");
|
|
797
|
-
console.log("");
|
|
798
|
-
console.log("Options:");
|
|
799
|
-
console.log(" -n, --no-line-numbers Hide line numbers (shown by default)");
|
|
800
|
-
console.log(" -s, --start N Starting line number to display from");
|
|
801
|
-
console.log(" -e, --end N Ending line number to display to");
|
|
802
|
-
console.log(" --headings Show only headings and code block IDs");
|
|
803
|
-
console.log(" --id <id> Retrieve a specific code block by ID");
|
|
804
|
-
console.log(" --tree Show only the topic tree");
|
|
805
|
-
console.log("");
|
|
806
|
-
console.log("Examples:");
|
|
807
|
-
console.log(" fragno-cli corpus # List all available topics");
|
|
808
|
-
console.log(" fragno-cli corpus --tree # Show only the topic tree");
|
|
809
|
-
console.log(" fragno-cli corpus defining-routes # Show route definition examples");
|
|
810
|
-
console.log(" fragno-cli corpus --headings database-querying");
|
|
811
|
-
console.log(" # Show structure overview");
|
|
812
|
-
console.log(" fragno-cli corpus --start 10 --end 50 database-querying");
|
|
813
|
-
console.log(" # Show specific lines");
|
|
814
|
-
console.log(" fragno-cli corpus --id create-user # Get code block by ID");
|
|
815
|
-
console.log(" fragno-cli corpus database-adapters kysely-adapter");
|
|
816
|
-
console.log(" # Show multiple topics");
|
|
817
|
-
console.log("");
|
|
818
|
-
console.log("Available topics:");
|
|
819
|
-
printTopicTree();
|
|
820
|
-
}
|
|
821
|
-
const corpusCommand = define({
|
|
822
|
-
name: "corpus",
|
|
823
|
-
description: "View code examples and documentation for Fragno",
|
|
824
|
-
args: {
|
|
825
|
-
"no-line-numbers": {
|
|
826
|
-
type: "boolean",
|
|
827
|
-
short: "n",
|
|
828
|
-
description: "Hide line numbers (line numbers are shown by default)"
|
|
829
|
-
},
|
|
830
|
-
start: {
|
|
831
|
-
type: "number",
|
|
832
|
-
short: "s",
|
|
833
|
-
description: "Starting line number (1-based) to display from"
|
|
834
|
-
},
|
|
835
|
-
end: {
|
|
836
|
-
type: "number",
|
|
837
|
-
short: "e",
|
|
838
|
-
description: "Ending line number (1-based) to display to"
|
|
839
|
-
},
|
|
840
|
-
headings: {
|
|
841
|
-
type: "boolean",
|
|
842
|
-
description: "Show only section headings and code block IDs with line numbers"
|
|
843
|
-
},
|
|
844
|
-
id: {
|
|
845
|
-
type: "string",
|
|
846
|
-
description: "Retrieve a specific code block by ID"
|
|
847
|
-
},
|
|
848
|
-
tree: {
|
|
849
|
-
type: "boolean",
|
|
850
|
-
description: "Show only the topic tree (without help text)"
|
|
851
|
-
}
|
|
852
|
-
},
|
|
853
|
-
run: async (ctx) => {
|
|
854
|
-
const topics = ctx.positionals;
|
|
855
|
-
const showLineNumbers = !(ctx.values["no-line-numbers"] ?? false);
|
|
856
|
-
const startLine = ctx.values.start;
|
|
857
|
-
const endLine = ctx.values.end;
|
|
858
|
-
const headingsOnly = ctx.values.headings ?? false;
|
|
859
|
-
const codeBlockId = ctx.values.id;
|
|
860
|
-
const treeOnly = ctx.values.tree ?? false;
|
|
861
|
-
if (codeBlockId) {
|
|
862
|
-
await printCodeBlockById(codeBlockId, topics, showLineNumbers);
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
if (treeOnly) {
|
|
866
|
-
printTopicTree();
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
if (topics.length === 0) {
|
|
870
|
-
printCorpusHelp();
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
if (startLine !== void 0 && endLine !== void 0 && startLine > endLine) {
|
|
874
|
-
console.error("Error: --start must be less than or equal to --end");
|
|
875
|
-
process.exit(1);
|
|
876
|
-
}
|
|
877
|
-
try {
|
|
878
|
-
await printSubjects(getSubject(...topics), {
|
|
879
|
-
showLineNumbers,
|
|
880
|
-
startLine,
|
|
881
|
-
endLine,
|
|
882
|
-
headingsOnly
|
|
883
|
-
});
|
|
884
|
-
} catch (error) {
|
|
885
|
-
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
886
|
-
const missingTopics = topics.filter((topic) => {
|
|
887
|
-
try {
|
|
888
|
-
getSubject(topic);
|
|
889
|
-
return false;
|
|
890
|
-
} catch {
|
|
891
|
-
return true;
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
if (missingTopics.length === 1) console.error(`Error: Subject '${missingTopics[0]}' not found.`);
|
|
895
|
-
else if (missingTopics.length > 1) console.error(`Error: Subjects not found: ${missingTopics.map((t) => `'${t}'`).join(", ")}`);
|
|
896
|
-
else console.error("Error: One or more subjects not found.");
|
|
897
|
-
console.log("\nAvailable topics:");
|
|
898
|
-
printTopicTree();
|
|
899
|
-
} else {
|
|
900
|
-
console.error("Error loading topics:", error instanceof Error ? error.message : error);
|
|
901
|
-
console.log("\nRun 'fragno-cli corpus' to see available topics.");
|
|
902
|
-
}
|
|
903
|
-
process.exit(1);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
});
|
|
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
|
-
|
|
987
327
|
//#endregion
|
|
988
328
|
//#region src/cli.ts
|
|
989
329
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1007,15 +347,13 @@ async function run() {
|
|
|
1007
347
|
name: "fragno-cli search",
|
|
1008
348
|
version
|
|
1009
349
|
});
|
|
1010
|
-
else if (args[0] === "
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
});
|
|
1018
|
-
else if (args[0] === "db") {
|
|
350
|
+
else if (args[0] === "serve") {
|
|
351
|
+
const { serveCommand } = await import("./serve-eh3Tpjhc.js");
|
|
352
|
+
await cli(args.slice(1), serveCommand, {
|
|
353
|
+
name: "fragno-cli serve",
|
|
354
|
+
version
|
|
355
|
+
});
|
|
356
|
+
} else if (args[0] === "db") {
|
|
1019
357
|
const subCommandName = args[1];
|
|
1020
358
|
if (!subCommandName || subCommandName === "--help" || subCommandName === "-h") {
|
|
1021
359
|
console.log("Database management commands");
|
|
@@ -1060,13 +398,11 @@ async function run() {
|
|
|
1060
398
|
console.log(" serve Start a local HTTP server to serve fragments");
|
|
1061
399
|
console.log(" db Database management commands");
|
|
1062
400
|
console.log(" search Search the Fragno documentation");
|
|
1063
|
-
console.log(" corpus View code examples and documentation for Fragno");
|
|
1064
401
|
console.log("");
|
|
1065
402
|
console.log("For more info, run any command with the `--help` flag:");
|
|
1066
403
|
console.log(" fragno-cli serve --help");
|
|
1067
404
|
console.log(" fragno-cli db --help");
|
|
1068
405
|
console.log(" fragno-cli search --help");
|
|
1069
|
-
console.log(" fragno-cli corpus --help");
|
|
1070
406
|
console.log("");
|
|
1071
407
|
console.log("OPTIONS:");
|
|
1072
408
|
console.log(" -h, --help Display this help message");
|
|
@@ -1086,5 +422,5 @@ async function run() {
|
|
|
1086
422
|
if (import.meta.main) await run();
|
|
1087
423
|
|
|
1088
424
|
//#endregion
|
|
1089
|
-
export {
|
|
425
|
+
export { dbCommand, generateCommand, infoCommand, mainCommand, migrateCommand, run, searchCommand };
|
|
1090
426
|
//# sourceMappingURL=cli.js.map
|