@glyphjs/compiler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/index.cjs +809 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +168 -0
- package/dist/index.d.ts +168 -0
- package/dist/index.js +794 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { parseGlyphMarkdown } from '@glyphjs/parser';
|
|
2
|
+
import { generateBlockId, generateDocumentId, resolveBlockIdCollisions } from '@glyphjs/ir';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import { componentSchemas } from '@glyphjs/schemas';
|
|
5
|
+
|
|
6
|
+
// src/compile.ts
|
|
7
|
+
|
|
8
|
+
// src/inline.ts
|
|
9
|
+
function convertPhrasingContent(nodes) {
|
|
10
|
+
const result = [];
|
|
11
|
+
for (const node of nodes) {
|
|
12
|
+
const converted = convertSingleNode(node);
|
|
13
|
+
if (converted) {
|
|
14
|
+
result.push(converted);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
function convertSingleNode(node) {
|
|
20
|
+
switch (node.type) {
|
|
21
|
+
case "text":
|
|
22
|
+
return { type: "text", value: node.value };
|
|
23
|
+
case "strong":
|
|
24
|
+
return {
|
|
25
|
+
type: "strong",
|
|
26
|
+
children: convertPhrasingContent(node.children)
|
|
27
|
+
};
|
|
28
|
+
case "emphasis":
|
|
29
|
+
return {
|
|
30
|
+
type: "emphasis",
|
|
31
|
+
children: convertPhrasingContent(node.children)
|
|
32
|
+
};
|
|
33
|
+
case "delete":
|
|
34
|
+
return {
|
|
35
|
+
type: "delete",
|
|
36
|
+
children: convertPhrasingContent(node.children)
|
|
37
|
+
};
|
|
38
|
+
case "inlineCode":
|
|
39
|
+
return { type: "inlineCode", value: node.value };
|
|
40
|
+
case "link": {
|
|
41
|
+
const linkNode = {
|
|
42
|
+
type: "link",
|
|
43
|
+
url: node.url,
|
|
44
|
+
children: convertPhrasingContent(node.children)
|
|
45
|
+
};
|
|
46
|
+
if (node.title != null) {
|
|
47
|
+
linkNode.title = node.title;
|
|
48
|
+
}
|
|
49
|
+
return linkNode;
|
|
50
|
+
}
|
|
51
|
+
case "image": {
|
|
52
|
+
const imgNode = {
|
|
53
|
+
type: "image",
|
|
54
|
+
src: node.url
|
|
55
|
+
};
|
|
56
|
+
if (node.alt != null) {
|
|
57
|
+
imgNode.alt = node.alt;
|
|
58
|
+
}
|
|
59
|
+
if (node.title != null) {
|
|
60
|
+
imgNode.title = node.title;
|
|
61
|
+
}
|
|
62
|
+
return imgNode;
|
|
63
|
+
}
|
|
64
|
+
case "break":
|
|
65
|
+
return { type: "break" };
|
|
66
|
+
default: {
|
|
67
|
+
const unknown = node;
|
|
68
|
+
if (typeof unknown["value"] === "string") {
|
|
69
|
+
return { type: "text", value: unknown["value"] };
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/diagnostics.ts
|
|
77
|
+
function createDiagnostic(source, severity, code, message, position, details) {
|
|
78
|
+
const diag = { severity, code, message, source };
|
|
79
|
+
if (position) {
|
|
80
|
+
diag.position = position;
|
|
81
|
+
}
|
|
82
|
+
if (details !== void 0) {
|
|
83
|
+
diag.details = details;
|
|
84
|
+
}
|
|
85
|
+
return diag;
|
|
86
|
+
}
|
|
87
|
+
function createSchemaError(componentType, message, position, details) {
|
|
88
|
+
return createDiagnostic(
|
|
89
|
+
"schema",
|
|
90
|
+
"error",
|
|
91
|
+
"SCHEMA_VALIDATION_FAILED",
|
|
92
|
+
`Schema validation failed for ui:${componentType}: ${message}`,
|
|
93
|
+
position,
|
|
94
|
+
details
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
function createUnknownComponentInfo(componentType, position) {
|
|
98
|
+
return createDiagnostic(
|
|
99
|
+
"compiler",
|
|
100
|
+
"info",
|
|
101
|
+
"UNKNOWN_COMPONENT_TYPE",
|
|
102
|
+
`Unknown component type "ui:${componentType}". Block preserved as-is.`,
|
|
103
|
+
position
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
function createYamlError(componentType, yamlError, position) {
|
|
107
|
+
return createDiagnostic(
|
|
108
|
+
"parser",
|
|
109
|
+
"error",
|
|
110
|
+
"YAML_PARSE_ERROR",
|
|
111
|
+
`YAML parse error in ui:${componentType}: ${yamlError}`,
|
|
112
|
+
position
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/ast-to-ir.ts
|
|
117
|
+
var DEFAULT_POSITION = {
|
|
118
|
+
start: { line: 0, column: 0 },
|
|
119
|
+
end: { line: 0, column: 0 }
|
|
120
|
+
};
|
|
121
|
+
function isGlyphUIBlock(node) {
|
|
122
|
+
return node.type === "glyphUIBlock";
|
|
123
|
+
}
|
|
124
|
+
function translateNode(node, ctx) {
|
|
125
|
+
if (isGlyphUIBlock(node)) {
|
|
126
|
+
return translateGlyphUIBlock(node, ctx);
|
|
127
|
+
}
|
|
128
|
+
return translateMdastNode(node, ctx);
|
|
129
|
+
}
|
|
130
|
+
function translateGlyphUIBlock(node, ctx) {
|
|
131
|
+
const componentType = node.componentType;
|
|
132
|
+
const blockType = `ui:${componentType}`;
|
|
133
|
+
const position = node.position ?? DEFAULT_POSITION;
|
|
134
|
+
const blockDiagnostics = [];
|
|
135
|
+
const blockId = node.glyphId ? node.glyphId : generateBlockId(ctx.documentId, blockType, node.rawYaml);
|
|
136
|
+
if (node.glyphId) {
|
|
137
|
+
ctx.blockIdMap.set(node.glyphId, blockId);
|
|
138
|
+
}
|
|
139
|
+
if (node.yamlError) {
|
|
140
|
+
const diag = createYamlError(componentType, node.yamlError, position);
|
|
141
|
+
blockDiagnostics.push(diag);
|
|
142
|
+
ctx.diagnostics.push(diag);
|
|
143
|
+
}
|
|
144
|
+
let data = node.parsedData ?? {};
|
|
145
|
+
if (node.parsedData) {
|
|
146
|
+
const schema = componentSchemas.get(componentType);
|
|
147
|
+
if (schema) {
|
|
148
|
+
const result = schema.safeParse(node.parsedData);
|
|
149
|
+
if (!result.success) {
|
|
150
|
+
const zodErrors = result.error.issues.map(
|
|
151
|
+
(issue) => `${issue.path.join(".")}: ${issue.message}`
|
|
152
|
+
).join("; ");
|
|
153
|
+
const diag = createSchemaError(componentType, zodErrors, position, result.error.issues);
|
|
154
|
+
blockDiagnostics.push(diag);
|
|
155
|
+
ctx.diagnostics.push(diag);
|
|
156
|
+
data = node.parsedData;
|
|
157
|
+
} else {
|
|
158
|
+
data = result.data;
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
const diag = createUnknownComponentInfo(componentType, position);
|
|
162
|
+
blockDiagnostics.push(diag);
|
|
163
|
+
ctx.diagnostics.push(diag);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (node.refs && node.refs.length > 0) {
|
|
167
|
+
processRefs(node.refs, blockId, ctx);
|
|
168
|
+
}
|
|
169
|
+
const block = {
|
|
170
|
+
id: blockId,
|
|
171
|
+
type: blockType,
|
|
172
|
+
data,
|
|
173
|
+
position
|
|
174
|
+
};
|
|
175
|
+
if (blockDiagnostics.length > 0) {
|
|
176
|
+
block.diagnostics = blockDiagnostics;
|
|
177
|
+
}
|
|
178
|
+
return block;
|
|
179
|
+
}
|
|
180
|
+
function processRefs(refs, sourceBlockId, ctx) {
|
|
181
|
+
for (const ref of refs) {
|
|
182
|
+
const reference = {
|
|
183
|
+
id: generateBlockId(ctx.documentId, "ref", `${sourceBlockId}->${ref.target}`),
|
|
184
|
+
type: ref.type ?? "navigates-to",
|
|
185
|
+
sourceBlockId,
|
|
186
|
+
targetBlockId: ref.target
|
|
187
|
+
};
|
|
188
|
+
if (ref.label) {
|
|
189
|
+
reference.label = ref.label;
|
|
190
|
+
}
|
|
191
|
+
if (ref.sourceAnchor) {
|
|
192
|
+
reference.sourceAnchor = ref.sourceAnchor;
|
|
193
|
+
}
|
|
194
|
+
if (ref.targetAnchor) {
|
|
195
|
+
reference.targetAnchor = ref.targetAnchor;
|
|
196
|
+
}
|
|
197
|
+
if (ref.bidirectional) {
|
|
198
|
+
reference.bidirectional = ref.bidirectional;
|
|
199
|
+
}
|
|
200
|
+
reference.unresolved = true;
|
|
201
|
+
ctx.references.push(reference);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function translateMdastNode(node, ctx) {
|
|
205
|
+
const position = node.position ?? DEFAULT_POSITION;
|
|
206
|
+
switch (node.type) {
|
|
207
|
+
case "heading":
|
|
208
|
+
return translateHeading(node, position, ctx);
|
|
209
|
+
case "paragraph":
|
|
210
|
+
return translateParagraph(node, position, ctx);
|
|
211
|
+
case "list":
|
|
212
|
+
return translateList(node, position, ctx);
|
|
213
|
+
case "code":
|
|
214
|
+
return translateCode(node, position, ctx);
|
|
215
|
+
case "blockquote":
|
|
216
|
+
return translateBlockquote(node, position, ctx);
|
|
217
|
+
case "image":
|
|
218
|
+
return translateImage(node, position, ctx);
|
|
219
|
+
case "thematicBreak":
|
|
220
|
+
return translateThematicBreak(position, ctx);
|
|
221
|
+
case "html":
|
|
222
|
+
return translateHtml(node, position, ctx);
|
|
223
|
+
case "yaml":
|
|
224
|
+
return null;
|
|
225
|
+
default:
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function translateHeading(node, position, ctx) {
|
|
230
|
+
const depth = node["depth"] ?? 1;
|
|
231
|
+
const children = convertPhrasingContent(node.children ?? []);
|
|
232
|
+
const content = inlineNodesToText(children);
|
|
233
|
+
return {
|
|
234
|
+
id: generateBlockId(ctx.documentId, "heading", content),
|
|
235
|
+
type: "heading",
|
|
236
|
+
data: { depth, children },
|
|
237
|
+
position
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function translateParagraph(node, position, ctx) {
|
|
241
|
+
const children = convertPhrasingContent(node.children ?? []);
|
|
242
|
+
const content = inlineNodesToText(children);
|
|
243
|
+
return {
|
|
244
|
+
id: generateBlockId(ctx.documentId, "paragraph", content),
|
|
245
|
+
type: "paragraph",
|
|
246
|
+
data: { children },
|
|
247
|
+
position
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function translateList(node, position, ctx) {
|
|
251
|
+
const ordered = node["ordered"] ?? false;
|
|
252
|
+
const start = ordered ? node["start"] ?? 1 : void 0;
|
|
253
|
+
const items = translateListItems(node.children ?? []);
|
|
254
|
+
const content = listDataToText({ items });
|
|
255
|
+
const data = { ordered, items };
|
|
256
|
+
if (start !== void 0) {
|
|
257
|
+
data.start = start;
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
id: generateBlockId(ctx.documentId, "list", content),
|
|
261
|
+
type: "list",
|
|
262
|
+
data,
|
|
263
|
+
position
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function translateListItems(listItemNodes) {
|
|
267
|
+
const items = [];
|
|
268
|
+
for (const itemNode of listItemNodes) {
|
|
269
|
+
if (itemNode.type !== "listItem") continue;
|
|
270
|
+
const itemChildren = itemNode.children ?? [];
|
|
271
|
+
let inlineChildren = [];
|
|
272
|
+
let subList;
|
|
273
|
+
for (const child of itemChildren) {
|
|
274
|
+
if (child.type === "paragraph") {
|
|
275
|
+
inlineChildren = convertPhrasingContent(child.children ?? []);
|
|
276
|
+
} else if (child.type === "list") {
|
|
277
|
+
const subOrdered = child["ordered"] ?? false;
|
|
278
|
+
const subStart = subOrdered ? child["start"] ?? 1 : void 0;
|
|
279
|
+
const subItems = translateListItems(child.children ?? []);
|
|
280
|
+
subList = { ordered: subOrdered, items: subItems };
|
|
281
|
+
if (subStart !== void 0) {
|
|
282
|
+
subList.start = subStart;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const item = { children: inlineChildren };
|
|
287
|
+
if (subList) {
|
|
288
|
+
item.subList = subList;
|
|
289
|
+
}
|
|
290
|
+
items.push(item);
|
|
291
|
+
}
|
|
292
|
+
return items;
|
|
293
|
+
}
|
|
294
|
+
function translateCode(node, position, ctx) {
|
|
295
|
+
const value = node.value ?? "";
|
|
296
|
+
const language = node["lang"] ?? void 0;
|
|
297
|
+
const meta = node["meta"] ?? void 0;
|
|
298
|
+
const data = { value };
|
|
299
|
+
if (language) {
|
|
300
|
+
data["language"] = language;
|
|
301
|
+
}
|
|
302
|
+
if (meta) {
|
|
303
|
+
data["meta"] = meta;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
id: generateBlockId(ctx.documentId, "code", value),
|
|
307
|
+
type: "code",
|
|
308
|
+
data,
|
|
309
|
+
position
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function translateBlockquote(node, position, ctx) {
|
|
313
|
+
const allInlineNodes = [];
|
|
314
|
+
for (const child of node.children ?? []) {
|
|
315
|
+
if (child.type === "paragraph") {
|
|
316
|
+
const inlines = convertPhrasingContent(child.children ?? []);
|
|
317
|
+
allInlineNodes.push(...inlines);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const content = inlineNodesToText(allInlineNodes);
|
|
321
|
+
return {
|
|
322
|
+
id: generateBlockId(ctx.documentId, "blockquote", content),
|
|
323
|
+
type: "blockquote",
|
|
324
|
+
data: { children: allInlineNodes },
|
|
325
|
+
position
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function translateImage(node, position, ctx) {
|
|
329
|
+
const src = node["url"] ?? "";
|
|
330
|
+
const alt = node["alt"] ?? void 0;
|
|
331
|
+
const title = node["title"] ?? void 0;
|
|
332
|
+
const data = { src };
|
|
333
|
+
if (alt) {
|
|
334
|
+
data["alt"] = alt;
|
|
335
|
+
}
|
|
336
|
+
if (title) {
|
|
337
|
+
data["title"] = title;
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
id: generateBlockId(ctx.documentId, "image", src),
|
|
341
|
+
type: "image",
|
|
342
|
+
data,
|
|
343
|
+
position
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function translateThematicBreak(position, ctx) {
|
|
347
|
+
return {
|
|
348
|
+
id: generateBlockId(ctx.documentId, "thematic-break", "---"),
|
|
349
|
+
type: "thematic-break",
|
|
350
|
+
data: {},
|
|
351
|
+
position
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function translateHtml(node, position, ctx) {
|
|
355
|
+
const value = node.value ?? "";
|
|
356
|
+
return {
|
|
357
|
+
id: generateBlockId(ctx.documentId, "html", value),
|
|
358
|
+
type: "html",
|
|
359
|
+
data: { value },
|
|
360
|
+
position
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function inlineNodesToText(nodes) {
|
|
364
|
+
return nodes.map((node) => {
|
|
365
|
+
switch (node.type) {
|
|
366
|
+
case "text":
|
|
367
|
+
return node.value;
|
|
368
|
+
case "inlineCode":
|
|
369
|
+
return node.value;
|
|
370
|
+
case "strong":
|
|
371
|
+
case "emphasis":
|
|
372
|
+
case "delete":
|
|
373
|
+
return inlineNodesToText(node.children);
|
|
374
|
+
case "link":
|
|
375
|
+
return inlineNodesToText(node.children);
|
|
376
|
+
case "image":
|
|
377
|
+
return node.alt ?? "";
|
|
378
|
+
case "break":
|
|
379
|
+
return "\n";
|
|
380
|
+
default:
|
|
381
|
+
return "";
|
|
382
|
+
}
|
|
383
|
+
}).join("");
|
|
384
|
+
}
|
|
385
|
+
function listDataToText(data) {
|
|
386
|
+
return data.items.map((item) => {
|
|
387
|
+
let text = inlineNodesToText(item.children);
|
|
388
|
+
if (item.subList) {
|
|
389
|
+
text += "\n" + listDataToText(item.subList);
|
|
390
|
+
}
|
|
391
|
+
return text;
|
|
392
|
+
}).join("\n");
|
|
393
|
+
}
|
|
394
|
+
var GLYPH_LINK_PREFIX = "#glyph:";
|
|
395
|
+
function extractInlineReferences(block, documentId) {
|
|
396
|
+
const references = [];
|
|
397
|
+
const inlineNodes = extractInlineNodes(block);
|
|
398
|
+
for (const node of inlineNodes) {
|
|
399
|
+
if (node.type === "link" && node.url.startsWith(GLYPH_LINK_PREFIX)) {
|
|
400
|
+
const targetBlockId = node.url.slice(GLYPH_LINK_PREFIX.length);
|
|
401
|
+
if (!targetBlockId) continue;
|
|
402
|
+
const label = inlineNodesToPlainText(node.children);
|
|
403
|
+
const reference = {
|
|
404
|
+
id: generateBlockId(documentId, "ref", `${block.id}->${targetBlockId}`),
|
|
405
|
+
type: "navigates-to",
|
|
406
|
+
sourceBlockId: block.id,
|
|
407
|
+
targetBlockId,
|
|
408
|
+
unresolved: true
|
|
409
|
+
};
|
|
410
|
+
if (label) {
|
|
411
|
+
reference.label = label;
|
|
412
|
+
}
|
|
413
|
+
references.push(reference);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return references;
|
|
417
|
+
}
|
|
418
|
+
function extractAllInlineReferences(blocks, documentId) {
|
|
419
|
+
const references = [];
|
|
420
|
+
for (const block of blocks) {
|
|
421
|
+
const blockRefs = extractInlineReferences(block, documentId);
|
|
422
|
+
references.push(...blockRefs);
|
|
423
|
+
if (block.children) {
|
|
424
|
+
const childRefs = extractAllInlineReferences(block.children, documentId);
|
|
425
|
+
references.push(...childRefs);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return references;
|
|
429
|
+
}
|
|
430
|
+
function resolveReferences(references, blocks, diagnostics) {
|
|
431
|
+
const blockIdSet = collectAllBlockIds(blocks);
|
|
432
|
+
for (const ref of references) {
|
|
433
|
+
if (blockIdSet.has(ref.targetBlockId)) {
|
|
434
|
+
ref.unresolved = false;
|
|
435
|
+
} else {
|
|
436
|
+
ref.unresolved = true;
|
|
437
|
+
diagnostics.push(
|
|
438
|
+
createDiagnostic(
|
|
439
|
+
"compiler",
|
|
440
|
+
"warning",
|
|
441
|
+
"UNRESOLVED_REFERENCE",
|
|
442
|
+
`Reference target "${ref.targetBlockId}" was not found in the document.`
|
|
443
|
+
)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function collectAllBlockIds(blocks) {
|
|
449
|
+
const ids = /* @__PURE__ */ new Set();
|
|
450
|
+
for (const block of blocks) {
|
|
451
|
+
ids.add(block.id);
|
|
452
|
+
if (block.children) {
|
|
453
|
+
for (const id of collectAllBlockIds(block.children)) {
|
|
454
|
+
ids.add(id);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return ids;
|
|
459
|
+
}
|
|
460
|
+
function validateGlyphIdUniqueness(blockIdMap, blocks, diagnostics) {
|
|
461
|
+
const glyphIdOccurrences = /* @__PURE__ */ new Map();
|
|
462
|
+
for (const glyphId of blockIdMap.keys()) {
|
|
463
|
+
const count = countBlocksWithGlyphId(blocks, glyphId);
|
|
464
|
+
glyphIdOccurrences.set(glyphId, count);
|
|
465
|
+
}
|
|
466
|
+
for (const [glyphId, count] of glyphIdOccurrences) {
|
|
467
|
+
if (count > 1) {
|
|
468
|
+
diagnostics.push(
|
|
469
|
+
createDiagnostic(
|
|
470
|
+
"compiler",
|
|
471
|
+
"error",
|
|
472
|
+
"DUPLICATE_GLYPH_ID",
|
|
473
|
+
`Duplicate glyph-id "${glyphId}" found on ${String(count)} blocks. Block IDs must be unique within a document.`
|
|
474
|
+
)
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function countBlocksWithGlyphId(blocks, glyphId) {
|
|
480
|
+
let count = 0;
|
|
481
|
+
for (const block of blocks) {
|
|
482
|
+
if (block.id === glyphId) {
|
|
483
|
+
count++;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return count;
|
|
487
|
+
}
|
|
488
|
+
function extractInlineNodes(block) {
|
|
489
|
+
const data = block.data;
|
|
490
|
+
const nodes = [];
|
|
491
|
+
if (Array.isArray(data["children"])) {
|
|
492
|
+
nodes.push(...data["children"]);
|
|
493
|
+
}
|
|
494
|
+
if (Array.isArray(data["items"])) {
|
|
495
|
+
for (const item of data["items"]) {
|
|
496
|
+
if (Array.isArray(item.children)) {
|
|
497
|
+
nodes.push(...item.children);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return nodes;
|
|
502
|
+
}
|
|
503
|
+
function inlineNodesToPlainText(nodes) {
|
|
504
|
+
return nodes.map((node) => {
|
|
505
|
+
switch (node.type) {
|
|
506
|
+
case "text":
|
|
507
|
+
return node.value;
|
|
508
|
+
case "inlineCode":
|
|
509
|
+
return node.value;
|
|
510
|
+
case "strong":
|
|
511
|
+
case "emphasis":
|
|
512
|
+
case "delete":
|
|
513
|
+
return inlineNodesToPlainText(node.children);
|
|
514
|
+
case "link":
|
|
515
|
+
return inlineNodesToPlainText(node.children);
|
|
516
|
+
case "image":
|
|
517
|
+
return node.alt ?? "";
|
|
518
|
+
case "break":
|
|
519
|
+
return "\n";
|
|
520
|
+
default:
|
|
521
|
+
return "";
|
|
522
|
+
}
|
|
523
|
+
}).join("");
|
|
524
|
+
}
|
|
525
|
+
function compileContainerBlocks(blocks, ctx) {
|
|
526
|
+
for (const block of blocks) {
|
|
527
|
+
if (block.type === "ui:tabs") {
|
|
528
|
+
compileTabsBlock(block, ctx);
|
|
529
|
+
} else if (block.type === "ui:steps") {
|
|
530
|
+
compileStepsBlock(block, ctx);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function compileTabsBlock(block, ctx) {
|
|
535
|
+
const data = block.data;
|
|
536
|
+
const tabs = data["tabs"];
|
|
537
|
+
if (!Array.isArray(tabs)) return;
|
|
538
|
+
const allChildren = [];
|
|
539
|
+
for (const tab of tabs) {
|
|
540
|
+
if (typeof tab.content !== "string") continue;
|
|
541
|
+
const childBlocks = parseContentToBlocks(tab.content, block, ctx);
|
|
542
|
+
allChildren.push(...childBlocks);
|
|
543
|
+
}
|
|
544
|
+
if (allChildren.length > 0) {
|
|
545
|
+
block.children = allChildren;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function compileStepsBlock(block, ctx) {
|
|
549
|
+
const data = block.data;
|
|
550
|
+
const steps = data["steps"];
|
|
551
|
+
if (!Array.isArray(steps)) return;
|
|
552
|
+
const allChildren = [];
|
|
553
|
+
for (const step of steps) {
|
|
554
|
+
if (typeof step.content !== "string") continue;
|
|
555
|
+
const childBlocks = parseContentToBlocks(step.content, block, ctx);
|
|
556
|
+
allChildren.push(...childBlocks);
|
|
557
|
+
}
|
|
558
|
+
if (allChildren.length > 0) {
|
|
559
|
+
block.children = allChildren;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function parseContentToBlocks(content, parentBlock, ctx) {
|
|
563
|
+
const ast = parseGlyphMarkdown(content);
|
|
564
|
+
const blocks = [];
|
|
565
|
+
for (const child of ast.children) {
|
|
566
|
+
if (child.type === "glyphUIBlock") {
|
|
567
|
+
ctx.diagnostics.push(
|
|
568
|
+
createDiagnostic(
|
|
569
|
+
"compiler",
|
|
570
|
+
"warning",
|
|
571
|
+
"NESTED_UI_COMPONENT",
|
|
572
|
+
`Nested ui: component found inside container block "${parentBlock.id}". Nested ui: components inside tabs/steps are not supported in v1 and will be ignored.`,
|
|
573
|
+
child.position
|
|
574
|
+
)
|
|
575
|
+
);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
const block = translateNode(child, ctx);
|
|
579
|
+
if (block) {
|
|
580
|
+
blocks.push(block);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return blocks;
|
|
584
|
+
}
|
|
585
|
+
function hasNestedUiBlocks(content) {
|
|
586
|
+
return /```ui:/m.test(content);
|
|
587
|
+
}
|
|
588
|
+
function validateContainerBlocks(blocks, diagnostics) {
|
|
589
|
+
for (const block of blocks) {
|
|
590
|
+
if (block.type === "ui:tabs") {
|
|
591
|
+
validateTabsData(block, diagnostics);
|
|
592
|
+
} else if (block.type === "ui:steps") {
|
|
593
|
+
validateStepsData(block, diagnostics);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function validateTabsData(block, _diagnostics) {
|
|
598
|
+
const data = block.data;
|
|
599
|
+
const tabs = data["tabs"];
|
|
600
|
+
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
for (const tab of tabs) {
|
|
604
|
+
if (!tab.label) {
|
|
605
|
+
_diagnostics.push(
|
|
606
|
+
createDiagnostic(
|
|
607
|
+
"compiler",
|
|
608
|
+
"warning",
|
|
609
|
+
"MISSING_TAB_LABEL",
|
|
610
|
+
`A tab in block "${block.id}" is missing a label.`,
|
|
611
|
+
block.position
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function validateStepsData(block, _diagnostics) {
|
|
618
|
+
const data = block.data;
|
|
619
|
+
const steps = data["steps"];
|
|
620
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
for (const step of steps) {
|
|
624
|
+
if (!step.title) {
|
|
625
|
+
_diagnostics.push(
|
|
626
|
+
createDiagnostic(
|
|
627
|
+
"compiler",
|
|
628
|
+
"warning",
|
|
629
|
+
"MISSING_STEP_TITLE",
|
|
630
|
+
`A step in block "${block.id}" is missing a title.`,
|
|
631
|
+
block.position
|
|
632
|
+
)
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/compile.ts
|
|
639
|
+
function compile(markdown, options) {
|
|
640
|
+
const diagnostics = [];
|
|
641
|
+
const ast = parseGlyphMarkdown(markdown);
|
|
642
|
+
const { metadata, layout, frontmatterGlyphId } = extractFrontmatter(ast.children, diagnostics);
|
|
643
|
+
const documentId = options?.documentId ?? generateDocumentId({
|
|
644
|
+
glyphId: frontmatterGlyphId,
|
|
645
|
+
filePath: options?.filePath,
|
|
646
|
+
content: markdown
|
|
647
|
+
});
|
|
648
|
+
if (options?.filePath && !metadata.sourceFile) {
|
|
649
|
+
metadata.sourceFile = options.filePath;
|
|
650
|
+
}
|
|
651
|
+
const references = [];
|
|
652
|
+
const ctx = {
|
|
653
|
+
documentId,
|
|
654
|
+
diagnostics,
|
|
655
|
+
references,
|
|
656
|
+
blockIdMap: /* @__PURE__ */ new Map()
|
|
657
|
+
};
|
|
658
|
+
const blocks = [];
|
|
659
|
+
for (const child of ast.children) {
|
|
660
|
+
const block = translateNode(child, ctx);
|
|
661
|
+
if (block) {
|
|
662
|
+
blocks.push(block);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
compileContainerBlocks(blocks, ctx);
|
|
666
|
+
validateContainerBlocks(blocks, diagnostics);
|
|
667
|
+
inferMetadata(metadata, blocks);
|
|
668
|
+
const blockIds = blocks.map((b) => b.id);
|
|
669
|
+
const resolvedIds = resolveBlockIdCollisions(blockIds);
|
|
670
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
671
|
+
const block = blocks[i];
|
|
672
|
+
const resolvedId = resolvedIds[i];
|
|
673
|
+
if (block && resolvedId) {
|
|
674
|
+
block.id = resolvedId;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
validateGlyphIdUniqueness(ctx.blockIdMap, blocks, diagnostics);
|
|
678
|
+
const inlineRefs = extractAllInlineReferences(blocks, documentId);
|
|
679
|
+
references.push(...inlineRefs);
|
|
680
|
+
resolveReferences(references, blocks, diagnostics);
|
|
681
|
+
const ir = {
|
|
682
|
+
version: "1.0.0",
|
|
683
|
+
id: documentId,
|
|
684
|
+
metadata,
|
|
685
|
+
blocks,
|
|
686
|
+
references,
|
|
687
|
+
layout
|
|
688
|
+
};
|
|
689
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
690
|
+
return { ir, diagnostics, hasErrors };
|
|
691
|
+
}
|
|
692
|
+
function extractFrontmatter(children, diagnostics) {
|
|
693
|
+
const metadata = {};
|
|
694
|
+
let layout = { mode: "document", spacing: "normal" };
|
|
695
|
+
let frontmatterGlyphId;
|
|
696
|
+
const firstChild = children[0];
|
|
697
|
+
if (firstChild && firstChild.type === "yaml" && typeof firstChild.value === "string") {
|
|
698
|
+
try {
|
|
699
|
+
const parsed = parse(firstChild.value);
|
|
700
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
701
|
+
const fm = parsed;
|
|
702
|
+
if (typeof fm["glyph-id"] === "string") {
|
|
703
|
+
frontmatterGlyphId = fm["glyph-id"];
|
|
704
|
+
}
|
|
705
|
+
if (typeof fm["title"] === "string") {
|
|
706
|
+
metadata.title = fm["title"];
|
|
707
|
+
}
|
|
708
|
+
if (typeof fm["description"] === "string") {
|
|
709
|
+
metadata.description = fm["description"];
|
|
710
|
+
}
|
|
711
|
+
if (Array.isArray(fm["authors"])) {
|
|
712
|
+
metadata.authors = fm["authors"].filter(
|
|
713
|
+
(a) => typeof a === "string"
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
if (typeof fm["createdAt"] === "string") {
|
|
717
|
+
metadata.createdAt = fm["createdAt"];
|
|
718
|
+
}
|
|
719
|
+
if (Array.isArray(fm["tags"])) {
|
|
720
|
+
metadata.tags = fm["tags"].filter(
|
|
721
|
+
(t) => typeof t === "string"
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
if (fm["layout"] && typeof fm["layout"] === "object" && !Array.isArray(fm["layout"])) {
|
|
725
|
+
const rawLayout = fm["layout"];
|
|
726
|
+
layout = {
|
|
727
|
+
mode: isLayoutMode(rawLayout["mode"]) ? rawLayout["mode"] : "document",
|
|
728
|
+
spacing: isLayoutSpacing(rawLayout["spacing"]) ? rawLayout["spacing"] : "normal"
|
|
729
|
+
};
|
|
730
|
+
if (typeof rawLayout["columns"] === "number") {
|
|
731
|
+
layout.columns = rawLayout["columns"];
|
|
732
|
+
}
|
|
733
|
+
if (typeof rawLayout["maxWidth"] === "string") {
|
|
734
|
+
layout.maxWidth = rawLayout["maxWidth"];
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
} catch (err) {
|
|
739
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
740
|
+
diagnostics.push(
|
|
741
|
+
createDiagnostic(
|
|
742
|
+
"parser",
|
|
743
|
+
"error",
|
|
744
|
+
"FRONTMATTER_PARSE_ERROR",
|
|
745
|
+
`Failed to parse frontmatter YAML: ${message}`,
|
|
746
|
+
firstChild.position
|
|
747
|
+
)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return { metadata, layout, frontmatterGlyphId };
|
|
752
|
+
}
|
|
753
|
+
function isLayoutMode(value) {
|
|
754
|
+
return value === "document" || value === "dashboard" || value === "presentation";
|
|
755
|
+
}
|
|
756
|
+
function isLayoutSpacing(value) {
|
|
757
|
+
return value === "compact" || value === "normal" || value === "relaxed";
|
|
758
|
+
}
|
|
759
|
+
function inferMetadata(metadata, blocks) {
|
|
760
|
+
if (!metadata.title) {
|
|
761
|
+
const firstHeading = blocks.find(
|
|
762
|
+
(b) => b.type === "heading" && b.data["depth"] === 1
|
|
763
|
+
);
|
|
764
|
+
if (firstHeading) {
|
|
765
|
+
const data = firstHeading.data;
|
|
766
|
+
if (data.children) {
|
|
767
|
+
metadata.title = data.children.map((node) => {
|
|
768
|
+
if ("value" in node && typeof node.value === "string") {
|
|
769
|
+
return node.value;
|
|
770
|
+
}
|
|
771
|
+
return "";
|
|
772
|
+
}).join("");
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (!metadata.description) {
|
|
777
|
+
const firstParagraph = blocks.find((b) => b.type === "paragraph");
|
|
778
|
+
if (firstParagraph) {
|
|
779
|
+
const data = firstParagraph.data;
|
|
780
|
+
if (data.children) {
|
|
781
|
+
metadata.description = data.children.map((node) => {
|
|
782
|
+
if ("value" in node && typeof node.value === "string") {
|
|
783
|
+
return node.value;
|
|
784
|
+
}
|
|
785
|
+
return "";
|
|
786
|
+
}).join("");
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
export { compile, compileContainerBlocks, convertPhrasingContent, createDiagnostic, createSchemaError, createUnknownComponentInfo, createYamlError, extractAllInlineReferences, extractInlineReferences, hasNestedUiBlocks, resolveReferences, translateNode, validateContainerBlocks, validateGlyphIdUniqueness };
|
|
793
|
+
//# sourceMappingURL=index.js.map
|
|
794
|
+
//# sourceMappingURL=index.js.map
|