@functional-examples/documentation 0.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/commands/generate.d.ts +21 -0
  2. package/dist/commands/generate.d.ts.map +1 -0
  3. package/dist/commands/generate.js +243 -0
  4. package/dist/commands/generate.js.map +1 -0
  5. package/dist/commands/index.d.ts +21 -0
  6. package/dist/commands/index.d.ts.map +1 -0
  7. package/dist/commands/index.js +5 -0
  8. package/dist/commands/index.js.map +1 -0
  9. package/dist/extractor/extractor.d.ts +9 -0
  10. package/dist/extractor/extractor.d.ts.map +1 -0
  11. package/dist/extractor/extractor.js +95 -0
  12. package/dist/extractor/extractor.js.map +1 -0
  13. package/dist/index.d.ts +32 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +46 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/renderers/markdoc.d.ts +13 -0
  18. package/dist/renderers/markdoc.d.ts.map +1 -0
  19. package/dist/renderers/markdoc.js +42 -0
  20. package/dist/renderers/markdoc.js.map +1 -0
  21. package/dist/renderers/markdown.d.ts +6 -0
  22. package/dist/renderers/markdown.d.ts.map +1 -0
  23. package/dist/renderers/markdown.js +12 -0
  24. package/dist/renderers/markdown.js.map +1 -0
  25. package/dist/renderers/types.d.ts +14 -0
  26. package/dist/renderers/types.d.ts.map +1 -0
  27. package/dist/renderers/types.js +2 -0
  28. package/dist/renderers/types.js.map +1 -0
  29. package/dist/schema.d.ts +63 -0
  30. package/dist/schema.d.ts.map +1 -0
  31. package/dist/schema.js +49 -0
  32. package/dist/schema.js.map +1 -0
  33. package/dist/templates/consumption-tracker.d.ts +16 -0
  34. package/dist/templates/consumption-tracker.d.ts.map +1 -0
  35. package/dist/templates/consumption-tracker.js +21 -0
  36. package/dist/templates/consumption-tracker.js.map +1 -0
  37. package/dist/templates/engine.d.ts +123 -0
  38. package/dist/templates/engine.d.ts.map +1 -0
  39. package/dist/templates/engine.js +212 -0
  40. package/dist/templates/engine.js.map +1 -0
  41. package/dist/templates/guide-renderer.d.ts +59 -0
  42. package/dist/templates/guide-renderer.d.ts.map +1 -0
  43. package/dist/templates/guide-renderer.js +93 -0
  44. package/dist/templates/guide-renderer.js.map +1 -0
  45. package/dist/templates/helpers.d.ts +45 -0
  46. package/dist/templates/helpers.d.ts.map +1 -0
  47. package/dist/templates/helpers.js +104 -0
  48. package/dist/templates/helpers.js.map +1 -0
  49. package/dist/templates/prose-helpers.d.ts +26 -0
  50. package/dist/templates/prose-helpers.d.ts.map +1 -0
  51. package/dist/templates/prose-helpers.js +39 -0
  52. package/dist/templates/prose-helpers.js.map +1 -0
  53. package/dist/types.d.ts +48 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +20 -0
  56. package/dist/types.js.map +1 -0
  57. package/package.json +62 -0
  58. package/templates/@slug.md.template +40 -0
  59. package/templates/@slug.mdoc.template +40 -0
  60. package/templates/index.md.template +9 -0
  61. package/templates/index.mdoc.template +9 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Guide hydration renderer — expands example references in standalone
