@unrdf/kgn 5.0.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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/package.json +90 -0
  4. package/src/MIGRATION_COMPLETE.md +186 -0
  5. package/src/PORT-MAP.md +302 -0
  6. package/src/base/filter-templates.js +479 -0
  7. package/src/base/index.js +92 -0
  8. package/src/base/injection-targets.js +583 -0
  9. package/src/base/macro-templates.js +298 -0
  10. package/src/base/macro-templates.js.bak +461 -0
  11. package/src/base/shacl-templates.js +617 -0
  12. package/src/base/template-base.js +388 -0
  13. package/src/core/attestor.js +381 -0
  14. package/src/core/filters.js +518 -0
  15. package/src/core/index.js +21 -0
  16. package/src/core/kgen-engine.js +372 -0
  17. package/src/core/parser.js +447 -0
  18. package/src/core/post-processor.js +313 -0
  19. package/src/core/renderer.js +469 -0
  20. package/src/doc-generator/cli.mjs +122 -0
  21. package/src/doc-generator/index.mjs +28 -0
  22. package/src/doc-generator/mdx-generator.mjs +71 -0
  23. package/src/doc-generator/nav-generator.mjs +136 -0
  24. package/src/doc-generator/parser.mjs +291 -0
  25. package/src/doc-generator/rdf-builder.mjs +306 -0
  26. package/src/doc-generator/scanner.mjs +189 -0
  27. package/src/engine/index.js +42 -0
  28. package/src/engine/pipeline.js +448 -0
  29. package/src/engine/renderer.js +604 -0
  30. package/src/engine/template-engine.js +566 -0
  31. package/src/filters/array.js +436 -0
  32. package/src/filters/data.js +479 -0
  33. package/src/filters/index.js +270 -0
  34. package/src/filters/rdf.js +264 -0
  35. package/src/filters/text.js +369 -0
  36. package/src/index.js +109 -0
  37. package/src/inheritance/index.js +40 -0
  38. package/src/injection/api.js +260 -0
  39. package/src/injection/atomic-writer.js +327 -0
  40. package/src/injection/constants.js +136 -0
  41. package/src/injection/idempotency-manager.js +295 -0
  42. package/src/injection/index.js +28 -0
  43. package/src/injection/injection-engine.js +378 -0
  44. package/src/injection/integration.js +339 -0
  45. package/src/injection/modes/index.js +341 -0
  46. package/src/injection/rollback-manager.js +373 -0
  47. package/src/injection/target-resolver.js +323 -0
  48. package/src/injection/tests/atomic-writer.test.js +382 -0
  49. package/src/injection/tests/injection-engine.test.js +611 -0
  50. package/src/injection/tests/integration.test.js +392 -0
  51. package/src/injection/tests/run-tests.js +283 -0
  52. package/src/injection/validation-engine.js +547 -0
  53. package/src/linter/determinism-linter.js +473 -0
  54. package/src/linter/determinism.js +410 -0
  55. package/src/linter/index.js +6 -0
  56. package/src/linter/test-doubles.js +475 -0
  57. package/src/parser/frontmatter.js +228 -0
  58. package/src/parser/variables.js +344 -0
  59. package/src/renderer/deterministic.js +245 -0
  60. package/src/renderer/index.js +6 -0
  61. package/src/templates/latex/academic-paper.njk +186 -0
  62. package/src/templates/latex/index.js +104 -0
  63. package/src/templates/nextjs/app-page.njk +66 -0
  64. package/src/templates/nextjs/index.js +80 -0
  65. package/src/templates/office/docx/document.njk +368 -0
  66. package/src/templates/office/index.js +79 -0
  67. package/src/templates/office/word-report.njk +129 -0
  68. package/src/utils/template-utils.js +426 -0
