@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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/package.json +90 -0
- package/src/MIGRATION_COMPLETE.md +186 -0
- package/src/PORT-MAP.md +302 -0
- package/src/base/filter-templates.js +479 -0
- package/src/base/index.js +92 -0
- package/src/base/injection-targets.js +583 -0
- package/src/base/macro-templates.js +298 -0
- package/src/base/macro-templates.js.bak +461 -0
- package/src/base/shacl-templates.js +617 -0
- package/src/base/template-base.js +388 -0
- package/src/core/attestor.js +381 -0
- package/src/core/filters.js +518 -0
- package/src/core/index.js +21 -0
- package/src/core/kgen-engine.js +372 -0
- package/src/core/parser.js +447 -0
- package/src/core/post-processor.js +313 -0
- package/src/core/renderer.js +469 -0
- package/src/doc-generator/cli.mjs +122 -0
- package/src/doc-generator/index.mjs +28 -0
- package/src/doc-generator/mdx-generator.mjs +71 -0
- package/src/doc-generator/nav-generator.mjs +136 -0
- package/src/doc-generator/parser.mjs +291 -0
- package/src/doc-generator/rdf-builder.mjs +306 -0
- package/src/doc-generator/scanner.mjs +189 -0
- package/src/engine/index.js +42 -0
- package/src/engine/pipeline.js +448 -0
- package/src/engine/renderer.js +604 -0
- package/src/engine/template-engine.js +566 -0
- package/src/filters/array.js +436 -0
- package/src/filters/data.js +479 -0
- package/src/filters/index.js +270 -0
- package/src/filters/rdf.js +264 -0
- package/src/filters/text.js +369 -0
- package/src/index.js +109 -0
- package/src/inheritance/index.js +40 -0
- package/src/injection/api.js +260 -0
- package/src/injection/atomic-writer.js +327 -0
- package/src/injection/constants.js +136 -0
- package/src/injection/idempotency-manager.js +295 -0
- package/src/injection/index.js +28 -0
- package/src/injection/injection-engine.js +378 -0
- package/src/injection/integration.js +339 -0
- package/src/injection/modes/index.js +341 -0
- package/src/injection/rollback-manager.js +373 -0
- package/src/injection/target-resolver.js +323 -0
- package/src/injection/tests/atomic-writer.test.js +382 -0
- package/src/injection/tests/injection-engine.test.js +611 -0
- package/src/injection/tests/integration.test.js +392 -0
- package/src/injection/tests/run-tests.js +283 -0
- package/src/injection/validation-engine.js +547 -0
- package/src/linter/determinism-linter.js +473 -0
- package/src/linter/determinism.js +410 -0
- package/src/linter/index.js +6 -0
- package/src/linter/test-doubles.js +475 -0
- package/src/parser/frontmatter.js +228 -0
- package/src/parser/variables.js +344 -0
- package/src/renderer/deterministic.js +245 -0
- package/src/renderer/index.js +6 -0
- package/src/templates/latex/academic-paper.njk +186 -0
- package/src/templates/latex/index.js +104 -0
- package/src/templates/nextjs/app-page.njk +66 -0
- package/src/templates/nextjs/index.js +80 -0
- package/src/templates/office/docx/document.njk +368 -0
- package/src/templates/office/index.js +79 -0
- package/src/templates/office/word-report.njk +129 -0
- package/src/utils/template-utils.js +426 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview RDF Graph Builder
|
|
3
|
+
* Converts parsed JSDoc data to RDF triples
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* RDF Vocabulary Prefixes
|
|
10
|
+
*/
|
|
11
|
+
export const PREFIXES = {
|
|
12
|
+
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
13
|
+
rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
|
|
14
|
+
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
|
15
|
+
code: 'http://unrdf.org/vocab/code#',
|
|
16
|
+
fs: 'http://unrdf.org/vocab/fs#',
|
|
17
|
+
doc: 'http://unrdf.org/vocab/doc#',
|
|
18
|
+
ex: 'http://example.org/',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate a deterministic URI for a resource
|
|
23
|
+
* @param {string} path - Resource path
|
|
24
|
+
* @returns {string} URI
|
|
25
|
+
*/
|
|
26
|
+
function generateURI(path) {
|
|
27
|
+
// Use package path as namespace
|
|
28
|
+
const namespace = 'http://unrdf.org/packages/';
|
|
29
|
+
// Create deterministic ID from path
|
|
30
|
+
const id = path.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-');
|
|
31
|
+
return namespace + id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate a blank node ID
|
|
36
|
+
* @param {string} name - Name for blank node
|
|
37
|
+
* @returns {string} Blank node ID
|
|
38
|
+
*/
|
|
39
|
+
function blankNode(name) {
|
|
40
|
+
const hash = crypto.createHash('md5').update(name).digest('hex').substring(0, 8);
|
|
41
|
+
return `_:${name}_${hash}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Escape string for Turtle literal
|
|
46
|
+
* @param {string} str - String to escape
|
|
47
|
+
* @returns {string} Escaped string
|
|
48
|
+
*/
|
|
49
|
+
function escapeLiteral(str) {
|
|
50
|
+
if (!str) return '""';
|
|
51
|
+
return `"${str
|
|
52
|
+
.replace(/\\/g, '\\\\')
|
|
53
|
+
.replace(/"/g, '\\"')
|
|
54
|
+
.replace(/\n/g, '\\n')
|
|
55
|
+
.replace(/\r/g, '\\r')
|
|
56
|
+
.replace(/\t/g, '\\t')}"`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build RDF graph from parsed file data
|
|
61
|
+
* @param {Object} parsedFile - Output from parser.parseFile()
|
|
62
|
+
* @returns {string} Turtle RDF representation
|
|
63
|
+
*/
|
|
64
|
+
export function buildRDFGraph(parsedFile) {
|
|
65
|
+
const triples = [];
|
|
66
|
+
|
|
67
|
+
// File resource
|
|
68
|
+
const fileURI = generateURI(parsedFile.relativePath);
|
|
69
|
+
|
|
70
|
+
triples.push(`# File: ${parsedFile.relativePath}`);
|
|
71
|
+
triples.push(`${fileURI}`);
|
|
72
|
+
triples.push(` a fs:File , code:Module ;`);
|
|
73
|
+
triples.push(` fs:path ${escapeLiteral(parsedFile.relativePath)} ;`);
|
|
74
|
+
triples.push(` fs:absolutePath ${escapeLiteral(parsedFile.file)} ;`);
|
|
75
|
+
triples.push(` code:commentCount ${parsedFile.comments} .`);
|
|
76
|
+
triples.push('');
|
|
77
|
+
|
|
78
|
+
// Exports
|
|
79
|
+
parsedFile.exports.forEach((exp, idx) => {
|
|
80
|
+
const exportURI = `${fileURI}#${exp.name}`;
|
|
81
|
+
|
|
82
|
+
triples.push(`# Export: ${exp.name}`);
|
|
83
|
+
triples.push(`${exportURI}`);
|
|
84
|
+
|
|
85
|
+
if (exp.type === 'function') {
|
|
86
|
+
triples.push(` a code:Function ;`);
|
|
87
|
+
triples.push(` code:name ${escapeLiteral(exp.name)} ;`);
|
|
88
|
+
|
|
89
|
+
if (exp.description) {
|
|
90
|
+
triples.push(` doc:description ${escapeLiteral(exp.description)} ;`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (exp.async) {
|
|
94
|
+
triples.push(` code:async true ;`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Parameters
|
|
98
|
+
exp.params?.forEach((param, paramIdx) => {
|
|
99
|
+
const paramNode = blankNode(`${exp.name}_param_${param.name}`);
|
|
100
|
+
triples.push(` code:param ${paramNode} ;`);
|
|
101
|
+
|
|
102
|
+
const paramTriples = [];
|
|
103
|
+
paramTriples.push(`${paramNode}`);
|
|
104
|
+
paramTriples.push(` a code:Parameter ;`);
|
|
105
|
+
paramTriples.push(` code:name ${escapeLiteral(param.name)} ;`);
|
|
106
|
+
paramTriples.push(` code:type ${escapeLiteral(param.type)} ;`);
|
|
107
|
+
|
|
108
|
+
if (param.optional) {
|
|
109
|
+
paramTriples.push(` code:optional true ;`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (param.description) {
|
|
113
|
+
paramTriples.push(` doc:description ${escapeLiteral(param.description)} ;`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (param.default !== undefined) {
|
|
117
|
+
paramTriples.push(` code:default ${escapeLiteral(String(param.default))} ;`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
paramTriples.push(` code:position ${paramIdx} .`);
|
|
121
|
+
triples.push(...paramTriples);
|
|
122
|
+
triples.push('');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Returns
|
|
126
|
+
if (exp.returns) {
|
|
127
|
+
triples.push(` code:returns ${escapeLiteral(exp.returns.type)} ;`);
|
|
128
|
+
if (exp.returns.description) {
|
|
129
|
+
triples.push(` code:returnsDescription ${escapeLiteral(exp.returns.description)} ;`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Examples
|
|
134
|
+
exp.examples?.forEach((example, exIdx) => {
|
|
135
|
+
triples.push(` doc:example ${escapeLiteral(example)} ;`);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Remove trailing semicolon from last triple
|
|
139
|
+
const lastLine = triples[triples.length - 1];
|
|
140
|
+
triples[triples.length - 1] = lastLine.replace(/\s;$/, ' .');
|
|
141
|
+
|
|
142
|
+
} else if (exp.type === 'class') {
|
|
143
|
+
triples.push(` a code:Class ;`);
|
|
144
|
+
triples.push(` code:name ${escapeLiteral(exp.name)} ;`);
|
|
145
|
+
|
|
146
|
+
if (exp.description) {
|
|
147
|
+
triples.push(` doc:description ${escapeLiteral(exp.description)} ;`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (exp.extends) {
|
|
151
|
+
triples.push(` code:extends ${escapeLiteral(exp.extends)} ;`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Methods
|
|
155
|
+
exp.methods?.forEach(method => {
|
|
156
|
+
const methodNode = blankNode(`${exp.name}_${method.name}`);
|
|
157
|
+
triples.push(` code:method ${methodNode} ;`);
|
|
158
|
+
|
|
159
|
+
const methodTriples = [];
|
|
160
|
+
methodTriples.push(`${methodNode}`);
|
|
161
|
+
methodTriples.push(` a code:Method ;`);
|
|
162
|
+
methodTriples.push(` code:name ${escapeLiteral(method.name)} ;`);
|
|
163
|
+
methodTriples.push(` code:kind ${escapeLiteral(method.kind)} ;`);
|
|
164
|
+
|
|
165
|
+
if (method.static) {
|
|
166
|
+
methodTriples.push(` code:static true ;`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (method.async) {
|
|
170
|
+
methodTriples.push(` code:async true ;`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
methodTriples.push(` code:paramCount ${method.params?.length || 0} .`);
|
|
174
|
+
triples.push(...methodTriples);
|
|
175
|
+
triples.push('');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Remove trailing semicolon
|
|
179
|
+
const lastLine = triples[triples.length - 1];
|
|
180
|
+
if (lastLine.includes(';')) {
|
|
181
|
+
triples[triples.length - 1] = lastLine.replace(/\s;$/, ' .');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
} else if (exp.type === 'variable') {
|
|
185
|
+
triples.push(` a code:Variable ;`);
|
|
186
|
+
triples.push(` code:name ${escapeLiteral(exp.name)} ;`);
|
|
187
|
+
triples.push(` code:valueType ${escapeLiteral(exp.valueType)} .`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
triples.push('');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Imports (for cross-referencing)
|
|
194
|
+
parsedFile.imports?.forEach((imp, idx) => {
|
|
195
|
+
const importNode = blankNode(`import_${idx}`);
|
|
196
|
+
|
|
197
|
+
triples.push(`# Import from: ${imp.source}`);
|
|
198
|
+
triples.push(`${fileURI}`);
|
|
199
|
+
triples.push(` code:imports ${importNode} .`);
|
|
200
|
+
triples.push('');
|
|
201
|
+
|
|
202
|
+
triples.push(`${importNode}`);
|
|
203
|
+
triples.push(` a code:Import ;`);
|
|
204
|
+
triples.push(` code:source ${escapeLiteral(imp.source)} ;`);
|
|
205
|
+
|
|
206
|
+
imp.specifiers.forEach((spec, specIdx) => {
|
|
207
|
+
const specNode = blankNode(`import_${idx}_spec_${specIdx}`);
|
|
208
|
+
triples.push(` code:specifier ${specNode} ;`);
|
|
209
|
+
|
|
210
|
+
triples.push(`${specNode}`);
|
|
211
|
+
triples.push(` a code:ImportSpecifier ;`);
|
|
212
|
+
triples.push(` code:imported ${escapeLiteral(spec.imported)} ;`);
|
|
213
|
+
triples.push(` code:local ${escapeLiteral(spec.local)} ;`);
|
|
214
|
+
triples.push(` code:specifierType ${escapeLiteral(spec.type)} .`);
|
|
215
|
+
triples.push('');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
triples.push('');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Build complete Turtle document
|
|
222
|
+
const prefixDeclarations = Object.entries(PREFIXES)
|
|
223
|
+
.map(([prefix, uri]) => `@prefix ${prefix}: <${uri}> .`)
|
|
224
|
+
.join('\n');
|
|
225
|
+
|
|
226
|
+
return `${prefixDeclarations}\n\n${triples.join('\n')}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Build RDF graph for multiple files
|
|
231
|
+
* @param {Array} parsedFiles - Array of parsed file data
|
|
232
|
+
* @returns {string} Combined Turtle RDF
|
|
233
|
+
*/
|
|
234
|
+
export function buildRDFGraphs(parsedFiles) {
|
|
235
|
+
const graphs = parsedFiles
|
|
236
|
+
.filter(file => !file.error)
|
|
237
|
+
.map(file => buildRDFGraph(file));
|
|
238
|
+
|
|
239
|
+
const prefixDeclarations = Object.entries(PREFIXES)
|
|
240
|
+
.map(([prefix, uri]) => `@prefix ${prefix}: <${uri}> .`)
|
|
241
|
+
.join('\n');
|
|
242
|
+
|
|
243
|
+
// Remove duplicate prefix declarations from individual graphs
|
|
244
|
+
const graphsWithoutPrefixes = graphs.map(graph =>
|
|
245
|
+
graph.replace(/@prefix[^\n]+\n/g, '').trim()
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return `${prefixDeclarations}\n\n${graphsWithoutPrefixes.join('\n\n')}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Build JSON-LD representation
|
|
253
|
+
* @param {Object} parsedFile - Parsed file data
|
|
254
|
+
* @returns {Object} JSON-LD document
|
|
255
|
+
*/
|
|
256
|
+
export function buildJSONLD(parsedFile) {
|
|
257
|
+
const fileURI = generateURI(parsedFile.relativePath);
|
|
258
|
+
|
|
259
|
+
const document = {
|
|
260
|
+
'@context': PREFIXES,
|
|
261
|
+
'@id': fileURI,
|
|
262
|
+
'@type': ['fs:File', 'code:Module'],
|
|
263
|
+
'fs:path': parsedFile.relativePath,
|
|
264
|
+
'fs:absolutePath': parsedFile.file,
|
|
265
|
+
'code:commentCount': parsedFile.comments,
|
|
266
|
+
'code:exports': [],
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
parsedFile.exports.forEach(exp => {
|
|
270
|
+
const exportObj = {
|
|
271
|
+
'@id': `${fileURI}#${exp.name}`,
|
|
272
|
+
'@type': `code:${exp.type.charAt(0).toUpperCase() + exp.type.slice(1)}`,
|
|
273
|
+
'code:name': exp.name,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
if (exp.description) {
|
|
277
|
+
exportObj['doc:description'] = exp.description;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (exp.type === 'function') {
|
|
281
|
+
if (exp.params) {
|
|
282
|
+
exportObj['code:param'] = exp.params.map(param => ({
|
|
283
|
+
'@type': 'code:Parameter',
|
|
284
|
+
'code:name': param.name,
|
|
285
|
+
'code:type': param.type,
|
|
286
|
+
'code:optional': param.optional,
|
|
287
|
+
'doc:description': param.description,
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (exp.returns) {
|
|
292
|
+
exportObj['code:returns'] = exp.returns.type;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (exp.examples?.length) {
|
|
296
|
+
exportObj['doc:example'] = exp.examples;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
document['code:exports'].push(exportObj);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return document;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export default { buildRDFGraph, buildRDFGraphs, buildJSONLD, PREFIXES };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workspace scanner for batch documentation generation
|
|
3
|
+
* Scans packages and finds all documentable source files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdir, stat } from 'fs/promises';
|
|
7
|
+
import { join, relative } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Scan workspace for source files
|
|
14
|
+
* @param {string} rootDir - Workspace root directory
|
|
15
|
+
* @param {Object} options - Scanner options
|
|
16
|
+
* @returns {Promise<Array>} Array of file paths grouped by package
|
|
17
|
+
*/
|
|
18
|
+
export async function scanWorkspace(rootDir, options = {}) {
|
|
19
|
+
const {
|
|
20
|
+
packagesDir = 'packages',
|
|
21
|
+
include = ['**/*.mjs', '**/*.js'],
|
|
22
|
+
exclude = ['**/node_modules/**', '**/dist/**', '**/test/**', '**/*.test.*', '**/*.spec.*'],
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
const packagesPath = join(rootDir, packagesDir);
|
|
26
|
+
const packages = [];
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const packageDirs = await readdir(packagesPath);
|
|
30
|
+
|
|
31
|
+
for (const pkgName of packageDirs) {
|
|
32
|
+
const pkgPath = join(packagesPath, pkgName);
|
|
33
|
+
const pkgStat = await stat(pkgPath);
|
|
34
|
+
|
|
35
|
+
if (!pkgStat.isDirectory()) continue;
|
|
36
|
+
|
|
37
|
+
// Check if package has a src directory
|
|
38
|
+
const srcPath = join(pkgPath, 'src');
|
|
39
|
+
let hasSrc = false;
|
|
40
|
+
try {
|
|
41
|
+
const srcStat = await stat(srcPath);
|
|
42
|
+
hasSrc = srcStat.isDirectory();
|
|
43
|
+
} catch {
|
|
44
|
+
// No src directory
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!hasSrc) continue;
|
|
48
|
+
|
|
49
|
+
// Scan for source files
|
|
50
|
+
const sourceFiles = await scanDirectory(srcPath, {
|
|
51
|
+
include,
|
|
52
|
+
exclude,
|
|
53
|
+
rootDir,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (sourceFiles.length > 0) {
|
|
57
|
+
packages.push({
|
|
58
|
+
name: pkgName,
|
|
59
|
+
path: pkgPath,
|
|
60
|
+
relativePath: relative(rootDir, pkgPath),
|
|
61
|
+
srcPath,
|
|
62
|
+
sourceFiles,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return packages;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(`Failed to scan workspace: ${error.message}`);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Recursively scan directory for source files
|
|
76
|
+
* @param {string} dir - Directory to scan
|
|
77
|
+
* @param {Object} options - Scan options
|
|
78
|
+
* @returns {Promise<Array>} Array of file paths
|
|
79
|
+
*/
|
|
80
|
+
async function scanDirectory(dir, options) {
|
|
81
|
+
const { include, exclude, rootDir } = options;
|
|
82
|
+
const files = [];
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
86
|
+
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const fullPath = join(dir, entry.name);
|
|
89
|
+
const relativePath = relative(rootDir, fullPath);
|
|
90
|
+
|
|
91
|
+
// Check exclusions
|
|
92
|
+
if (shouldExclude(relativePath, exclude)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
// Recursively scan subdirectories
|
|
98
|
+
const subFiles = await scanDirectory(fullPath, options);
|
|
99
|
+
files.push(...subFiles);
|
|
100
|
+
} else if (entry.isFile()) {
|
|
101
|
+
// Check if file matches include patterns
|
|
102
|
+
if (shouldInclude(entry.name, include)) {
|
|
103
|
+
files.push(fullPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(`Failed to scan directory ${dir}: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return files;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if path should be excluded
|
|
116
|
+
* @param {string} path - Path to check
|
|
117
|
+
* @param {Array<string>} patterns - Exclusion patterns
|
|
118
|
+
* @returns {boolean} True if should exclude
|
|
119
|
+
*/
|
|
120
|
+
function shouldExclude(path, patterns) {
|
|
121
|
+
return patterns.some(pattern => {
|
|
122
|
+
const regex = patternToRegex(pattern);
|
|
123
|
+
return regex.test(path);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if filename should be included
|
|
129
|
+
* @param {string} filename - Filename to check
|
|
130
|
+
* @param {Array<string>} patterns - Inclusion patterns
|
|
131
|
+
* @returns {boolean} True if should include
|
|
132
|
+
*/
|
|
133
|
+
function shouldInclude(filename, patterns) {
|
|
134
|
+
return patterns.some(pattern => {
|
|
135
|
+
const regex = patternToRegex(pattern);
|
|
136
|
+
return regex.test(filename);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Convert glob pattern to regex
|
|
142
|
+
* @param {string} pattern - Glob pattern
|
|
143
|
+
* @returns {RegExp} Regular expression
|
|
144
|
+
*/
|
|
145
|
+
function patternToRegex(pattern) {
|
|
146
|
+
// Simple glob to regex conversion
|
|
147
|
+
const regexPattern = pattern
|
|
148
|
+
.replace(/\*\*/g, '.*')
|
|
149
|
+
.replace(/\*/g, '[^/]*')
|
|
150
|
+
.replace(/\./g, '\\.');
|
|
151
|
+
|
|
152
|
+
return new RegExp(`^${regexPattern}$`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Group files by package and module
|
|
157
|
+
* @param {Array} packages - Scanned packages
|
|
158
|
+
* @returns {Object} Grouped file structure
|
|
159
|
+
*/
|
|
160
|
+
export function groupFilesByModule(packages) {
|
|
161
|
+
const grouped = {};
|
|
162
|
+
|
|
163
|
+
packages.forEach(pkg => {
|
|
164
|
+
const modules = new Map();
|
|
165
|
+
|
|
166
|
+
pkg.sourceFiles.forEach(filePath => {
|
|
167
|
+
const relativePath = relative(pkg.srcPath, filePath);
|
|
168
|
+
const parts = relativePath.split('/');
|
|
169
|
+
|
|
170
|
+
// Group by top-level directory in src/
|
|
171
|
+
const moduleName = parts.length > 1 ? parts[0] : 'root';
|
|
172
|
+
|
|
173
|
+
if (!modules.has(moduleName)) {
|
|
174
|
+
modules.set(moduleName, []);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
modules.get(moduleName).push(filePath);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
grouped[pkg.name] = {
|
|
181
|
+
...pkg,
|
|
182
|
+
modules: Object.fromEntries(modules),
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return grouped;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default { scanWorkspace, groupFilesByModule };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DETERMINISTIC Template Engine - Complete Exports
|
|
3
|
+
*
|
|
4
|
+
* 4-Stage Pipeline: plan → render → post → attest
|
|
5
|
+
* 100% Deterministic guarantees with cryptographic attestation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { TemplateEngine } from './template-engine.js';
|
|
9
|
+
export { DeterministicRenderer } from './renderer.js';
|
|
10
|
+
export { DeterministicPipeline } from './pipeline.js';
|
|
11
|
+
|
|
12
|
+
// Factory for deterministic engine with complete pipeline
|
|
13
|
+
export function createDeterministicEngine(options = {}) {
|
|
14
|
+
const pipeline = new DeterministicPipeline(options);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
pipeline,
|
|
18
|
+
renderer: pipeline.renderer,
|
|
19
|
+
|
|
20
|
+
async render(template, data, opts = {}) {
|
|
21
|
+
return await pipeline.execute(template, data, opts);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async verifyDeterminism(template, data, iterations = 3) {
|
|
25
|
+
return await pipeline.verifyDeterminism(template, data, iterations);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async renderBatch(batch, opts = {}) {
|
|
29
|
+
return await pipeline.executeBatch(batch, opts);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
getStats() { return pipeline.getStats(); },
|
|
33
|
+
resetStats() { pipeline.resetStats(); }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
TemplateEngine,
|
|
39
|
+
DeterministicRenderer,
|
|
40
|
+
DeterministicPipeline,
|
|
41
|
+
createDeterministicEngine
|
|
42
|
+
};
|