@nuucognition/flint-cli 0.1.0-beta.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.
- package/README.md +344 -0
- package/bin/flint-prod.js +7 -0
- package/bin/flint.js +19 -0
- package/dist/chunk-BVYUS7NX.js +386 -0
- package/dist/chunk-EICASOEX.js +1524 -0
- package/dist/chunk-F5Q2BJFK.js +233 -0
- package/dist/exports-Y3NVZFFT.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +11118 -0
- package/dist/mesh-config-O3UKKKAO.js +112 -0
- package/dist/registry-FUIZ2SKS.js +30 -0
- package/package.json +39 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// ../../packages/flint/src/exports.ts
|
|
2
|
+
import { readdir, readFile, mkdir, writeFile, rm, stat } from "fs/promises";
|
|
3
|
+
import { join, basename, dirname, resolve, relative, sep } from "path";
|
|
4
|
+
async function exists(path) {
|
|
5
|
+
try {
|
|
6
|
+
await stat(path);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function isPathWithinRoot(root, target) {
|
|
13
|
+
const resolvedRoot = resolve(root);
|
|
14
|
+
const resolvedTarget = resolve(target);
|
|
15
|
+
if (resolvedTarget === resolvedRoot) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return resolvedTarget.startsWith(resolvedRoot + sep);
|
|
19
|
+
}
|
|
20
|
+
function stripFrontmatter(content) {
|
|
21
|
+
return content.replace(/^---\n[\s\S]*?\n---\n*/, "");
|
|
22
|
+
}
|
|
23
|
+
function getContentWithoutFrontmatter(content) {
|
|
24
|
+
const match = content.match(/^---\n[\s\S]*?\n---\n*/);
|
|
25
|
+
if (match) {
|
|
26
|
+
return content.slice(match[0].length);
|
|
27
|
+
}
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
async function collectReferencedFiles(content, flintPath, collected = /* @__PURE__ */ new Set(), visited = /* @__PURE__ */ new Set(), depth = -1, currentDepth = 0) {
|
|
31
|
+
if (depth !== -1 && currentDepth >= depth) {
|
|
32
|
+
return collected;
|
|
33
|
+
}
|
|
34
|
+
const contentWithoutFrontmatter = getContentWithoutFrontmatter(content);
|
|
35
|
+
const linkRegex = /!?\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]/g;
|
|
36
|
+
let match;
|
|
37
|
+
while ((match = linkRegex.exec(contentWithoutFrontmatter)) !== null) {
|
|
38
|
+
const captured = match[1];
|
|
39
|
+
if (!captured) continue;
|
|
40
|
+
const target = captured.trim();
|
|
41
|
+
const sourcePath = await resolveDocument(target, flintPath);
|
|
42
|
+
if (!sourcePath || visited.has(sourcePath)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
collected.add(sourcePath);
|
|
46
|
+
visited.add(sourcePath);
|
|
47
|
+
if (depth === -1 || currentDepth + 1 < depth) {
|
|
48
|
+
const referencedContent = await readFile(sourcePath, "utf-8");
|
|
49
|
+
await collectReferencedFiles(referencedContent, flintPath, collected, visited, depth, currentDepth + 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return collected;
|
|
53
|
+
}
|
|
54
|
+
async function expandEmbeds(content, flintPath, visited = /* @__PURE__ */ new Set()) {
|
|
55
|
+
const embedRegex = /!\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]/g;
|
|
56
|
+
let result = content;
|
|
57
|
+
let match;
|
|
58
|
+
const matches = [];
|
|
59
|
+
while ((match = embedRegex.exec(content)) !== null) {
|
|
60
|
+
const captured = match[1];
|
|
61
|
+
if (!captured) continue;
|
|
62
|
+
matches.push({ full: match[0], target: captured.trim() });
|
|
63
|
+
}
|
|
64
|
+
for (const { full, target } of matches) {
|
|
65
|
+
const sourcePath = await resolveDocument(target, flintPath);
|
|
66
|
+
if (!sourcePath) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (visited.has(sourcePath)) {
|
|
70
|
+
result = result.replace(full, `<!-- Circular embed removed: ${target} -->`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
let embeddedContent = await readFile(sourcePath, "utf-8");
|
|
74
|
+
embeddedContent = embeddedContent.replace(/^---\n[\s\S]*?\n---\n*/, "");
|
|
75
|
+
const newVisited = new Set(visited);
|
|
76
|
+
newVisited.add(sourcePath);
|
|
77
|
+
embeddedContent = await expandEmbeds(embeddedContent, flintPath, newVisited);
|
|
78
|
+
const fileName = basename(sourcePath, ".md");
|
|
79
|
+
const quotedContent = embeddedContent.trim().split("\n").map((line) => `> ${line}`).join("\n");
|
|
80
|
+
const formattedEmbed = `> **${fileName}**
|
|
81
|
+
>
|
|
82
|
+
${quotedContent}`;
|
|
83
|
+
result = result.replace(full, formattedEmbed);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
async function rewriteWikilinks(content, flintName, flintPath, exportNameBySourcePath, resolveCache) {
|
|
88
|
+
const wikilinkRegex = /\[\[([^\]|#]+)(#[^\]|]*)?((\|)([^\]]*))?\]\]/g;
|
|
89
|
+
let result = "";
|
|
90
|
+
let lastIndex = 0;
|
|
91
|
+
let match;
|
|
92
|
+
while ((match = wikilinkRegex.exec(content)) !== null) {
|
|
93
|
+
const matchIndex = match.index ?? 0;
|
|
94
|
+
result += content.slice(lastIndex, matchIndex);
|
|
95
|
+
const target = match[1]?.trim();
|
|
96
|
+
const section = match[2] || "";
|
|
97
|
+
const alias = match[5];
|
|
98
|
+
if (!target) {
|
|
99
|
+
result += match[0];
|
|
100
|
+
lastIndex = matchIndex + match[0].length;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const resolvedPath = await resolveDocumentCached(target, flintPath, resolveCache);
|
|
104
|
+
const exportBaseName = resolvedPath ? exportNameBySourcePath.get(resolvedPath) : void 0;
|
|
105
|
+
if (exportBaseName) {
|
|
106
|
+
const prefixedName = `Export - (${flintName}) ${exportBaseName}`;
|
|
107
|
+
const displayAlias = alias || target;
|
|
108
|
+
result += `[[${prefixedName}${section}|${displayAlias}]]`;
|
|
109
|
+
} else {
|
|
110
|
+
result += match[0];
|
|
111
|
+
}
|
|
112
|
+
lastIndex = matchIndex + match[0].length;
|
|
113
|
+
}
|
|
114
|
+
result += content.slice(lastIndex);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function parseExportDocument(content, name, sourcePath) {
|
|
118
|
+
const files = [];
|
|
119
|
+
let description;
|
|
120
|
+
let depth = 1;
|
|
121
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
122
|
+
if (frontmatterMatch && frontmatterMatch[1]) {
|
|
123
|
+
const descMatch = frontmatterMatch[1].match(/description:\s*(.+)/);
|
|
124
|
+
if (descMatch && descMatch[1]) {
|
|
125
|
+
description = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
126
|
+
}
|
|
127
|
+
const depthMatch = frontmatterMatch[1].match(/export-depth:\s*(-?\d+)/);
|
|
128
|
+
if (depthMatch && depthMatch[1]) {
|
|
129
|
+
depth = parseInt(depthMatch[1], 10);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const contentWithoutFrontmatter = getContentWithoutFrontmatter(content);
|
|
133
|
+
const wikilinkRegex = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
|
|
134
|
+
let match;
|
|
135
|
+
while ((match = wikilinkRegex.exec(contentWithoutFrontmatter)) !== null) {
|
|
136
|
+
if (match[1]) files.push(match[1]);
|
|
137
|
+
}
|
|
138
|
+
const mdLinkRegex = /\[([^\]]+)\]\(([^)]+\.md)\)/g;
|
|
139
|
+
while ((match = mdLinkRegex.exec(contentWithoutFrontmatter)) !== null) {
|
|
140
|
+
if (match[2]) files.push(match[2]);
|
|
141
|
+
}
|
|
142
|
+
return { name, description, depth, files, sourcePath };
|
|
143
|
+
}
|
|
144
|
+
async function findFilesRecursively(dir, fileName, matches = []) {
|
|
145
|
+
try {
|
|
146
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
147
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const fullPath = join(dir, entry.name);
|
|
150
|
+
if (entry.isFile() && entry.name === fileName) {
|
|
151
|
+
matches.push(fullPath);
|
|
152
|
+
}
|
|
153
|
+
if (entry.isDirectory()) {
|
|
154
|
+
await findFilesRecursively(fullPath, fileName, matches);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
return matches;
|
|
160
|
+
}
|
|
161
|
+
async function findExportFiles(dir, files = []) {
|
|
162
|
+
try {
|
|
163
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
const fullPath = join(dir, entry.name);
|
|
166
|
+
if (entry.isFile() && entry.name.startsWith("(Export) ") && entry.name.endsWith(".md")) {
|
|
167
|
+
files.push(fullPath);
|
|
168
|
+
}
|
|
169
|
+
if (entry.isDirectory()) {
|
|
170
|
+
await findExportFiles(fullPath, files);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
return files;
|
|
176
|
+
}
|
|
177
|
+
async function resolveDocument(docRef, flintPath) {
|
|
178
|
+
const trimmedRef = docRef.trim();
|
|
179
|
+
if (!trimmedRef) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const normalizedRef = trimmedRef.replace(/\\/g, "/");
|
|
183
|
+
const hasPath = normalizedRef.includes("/");
|
|
184
|
+
if (hasPath) {
|
|
185
|
+
const cleanedRef = normalizedRef.replace(/^\.?\//, "");
|
|
186
|
+
const withExt = cleanedRef.endsWith(".md") ? cleanedRef : `${cleanedRef}.md`;
|
|
187
|
+
const directCandidate = resolve(flintPath, withExt);
|
|
188
|
+
if (isPathWithinRoot(flintPath, directCandidate) && await exists(directCandidate)) {
|
|
189
|
+
return directCandidate;
|
|
190
|
+
}
|
|
191
|
+
if (!cleanedRef.startsWith("Mesh/")) {
|
|
192
|
+
const meshCandidate = resolve(flintPath, "Mesh", withExt);
|
|
193
|
+
if (isPathWithinRoot(flintPath, meshCandidate) && await exists(meshCandidate)) {
|
|
194
|
+
return meshCandidate;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const docName = basename(trimmedRef);
|
|
199
|
+
const fileName = docName.endsWith(".md") ? docName : `${docName}.md`;
|
|
200
|
+
const meshDir = join(flintPath, "Mesh");
|
|
201
|
+
const matches = await findFilesRecursively(meshDir, fileName);
|
|
202
|
+
if (matches.length === 0) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
matches.sort();
|
|
206
|
+
return matches[0] ?? null;
|
|
207
|
+
}
|
|
208
|
+
async function resolveDocumentCached(docRef, flintPath, cache) {
|
|
209
|
+
const key = docRef.trim();
|
|
210
|
+
if (cache.has(key)) {
|
|
211
|
+
return cache.get(key) ?? null;
|
|
212
|
+
}
|
|
213
|
+
const resolved = await resolveDocument(key, flintPath);
|
|
214
|
+
cache.set(key, resolved);
|
|
215
|
+
return resolved;
|
|
216
|
+
}
|
|
217
|
+
function getDisambiguatedExportName(flintPath, sourcePath, baseName) {
|
|
218
|
+
const relativePath = relative(flintPath, sourcePath);
|
|
219
|
+
const relativeDir = dirname(relativePath);
|
|
220
|
+
if (!relativeDir || relativeDir === ".") {
|
|
221
|
+
return baseName;
|
|
222
|
+
}
|
|
223
|
+
const dirLabel = relativeDir.split(sep).join(" - ");
|
|
224
|
+
return `${baseName} (${dirLabel})`;
|
|
225
|
+
}
|
|
226
|
+
async function scanExports(flintPath) {
|
|
227
|
+
const meshDir = join(flintPath, "Mesh");
|
|
228
|
+
if (!await exists(meshDir)) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const exportFiles = await findExportFiles(meshDir);
|
|
232
|
+
const manifests = [];
|
|
233
|
+
for (const filePath of exportFiles) {
|
|
234
|
+
try {
|
|
235
|
+
const content = await readFile(filePath, "utf-8");
|
|
236
|
+
const fileName = basename(filePath, ".md");
|
|
237
|
+
const name = fileName.replace(/^\(Export\)\s*/, "");
|
|
238
|
+
const manifest = parseExportDocument(content, name, filePath);
|
|
239
|
+
manifests.push(manifest);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return manifests;
|
|
244
|
+
}
|
|
245
|
+
async function buildExport(manifest, flintPath) {
|
|
246
|
+
const exportsDir = join(flintPath, "Exports");
|
|
247
|
+
const outputDir = join(exportsDir, manifest.name);
|
|
248
|
+
if (await exists(outputDir)) {
|
|
249
|
+
await rm(outputDir, { recursive: true });
|
|
250
|
+
}
|
|
251
|
+
await mkdir(outputDir, { recursive: true });
|
|
252
|
+
const folderName = basename(flintPath);
|
|
253
|
+
const flintName = folderName.replace(/^\(Mesh\)\s*/, "").replace(/^\(Flint\)\s*/, "");
|
|
254
|
+
const resolvedFiles = [];
|
|
255
|
+
const processedPaths = /* @__PURE__ */ new Set();
|
|
256
|
+
processedPaths.add(manifest.sourcePath);
|
|
257
|
+
for (const docRef of manifest.files) {
|
|
258
|
+
const sourcePath = await resolveDocument(docRef, flintPath);
|
|
259
|
+
if (!sourcePath || processedPaths.has(sourcePath)) continue;
|
|
260
|
+
resolvedFiles.push({ sourcePath, docRef });
|
|
261
|
+
processedPaths.add(sourcePath);
|
|
262
|
+
}
|
|
263
|
+
const depth = manifest.depth;
|
|
264
|
+
const allReferencedFiles = /* @__PURE__ */ new Set();
|
|
265
|
+
const exportContent = await readFile(manifest.sourcePath, "utf-8");
|
|
266
|
+
await collectReferencedFiles(exportContent, flintPath, allReferencedFiles, new Set(processedPaths), depth, 0);
|
|
267
|
+
for (const { sourcePath } of resolvedFiles) {
|
|
268
|
+
const content = await readFile(sourcePath, "utf-8");
|
|
269
|
+
await collectReferencedFiles(content, flintPath, allReferencedFiles, new Set(processedPaths), depth, 1);
|
|
270
|
+
}
|
|
271
|
+
for (const refPath of allReferencedFiles) {
|
|
272
|
+
if (!processedPaths.has(refPath)) {
|
|
273
|
+
resolvedFiles.push({ sourcePath: refPath, docRef: basename(refPath, ".md") });
|
|
274
|
+
processedPaths.add(refPath);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const allExportFiles = [
|
|
278
|
+
{ sourcePath: manifest.sourcePath, baseName: manifest.name },
|
|
279
|
+
...resolvedFiles.map((f) => ({ sourcePath: f.sourcePath, baseName: basename(f.sourcePath, ".md") }))
|
|
280
|
+
];
|
|
281
|
+
const baseNameCounts = /* @__PURE__ */ new Map();
|
|
282
|
+
for (const { baseName } of allExportFiles) {
|
|
283
|
+
baseNameCounts.set(baseName, (baseNameCounts.get(baseName) || 0) + 1);
|
|
284
|
+
}
|
|
285
|
+
const duplicateBaseNames = new Set(
|
|
286
|
+
Array.from(baseNameCounts.entries()).filter(([, count]) => count > 1).map(([name]) => name)
|
|
287
|
+
);
|
|
288
|
+
const exportNameBySourcePath = /* @__PURE__ */ new Map();
|
|
289
|
+
for (const { sourcePath, baseName } of allExportFiles) {
|
|
290
|
+
const exportName = duplicateBaseNames.has(baseName) ? getDisambiguatedExportName(flintPath, sourcePath, baseName) : baseName;
|
|
291
|
+
exportNameBySourcePath.set(sourcePath, exportName);
|
|
292
|
+
}
|
|
293
|
+
let filesCopied = 0;
|
|
294
|
+
const copiedFiles = [];
|
|
295
|
+
const resolveCache = /* @__PURE__ */ new Map();
|
|
296
|
+
let rootContent = await readFile(manifest.sourcePath, "utf-8");
|
|
297
|
+
rootContent = stripFrontmatter(rootContent);
|
|
298
|
+
rootContent = await expandEmbeds(rootContent, flintPath, /* @__PURE__ */ new Set([manifest.sourcePath]));
|
|
299
|
+
rootContent = await rewriteWikilinks(rootContent, flintName, flintPath, exportNameBySourcePath, resolveCache);
|
|
300
|
+
const rootExportName = exportNameBySourcePath.get(manifest.sourcePath) || manifest.name;
|
|
301
|
+
const rootFileName = `Export - (${flintName}) ${rootExportName}.md`;
|
|
302
|
+
await writeFile(join(outputDir, rootFileName), rootContent);
|
|
303
|
+
copiedFiles.push(rootFileName);
|
|
304
|
+
filesCopied++;
|
|
305
|
+
for (const { sourcePath } of resolvedFiles) {
|
|
306
|
+
const exportBaseName = exportNameBySourcePath.get(sourcePath) || basename(sourcePath, ".md");
|
|
307
|
+
const prefixedName = `Export - (${flintName}) ${exportBaseName}.md`;
|
|
308
|
+
const destPath = join(outputDir, prefixedName);
|
|
309
|
+
let content = await readFile(sourcePath, "utf-8");
|
|
310
|
+
content = stripFrontmatter(content);
|
|
311
|
+
content = await expandEmbeds(content, flintPath, /* @__PURE__ */ new Set([sourcePath]));
|
|
312
|
+
content = await rewriteWikilinks(content, flintName, flintPath, exportNameBySourcePath, resolveCache);
|
|
313
|
+
await writeFile(destPath, content);
|
|
314
|
+
copiedFiles.push(prefixedName);
|
|
315
|
+
filesCopied++;
|
|
316
|
+
}
|
|
317
|
+
const manifestJson = {
|
|
318
|
+
name: manifest.name,
|
|
319
|
+
flint: flintName,
|
|
320
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
321
|
+
files: copiedFiles
|
|
322
|
+
};
|
|
323
|
+
await writeFile(
|
|
324
|
+
join(outputDir, "_manifest.json"),
|
|
325
|
+
JSON.stringify(manifestJson, null, 2)
|
|
326
|
+
);
|
|
327
|
+
return {
|
|
328
|
+
name: manifest.name,
|
|
329
|
+
outputPath: outputDir,
|
|
330
|
+
filesCopied
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
async function buildExportByName(flintPath, exportName) {
|
|
334
|
+
const manifests = await scanExports(flintPath);
|
|
335
|
+
const manifest = manifests.find((m) => m.name === exportName);
|
|
336
|
+
if (!manifest) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
return buildExport(manifest, flintPath);
|
|
340
|
+
}
|
|
341
|
+
async function cleanupStaleExports(flintPath) {
|
|
342
|
+
const exportsDir = join(flintPath, "Exports");
|
|
343
|
+
const removed = [];
|
|
344
|
+
if (!await exists(exportsDir)) {
|
|
345
|
+
return removed;
|
|
346
|
+
}
|
|
347
|
+
const manifests = await scanExports(flintPath);
|
|
348
|
+
const validExportNames = new Set(manifests.map((m) => m.name));
|
|
349
|
+
const entries = await readdir(exportsDir, { withFileTypes: true });
|
|
350
|
+
for (const entry of entries) {
|
|
351
|
+
if (entry.isDirectory()) {
|
|
352
|
+
if (!validExportNames.has(entry.name)) {
|
|
353
|
+
try {
|
|
354
|
+
await rm(join(exportsDir, entry.name), { recursive: true });
|
|
355
|
+
removed.push(entry.name);
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
360
|
+
try {
|
|
361
|
+
await rm(join(exportsDir, entry.name));
|
|
362
|
+
removed.push(`${entry.name} (legacy manifest)`);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return removed;
|
|
368
|
+
}
|
|
369
|
+
async function buildAllExports(flintPath) {
|
|
370
|
+
const removed = await cleanupStaleExports(flintPath);
|
|
371
|
+
const manifests = await scanExports(flintPath);
|
|
372
|
+
const results = [];
|
|
373
|
+
for (const manifest of manifests) {
|
|
374
|
+
const result = await buildExport(manifest, flintPath);
|
|
375
|
+
results.push(result);
|
|
376
|
+
}
|
|
377
|
+
return { results, removed };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export {
|
|
381
|
+
scanExports,
|
|
382
|
+
buildExport,
|
|
383
|
+
buildExportByName,
|
|
384
|
+
cleanupStaleExports,
|
|
385
|
+
buildAllExports
|
|
386
|
+
};
|