flowdoc-gen 0.1.0

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.
@@ -0,0 +1,604 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/generate.ts
4
+ import { writeFileSync, mkdirSync, cpSync, existsSync as existsSync3 } from "fs";
5
+ import { resolve as resolve3, join as join3, dirname as dirname2 } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import chalk from "chalk";
8
+ import ora from "ora";
9
+
10
+ // ../core/dist/index.js
11
+ import { existsSync } from "fs";
12
+ import { resolve, join } from "path";
13
+ var CONFIG_FILES = [
14
+ "flowdoc.config.ts",
15
+ "flowdoc.config.js",
16
+ "flowdoc.config.mjs"
17
+ ];
18
+ var findConfigFile = (cwd) => {
19
+ for (const file of CONFIG_FILES) {
20
+ const full = join(cwd, file);
21
+ if (existsSync(full)) return full;
22
+ }
23
+ return null;
24
+ };
25
+ var loadConfig = async (configPath) => {
26
+ const resolved = resolve(configPath);
27
+ const mod = await import(resolved);
28
+ const config = "default" in mod ? mod.default : mod;
29
+ if (!config) throw new Error(`No config exported from ${configPath}`);
30
+ return config;
31
+ };
32
+ var resolveConfig = (config, cwd) => ({
33
+ version: "1.0.0",
34
+ baseUrl: "http://localhost:3000",
35
+ ...config,
36
+ entry: resolve(cwd, config.entry),
37
+ output: resolve(cwd, config.output ?? "./docs-output")
38
+ });
39
+
40
+ // ../parser/dist/index.js
41
+ import {
42
+ Project,
43
+ Node as Node2,
44
+ SyntaxKind as SyntaxKind2
45
+ } from "ts-morph";
46
+ import { glob } from "glob";
47
+ import { resolve as resolve2, dirname, join as join2 } from "path";
48
+ import { existsSync as existsSync2 } from "fs";
49
+ import { Node } from "ts-morph";
50
+ var zodNodeToJsonSchema = (node) => {
51
+ if (!Node.isCallExpression(node)) return {};
52
+ const expr = node.getExpression();
53
+ const callText = expr.getText();
54
+ if (callText === "z.string") return buildStringSchema(node);
55
+ if (callText === "z.number") return buildNumberSchema(node);
56
+ if (callText === "z.boolean") return { type: "boolean" };
57
+ if (callText === "z.null") return { type: "null" };
58
+ if (callText === "z.literal") return buildLiteralSchema(node);
59
+ if (callText === "z.enum") return buildEnumSchema(node);
60
+ if (callText === "z.nativeEnum") return { type: "string" };
61
+ if (callText === "z.object") return buildObjectSchema(node);
62
+ if (callText === "z.array") return buildArraySchema(node);
63
+ if (callText === "z.union") return buildUnionSchema(node);
64
+ if (callText === "z.optional") return buildOptionalSchema(node);
65
+ if (callText === "z.date") return { type: "string", format: "date-time" };
66
+ if (callText === "z.any") return {};
67
+ if (callText === "z.unknown") return {};
68
+ if (Node.isPropertyAccessExpression(expr)) {
69
+ return buildChainedSchema(node);
70
+ }
71
+ return {};
72
+ };
73
+ var buildStringSchema = (node) => {
74
+ const schema = { type: "string" };
75
+ applyChainedValidations(node, schema);
76
+ return schema;
77
+ };
78
+ var buildNumberSchema = (node) => {
79
+ const schema = { type: "number" };
80
+ applyChainedValidations(node, schema);
81
+ return schema;
82
+ };
83
+ var buildLiteralSchema = (node) => {
84
+ const arg = node.getArguments()[0];
85
+ if (!arg) return {};
86
+ const text = arg.getText().replace(/['"]/g, "");
87
+ return { type: "string", enum: [text] };
88
+ };
89
+ var buildEnumSchema = (node) => {
90
+ const arg = node.getArguments()[0];
91
+ if (!arg || !Node.isArrayLiteralExpression(arg)) return { type: "string" };
92
+ const values = arg.getElements().map((el) => el.getText().replace(/['"]/g, ""));
93
+ return { type: "string", enum: values };
94
+ };
95
+ var buildObjectSchema = (node) => {
96
+ const arg = node.getArguments()[0];
97
+ if (!arg || !Node.isObjectLiteralExpression(arg)) return { type: "object" };
98
+ const properties = {};
99
+ const required = [];
100
+ for (const prop of arg.getProperties()) {
101
+ if (!Node.isPropertyAssignment(prop)) continue;
102
+ const name = prop.getName();
103
+ const init = prop.getInitializer();
104
+ if (!init) continue;
105
+ const childSchema = zodNodeToJsonSchema(init);
106
+ properties[name] = childSchema;
107
+ if (!isOptionalZodExpr(init)) {
108
+ required.push(name);
109
+ }
110
+ }
111
+ return {
112
+ type: "object",
113
+ properties,
114
+ ...required.length > 0 ? { required } : {}
115
+ };
116
+ };
117
+ var buildArraySchema = (node) => {
118
+ const arg = node.getArguments()[0];
119
+ if (!arg) return { type: "array" };
120
+ return { type: "array", items: zodNodeToJsonSchema(arg) };
121
+ };
122
+ var buildUnionSchema = (node) => {
123
+ const arg = node.getArguments()[0];
124
+ if (!arg || !Node.isArrayLiteralExpression(arg)) return {};
125
+ const schemas = arg.getElements().map((el) => zodNodeToJsonSchema(el));
126
+ return { anyOf: schemas };
127
+ };
128
+ var buildOptionalSchema = (node) => {
129
+ const arg = node.getArguments()[0];
130
+ if (!arg) return {};
131
+ return zodNodeToJsonSchema(arg);
132
+ };
133
+ var buildChainedSchema = (node) => {
134
+ const chain = unwrapChain(node);
135
+ if (!chain.root) return {};
136
+ const base = zodNodeToJsonSchema(chain.root);
137
+ applyChainedCallsToSchema(chain.methods, base);
138
+ return base;
139
+ };
140
+ var unwrapChain = (node) => {
141
+ const methods = [];
142
+ let current = node;
143
+ while (Node.isCallExpression(current)) {
144
+ const expr = current.getExpression();
145
+ if (Node.isPropertyAccessExpression(expr)) {
146
+ const obj = expr.getExpression();
147
+ if (Node.isIdentifier(obj)) {
148
+ return { root: current, methods };
149
+ }
150
+ const methodName = expr.getName();
151
+ const args = current.getArguments();
152
+ methods.unshift({ name: methodName, args });
153
+ current = obj;
154
+ } else {
155
+ return { root: current, methods };
156
+ }
157
+ }
158
+ return { root: null, methods };
159
+ };
160
+ var applyChainedValidations = (node, schema) => {
161
+ const chain = unwrapChain(node);
162
+ applyChainedCallsToSchema(chain.methods, schema);
163
+ };
164
+ var applyChainedCallsToSchema = (methods, schema) => {
165
+ for (const { name, args } of methods) {
166
+ switch (name) {
167
+ case "email":
168
+ schema.format = "email";
169
+ break;
170
+ case "url":
171
+ schema.format = "uri";
172
+ break;
173
+ case "uuid":
174
+ schema.format = "uuid";
175
+ break;
176
+ case "datetime":
177
+ schema.format = "date-time";
178
+ break;
179
+ case "min": {
180
+ const val = getNumericArg(args[0]);
181
+ if (val !== null) {
182
+ if (schema.type === "string") schema.minLength = val;
183
+ else schema.minimum = val;
184
+ }
185
+ break;
186
+ }
187
+ case "max": {
188
+ const val = getNumericArg(args[0]);
189
+ if (val !== null) {
190
+ if (schema.type === "string") schema.maxLength = val;
191
+ else schema.maximum = val;
192
+ }
193
+ break;
194
+ }
195
+ case "describe": {
196
+ const desc = getStringArg(args[0]);
197
+ if (desc) schema.description = desc;
198
+ break;
199
+ }
200
+ case "default": {
201
+ const arg = args[0];
202
+ if (arg) schema.default = tryParseValue(arg.getText());
203
+ break;
204
+ }
205
+ case "optional":
206
+ case "nullish":
207
+ schema.nullable = true;
208
+ break;
209
+ case "positive":
210
+ schema.minimum = 0;
211
+ break;
212
+ case "negative":
213
+ schema.maximum = 0;
214
+ break;
215
+ case "int":
216
+ schema.type = "integer";
217
+ break;
218
+ case "regex": {
219
+ const pattern = args[0]?.getText();
220
+ if (pattern) schema.pattern = pattern.replace(/^\/|\/[gimsuy]*$/g, "");
221
+ break;
222
+ }
223
+ }
224
+ }
225
+ };
226
+ var isOptionalZodExpr = (node) => {
227
+ const text = node.getText();
228
+ return text.includes(".optional()") || text.includes(".nullish()") || text.startsWith("z.optional(");
229
+ };
230
+ var getNumericArg = (node) => {
231
+ if (!node) return null;
232
+ const val = Number(node.getText());
233
+ return isNaN(val) ? null : val;
234
+ };
235
+ var getStringArg = (node) => {
236
+ if (!node) return null;
237
+ const text = node.getText();
238
+ const match = text.match(/^['"`](.*?)['"`]$/s);
239
+ return match?.[1] ?? null;
240
+ };
241
+ var tryParseValue = (text) => {
242
+ try {
243
+ return JSON.parse(text);
244
+ } catch {
245
+ return text.replace(/['"]/g, "");
246
+ }
247
+ };
248
+ var extractZodSchemas = (sourceFile) => {
249
+ const schemas = {};
250
+ const varDeclarations = sourceFile.getVariableDeclarations();
251
+ for (const decl of varDeclarations) {
252
+ const init = decl.getInitializer();
253
+ if (!init) continue;
254
+ const text = init.getText();
255
+ if (!text.startsWith("z.")) continue;
256
+ const name = decl.getName();
257
+ try {
258
+ schemas[name] = zodNodeToJsonSchema(init);
259
+ } catch {
260
+ }
261
+ }
262
+ return schemas;
263
+ };
264
+ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
265
+ var findTsConfig = (startDir) => {
266
+ let dir = startDir;
267
+ for (let i = 0; i < 5; i++) {
268
+ const candidate = join2(dir, "tsconfig.json");
269
+ if (existsSync2(candidate)) return candidate;
270
+ const parent = dirname(dir);
271
+ if (parent === dir) break;
272
+ dir = parent;
273
+ }
274
+ return void 0;
275
+ };
276
+ var extractExpressRoutes = async (config) => {
277
+ const cwd = existsSync2(config.entry) && !config.entry.endsWith(".ts") && !config.entry.endsWith(".js") ? config.entry : dirname(config.entry);
278
+ const tsConfigPath = findTsConfig(cwd);
279
+ const project = tsConfigPath ? new Project({ tsConfigFilePath: tsConfigPath, skipAddingFilesFromTsConfig: true }) : new Project({ compilerOptions: { allowJs: true, strict: false } });
280
+ const patterns = ["**/*.ts", "**/*.js"].map((p) => resolve2(cwd, "**", p));
281
+ const files = await glob(`${cwd}/**/*.{ts,js}`, {
282
+ ignore: ["**/node_modules/**", "**/dist/**", "**/*.d.ts", "**/*.spec.*", "**/*.test.*"]
283
+ });
284
+ for (const file of files) {
285
+ project.addSourceFileAtPath(file);
286
+ }
287
+ const globalZodSchemas = {};
288
+ for (const sourceFile of project.getSourceFiles()) {
289
+ Object.assign(globalZodSchemas, extractZodSchemas(sourceFile));
290
+ }
291
+ const allRoutes = [];
292
+ for (const sourceFile of project.getSourceFiles()) {
293
+ const ctx = { zodSchemas: globalZodSchemas, routerPrefix: "" };
294
+ const routes = extractRoutesFromFile(sourceFile, ctx);
295
+ allRoutes.push(...routes);
296
+ }
297
+ return deduplicateRoutes(allRoutes);
298
+ };
299
+ var extractRoutesFromFile = (sourceFile, ctx) => {
300
+ const routes = [];
301
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind2.CallExpression);
302
+ for (const call of callExpressions) {
303
+ const route = tryExtractRoute(call, ctx);
304
+ if (route) routes.push(route);
305
+ }
306
+ return routes;
307
+ };
308
+ var tryExtractRoute = (call, ctx) => {
309
+ const expr = call.getExpression();
310
+ if (!Node2.isPropertyAccessExpression(expr)) return null;
311
+ const methodName = expr.getName().toUpperCase();
312
+ if (!HTTP_METHODS.includes(methodName)) return null;
313
+ const args = call.getArguments();
314
+ if (args.length < 2) return null;
315
+ const firstArg = args[0];
316
+ if (!firstArg) return null;
317
+ if (!Node2.isStringLiteral(firstArg) && !Node2.isTemplateExpression(firstArg) && !Node2.isNoSubstitutionTemplateLiteral(firstArg)) {
318
+ return null;
319
+ }
320
+ const rawPath = firstArg.getText().replace(/['"'`]/g, "");
321
+ const path = normalizePath(ctx.routerPrefix, rawPath);
322
+ const method = methodName;
323
+ const middlewareArgs = args.slice(1, -1);
324
+ const handlerArg = args[args.length - 1];
325
+ const { requestBody, parameters } = extractFromMiddleware(middlewareArgs, ctx);
326
+ const pathParams = extractPathParameters(path);
327
+ const handlerInfo = extractHandlerInfo(handlerArg);
328
+ const allParameters = mergeParameters(parameters, pathParams);
329
+ const tags = inferTags(path);
330
+ const route = {
331
+ method,
332
+ path,
333
+ tags,
334
+ parameters: allParameters,
335
+ responses: buildDefaultResponses(method),
336
+ middleware: middlewareArgs.map((m) => m.getText())
337
+ };
338
+ if (handlerInfo.summary !== void 0) route.summary = handlerInfo.summary;
339
+ if (handlerInfo.description !== void 0) route.description = handlerInfo.description;
340
+ if (requestBody !== null) route.requestBody = requestBody;
341
+ return route;
342
+ };
343
+ var extractFromMiddleware = (middleware, ctx) => {
344
+ let requestBody = null;
345
+ const parameters = [];
346
+ for (const mw of middleware) {
347
+ const text = mw.getText();
348
+ const bodyMatch = text.match(/(?:validateBody|validate|zodValidate|bodyParser)\((\w+)\)/);
349
+ if (bodyMatch) {
350
+ const schemaName = bodyMatch[1];
351
+ const schema = schemaName ? ctx.zodSchemas[schemaName] : null;
352
+ if (schema) {
353
+ requestBody = {
354
+ required: true,
355
+ content: { "application/json": { schema } }
356
+ };
357
+ }
358
+ }
359
+ const queryMatch = text.match(/(?:validateQuery|queryValidator)\((\w+)\)/);
360
+ if (queryMatch) {
361
+ const schemaName = queryMatch[1];
362
+ const schema = schemaName ? ctx.zodSchemas[schemaName] : null;
363
+ if (schema?.type === "object" && schema.properties) {
364
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
365
+ parameters.push({
366
+ name,
367
+ in: "query",
368
+ required: schema.required?.includes(name) ?? false,
369
+ schema: propSchema
370
+ });
371
+ }
372
+ }
373
+ }
374
+ }
375
+ return { requestBody, parameters };
376
+ };
377
+ var extractPathParameters = (path) => {
378
+ const paramRegex = /:(\w+)/g;
379
+ const params = [];
380
+ let match;
381
+ while ((match = paramRegex.exec(path)) !== null) {
382
+ const name = match[1];
383
+ if (name) {
384
+ params.push({
385
+ name,
386
+ in: "path",
387
+ required: true,
388
+ schema: { type: "string" }
389
+ });
390
+ }
391
+ }
392
+ return params;
393
+ };
394
+ var extractHandlerInfo = (handler) => {
395
+ const leadingComments = handler.getLeadingCommentRanges();
396
+ if (leadingComments.length === 0) return {};
397
+ const comment = leadingComments[leadingComments.length - 1];
398
+ if (!comment) return {};
399
+ const text = comment.getText();
400
+ const summary = extractJsDocTag(text, null);
401
+ const description = extractJsDocTag(text, "description");
402
+ const result = {};
403
+ if (summary !== null) result.summary = summary;
404
+ if (description !== null) result.description = description;
405
+ return result;
406
+ };
407
+ var extractJsDocTag = (comment, tag) => {
408
+ if (tag === null) {
409
+ const match2 = comment.match(/\/\*\*\s*\n?\s*\*\s*(.+)/);
410
+ return match2?.[1]?.trim() ?? null;
411
+ }
412
+ const match = comment.match(new RegExp(`@${tag}\\s+(.+)`));
413
+ return match?.[1]?.trim() ?? null;
414
+ };
415
+ var mergeParameters = (fromMiddleware, fromPath) => {
416
+ const existing = new Set(fromMiddleware.map((p) => `${p.in}:${p.name}`));
417
+ const merged = [...fromMiddleware];
418
+ for (const param of fromPath) {
419
+ if (!existing.has(`${param.in}:${param.name}`)) {
420
+ merged.push(param);
421
+ }
422
+ }
423
+ return merged;
424
+ };
425
+ var normalizePath = (prefix, path) => {
426
+ const combined = `${prefix}${path}`.replace(/\/+/g, "/");
427
+ return combined.startsWith("/") ? combined : `/${combined}`;
428
+ };
429
+ var inferTags = (path) => {
430
+ const segments = path.split("/").filter(Boolean);
431
+ const tag = segments.find((s) => !s.startsWith(":"));
432
+ return tag ? [tag] : ["default"];
433
+ };
434
+ var buildDefaultResponses = (method) => {
435
+ const responses = {
436
+ "200": { description: method === "DELETE" ? "Success" : "Successful response" },
437
+ "400": { description: "Bad request" },
438
+ "401": { description: "Unauthorized" },
439
+ "500": { description: "Internal server error" }
440
+ };
441
+ if (method === "POST") {
442
+ responses["201"] = { description: "Created" };
443
+ }
444
+ if (method === "DELETE") {
445
+ responses["204"] = { description: "No content" };
446
+ }
447
+ return responses;
448
+ };
449
+ var deduplicateRoutes = (routes) => {
450
+ const seen = /* @__PURE__ */ new Set();
451
+ return routes.filter((r) => {
452
+ const key = `${r.method}:${r.path}`;
453
+ if (seen.has(key)) return false;
454
+ seen.add(key);
455
+ return true;
456
+ });
457
+ };
458
+ var buildSpec = (routes, config) => {
459
+ const groups = groupRoutes(routes, config.groups);
460
+ return {
461
+ info: {
462
+ title: config.name,
463
+ version: config.version ?? "1.0.0",
464
+ baseUrl: config.baseUrl ?? "http://localhost:3000",
465
+ ...config.description !== void 0 ? { description: config.description } : {}
466
+ },
467
+ ...config.auth !== void 0 ? { auth: config.auth } : {},
468
+ groups,
469
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
470
+ sourceFramework: config.framework
471
+ };
472
+ };
473
+ var groupRoutes = (routes, groupConfig) => {
474
+ if (!groupConfig) {
475
+ return groupByTag(routes);
476
+ }
477
+ const groups = [];
478
+ const assignedRoutes = /* @__PURE__ */ new Set();
479
+ for (const [groupName, patterns] of Object.entries(groupConfig)) {
480
+ const matched = routes.filter((r) => {
481
+ const key = `${r.method}:${r.path}`;
482
+ if (assignedRoutes.has(key)) return false;
483
+ const matches = patterns.some((pattern) => matchesGlob(r.path, pattern));
484
+ if (matches) assignedRoutes.add(key);
485
+ return matches;
486
+ });
487
+ if (matched.length > 0) {
488
+ groups.push({ name: groupName, routes: matched });
489
+ }
490
+ }
491
+ const unassigned = routes.filter(
492
+ (r) => !assignedRoutes.has(`${r.method}:${r.path}`)
493
+ );
494
+ if (unassigned.length > 0) {
495
+ groups.push({ name: "Other", routes: unassigned });
496
+ }
497
+ return groups;
498
+ };
499
+ var groupByTag = (routes) => {
500
+ const tagMap = /* @__PURE__ */ new Map();
501
+ for (const route of routes) {
502
+ const tag = route.tags[0] ?? "default";
503
+ if (!tagMap.has(tag)) tagMap.set(tag, []);
504
+ tagMap.get(tag).push(route);
505
+ }
506
+ return Array.from(tagMap.entries()).map(([name, groupRoutes2]) => ({
507
+ name: capitalize(name),
508
+ routes: groupRoutes2
509
+ }));
510
+ };
511
+ var matchesGlob = (path, pattern) => {
512
+ const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\/\*\*$/, "(/.*)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
513
+ return new RegExp(`^${regexStr}$`).test(path);
514
+ };
515
+ var capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
516
+
517
+ // src/generate.ts
518
+ var generate = async (opts = {}) => {
519
+ const cwd = process.cwd();
520
+ const spinner = opts.quiet ? null : ora();
521
+ spinner?.start("Loading flowdoc config...");
522
+ const configPath = opts.config ? resolve3(cwd, opts.config) : findConfigFile(cwd);
523
+ if (!configPath) {
524
+ spinner?.fail(chalk.red("No flowdoc.config.ts found. Run `flowdoc init` first."));
525
+ process.exit(1);
526
+ }
527
+ let rawConfig;
528
+ try {
529
+ rawConfig = await loadConfig(configPath);
530
+ } catch (err) {
531
+ spinner?.fail(chalk.red(`Failed to load config: ${String(err)}`));
532
+ process.exit(1);
533
+ }
534
+ const config = resolveConfig(rawConfig, cwd);
535
+ spinner?.succeed(`Config loaded \u2014 ${chalk.cyan(config.name)}`);
536
+ spinner?.start(`Scanning ${chalk.cyan(config.entry)} for routes...`);
537
+ let routes;
538
+ try {
539
+ routes = await extractExpressRoutes(config);
540
+ } catch (err) {
541
+ spinner?.fail(chalk.red(`Parse failed: ${String(err)}`));
542
+ process.exit(1);
543
+ }
544
+ spinner?.succeed(
545
+ `Found ${chalk.green(String(routes.length))} routes across ${chalk.cyan(config.framework)} app`
546
+ );
547
+ const spec = buildSpec(routes, config);
548
+ const outputDir = opts.output ? resolve3(cwd, opts.output) : config.output ?? resolve3(cwd, "docs-output");
549
+ mkdirSync(outputDir, { recursive: true });
550
+ const specPath = join3(outputDir, "flowdoc.json");
551
+ writeFileSync(specPath, JSON.stringify(spec, null, 2), "utf-8");
552
+ await writeUiHtml(outputDir, config);
553
+ if (!opts.quiet) {
554
+ console.log();
555
+ console.log(chalk.bold(" flowdoc generated successfully"));
556
+ console.log();
557
+ console.log(` ${chalk.gray("Spec:")} ${chalk.cyan(specPath)}`);
558
+ console.log(` ${chalk.gray("UI:")} ${chalk.cyan(join3(outputDir, "index.html"))}`);
559
+ console.log();
560
+ console.log(` ${chalk.gray("Routes:")} ${chalk.green(String(routes.length))}`);
561
+ console.log(` ${chalk.gray("Groups:")} ${chalk.green(String(spec.groups.length))}`);
562
+ console.log();
563
+ }
564
+ return spec;
565
+ };
566
+ var writeUiHtml = async (outputDir, config) => {
567
+ const brand = config.theme?.brand ?? "#6366f1";
568
+ const title = config.name;
569
+ const darkMode = config.theme?.darkMode !== false;
570
+ const cliRoot = dirname2(dirname2(fileURLToPath(import.meta.url)));
571
+ const uiAssetsSource = join3(cliRoot, "ui-assets");
572
+ const uiAssetsDest = join3(outputDir, "assets");
573
+ if (existsSync3(uiAssetsSource)) {
574
+ mkdirSync(uiAssetsDest, { recursive: true });
575
+ cpSync(uiAssetsSource, uiAssetsDest, { recursive: true });
576
+ }
577
+ const html = generateHtmlShell({ title, brand, darkMode });
578
+ writeFileSync(join3(outputDir, "index.html"), html, "utf-8");
579
+ };
580
+ var generateHtmlShell = ({ title, brand, darkMode }) => `<!DOCTYPE html>
581
+ <html lang="en" class="${darkMode ? "dark" : ""}">
582
+ <head>
583
+ <meta charset="UTF-8" />
584
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
585
+ <title>${title} \u2014 API Docs</title>
586
+ <meta name="description" content="API documentation generated by flowdoc" />
587
+ <script>
588
+ window.__FLOWDOC_BRAND__ = "${brand}";
589
+ window.__FLOWDOC_DARK__ = ${String(darkMode)};
590
+ </script>
591
+ <script type="module" crossorigin src="./assets/ui.js"></script>
592
+ <link rel="stylesheet" href="./assets/index.css" />
593
+ </head>
594
+ <body>
595
+ <div id="root"></div>
596
+ </body>
597
+ </html>`;
598
+
599
+ export {
600
+ findConfigFile,
601
+ loadConfig,
602
+ resolveConfig,
603
+ generate
604
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ generate
3
+ } from "./chunk-SAMPAR3A.js";
4
+ export {
5
+ generate
6
+ };
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generate
4
+ } from "./chunk-3GGK6LWE.js";
5
+ export {
6
+ generate
7
+ };
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generate
4
+ } from "./chunk-XXW6UJOX.js";
5
+ export {
6
+ generate
7
+ };
@@ -0,0 +1,36 @@
1
+ import { FlowDocSpec } from '@flowdoc/core';
2
+ import { Request, Response, NextFunction } from 'express';
3
+
4
+ interface GenerateOptions {
5
+ config?: string;
6
+ output?: string;
7
+ quiet?: boolean;
8
+ }
9
+ declare const generate: (opts?: GenerateOptions) => Promise<FlowDocSpec>;
10
+
11
+ interface ServeOptions extends GenerateOptions {
12
+ port?: number;
13
+ noOpen?: boolean;
14
+ watch?: boolean;
15
+ }
16
+ declare const serve: (opts?: ServeOptions) => Promise<void>;
17
+
18
+ declare const init: (cwd?: string) => void;
19
+
20
+ interface FlowDocMiddlewareOptions {
21
+ /** Path to flowdoc.config.ts — defaults to auto-discovery from cwd */
22
+ config?: string;
23
+ /** Route prefix the middleware is mounted at — used only for the HTML shell */
24
+ path?: string;
25
+ }
26
+ /**
27
+ * Express middleware that serves flowdoc docs at whatever route you mount it on.
28
+ * baseUrl is auto-derived from each incoming request — no manual config needed.
29
+ *
30
+ * Usage:
31
+ * import { flowdoc } from "flowdoc";
32
+ * app.use("/docs", flowdoc());
33
+ */
34
+ declare const flowdoc: (opts?: FlowDocMiddlewareOptions) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
35
+
36
+ export { type FlowDocMiddlewareOptions, type GenerateOptions, type ServeOptions, flowdoc, generate, init, serve };