3
+ * markdown guides that reference files/regions from *any* scanned example.
4
+ *
5
+ * Unlike prose helpers (scoped to a single example), the guide renderer
6
+ * creates a cross-example lookup so authors can write:
7
+ *
8
+ * ```markdown
9
+ * <%= example('basic-usage').file('scan.ts') %>
10
+ * <%= example('basic-usage').region('config-setup') %>
11
+ * ```
12
+ */
13
+ import { Eta } from 'eta';
14
+ import * as fs from 'node:fs/promises';
15
+ import { templateHelpers } from './helpers.js';
16
+ import { fencedBlock } from './prose-helpers.js';
17
+ /**
18
+ * Create a guide renderer bound to a set of scanned examples.
19
+ *
20
+ * Guide templates can reference any example by ID:
21
+ * ```markdown
22
+ * <%= example('basic-usage').file('scan.ts') %>
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { createGuideRenderer } from '@functional-examples/documentation';
28
+ * import { scan } from 'functional-examples';
29
+ *
30
+ * const { examples } = await scan({ root: workspaceRoot });
31
+ * const renderer = createGuideRenderer(examples);
32
+ * const html = await renderer.renderFile('docs/guides/getting-started.md');
33
+ * ```
34
+ */
35
+ export function createGuideRenderer(examples) {
36
+ // Build lookup map: example ID → ScannedExample
37
+ const examplesById = new Map(examples.map((ex) => [ex.id, ex]));
38
+ /**
39
+ * Create a scoped accessor for a single example.
40
+ */
41
+ function exampleAccessor(id) {
42
+ const ex = examplesById.get(id);
43
+ if (!ex) {
44
+ const available = [...examplesById.keys()].join(', ');
45
+ throw new Error(`Guide helper example(): no example found with id "${id}". Available: ${available}`);
46
+ }
47
+ return {
48
+ file(relativePath) {
49
+ const found = ex.files.find((f) => f.relativePath === relativePath);
50
+ if (!found) {
51
+ const fileList = ex.files.map((f) => f.relativePath).join(', ');
52
+ throw new Error(`Guide helper example('${id}').file(): no file "${relativePath}". Available: ${fileList}`);
53
+ }
54
+ const lang = templateHelpers.langFromPath(relativePath);
55
+ const content = found.parsed ?? found.raw ?? '';
56
+ return fencedBlock(lang, content);
57
+ },
58
+ region(regionId) {
59
+ const match = templateHelpers.region(ex.files, regionId);
60
+ if (!match) {
61
+ throw new Error(`Guide helper example('${id}').region(): no region "${regionId}" found`);
62
+ }
63
+ const lang = templateHelpers.langFromPath(match.file.relativePath);
64
+ return fencedBlock(lang, match.content);
65
+ },
66
+ files: ex.files,
67
+ metadata: ex.metadata,
68
+ title: ex.title,
69
+ description: ex.description,
70
+ };
71
+ }
72
+ // Separate Eta instance for guide templates with injected helper names
73
+ const guideEta = new Eta({
74
+ autoEscape: false,
75
+ autoTrim: false,
76
+ functionHeader: 'var example = it.example, examples = it.examples, helpers = it.helpers;',
77
+ });
78
+ function render(markdown) {
79
+ return guideEta
80
+ .renderString(markdown, {
81
+ example: exampleAccessor,
82
+ examples,
83
+ helpers: templateHelpers,
84
+ })
85
+ .replaceAll('\\%', '%');
86
+ }
87
+ async function renderFile(filePath) {
88
+ const content = await fs.readFile(filePath, 'utf-8');
89
+ return render(content);
90
+ }
91
+ return { render, renderFile };
92
+ }
93
+ //# sourceMappingURL=guide-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide-renderer.js","sourceRoot":"","sources":["../../src/templates/guide-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { ExampleFile } from '@functional-examples/devkit';
2
+ /**
3
+ * Infer a code fence language identifier from a file path's extension.
4
+ */
5
+ export declare function langFromPath(filePath: string): string;
6
+ /**
7
+ * Select a specific hunk (region) by ID across all files.
8
+ * Returns the first matching hunk or undefined.
9
+ */
10
+ export declare function region(files: ExampleFile[], regionId: string): {
11
+ file: ExampleFile;
12
+ content: string;
13
+ } | undefined;
14
+ /**
15
+ * Filter files by extension.
16
+ */
17
+ export declare function filesByExt(files: ExampleFile[], ext: string): ExampleFile[];
18
+ /**
19
+ * Check whether a file is a prose/documentation file (e.g. README.md).
20
+ */
21
+ export declare function isProseFile(file: ExampleFile): boolean;
22
+ /**
23
+ * Look up a hunk description from the docs metadata.
24
+ * Returns the description string or undefined.
25
+ */
26
+ export declare function hunkDescription(metadata: Record<string, unknown>, hunkId: string): string | undefined;
27
+ /**
28
+ * Convert a string to a URL-friendly slug.
29
+ * Strips backticks, lowercases, replaces non-alphanumeric runs with dashes,
30
+ * and trims leading/trailing dashes.
31
+ */
32
+ export declare function slugify(text: string): string;
33
+ /**
34
+ * Collected template helpers exposed as `it.helpers`.
35
+ */
36
+ export declare const templateHelpers: {
37
+ readonly langFromPath: typeof langFromPath;
38
+ readonly region: typeof region;
39
+ readonly filesByExt: typeof filesByExt;
40
+ readonly isProseFile: typeof isProseFile;
41
+ readonly hunkDescription: typeof hunkDescription;
42
+ readonly slugify: typeof slugify;
43
+ };
44
+ export type TemplateHelpers = typeof templateHelpers;
45
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/templates/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAgC/D;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;GAGG;AACH,wBAAgB,MAAM,CACpB,KAAK,EAAE,WAAW,EAAE,EACpB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CASpD;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,WAAW,EAAE,EACpB,GAAG,EAAE,MAAM,GACV,WAAW,EAAE,CAGf;AAKD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAItD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,SAAS,CAKpB;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;CAOlB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC"}
@@ -0,0 +1,104 @@
1
+ /** Map of file extensions to language identifiers for code fences. */
2
+ const EXT_TO_LANG = {
3
+ '.ts': 'typescript',
4
+ '.tsx': 'tsx',
5
+ '.js': 'javascript',
6
+ '.jsx': 'jsx',
7
+ '.mjs': 'javascript',
8
+ '.cjs': 'javascript',
9
+ '.mts': 'typescript',
10
+ '.cts': 'typescript',
11
+ '.json': 'json',
12
+ '.yaml': 'yaml',
13
+ '.yml': 'yaml',
14
+ '.md': 'markdown',
15
+ '.css': 'css',
16
+ '.scss': 'scss',
17
+ '.html': 'html',
18
+ '.sh': 'bash',
19
+ '.bash': 'bash',
20
+ '.py': 'python',
21
+ '.rs': 'rust',
22
+ '.go': 'go',
23
+ '.toml': 'toml',
24
+ '.xml': 'xml',
25
+ '.sql': 'sql',
26
+ '.graphql': 'graphql',
27
+ '.vue': 'vue',
28
+ '.svelte': 'svelte',
29
+ };
30
+ /**
31
+ * Infer a code fence language identifier from a file path's extension.
32
+ */
33
+ export function langFromPath(filePath) {
34
+ const dotIdx = filePath.lastIndexOf('.');
35
+ if (dotIdx === -1)
36
+ return '';
37
+ const ext = filePath.slice(dotIdx).toLowerCase();
38
+ return EXT_TO_LANG[ext] ?? ext.slice(1);
39
+ }
40
+ /**
41
+ * Select a specific hunk (region) by ID across all files.
42
+ * Returns the first matching hunk or undefined.
43
+ */
44
+ export function region(files, regionId) {
45
+ for (const file of files) {
46
+ if (!file.hunks)
47
+ continue;
48
+ const hunk = file.hunks.find((h) => h.id === regionId);
49
+ if (hunk) {
50
+ return { file, content: hunk.content };
51
+ }
52
+ }
53
+ return undefined;
54
+ }
55
+ /**
56
+ * Filter files by extension.
57
+ */
58
+ export function filesByExt(files, ext) {
59
+ const normalized = ext.startsWith('.') ? ext : `.${ext}`;
60
+ return files.filter((f) => f.relativePath.endsWith(normalized));
61
+ }
62
+ /** Extensions considered prose/documentation rather than code. */
63
+ const PROSE_EXTENSIONS = new Set(['.md', '.mdx', '.mdoc', '.txt', '.rst']);
64
+ /**
65
+ * Check whether a file is a prose/documentation file (e.g. README.md).
66
+ */
67
+ export function isProseFile(file) {
68
+ const dotIdx = file.relativePath.lastIndexOf('.');
69
+ if (dotIdx === -1)
70
+ return false;
71
+ return PROSE_EXTENSIONS.has(file.relativePath.slice(dotIdx).toLowerCase());
72
+ }
73
+ /**
74
+ * Look up a hunk description from the docs metadata.
75
+ * Returns the description string or undefined.
76
+ */
77
+ export function hunkDescription(metadata, hunkId) {
78
+ const docs = metadata.docs;
79
+ return docs?.hunks?.[hunkId];
80
+ }
81
+ /**
82
+ * Convert a string to a URL-friendly slug.
83
+ * Strips backticks, lowercases, replaces non-alphanumeric runs with dashes,
84
+ * and trims leading/trailing dashes.
85
+ */
86
+ export function slugify(text) {
87
+ return text
88
+ .replace(/`/g, '')
89
+ .toLowerCase()
90
+ .replace(/[^a-z0-9]+/g, '-')
91
+ .replace(/^-|-$/g, '');
92
+ }
93
+ /**
94
+ * Collected template helpers exposed as `it.helpers`.
95
+ */
96
+ export const templateHelpers = {
97
+ langFromPath,
98
+ region,
99
+ filesByExt,
100
+ isProseFile,
101
+ hunkDescription,
102
+ slugify,
103
+ };
104
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/templates/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ExampleFile } from '@functional-examples/devkit';
2
+ import { ConsumptionTracker } from './consumption-tracker.js';
3
+ import { templateHelpers } from './helpers.js';
4
+ /** Helpers injected into prose Eta templates as top-level variables. */
5
+ export interface ProseHelpers {
6
+ /** Render a file as a fenced code block and mark it consumed. */
7
+ file: (relativePath: string) => string;
8
+ /** Render a region/hunk as a fenced code block and mark its file consumed. */
9
+ region: (regionId: string) => string;
10
+ /** All example files (read-only). */
11
+ files: ExampleFile[];
12
+ /** Standard template helpers (langFromPath, slugify, etc.). */
13
+ helpers: typeof templateHelpers;
14
+ }
15
+ /**
16
+ * Build a fenced code block string for a given language and content.
17
+ */
18
+ export declare function fencedBlock(lang: string, content: string): string;
19
+ /**
20
+ * Create prose-template helpers bound to a specific set of files and tracker.
21
+ *
22
+ * These helpers are injected into Eta prose templates via `functionHeader`
23
+ * so authors can write `<%= file('utils.ts') %>` instead of `<%= it.file('utils.ts') %>`.
24
+ */
25
+ export declare function createProseHelpers(files: ExampleFile[], tracker: ConsumptionTracker): ProseHelpers;
26
+ //# sourceMappingURL=prose-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prose-helpers.d.ts","sourceRoot":"","sources":["../../src/templates/prose-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,IAAI,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,8EAA8E;IAC9E,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,qCAAqC;IACrC,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,+DAA+D;IAC/D,OAAO,EAAE,OAAO,eAAe,CAAC;CACjC;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,EAAE,kBAAkB,GAC1B,YAAY,CA8Bd"}
@@ -0,0 +1,39 @@
1
+ import { templateHelpers } from './helpers.js';
2
+ /**
3
+ * Build a fenced code block string for a given language and content.
4
+ */
5
+ export function fencedBlock(lang, content) {
6
+ return `\`\`\`${lang}\n${content}\n\`\`\``;
7
+ }
8
+ /**
9
+ * Create prose-template helpers bound to a specific set of files and tracker.
10
+ *
11
+ * These helpers are injected into Eta prose templates via `functionHeader`
12
+ * so authors can write `<%= file('utils.ts') %>` instead of `<%= it.file('utils.ts') %>`.
13
+ */
14
+ export function createProseHelpers(files, tracker) {
15
+ return {
16
+ file(relativePath) {
17
+ const found = files.find((f) => f.relativePath === relativePath);
18
+ if (!found) {
19
+ throw new Error(`Prose helper file(): no file found with relativePath "${relativePath}"`);
20
+ }
21
+ tracker.consume(relativePath);
22
+ const lang = templateHelpers.langFromPath(relativePath);
23
+ const content = found.parsed ?? found.raw ?? '';
24
+ return fencedBlock(lang, content);
25
+ },
26
+ region(regionId) {
27
+ const match = templateHelpers.region(files, regionId);
28
+ if (!match) {
29
+ throw new Error(`Prose helper region(): no region found with id "${regionId}"`);
30
+ }
31
+ tracker.consume(match.file.relativePath);
32
+ const lang = templateHelpers.langFromPath(match.file.relativePath);
33
+ return fencedBlock(lang, match.content);
34
+ },
35
+ files,
36
+ helpers: templateHelpers,
37
+ };
38
+ }
39
+ //# sourceMappingURL=prose-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prose-helpers.js","sourceRoot":"","sources":["../../src/templates/prose-helpers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAc/C;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAe;IACvD,OAAO,SAAS,IAAI,KAAK,OAAO,UAAU,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAoB,EACpB,OAA2B;IAE3B,OAAO;QACL,IAAI,CAAC,YAAoB;YACvB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC;YACjE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CACb,yDAAyD,YAAY,GAAG,CACzE,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAG,eAAe,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;YACxD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC;YAChD,OAAO,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;QAED,MAAM,CAAC,QAAgB;YACrB,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACtD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CACb,mDAAmD,QAAQ,GAAG,CAC/D,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzC,MAAM,IAAI,GAAG,eAAe,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACnE,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;QAED,KAAK;QACL,OAAO,EAAE,eAAe;KACzB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Options for the documentation plugin factory.
3
+ */
4
+ export interface DocumentationPluginOptions {
5
+ /** Output directory for generated docs (default: './docs') */
6
+ outputDir?: string;
7
+ /** Output format: 'markdown' or 'mdoc' (default: 'markdown') */
8
+ format?: 'markdown' | 'mdoc';
9
+ /** Path to custom Eta template file */
10
+ template?: string;
11
+ /** Enable the markdown frontmatter extractor (default: false) */
12
+ enableExtractor?: boolean;
13
+ }
14
+ /**
15
+ * Resolved options with defaults applied.
16
+ */
17
+ export interface ResolvedDocumentationPluginOptions {
18
+ outputDir: string;
19
+ format: 'markdown' | 'mdoc';
20
+ template?: string;
21
+ enableExtractor: boolean;
22
+ }
23
+ /**
24
+ * Documentation-specific metadata that examples can declare.
25
+ */
26
+ export interface DocsMetadata {
27
+ docs?: {
28
+ /** Per-example template override (path to .eta file) */
29
+ template?: string;
30
+ /** Skip this example during doc generation */
31
+ skip?: boolean;
32
+ /** Custom output filename (without extension) */
33
+ outputName?: string;
34
+ };
35
+ }
36
+ /**
37
+ * Default option values.
38
+ */
39
+ export declare const DEFAULTS: {
40
+ outputDir: string;
41
+ format: "markdown";
42
+ enableExtractor: false;
43
+ };
44
+ /**
45
+ * Resolve user options with defaults.
46
+ */
47
+ export declare function resolveOptions(options?: DocumentationPluginOptions): ResolvedDocumentationPluginOptions;
48
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAC7B,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,kCAAkC;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE;QACL,wDAAwD;QACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,8CAA8C;QAC9C,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,iDAAiD;QACjD,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,eAAO,MAAM,QAAQ;;;;CAMpB,CAAC;AAEF;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,GAAE,0BAA+B,GACvC,kCAAkC,CAOpC"}
package/dist/types.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Default option values.
3
+ */
4
+ export const DEFAULTS = {
5
+ outputDir: './docs',
6
+ format: 'markdown',
7
+ enableExtractor: false,
8
+ };
9
+ /**
10
+ * Resolve user options with defaults.
11
+ */
12
+ export function resolveOptions(options = {}) {
13
+ return {
14
+ outputDir: options.outputDir ?? DEFAULTS.outputDir,
15
+ format: options.format ?? DEFAULTS.format,
16
+ template: options.template,
17
+ enableExtractor: options.enableExtractor ?? DEFAULTS.enableExtractor,
18
+ };
19
+ }
20
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAsCA;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,UAAmB;IAC3B,eAAe,EAAE,KAAK;CAGvB,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAsC,EAAE;IAExC,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS;QAClD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;QACzC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,QAAQ,CAAC,eAAe;KACrE,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@functional-examples/documentation",
3
+ "version": "0.0.0-alpha.1",
4
+ "type": "module",
5
+ "description": "Documentation generation plugin for functional-examples",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "templates"
21
+ ],
22
+ "peerDependencies": {
23
+ "functional-examples": "0.0.0-alpha.1"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "yaml": {
27
+ "optional": true
28
+ }
29
+ },
30
+ "dependencies": {
31
+ "cli-forge": "1.2.0",
32
+ "eta": "^3.5.0",
33
+ "zod": "4.3.6",
34
+ "@functional-examples/devkit": "0.0.0-alpha.1"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "22.19.8",
38
+ "typescript": "^5.7.2",
39
+ "vitest": "^1.3.1",
40
+ "yaml": "^2.7.1",
41
+ "@functional-examples/devkit": "0.0.0-alpha.1",
42
+ "functional-examples": "0.0.0-alpha.1"
43
+ },
44
+ "license": "MIT",
45
+ "author": {
46
+ "name": "Craigory Coppola",
47
+ "url": "https://craigory.dev"
48
+ },
49
+ "homepage": "https://craigory.dev/functional-examples",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/AgentEnder/functional-examples.git"
53
+ },
54
+ "bugs": {
55
+ "url": "https://github.com/AgentEnder/functional-examples/issues"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc -p tsconfig.lib.json",
59
+ "test": "vitest run",
60
+ "test:watch": "vitest"
61
+ }
62
+ }
@@ -0,0 +1,40 @@
1
+ ---
2
+ generated: true
3
+ ---
4
+
5
+ # <%= it.title %>
6
+
7
+ <% if (it.description) { -%>
8
+ <%= it.description %>
9
+
10
+ <% } -%>
11
+ <% /* ── Rendered prose (README, docs, etc. with file/region helpers expanded) ── */ -%>
12
+ <% it.renderedProse.forEach(function(content) { -%>
13
+ <%= content %>
14
+
15
+ <% }); -%>
16
+ <% /* ── Unconsumed code files ── */ -%>
17
+ <% it.unconsumedFiles.forEach(function(file) { -%>
18
+ ## `<%= file.relativePath %>`
19
+
20
+ <% if (file.hunks && file.hunks.length > 0) { -%>
21
+ <% file.hunks.forEach(function(hunk) { -%>
22
+ ### Region: `<%= hunk.id %>`
23
+
24
+ <% var desc = it.helpers.hunkDescription(it.metadata, hunk.id); -%>
25
+ <% if (desc) { -%>
26
+ <%= desc %>
27
+
28
+ <% } -%>
29
+ ```<%= it.helpers.langFromPath(file.relativePath) %>
30
+ <%= hunk.content %>
31
+ ```
32
+
33
+ <% }); -%>
34
+ <% } else { -%>
35
+ ```<%= it.helpers.langFromPath(file.relativePath) %>
36
+ <%= file.parsed || file.raw || '' %>
37
+ ```
38
+
39
+ <% } -%>
40
+ <% }); -%>
@@ -0,0 +1,40 @@
1
+ ---
2
+ generated: true
3
+ ---
4
+
5
+ # <%= it.title %> {% #<%= it.helpers.slugify(it.title) %> %}
6
+
7
+ <% if (it.description) { -%>
8
+ <%= it.description %>
9
+
10
+ <% } -%>
11
+ <% /* ── Rendered prose (README, docs, etc. with file/region helpers expanded) ── */ -%>
12
+ <% it.renderedProse.forEach(function(content) { -%>
13
+ <%= content %>
14
+
15
+ <% }); -%>
16
+ <% /* ── Unconsumed code files ── */ -%>
17
+ <% it.unconsumedFiles.forEach(function(file) { -%>
18
+ ## `<%= file.relativePath %>` {% #<%= it.helpers.slugify(file.relativePath) %> %}
19
+
20
+ <% if (file.hunks && file.hunks.length > 0) { -%>
21
+ <% file.hunks.forEach(function(hunk) { -%>
22
+ ### Region: `<%= hunk.id %>` {% #region-<%= it.helpers.slugify(hunk.id) %> %}
23
+
24
+ <% var desc = it.helpers.hunkDescription(it.metadata, hunk.id); -%>
25
+ <% if (desc) { -%>
26
+ <%= desc %>
27
+
28
+ <% } -%>
29
+ ```<%= it.helpers.langFromPath(file.relativePath) %> {% process=true %}
30
+ <%= hunk.content %>
31
+ ```
32
+
33
+ <% }); -%>
34
+ <% } else { -%>
35
+ ```<%= it.helpers.langFromPath(file.relativePath) %> {% process=true %}
36
+ <%= file.parsed || file.raw || '' %>
37
+ ```
38
+
39
+ <% } -%>
40
+ <% }); -%>
@@ -0,0 +1,9 @@
1
+ ---
2
+ generated: true
3
+ ---
4
+
5
+ # Examples
6
+
7
+ <% it.examples.forEach(function(example) { -%>
8
+ - [<%= example.title %>](./<%= example.id %>.md)<% if (example.description) { %> — <%= example.description %><% } %>
9
+ <% }); -%>
@@ -0,0 +1,9 @@
1
+ ---
2
+ generated: true
3
+ ---
4
+
5
+ # Examples {% #examples %}
6
+
7
+ <% it.examples.forEach(function(example) { -%>
8
+ - [<%= example.title %>](./<%= example.id %>.mdoc)<% if (example.description) { %> — <%= example.description %><% } %>
9
+ <% }); -%>