@@ -0,0 +1,469 @@
1
+ /**
2
+ * KGEN Template Renderer - Execute template logic without nunjucks
3
+ *
4
+ * Handles:
5
+ * - Variable interpolation: {{ variable }}
6
+ * - Filter application: {{ variable | filter }}
7
+ * - Conditional rendering: {% if %}...{% endif %}
8
+ * - Loop rendering: {% for %}...{% endfor %}
9
+ * - Include processing: {% include %}
10
+ */
11
+
12
+ export class KGenRenderer {
13
+ constructor(options = {}) {
14
+ this.options = {
15
+ maxDepth: options.maxDepth || 10,
16
+ enableIncludes: options.enableIncludes !== false,
17
+ strictMode: options.strictMode !== false,
18
+ deterministicMode: options.deterministicMode !== false,
19
+ ...options
20
+ };
21
+
22
+ // Rendering patterns
23
+ this.patterns = {
24
+ variable: /\{\{\s*([^}]+)\s*\}\}/g,
25
+ expression: /\{\%\s*([^%]+)\s*\%\}/g,
26
+ comment: /\{#\s*([^#]+)\s*#\}/g
27
+ };
28
+
29
+ this.renderDepth = 0;
30
+ }
31
+
32
+ /**
33
+ * Render template with context
34
+ */
35
+ async render(template, context, options = {}) {
36
+ this.renderDepth = 0;
37
+
38
+ const { filters } = options;
39
+
40
+ if (!filters) {
41
+ throw new Error('Filters instance required for rendering');
42
+ }
43
+
44
+ try {
45
+ const result = await this.processTemplate(template, context, filters);
46
+
47
+ return {
48
+ content: result,
49
+ metadata: {
50
+ renderTime: this.options.deterministicMode ? '2024-01-01T00:00:00.000Z' : new Date().toISOString(),
51
+ maxDepthReached: this.renderDepth,
52
+ deterministicMode: this.options.deterministicMode
53
+ }
54
+ };
55
+ } catch (error) {
56
+ throw new Error(`Rendering failed: ${error.message}`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Process template content recursively
62
+ */
63
+ async processTemplate(template, context, filters, depth = 0) {
64
+ if (depth > this.options.maxDepth) {
65
+ throw new Error(`Maximum rendering depth ${this.options.maxDepth} exceeded`);
66
+ }
67
+
68
+ this.renderDepth = Math.max(this.renderDepth, depth);
69
+
70
+ let processed = template;
71
+
72
+ // Remove comments first
73
+ processed = this.removeComments(processed);
74
+
75
+ // Process expressions (conditionals, loops, etc.)
76
+ processed = await this.processExpressions(processed, context, filters, depth);
77
+
78
+ // Process variables and filters
79
+ processed = this.processVariables(processed, context, filters);
80
+
81
+ return processed;
82
+ }
83
+
84
+ /**
85
+ * Remove template comments
86
+ */
87
+ removeComments(template) {
88
+ return template.replace(this.patterns.comment, '');
89
+ }
90
+
91
+ /**
92
+ * Process template expressions (if, for, etc.)
93
+ */
94
+ async processExpressions(template, context, filters, depth) {
95
+ let processed = template;
96
+
97
+ // Process conditionals
98
+ processed = await this.processConditionals(processed, context, filters, depth);
99
+
100
+ // Process loops
101
+ processed = await this.processLoops(processed, context, filters, depth);
102
+
103
+ // Process includes
104
+ if (this.options.enableIncludes) {
105
+ processed = await this.processIncludes(processed, context, filters, depth);
106
+ }
107
+
108
+ return processed;
109
+ }
110
+
111
+ /**
112
+ * Process conditional expressions
113
+ */
114
+ async processConditionals(template, context, filters, depth) {
115
+ const ifPattern = /\{\%\s*if\s+([^%]+)\s*\%\}([\s\S]*?)(\{\%\s*else\s*\%\}([\s\S]*?))?\{\%\s*endif\s*\%\}/g;
116
+
117
+ let match;
118
+ let processed = template;
119
+
120
+ // Process from end to start to avoid position shifts
121
+ const matches = [];
122
+ while ((match = ifPattern.exec(template)) !== null) {
123
+ matches.push(match);
124
+ }
125
+
126
+ for (let i = matches.length - 1; i >= 0; i--) {
127
+ match = matches[i];
128
+ const [fullMatch, condition, ifContent, elseBlock, elseContent] = match;
129
+
130
+ try {
131
+ const conditionResult = this.evaluateCondition(condition.trim(), context);
132
+ let replacement;
133
+
134
+ if (conditionResult) {
135
+ replacement = await this.processTemplate(ifContent, context, filters, depth + 1);
136
+ } else if (elseContent !== undefined) {
137
+ replacement = await this.processTemplate(elseContent, context, filters, depth + 1);
138
+ } else {
139
+ replacement = '';
140
+ }
141
+
142
+ processed = processed.substring(0, match.index) + replacement + processed.substring(match.index + fullMatch.length);
143
+ } catch (error) {
144
+ if (this.options.strictMode) {
145
+ throw new Error(`Conditional evaluation failed: ${error.message}`);
146
+ }
147
+ // Replace with empty string in non-strict mode
148
+ const replacement = '';
149
+ processed = processed.substring(0, match.index) + replacement + processed.substring(match.index + fullMatch.length);
150
+ }
151
+ }
152
+
153
+ return processed;
154
+ }
155
+
156
+ /**
157
+ * Process loop expressions
158
+ */
159
+ async processLoops(template, context, filters, depth) {
160
+ const forPattern = /\{\%\s*for\s+(\w+)\s+in\s+([^%]+)\s*\%\}([\s\S]*?)\{\%\s*endfor\s*\%\}/g;
161
+
162
+ let match;
163
+ let processed = template;
164
+
165
+ // Process from end to start to avoid position shifts
166
+ const matches = [];
167
+ while ((match = forPattern.exec(template)) !== null) {
168
+ matches.push(match);
169
+ }
170
+
171
+ for (let i = matches.length - 1; i >= 0; i--) {
172
+ match = matches[i];
173
+ const [fullMatch, itemVar, arrayExpr, loopContent] = match;
174
+
175
+ try {
176
+ const array = this.evaluateExpression(arrayExpr.trim(), context);
177
+ let replacement = '';
178
+
179
+ if (Array.isArray(array)) {
180
+ for (let index = 0; index < array.length; index++) {
181
+ const item = array[index];
182
+
183
+ // Create loop context
184
+ const loopContext = {
185
+ ...context,
186
+ [itemVar]: item,
187
+ loop: {
188
+ index: index,
189
+ index0: index,
190
+ index1: index + 1,
191
+ first: index === 0,
192
+ last: index === array.length - 1,
193
+ length: array.length,
194
+ revindex: array.length - index,
195
+ revindex0: array.length - index - 1
196
+ }
197
+ };
198
+
199
+ const loopResult = await this.processTemplate(loopContent, loopContext, filters, depth + 1);
200
+ replacement += loopResult;
201
+ }
202
+ } else if (array) {
203
+ // Handle single item as array of one
204
+ const loopContext = {
205
+ ...context,
206
+ [itemVar]: array,
207
+ loop: {
208
+ index: 0,
209
+ index0: 0,
210
+ index1: 1,
211
+ first: true,
212
+ last: true,
213
+ length: 1,
214
+ revindex: 1,
215
+ revindex0: 0
216
+ }
217
+ };
218
+
219
+ replacement = await this.processTemplate(loopContent, loopContext, filters, depth + 1);
220
+ }
221
+
222
+ processed = processed.substring(0, match.index) + replacement + processed.substring(match.index + fullMatch.length);
223
+ } catch (error) {
224
+ if (this.options.strictMode) {
225
+ throw new Error(`Loop processing failed: ${error.message}`);
226
+ }
227
+ // Replace with empty string in non-strict mode
228
+ const replacement = '';
229
+ processed = processed.substring(0, match.index) + replacement + processed.substring(match.index + fullMatch.length);
230
+ }
231
+ }
232
+
233
+ return processed;
234
+ }
235
+
236
+ /**
237
+ * Process include expressions
238
+ */
239
+ async processIncludes(template, context, filters, depth) {
240
+ const includePattern = /\{\%\s*include\s+['"]([^'"]+)['"]\s*\%\}/g;
241
+
242
+ let match;
243
+ let processed = template;
244
+
245
+ // Process includes (simplified - no actual file loading for security)
246
+ while ((match = includePattern.exec(processed)) !== null) {
247
+ const [fullMatch, includePath] = match;
248
+
249
+ if (this.options.strictMode) {
250
+ throw new Error(`Include processing not implemented: ${includePath}`);
251
+ }
252
+
253
+ // In non-strict mode, replace with comment
254
+ const replacement = `<!-- Include: ${includePath} -->`;
255
+ processed = processed.replace(fullMatch, replacement);
256
+ }
257
+
258
+ return processed;
259
+ }
260
+
261
+ /**
262
+ * Process variable interpolations and filters
263
+ */
264
+ processVariables(template, context, filters) {
265
+ return template.replace(this.patterns.variable, (match, expression) => {
266
+ try {
267
+ const trimmed = expression.trim();
268
+
269
+ // Check for filters: {{ variable | filter1 | filter2 }}
270
+ const parts = trimmed.split('|').map(p => p.trim());
271
+ let value = this.evaluateExpression(parts[0], context);
272
+
273
+ // Apply filters in sequence
274
+ for (let i = 1; i < parts.length; i++) {
275
+ const filterExpr = parts[i].trim();
276
+
277
+ // Parse filter with parentheses: filter(arg1, arg2) or filter arg1 arg2
278
+ let filterName, filterArgs = [];
279
+
280
+ if (filterExpr.includes('(')) {
281
+ // Handle filter(arg1, arg2) syntax
282
+ const match = filterExpr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$/);
283
+ if (match) {
284
+ filterName = match[1];
285
+ const argsStr = match[2].trim();
286
+ if (argsStr) {
287
+ // Split arguments by comma, respecting quotes
288
+ filterArgs = this.parseFilterArguments(argsStr);
289
+ }
290
+ } else {
291
+ throw new Error(`Invalid filter syntax: ${filterExpr}`);
292
+ }
293
+ } else {
294
+ // Handle filter arg1 arg2 syntax
295
+ const parts = filterExpr.split(/\s+/);
296
+ filterName = parts[0];
297
+ filterArgs = parts.slice(1);
298
+ }
299
+
300
+ // Parse filter arguments
301
+ const parsedArgs = filterArgs.map(arg => this.parseArgument(arg, context));
302
+
303
+ value = filters.apply(filterName, value, ...parsedArgs);
304
+ }
305
+
306
+ return String(value !== null && value !== undefined ? value : '');
307
+ } catch (error) {
308
+ if (this.options.strictMode) {
309
+ throw new Error(`Variable processing failed: ${error.message}`);
310
+ }
311
+ return match; // Return original expression on error
312
+ }
313
+ });
314
+ }
315
+
316
+ /**
317
+ * Evaluate condition expression
318
+ */
319
+ evaluateCondition(condition, context) {
320
+ // Simple condition evaluation
321
+ // Supports: variable, variable == value, variable != value, !variable
322
+
323
+ if (condition.includes('==')) {
324
+ const [left, right] = condition.split('==').map(s => s.trim());
325
+ return this.evaluateExpression(left, context) == this.parseValue(right, context);
326
+ }
327
+
328
+ if (condition.includes('!=')) {
329
+ const [left, right] = condition.split('!=').map(s => s.trim());
330
+ return this.evaluateExpression(left, context) != this.parseValue(right, context);
331
+ }
332
+
333
+ if (condition.startsWith('!')) {
334
+ const expr = condition.substring(1).trim();
335
+ return !this.isTruthy(this.evaluateExpression(expr, context));
336
+ }
337
+
338
+ // Simple truthiness check
339
+ return this.isTruthy(this.evaluateExpression(condition, context));
340
+ }
341
+
342
+ /**
343
+ * Evaluate expression to get value from context
344
+ */
345
+ evaluateExpression(expr, context) {
346
+ if (!expr) return '';
347
+
348
+ // Handle literals
349
+ if (expr.startsWith('"') && expr.endsWith('"')) {
350
+ return expr.slice(1, -1);
351
+ }
352
+ if (expr.startsWith("'") && expr.endsWith("'")) {
353
+ return expr.slice(1, -1);
354
+ }
355
+
356
+ // Handle numbers
357
+ if (/^-?\d+(\.\d+)?$/.test(expr)) {
358
+ return parseFloat(expr);
359
+ }
360
+
361
+ // Handle booleans
362
+ if (expr === 'true') return true;
363
+ if (expr === 'false') return false;
364
+ if (expr === 'null') return null;
365
+
366
+ // Handle object property access
367
+ const parts = expr.split('.');
368
+ let value = context;
369
+
370
+ for (const part of parts) {
371
+ if (value === null || value === undefined) return '';
372
+ value = value[part];
373
+ }
374
+
375
+ return value !== undefined ? value : '';
376
+ }
377
+
378
+ /**
379
+ * Parse filter arguments from a comma-separated string, respecting quotes
380
+ */
381
+ parseFilterArguments(argsStr) {
382
+ const args = [];
383
+ let current = '';
384
+ let inQuotes = false;
385
+ let quoteChar = null;
386
+
387
+ for (let i = 0; i < argsStr.length; i++) {
388
+ const char = argsStr[i];
389
+
390
+ if ((char === '"' || char === "'") && !inQuotes) {
391
+ inQuotes = true;
392
+ quoteChar = char;
393
+ current += char;
394
+ } else if (char === quoteChar && inQuotes) {
395
+ inQuotes = false;
396
+ quoteChar = null;
397
+ current += char;
398
+ } else if (char === ',' && !inQuotes) {
399
+ args.push(current.trim());
400
+ current = '';
401
+ } else {
402
+ current += char;
403
+ }
404
+ }
405
+
406
+ if (current.trim()) {
407
+ args.push(current.trim());
408
+ }
409
+
410
+ return args;
411
+ }
412
+
413
+ /**
414
+ * Parse argument value (string, number, or variable reference)
415
+ */
416
+ parseArgument(arg, context) {
417
+ // Handle quoted strings
418
+ if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) {
419
+ return arg.slice(1, -1);
420
+ }
421
+
422
+ // Handle numbers
423
+ if (/^-?\d+(\.\d+)?$/.test(arg)) {
424
+ return parseFloat(arg);
425
+ }
426
+
427
+ // Handle booleans
428
+ if (arg === 'true') return true;
429
+ if (arg === 'false') return false;
430
+ if (arg === 'null') return null;
431
+
432
+ // Handle variable reference
433
+ return this.evaluateExpression(arg, context);
434
+ }
435
+
436
+ /**
437
+ * Parse value with context substitution
438
+ */
439
+ parseValue(value, context) {
440
+ return this.parseArgument(value, context);
441
+ }
442
+
443
+ /**
444
+ * Check if value is truthy in template context
445
+ */
446
+ isTruthy(value) {
447
+ if (value === null || value === undefined) return false;
448
+ if (value === '') return false;
449
+ if (value === 0) return false;
450
+ if (value === false) return false;
451
+ if (Array.isArray(value) && value.length === 0) return false;
452
+ if (typeof value === 'object' && Object.keys(value).length === 0) return false;
453
+
454
+ return true;
455
+ }
456
+
457
+ /**
458
+ * Get renderer statistics
459
+ */
460
+ getStats() {
461
+ return {
462
+ ...this.options,
463
+ maxDepthReached: this.renderDepth,
464
+ supportedExpressions: ['if/else/endif', 'for/endfor', 'include', 'variables', 'filters']
465
+ };
466
+ }
467
+ }
468
+
469
+ export default KGenRenderer;
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview CLI tool for documentation generation
4
+ * Usage: node cli.mjs [options]
5
+ */
6
+
7
+ import { parseFiles } from './parser.mjs';
8
+ import { generateModulesMDX } from './mdx-generator.mjs';
9
+ import { scanWorkspace, groupFilesByModule } from './scanner.mjs';
10
+ import { generateNavigation, generatePackageIndex, generateAPIIndex } from './nav-generator.mjs';
11
+ import { mkdir, writeFile } from 'fs/promises';
12
+ import { join, dirname } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+
18
+ /**
19
+ * Main CLI function
20
+ */
21
+ async function main() {
22
+ console.log('📚 @unrdf/kgn Documentation Generator\n');
23
+
24
+ // Default options
25
+ const rootDir = join(__dirname, '../../../..');
26
+ const outputDir = join(rootDir, 'packages/nextra/pages/api');
27
+
28
+ console.log(`Root: ${rootDir}`);
29
+ console.log(`Output: ${outputDir}\n`);
30
+
31
+ try {
32
+ // Phase 1: Scan workspace
33
+ console.log('🔍 Phase 1: Scanning workspace...');
34
+ const packages = await scanWorkspace(rootDir);
35
+ console.log(` Found ${packages.length} packages\n`);
36
+
37
+ if (packages.length === 0) {
38
+ console.log('⚠️ No packages found with source files');
39
+ return;
40
+ }
41
+
42
+ // Group files by module
43
+ const groupedFiles = groupFilesByModule(packages);
44
+
45
+ // Phase 2: Parse all files
46
+ console.log('📖 Phase 2: Parsing JSDoc...');
47
+ let totalFiles = 0;
48
+ const parsedByPackage = {};
49
+
50
+ for (const [pkgName, pkgData] of Object.entries(groupedFiles)) {
51
+ console.log(` Parsing @unrdf/${pkgName}...`);
52
+ const parsed = parseFiles(pkgData.sourceFiles, rootDir);
53
+ parsedByPackage[pkgName] = parsed;
54
+ totalFiles += parsed.length;
55
+ }
56
+ console.log(` Parsed ${totalFiles} files\n`);
57
+
58
+ // Phase 3: Generate MDX
59
+ console.log('📝 Phase 3: Generating MDX...');
60
+ let generatedCount = 0;
61
+
62
+ for (const [pkgName, parsed] of Object.entries(parsedByPackage)) {
63
+ const mdxMap = generateModulesMDX(parsed);
64
+
65
+ // Write MDX files
66
+ for (const [relativePath, mdx] of mdxMap) {
67
+ const fileName = relativePath.split('/').pop().replace(/\.(m?js)$/, '.mdx');
68
+ const pkgOutputDir = join(outputDir, pkgName);
69
+
70
+ // Create directory if needed
71
+ await mkdir(pkgOutputDir, { recursive: true });
72
+
73
+ // Write MDX file
74
+ const mdxPath = join(pkgOutputDir, fileName);
75
+ await writeFile(mdxPath, mdx, 'utf-8');
76
+
77
+ generatedCount++;
78
+ }
79
+
80
+ console.log(` Generated ${mdxMap.size} files for @unrdf/${pkgName}`);
81
+ }
82
+ console.log(` Total: ${generatedCount} MDX files\n`);
83
+
84
+ // Phase 4: Generate navigation
85
+ console.log('🗂️ Phase 4: Generating navigation...');
86
+
87
+ // Generate _meta.json
88
+ const navigation = generateNavigation(groupedFiles);
89
+ const metaPath = join(outputDir, '_meta.json');
90
+ await writeFile(metaPath, JSON.stringify(navigation, null, 2), 'utf-8');
91
+ console.log(` Created _meta.json`);
92
+
93
+ // Generate index pages
94
+ const apiIndex = generateAPIIndex(groupedFiles);
95
+ const apiIndexPath = join(outputDir, 'index.mdx');
96
+ await writeFile(apiIndexPath, apiIndex, 'utf-8');
97
+ console.log(` Created API index`);
98
+
99
+ for (const [pkgName, pkgData] of Object.entries(groupedFiles)) {
100
+ const pkgIndex = generatePackageIndex(pkgName, pkgData);
101
+ const pkgIndexPath = join(outputDir, pkgName, 'index.mdx');
102
+ await writeFile(pkgIndexPath, pkgIndex, 'utf-8');
103
+ console.log(` Created @unrdf/${pkgName} index`);
104
+ }
105
+
106
+ console.log('\n✅ Documentation generation complete!');
107
+ console.log(`\n📊 Summary:`);
108
+ console.log(` Packages: ${packages.length}`);
109
+ console.log(` Source files: ${totalFiles}`);
110
+ console.log(` MDX files: ${generatedCount}`);
111
+ console.log(` Output: ${outputDir}`);
112
+
113
+ } catch (error) {
114
+ console.error('\n❌ Documentation generation failed:');
115
+ console.error(error.message);
116
+ console.error(error.stack);
117
+ process.exit(1);
118
+ }
119
+ }
120
+
121
+ // Run CLI
122
+ main();
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @fileoverview Main orchestrator for JSDoc→RDF→MDX documentation generator
3
+ */
4
+
5
+ export { parseFile, parseFiles } from './parser.mjs';
6
+ export { buildRDFGraph, buildRDFGraphs, buildJSONLD, PREFIXES } from './rdf-builder.mjs';
7
+ export { generateModuleMDX, generateModulesMDX } from './mdx-generator.mjs';
8
+
9
+ /**
10
+ * Generate documentation for a single file
11
+ * @param {string} filePath - Source file path
12
+ * @param {Object} options - Generation options
13
+ * @returns {Object} Generated documentation
14
+ */
15
+ export async function generateDocs(filePath, options = {}) {
16
+ const { parseFile } = await import('./parser.mjs');
17
+ const { buildRDFGraph, buildJSONLD } = await import('./rdf-builder.mjs');
18
+ const { generateModuleMDX } = await import('./mdx-generator.mjs');
19
+
20
+ const parsed = parseFile(filePath, options.rootDir);
21
+ const rdf = buildRDFGraph(parsed);
22
+ const jsonld = buildJSONLD(parsed);
23
+ const mdx = generateModuleMDX(parsed);
24
+
25
+ return { parsed, rdf, jsonld, mdx };
26
+ }
27
+
28
+ export default { generateDocs };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @fileoverview MDX Generator using @unrdf/kgn templates
3
+ * Converts parsed JSDoc + RDF data to MDX files for Nextra
4
+ */
5
+
6
+ import nunjucks from 'nunjucks';
7
+ import { dirname, join } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { readFileSync } from 'fs';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ /**
15
+ * Generate MDX from parsed module data
16
+ * @param {Object} parsedModule - Output from parser.parseFile()
17
+ * @param {string} templatePath - Path to Nunjucks template
18
+ * @returns {string} Generated MDX content
19
+ */
20
+ export function generateModuleMDX(parsedModule, templatePath = null) {
21
+ // Set up Nunjucks environment
22
+ const templatesDir = join(__dirname, '../../templates');
23
+ const env = new nunjucks.Environment(
24
+ new nunjucks.FileSystemLoader(templatesDir),
25
+ { autoescape: false, trimBlocks: true, lstripBlocks: true }
26
+ );
27
+
28
+ // Add custom filters
29
+ env.addFilter('map', (arr, attr) => arr.map(item => item[attr]));
30
+ env.addFilter('join', (arr, sep) => arr.join(sep));
31
+
32
+ // Default template
33
+ if (!templatePath) {
34
+ templatePath = 'api/module.njk';
35
+ }
36
+
37
+ // Prepare data for template
38
+ const templateData = {
39
+ module: {
40
+ name: parsedModule.relativePath.split('/').pop().replace(/\.(m?js|ts)$/, ''),
41
+ relativePath: parsedModule.relativePath,
42
+ file: parsedModule.file,
43
+ exports: parsedModule.exports,
44
+ imports: parsedModule.imports,
45
+ commentCount: parsedModule.comments,
46
+ }
47
+ };
48
+
49
+ // Render template
50
+ return env.render(templatePath, templateData);
51
+ }
52
+
53
+ /**
54
+ * Generate MDX for multiple modules
55
+ * @param {Array} parsedModules - Array of parsed module data
56
+ * @returns {Map} Map of file paths to MDX content
57
+ */
58
+ export function generateModulesMDX(parsedModules) {
59
+ const mdxMap = new Map();
60
+
61
+ parsedModules.forEach(module => {
62
+ if (module.error) return;
63
+
64
+ const mdx = generateModuleMDX(module);
65
+ mdxMap.set(module.relativePath, mdx);
66
+ });
67
+
68
+ return mdxMap;
69
+ }
70
+
71
+ export default { generateModuleMDX, generateModulesMDX };