@bitcobblers/wod-wiki-engine 0.35.2116 → 0.35.2117
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/dist/bin/wod.cjs +912 -937
- package/dist/bin/wod.cjs.map +1 -1
- package/dist/bin/wod.mjs +2 -2
- package/dist/{chunk-B2OYLNEZ.mjs → chunk-AKNXDA75.mjs} +735 -679
- package/dist/chunk-AKNXDA75.mjs.map +1 -0
- package/dist/chunk-Y6WO6MXS.mjs +187 -0
- package/dist/chunk-Y6WO6MXS.mjs.map +1 -0
- package/dist/{compiler-KTBR6OEL.mjs → compiler-MQRZVZQW.mjs} +64 -326
- package/dist/compiler-MQRZVZQW.mjs.map +1 -0
- package/dist/index.cjs +924 -936
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +93 -23
- package/dist/index.d.ts +93 -23
- package/dist/index.mjs +8 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-6L5ZDZJ7.mjs +0 -15
- package/dist/chunk-6L5ZDZJ7.mjs.map +0 -1
- package/dist/chunk-B2OYLNEZ.mjs.map +0 -1
- package/dist/compiler-KTBR6OEL.mjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
|
-
import './chunk-
|
|
3
|
-
import { parseScript
|
|
4
|
-
import { parseDocumentSections } from '@bitcobblers/wod-wiki-core';
|
|
2
|
+
import { parseFrontmatter, SEED_REGISTERED_WIDGETS, canvasRouteSlug, seedNoteSourceId, parseEffortFile, TEMPLATE_CHUNK_ID, SEED_SCHEMA, DASHBOARDS_CHUNK_ID } from './chunk-Y6WO6MXS.mjs';
|
|
3
|
+
import { parseScript } from '@bitcobblers/wod-wiki-lang';
|
|
4
|
+
import { reconcileNoteBlocks, parseDocumentSections, blockContentId } from '@bitcobblers/wod-wiki-core';
|
|
5
5
|
import { parseDashboardNote, buildDashboardDocument, isDashboardWidgetType, resolveWidgetType, parseQuery } from '@bitcobblers/wod-wiki-wql';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
7
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, rmSync, readdirSync } from 'fs';
|
|
@@ -12,20 +12,6 @@ if (typeof globalThis.require === 'undefined') {
|
|
|
12
12
|
try { globalThis.require = createRequire(import.meta.url); } catch {}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// src/cli/seedbuild/contract.ts
|
|
16
|
-
var SEED_SCHEMA = 7;
|
|
17
|
-
var TEMPLATE_CHUNK_ID = "template";
|
|
18
|
-
var DASHBOARDS_CHUNK_ID = "dashboards";
|
|
19
|
-
var BLOCK_INDEX_CHUNK_PREFIX = "block-index.";
|
|
20
|
-
var BLOCK_EFFORTS_CHUNK_PREFIX = "block-efforts.";
|
|
21
|
-
function canvasRouteSlug(content) {
|
|
22
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
23
|
-
if (fm?.[1] && !/^template:\s*canvas\s*$/m.test(fm[1])) return null;
|
|
24
|
-
const route = fm?.[1]?.match(/^route:\s*(\S+)\s*$/m)?.[1];
|
|
25
|
-
if (!route) return null;
|
|
26
|
-
return route.replace(/^\//, "") || null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
15
|
// src/cli/seedbuild/effort-slugs.ts
|
|
30
16
|
function extractEffortSlugs(statements) {
|
|
31
17
|
const slugs = /* @__PURE__ */ new Set();
|
|
@@ -84,141 +70,6 @@ function fileToDisplayName(filename) {
|
|
|
84
70
|
}
|
|
85
71
|
return titled.join(" ");
|
|
86
72
|
}
|
|
87
|
-
|
|
88
|
-
// src/cli/seedbuild/frontmatter.ts
|
|
89
|
-
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
90
|
-
function unquote(value) {
|
|
91
|
-
return value.replace(
|
|
92
|
-
/^(['"])(.*)\1$/,
|
|
93
|
-
(_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
function parseFrontmatter(raw) {
|
|
97
|
-
const match = raw.match(FRONTMATTER_RE);
|
|
98
|
-
if (!match) return { meta: {}, body: raw };
|
|
99
|
-
return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
|
|
100
|
-
}
|
|
101
|
-
function parseMetaLines(lines) {
|
|
102
|
-
const meta = {};
|
|
103
|
-
for (let i = 0; i < lines.length; i++) {
|
|
104
|
-
const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
|
|
105
|
-
if (!keyMatch) continue;
|
|
106
|
-
const key = keyMatch[1];
|
|
107
|
-
const rawVal = keyMatch[2].trim();
|
|
108
|
-
if (rawVal === "") {
|
|
109
|
-
const items = [];
|
|
110
|
-
while (i + 1 < lines.length) {
|
|
111
|
-
const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
|
|
112
|
-
if (!itemMatch) break;
|
|
113
|
-
items.push(itemMatch[1].trim());
|
|
114
|
-
i++;
|
|
115
|
-
}
|
|
116
|
-
meta[key] = items.length > 0 ? items : "";
|
|
117
|
-
} else {
|
|
118
|
-
const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
|
|
119
|
-
const value = unquote(rawVal);
|
|
120
|
-
const num = Number(value);
|
|
121
|
-
meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return meta;
|
|
125
|
-
}
|
|
126
|
-
function getScalar(meta, key) {
|
|
127
|
-
const value = meta[key];
|
|
128
|
-
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
129
|
-
}
|
|
130
|
-
function getList(meta, key) {
|
|
131
|
-
const value = meta[key];
|
|
132
|
-
return Array.isArray(value) ? value : [];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// src/cli/seedbuild/effort-file.ts
|
|
136
|
-
function parseDiscipline(val) {
|
|
137
|
-
if (!val) return void 0;
|
|
138
|
-
const normalized = unquoteYaml(val.trim()).toLowerCase();
|
|
139
|
-
if (isEffortDiscipline(normalized)) return normalized;
|
|
140
|
-
console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
|
|
141
|
-
return void 0;
|
|
142
|
-
}
|
|
143
|
-
function unquoteYaml(val) {
|
|
144
|
-
if (val.startsWith('"') && val.endsWith('"')) {
|
|
145
|
-
return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
146
|
-
}
|
|
147
|
-
if (val.startsWith("'") && val.endsWith("'")) {
|
|
148
|
-
return val.slice(1, -1).replace(/\\'/g, "'");
|
|
149
|
-
}
|
|
150
|
-
return val;
|
|
151
|
-
}
|
|
152
|
-
function coerceNestedScalar(raw) {
|
|
153
|
-
if (raw === "true") return true;
|
|
154
|
-
if (raw === "false") return false;
|
|
155
|
-
if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
|
|
156
|
-
return raw;
|
|
157
|
-
}
|
|
158
|
-
function readNestedSection(lines, key) {
|
|
159
|
-
const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
|
|
160
|
-
if (start === -1) return void 0;
|
|
161
|
-
const result = {};
|
|
162
|
-
let subKey = null;
|
|
163
|
-
for (let i = start + 1; i < lines.length; i++) {
|
|
164
|
-
const line = lines[i];
|
|
165
|
-
if (/^\S/.test(line)) break;
|
|
166
|
-
const trimmed = line.trim();
|
|
167
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
168
|
-
const indent = line.length - line.trimStart().length;
|
|
169
|
-
const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
|
|
170
|
-
if (!m) continue;
|
|
171
|
-
const [, k, rawVal] = m;
|
|
172
|
-
if (indent === 2) {
|
|
173
|
-
if (rawVal === "") {
|
|
174
|
-
subKey = k;
|
|
175
|
-
result[k] = {};
|
|
176
|
-
} else {
|
|
177
|
-
subKey = null;
|
|
178
|
-
result[k] = coerceNestedScalar(rawVal);
|
|
179
|
-
}
|
|
180
|
-
} else if (indent >= 4 && subKey !== null) {
|
|
181
|
-
result[subKey][k] = coerceNestedScalar(rawVal);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
185
|
-
}
|
|
186
|
-
function parseEffortFile(raw) {
|
|
187
|
-
const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
188
|
-
if (!blockMatch) return null;
|
|
189
|
-
const { meta, body } = parseFrontmatter(raw);
|
|
190
|
-
const innerLines = blockMatch[1].split(/\r?\n/);
|
|
191
|
-
const id = getScalar(meta, "id");
|
|
192
|
-
const slug = getScalar(meta, "slug");
|
|
193
|
-
const label = getScalar(meta, "label");
|
|
194
|
-
const aliases = getList(meta, "aliases");
|
|
195
|
-
const baseAttrs = readNestedSection(innerLines, "baseAttributes");
|
|
196
|
-
const hints = readNestedSection(innerLines, "hints");
|
|
197
|
-
const derivation = readNestedSection(innerLines, "derivation");
|
|
198
|
-
const metRaw = baseAttrs?.met;
|
|
199
|
-
const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
|
|
200
|
-
if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
|
|
201
|
-
const discipline = parseDiscipline(
|
|
202
|
-
typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
|
|
203
|
-
);
|
|
204
|
-
const intensityTier = baseAttrs?.intensityTier;
|
|
205
|
-
const trimmedBody = body.trim();
|
|
206
|
-
return {
|
|
207
|
-
id: String(id),
|
|
208
|
-
slug: String(slug),
|
|
209
|
-
label: String(label),
|
|
210
|
-
aliases,
|
|
211
|
-
baseAttributes: {
|
|
212
|
-
met,
|
|
213
|
-
...discipline ? { discipline } : {},
|
|
214
|
-
...intensityTier ? { intensityTier } : {}
|
|
215
|
-
},
|
|
216
|
-
registrySource: "bundled",
|
|
217
|
-
...trimmedBody ? { body: trimmedBody } : {},
|
|
218
|
-
...hints ? { hints } : {},
|
|
219
|
-
...derivation ? { derivation } : {}
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
73
|
var UNKNOWN_GIT_VERSION = {
|
|
223
74
|
version: 0,
|
|
224
75
|
versionLabel: "unknown",
|
|
@@ -381,26 +232,6 @@ function gateDashboards(groups, corpusRoot) {
|
|
|
381
232
|
}
|
|
382
233
|
}
|
|
383
234
|
}
|
|
384
|
-
function toSegmentDataType(section) {
|
|
385
|
-
switch (section.type) {
|
|
386
|
-
case "time":
|
|
387
|
-
case "log":
|
|
388
|
-
return "wod";
|
|
389
|
-
case "title": {
|
|
390
|
-
const l = section.level ?? 1;
|
|
391
|
-
return `h${Math.min(Math.max(l, 1), 6)}`;
|
|
392
|
-
}
|
|
393
|
-
case "frontmatter":
|
|
394
|
-
return "frontmatter";
|
|
395
|
-
case "markdown":
|
|
396
|
-
case "widget":
|
|
397
|
-
case "embed":
|
|
398
|
-
case "code":
|
|
399
|
-
return "markdown";
|
|
400
|
-
default:
|
|
401
|
-
return "markdown";
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
235
|
function getFileCreatedAt(file, repoRoot) {
|
|
405
236
|
try {
|
|
406
237
|
const out = execSync(`git log -1 --format=%ct -- '${file}'`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
@@ -415,106 +246,32 @@ function feedDateToCreatedAt(dateKey) {
|
|
|
415
246
|
const ts = Date.parse(`${dateKey}T00:00:00Z`);
|
|
416
247
|
return Number.isNaN(ts) ? 0 : ts;
|
|
417
248
|
}
|
|
418
|
-
|
|
249
|
+
function compileSeedRow(path, content, metadata = {}) {
|
|
250
|
+
let ordinal = 0;
|
|
251
|
+
const blocks = reconcileNoteBlocks(content, { sourceRevision: 1, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${path}:${ordinal++}` });
|
|
252
|
+
const noteId = metadata.routeId ?? path;
|
|
253
|
+
const sections = parseDocumentSections(content);
|
|
254
|
+
const blockIndex = Object.values(blocks).map((block, position) => {
|
|
255
|
+
const section = sections.find((section2) => section2.from === block.from && section2.to === block.to);
|
|
256
|
+
return { id: `${noteId}:${block.id}`, noteId, blockId: block.id, blockVersion: 1, sourceRevision: 1, from: block.from, to: block.to, position, dataType: block.kind, ...block.kind === "wod" ? { blockContentId: blockContentId(section?.scriptBlock?.content ?? content.slice(block.from, block.to)) } : {}, rawContent: content.slice(block.from, block.to), noteTitle: metadata.noteTitle ?? fileToDisplayName(path.split("/").at(-1) ?? path), createdAt: metadata.createdAt ?? 0, isStatic: true, sourceId: metadata.sourceId, sourcePath: path, routeId: metadata.routeId };
|
|
257
|
+
});
|
|
258
|
+
return { path, content, blocks, blockIndex, blockEfforts: [] };
|
|
259
|
+
}
|
|
419
260
|
function buildBlockIndexRows(markdownDir, corpusRoot) {
|
|
420
|
-
const
|
|
421
|
-
for (const
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
const noteTitle = headingMatch?.[1]?.trim() || fileToDisplayName(isReadme ? dirName : fileNameExt);
|
|
433
|
-
const createdAt = getFileCreatedAt(rel, corpusRoot);
|
|
434
|
-
const sections = parseDocumentSections(content);
|
|
435
|
-
let position = 0;
|
|
436
|
-
for (const section of sections) {
|
|
437
|
-
const blockContentId = section.scriptBlock?.contentId ?? void 0;
|
|
438
|
-
index.push({
|
|
439
|
-
id: `static:${noteId}:${section.id}:1`,
|
|
440
|
-
noteId,
|
|
441
|
-
segmentId: section.id,
|
|
442
|
-
segmentVersion: 1,
|
|
443
|
-
position: position++,
|
|
444
|
-
dataType: toSegmentDataType(section),
|
|
445
|
-
blockContentId,
|
|
446
|
-
rawContent: section.displayContent,
|
|
447
|
-
noteTitle,
|
|
448
|
-
createdAt,
|
|
449
|
-
isStatic: true,
|
|
450
|
-
sourceId: isReadme ? `collection:${dirName}` : `collection:${noteId}`,
|
|
451
|
-
sourcePath: rel
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
for (const file of listMarkdownFiles(join(markdownDir, "feeds"))) {
|
|
456
|
-
const rel = `markdown/feeds/${file}`;
|
|
457
|
-
const parts = rel.split("/");
|
|
458
|
-
if (parts.length < 5) continue;
|
|
459
|
-
const dirName = parts[parts.length - 3];
|
|
460
|
-
const dateKey = parts[parts.length - 2];
|
|
461
|
-
const fileNameExt = parts[parts.length - 1];
|
|
462
|
-
if (fileNameExt.toLowerCase() === "readme.md") continue;
|
|
463
|
-
const fileName = fileNameExt.replace(/\.md$/, "");
|
|
464
|
-
const noteId = `feeds/${dirName}/${dateKey}/${fileName}`;
|
|
465
|
-
const noteTitle = fileToDisplayName(fileNameExt);
|
|
466
|
-
const content = readFileSync(join(corpusRoot, rel), "utf8");
|
|
467
|
-
const sections = parseDocumentSections(content);
|
|
468
|
-
let position = 0;
|
|
469
|
-
for (const section of sections) {
|
|
470
|
-
const blockContentId = section.scriptBlock?.contentId ?? void 0;
|
|
471
|
-
index.push({
|
|
472
|
-
id: `static:${noteId}:${section.id}:1`,
|
|
473
|
-
noteId,
|
|
474
|
-
segmentId: section.id,
|
|
475
|
-
segmentVersion: 1,
|
|
476
|
-
position: position++,
|
|
477
|
-
dataType: toSegmentDataType(section),
|
|
478
|
-
blockContentId,
|
|
479
|
-
rawContent: section.displayContent,
|
|
480
|
-
noteTitle,
|
|
481
|
-
createdAt: feedDateToCreatedAt(dateKey),
|
|
482
|
-
isStatic: true,
|
|
483
|
-
sourceId: `feed:${noteId}`,
|
|
484
|
-
sourcePath: rel
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
for (const file of listMarkdownFiles(join(markdownDir, "canvas"))) {
|
|
489
|
-
const rel = `markdown/canvas/${file}`;
|
|
490
|
-
const content = readFileSync(join(corpusRoot, rel), "utf8");
|
|
491
|
-
const noteId = canvasRouteSlug(content);
|
|
492
|
-
if (!noteId) continue;
|
|
493
|
-
const fileNameExt = file.split("/").pop() ?? file;
|
|
494
|
-
const noteTitle = fileToDisplayName(fileNameExt);
|
|
495
|
-
const createdAt = getFileCreatedAt(rel, corpusRoot);
|
|
496
|
-
const sections = parseDocumentSections(content);
|
|
497
|
-
let position = 0;
|
|
498
|
-
for (const section of sections) {
|
|
499
|
-
const blockContentId = section.scriptBlock?.contentId ?? void 0;
|
|
500
|
-
index.push({
|
|
501
|
-
id: `static:${noteId}:${section.id}:1`,
|
|
502
|
-
noteId,
|
|
503
|
-
segmentId: section.id,
|
|
504
|
-
segmentVersion: 1,
|
|
505
|
-
position: position++,
|
|
506
|
-
dataType: toSegmentDataType(section),
|
|
507
|
-
blockContentId,
|
|
508
|
-
rawContent: section.displayContent,
|
|
509
|
-
noteTitle,
|
|
510
|
-
createdAt,
|
|
511
|
-
isStatic: true,
|
|
512
|
-
sourceId: `guides:${noteId}`,
|
|
513
|
-
sourcePath: rel
|
|
514
|
-
});
|
|
515
|
-
}
|
|
261
|
+
const rows = [];
|
|
262
|
+
for (const path of collectCorpusMarkdown(markdownDir)) {
|
|
263
|
+
const chunkId = chunkIdFor(path);
|
|
264
|
+
if (!chunkId) continue;
|
|
265
|
+
const content = readFileSync(join(corpusRoot, path), "utf8");
|
|
266
|
+
const parts = path.split("/");
|
|
267
|
+
const stem = (parts.at(-1) ?? "").replace(/\.md$/, "");
|
|
268
|
+
const catalog = parts[2];
|
|
269
|
+
const routeId = chunkId === "canvas" ? canvasRouteSlug(content) ?? void 0 : chunkId.startsWith("collection.") ? stem.toLowerCase() === "readme" ? catalog : `${catalog}/${stem}` : chunkId.startsWith("feed.") ? parts.slice(1).join("/").replace(/\.md$/, "") : void 0;
|
|
270
|
+
const sourceId = seedNoteSourceId(path, content, chunkId);
|
|
271
|
+
const createdAt = chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path, corpusRoot);
|
|
272
|
+
rows.push(...compileSeedRow(path, content, { routeId, sourceId, createdAt }).blockIndex);
|
|
516
273
|
}
|
|
517
|
-
return
|
|
274
|
+
return rows;
|
|
518
275
|
}
|
|
519
276
|
var NON_EFFORT_SLUGS = {
|
|
520
277
|
"amrap": true,
|
|
@@ -591,7 +348,8 @@ function discoverUncatalogedEfforts(markdownDir, corpusRoot) {
|
|
|
591
348
|
const discovered = /* @__PURE__ */ new Map();
|
|
592
349
|
for (const block of blocks) {
|
|
593
350
|
if (block.dataType !== "wod" || !block.rawContent) continue;
|
|
594
|
-
const
|
|
351
|
+
const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
|
|
352
|
+
const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);
|
|
595
353
|
for (const statement of script.statements) {
|
|
596
354
|
const effortMetric = statement.metrics?.getMetric?.("effort");
|
|
597
355
|
const effortLabel = (effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0) ?? statement.exerciseId;
|
|
@@ -647,24 +405,27 @@ ${aliases.join("\n")}` : "aliases: []",
|
|
|
647
405
|
}
|
|
648
406
|
return createdPaths;
|
|
649
407
|
}
|
|
650
|
-
function buildBlockEffortRows(markdownDir, corpusRoot) {
|
|
408
|
+
function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
|
|
651
409
|
const efforts = [];
|
|
652
410
|
const { aliasToCanonical } = loadExistingEffortMap(markdownDir);
|
|
653
|
-
const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
|
|
654
411
|
for (const block of blocks) {
|
|
655
412
|
if (block.dataType !== "wod" || !block.rawContent) continue;
|
|
656
|
-
const
|
|
413
|
+
const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
|
|
414
|
+
const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);
|
|
657
415
|
const effortSlugs = extractEffortSlugs(script.statements);
|
|
416
|
+
const linked = /* @__PURE__ */ new Set();
|
|
658
417
|
for (const rawSlug of effortSlugs) {
|
|
659
418
|
const norm = rawSlug.toLowerCase().trim();
|
|
660
419
|
const canonicalSlug = aliasToCanonical.get(norm) ?? aliasToCanonical.get(norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")) ?? norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
661
420
|
if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[norm]) {
|
|
662
421
|
continue;
|
|
663
422
|
}
|
|
423
|
+
if (linked.has(canonicalSlug)) continue;
|
|
424
|
+
linked.add(canonicalSlug);
|
|
664
425
|
efforts.push({
|
|
665
426
|
id: `${block.id}:${canonicalSlug}`,
|
|
666
427
|
noteId: block.noteId,
|
|
667
|
-
blockId: block.
|
|
428
|
+
blockId: block.blockId,
|
|
668
429
|
blockContentId: block.blockContentId,
|
|
669
430
|
effortSlug: canonicalSlug,
|
|
670
431
|
isStatic: block.isStatic,
|
|
@@ -675,20 +436,6 @@ function buildBlockEffortRows(markdownDir, corpusRoot) {
|
|
|
675
436
|
}
|
|
676
437
|
return efforts;
|
|
677
438
|
}
|
|
678
|
-
function buildStaticParentMap(blockRows) {
|
|
679
|
-
const map = {};
|
|
680
|
-
for (const row of blockRows) {
|
|
681
|
-
if (!row.sourcePath) continue;
|
|
682
|
-
const prev = map[row.noteId];
|
|
683
|
-
if (prev && prev.sourcePath !== row.sourcePath) {
|
|
684
|
-
throw new Error(
|
|
685
|
-
`[generate-seed] ambiguous static parent '${row.noteId}' maps to both '${prev.sourcePath}' and '${row.sourcePath}'`
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
|
-
map[row.noteId] = { sourcePath: row.sourcePath, sourceId: row.sourceId };
|
|
689
|
-
}
|
|
690
|
-
return map;
|
|
691
|
-
}
|
|
692
439
|
function sha256Hex(data) {
|
|
693
440
|
return createHash("sha256").update(data).digest("hex");
|
|
694
441
|
}
|
|
@@ -718,14 +465,35 @@ function generateSeed(options = {}) {
|
|
|
718
465
|
rmSync(outDir, { recursive: true, force: true });
|
|
719
466
|
mkdirSync(chunksDir, { recursive: true });
|
|
720
467
|
const manifestChunks = [];
|
|
468
|
+
const tBlockIndex = Date.now();
|
|
469
|
+
const blockRows = buildBlockIndexRows(markdownDir, corpusRoot);
|
|
470
|
+
passMs.blockIndex = Date.now() - tBlockIndex;
|
|
471
|
+
const tBlockEfforts = Date.now();
|
|
472
|
+
const effortRows = buildBlockEffortRows(markdownDir, corpusRoot, blockRows);
|
|
473
|
+
passMs.blockEfforts = Date.now() - tBlockEfforts;
|
|
474
|
+
const blocksByPath = /* @__PURE__ */ new Map();
|
|
475
|
+
const effortsByPath = /* @__PURE__ */ new Map();
|
|
476
|
+
for (const row of blockRows) {
|
|
477
|
+
const rows = blocksByPath.get(row.sourcePath) ?? [];
|
|
478
|
+
rows.push(row);
|
|
479
|
+
blocksByPath.set(row.sourcePath, rows);
|
|
480
|
+
}
|
|
481
|
+
for (const row of effortRows) {
|
|
482
|
+
const rows = effortsByPath.get(row.sourcePath) ?? [];
|
|
483
|
+
rows.push(row);
|
|
484
|
+
effortsByPath.set(row.sourcePath, rows);
|
|
485
|
+
}
|
|
721
486
|
let totalFiles = 0;
|
|
722
487
|
const tWrite = Date.now();
|
|
723
488
|
for (const { id, files } of groups) {
|
|
724
489
|
const templateSource = id === TEMPLATE_CHUNK_ID ? options.templateFile : void 0;
|
|
725
|
-
const rows = files.map((p) =>
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
490
|
+
const rows = files.map((p) => {
|
|
491
|
+
const content = readFileSync(templateSource ?? join(corpusRoot, p), "utf8");
|
|
492
|
+
const compiled = compileSeedRow(p, content);
|
|
493
|
+
compiled.blockIndex = blocksByPath.get(p) ?? compiled.blockIndex;
|
|
494
|
+
compiled.blockEfforts = effortsByPath.get(p) ?? [];
|
|
495
|
+
return compiled;
|
|
496
|
+
});
|
|
729
497
|
const bytes = Buffer.from(JSON.stringify(rows), "utf8");
|
|
730
498
|
const sha = sha256Hex(bytes);
|
|
731
499
|
const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
|
|
@@ -733,36 +501,6 @@ function generateSeed(options = {}) {
|
|
|
733
501
|
manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: rows.length });
|
|
734
502
|
totalFiles += rows.length;
|
|
735
503
|
}
|
|
736
|
-
const tBlockIndex = Date.now();
|
|
737
|
-
const blockRows = buildBlockIndexRows(markdownDir, corpusRoot);
|
|
738
|
-
passMs.blockIndex = Date.now() - tBlockIndex;
|
|
739
|
-
writeFileSync(
|
|
740
|
-
join(outDir, "block-parent-map.json"),
|
|
741
|
-
JSON.stringify(buildStaticParentMap(blockRows), null, 2) + "\n"
|
|
742
|
-
);
|
|
743
|
-
for (let i = 0; blockRows.length > 0 && i * BLOCK_INDEX_ROWS_PER_CHUNK < blockRows.length; i++) {
|
|
744
|
-
const part = blockRows.slice(i * BLOCK_INDEX_ROWS_PER_CHUNK, (i + 1) * BLOCK_INDEX_ROWS_PER_CHUNK);
|
|
745
|
-
if (part.length === 0) break;
|
|
746
|
-
const id = `${BLOCK_INDEX_CHUNK_PREFIX}${i}`;
|
|
747
|
-
const bytes = Buffer.from(JSON.stringify(part), "utf8");
|
|
748
|
-
const sha = sha256Hex(bytes);
|
|
749
|
-
const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
|
|
750
|
-
writeFileSync(join(outDir, file), bytes);
|
|
751
|
-
manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: part.length, kind: "block-index" });
|
|
752
|
-
}
|
|
753
|
-
const tBlockEfforts = Date.now();
|
|
754
|
-
const effortRows = buildBlockEffortRows(markdownDir, corpusRoot);
|
|
755
|
-
passMs.blockEfforts = Date.now() - tBlockEfforts;
|
|
756
|
-
for (let i = 0; effortRows.length > 0 && i * BLOCK_INDEX_ROWS_PER_CHUNK < effortRows.length; i++) {
|
|
757
|
-
const part = effortRows.slice(i * BLOCK_INDEX_ROWS_PER_CHUNK, (i + 1) * BLOCK_INDEX_ROWS_PER_CHUNK);
|
|
758
|
-
if (part.length === 0) break;
|
|
759
|
-
const id = `${BLOCK_EFFORTS_CHUNK_PREFIX}${i}`;
|
|
760
|
-
const bytes = Buffer.from(JSON.stringify(part), "utf8");
|
|
761
|
-
const sha = sha256Hex(bytes);
|
|
762
|
-
const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
|
|
763
|
-
writeFileSync(join(outDir, file), bytes);
|
|
764
|
-
manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: part.length, kind: "block-efforts" });
|
|
765
|
-
}
|
|
766
504
|
passMs.write = Date.now() - tWrite;
|
|
767
505
|
const manifest = {
|
|
768
506
|
schema: SEED_SCHEMA,
|
|
@@ -782,6 +520,6 @@ function generateSeed(options = {}) {
|
|
|
782
520
|
return { manifest, outDir, totalFiles, reportPath, passMs };
|
|
783
521
|
}
|
|
784
522
|
|
|
785
|
-
export { buildBlockEffortRows, buildBlockIndexRows,
|
|
786
|
-
//# sourceMappingURL=compiler-
|
|
787
|
-
//# sourceMappingURL=compiler-
|
|
523
|
+
export { buildBlockEffortRows, buildBlockIndexRows, chunkIdFor, collectCorpusMarkdown, compileSeedRow, discoverUncatalogedEfforts, feedDateToCreatedAt, generateSeed, loadExistingEffortMap, partitionIntoChunks, scaffoldDiscoveredEfforts, validateDashboardNote };
|
|
524
|
+
//# sourceMappingURL=compiler-MQRZVZQW.mjs.map
|
|
525
|
+
//# sourceMappingURL=compiler-MQRZVZQW.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/seedbuild/effort-slugs.ts","../src/cli/seedbuild/filename.ts","../src/cli/seedbuild/git-version.ts","../src/cli/seedbuild/report.ts","../src/cli/seedbuild/compiler.ts"],"names":["join","execSync","section","writeFileSync"],"mappings":"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","file":"compiler-MQRZVZQW.mjs","sourcesContent":["/**\n * extractEffortSlugs — port of wod-wiki's\n * apps/playground/src/services/storage/StorageService.ts (the pure function\n * only). Derives canonical effort slugs referenced by script statements for\n * the block-efforts containment index.\n */\nimport type { ICodeStatement } from '@bitcobblers/wod-wiki-core';\n\nexport function extractEffortSlugs(statements: ICodeStatement[]): string[] {\n const slugs = new Set<string>();\n for (const statement of statements) {\n if (statement.exerciseId) {\n slugs.add(statement.exerciseId);\n } else {\n const effortMetric = statement.metrics?.getMetric?.('effort');\n const effort = effortMetric && typeof effortMetric.value === 'string' ? effortMetric.value : undefined;\n if (effort) {\n const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n if (slug) slugs.add(slug);\n }\n }\n }\n return Array.from(slugs);\n}\n","/**\n * fileToDisplayName — port of wod-wiki's\n * apps/playground/src/repositories/groupings.ts (display-name derivation\n * only). Block-index rows carry the same note titles the app renders, so\n * titles must derive identically on both sides.\n */\n\n/** Lowercased mid-title words — \"and\", \"of\", \"70.3\"-style fragments read wrong capped. */\nconst FILE_TITLE_STOP_WORDS: Record<string, true> = {\n and: true, or: true, of: true, for: true, to: true, in: true, on: true,\n at: true, by: true, with: true, the: true, a: true, an: true,\n};\n\n/**\n * Parse a filename into a display name.\n * \"simple-and-sinister.md\" -> \"Simple and Sinister\"\n */\nexport function fileToDisplayName(filename: string): string {\n const base = filename.replace(/\\.md$/, '');\n if (base.toUpperCase() === 'README') return 'Overview';\n // Strip leading \"day-01-\" prefixes if present, then humanise\n const cleaned = base.replace(/^day-\\d+-/, '');\n const tokens = cleaned.split(/[-_]/);\n const titled: string[] = [];\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]!;\n const next = tokens[i + 1];\n const after = tokens[i + 2];\n const prev = tokens[i - 1];\n // Race distances keep their dot: a lone 1-2-digit pair (\"70-3\" -> \"70.3\").\n // Longer numeric runs are dates (2009-12-01) or rep ladders (5-3-1) —\n // only an isolated pair between words is a distance.\n if (\n /^\\d{1,2}$/.test(token) && /^\\d{1,2}$/.test(next ?? '')\n && !/^\\d+$/.test(prev ?? '') && !/^\\d+$/.test(after ?? '')\n ) {\n titled.push(`${token}.${next}`);\n i++;\n continue;\n }\n if (titled.length > 0 && FILE_TITLE_STOP_WORDS[token.toLowerCase()]) {\n titled.push(token.toLowerCase());\n continue;\n }\n titled.push(token.charAt(0).toUpperCase() + token.slice(1));\n }\n return titled.join(' ');\n}\n","/**\n * Git-derived versioning — the seed artifact's version IS this repo's git\n * history. Every merged MR advances `version` (commit count), and\n * `versionLabel`/`commit` make an artifact traceable to its exact source\n * tree, so the app (and humans diffing manifests) can answer \"what changed\n * and what needs syncing\" from the manifest alone.\n */\nimport { execSync } from 'node:child_process';\n\nexport interface GitVersion {\n /** Monotonic commit count — the import-order version stamped on the manifest. */\n version: number;\n /** `git describe --tags --long --always --dirty`, e.g. `v1.2.0-4-gdeadbee`. */\n versionLabel: string;\n /** Full HEAD sha. */\n commit: string;\n /** True when built with uncommitted working-tree changes. */\n dirty: boolean;\n}\n\n/** Unversioned fallback for source trees without git history (e.g. exported tarballs). */\nexport const UNKNOWN_GIT_VERSION: GitVersion = {\n version: 0,\n versionLabel: 'unknown',\n commit: '',\n dirty: false,\n};\n\nexport function resolveGitVersion(repoRoot: string): GitVersion {\n try {\n const run = (args: string): string =>\n execSync(`git ${args}`, { cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n const count = Number(run('rev-list --count HEAD'));\n return {\n version: Number.isFinite(count) && count > 0 ? count : 0,\n versionLabel: run('describe --tags --long --always --dirty'),\n commit: run('rev-parse HEAD'),\n dirty: run('status --porcelain') !== '',\n };\n } catch {\n return UNKNOWN_GIT_VERSION;\n }\n}\n","/**\n * Build report — a static HTML benchmark page for each seed build\n * (`dist/report.html`). Per-chunk sizes, row counts, pass timings, and the\n * git version stamp, so CI and contributors see build health at a glance.\n * Deliberately dependency-free (one string template + a file write).\n */\nimport { writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { SeedManifest } from './contract';\n\nexport interface BuildReportData {\n manifest: SeedManifest;\n /** Wall-clock ms for the full build. */\n totalMs: number;\n /** Per-pass timings (ms): corpus scan, block-index derivation, effort-row derivation, chunk writes. */\n passMs: { scan: number; blockIndex: number; blockEfforts: number; write: number };\n /** Number of corpus markdown files. */\n fileCount: number;\n outDir: string;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\nfunction rowHtml(c: SeedManifest['chunks'][number]): string {\n const kind = c.kind ?? 'notes';\n return `<tr><td>${escapeHtml(c.id)}</td><td>${kind}</td><td>${c.count}</td><td>${(c.bytes / 1024).toFixed(1)} KB</td><td><code>${c.sha256.slice(0, 8)}</code></td></tr>`;\n}\n\nexport function writeBuildReport(outDir: string, data: BuildReportData): string {\n const { manifest, totalMs, passMs, fileCount } = data;\n const totalBytes = manifest.chunks.reduce((s, c) => s + c.bytes, 0);\n const dirtyBadge = manifest.dirty\n ? ' <strong style=\"color:#b45309\">(dirty working tree — not reproducible from <code>' + escapeHtml(manifest.commit ?? '') + '</code>)</strong>'\n : '';\n const html = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>Seed build report</title>\n<style>\n body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #1f2937; }\n h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }\n table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }\n th, td { text-align: left; padding: 0.3rem 0.6rem; border-bottom: 1px solid #e5e7eb; }\n code { font-size: 0.85em; }\n dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 1rem; font-size: 0.95rem; }\n dt { font-weight: 600; }\n</style>\n</head>\n<body>\n<h1>Seed build report</h1>\n<h2>Version</h2>\n<dl>\n <dt>version</dt><dd>${manifest.version} <code>${escapeHtml(manifest.versionLabel ?? '')}</code>${dirtyBadge}</dd>\n <dt>commit</dt><dd><code>${escapeHtml(manifest.commit ?? 'unknown')}</code></dd>\n <dt>built at</dt><dd>${escapeHtml(manifest.builtAt)}</dd>\n <dt>schema</dt><dd>v${manifest.schema}</dd>\n</dl>\n<h2>Benchmarks</h2>\n<dl>\n <dt>total</dt><dd>${totalMs} ms</dd>\n <dt>corpus scan + partition</dt><dd>${passMs.scan} ms (${fileCount} files)</dd>\n <dt>block-index derivation</dt><dd>${passMs.blockIndex} ms</dd>\n <dt>block-efforts derivation</dt><dd>${passMs.blockEfforts} ms</dd>\n <dt>chunk writes</dt><dd>${passMs.write} ms</dd>\n</dl>\n<h2>Chunks (${manifest.chunks.length}, ${(totalBytes / 1024).toFixed(0)} KB total)</h2>\n<table>\n<thead><tr><th>id</th><th>kind</th><th>rows</th><th>size</th><th>sha256</th></tr></thead>\n<tbody>\n${manifest.chunks.map(rowHtml).join('\\n')}\n</tbody>\n</table>\n</body>\n</html>\n`;\n const file = join(outDir, 'report.html');\n writeFileSync(file, html, 'utf8');\n return file;\n}\n","/** Compiles exact source, occurrence maps and sparse projections into atomic notes chunks. */\nimport { parseScript } from '@bitcobblers/wod-wiki-lang';\nimport { blockContentId, parseDocumentSections, reconcileNoteBlocks } from '@bitcobblers/wod-wiki-core';\nimport type { BlockEffort, BlockIndexRow } from '@bitcobblers/wod-wiki-storage';\nimport {\n buildDashboardDocument,\n isDashboardWidgetType,\n parseDashboardNote,\n parseQuery,\n resolveWidgetType,\n} from '@bitcobblers/wod-wiki-wql';\nimport {\n DASHBOARDS_CHUNK_ID,\n SEED_SCHEMA,\n SEED_REGISTERED_WIDGETS,\n TEMPLATE_CHUNK_ID,\n canvasRouteSlug,\n seedNoteSourceId,\n type ManifestChunk,\n type SeedManifest,\n type SeedRow,\n} from './contract';\nimport { extractEffortSlugs } from './effort-slugs';\nimport { fileToDisplayName } from './filename';\nimport { parseEffortFile } from './effort-file';\nimport { resolveGitVersion } from './git-version';\nimport { writeBuildReport } from './report';\nimport { parseFrontmatter } from './frontmatter';\nimport { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { join, relative } from 'node:path';\n\n// BlockEffort/BlockIndexRow row types come from @bitcobblers/wod-wiki-storage\n// (type-only import — chunk rows are plain JSON applied structurally by the\n// app, mirroring cli/seed.ts).\n\n/** Scan `dir` recursively for `*.md` files; returns POSIX paths relative to `dir`, sorted. */\nfunction listMarkdownFiles(dir: string, prefix = ''): string[] {\n if (!existsSync(dir)) return [];\n const out: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) out.push(...listMarkdownFiles(join(dir, entry.name), rel));\n else if (entry.isFile() && entry.name.endsWith('.md')) out.push(rel);\n }\n return out.sort();\n}\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\nexport interface ChunkGroup {\n id: string;\n files: string[];\n}\n\nexport interface GenerateSeedOptions {\n /** The corpus directory (`markdown/`). Defaults to `<corpusRoot>/markdown`. */\n markdownDir?: string;\n /** Optional playground template file — the host passes its own; standalone builds omit the `template` chunk. */\n templateFile?: string;\n /**\n * Row path recorded for the template chunk (the runtime derives the\n * template note's id from it, so it must stay stable across builds). The\n * host pins its historical in-repo path; defaults to the path relative to\n * `corpusRoot`.\n */\n templateRowPath?: string;\n /** Output directory. Defaults to `<corpusRoot>/dist`. */\n outDir?: string;\n /**\n * Explicit manifest version (tests). Defaults to the corpus repo's git\n * commit count — see git-version.ts.\n */\n version?: number;\n /** Repo root the corpus paths resolve against and versioning derives from. Defaults to cwd. */\n corpusRoot?: string;\n /** Automatically scaffold markdown files for newly discovered efforts. */\n scaffoldDiscoveredEfforts?: boolean;\n}\n\nexport interface GenerateSeedResult {\n manifest: SeedManifest;\n outDir: string;\n totalFiles: number;\n reportPath: string;\n /** ms timings per pass (mirrored into report.html). */\n passMs: { scan: number; blockIndex: number; blockEfforts: number; write: number };\n}\n\n// ── Corpus partition ──────────────────────────────────────────────────────\n\n/**\n * Map one `markdown/…` path to its chunk id. Returns null for files outside\n * the known corpora — callers MUST treat that as a parity failure so new\n * content directories cannot bypass the seed.\n */\nexport function chunkIdFor(relPath: string): string | null {\n const parts = relPath.split('/');\n if (parts[0] !== 'markdown' || parts.length < 2) return null;\n const corpus = parts[1];\n // markdown/<corpus>/<rest…>; a file directly under markdown/ has no corpus.\n if (parts.length === 2) return null;\n const slug = parts[2]?.replace(/\\.md$/, '');\n switch (corpus) {\n case 'canvas':\n case 'dashboards':\n case 'efforts':\n return corpus;\n case 'collections':\n return parts.length === 3 ? `collection._root` : `collection.${slug}`;\n case 'feeds':\n return parts.length === 3 ? `feed._root` : `feed.${slug}`;\n default:\n return null;\n }\n}\n\n/** Scan the markdown corpus; returns repo-relative POSIX paths of all .md files. */\nexport function collectCorpusMarkdown(markdownDir: string): string[] {\n return listMarkdownFiles(markdownDir).map((p) => `markdown/${p}`);\n}\n\n/** Group markdown corpus paths into chunk groups; throws on paths outside the known corpora. */\nexport function partitionIntoChunks(paths: string[]): ChunkGroup[] {\n const byId = new Map<string, string[]>();\n for (const p of paths) {\n const id = chunkIdFor(p);\n if (id === null) {\n throw new Error(\n `[generate-seed] Parity failure: '${p}' is not part of any known corpus ` +\n `(canvas | dashboards | efforts | collections | feeds). ` +\n `Extend chunkIdFor() if this is a new corpus.`,\n );\n }\n const list = byId.get(id);\n if (list) list.push(p);\n else byId.set(id, [p]);\n }\n return [...byId.entries()]\n .sort(([a], [b]) => (a < b ? -1 : 1))\n .map(([id, files]) => ({ id, files }));\n}\n\n// ── Dashboard gate (#906) ─────────────────────────────────────────────────\n\n/**\n * Validate one dashboard note in the locked #899 format: dashboard\n * frontmatter, a title, known widget types, WQL bodies that parse (and are\n * aggregate queries, not find queries), and every `$token` reference\n * declared. Returns the problems found (empty = valid).\n */\nexport function validateDashboardNote(raw: string): string[] {\n const problems: string[] = [];\n const { meta } = parseFrontmatter(raw);\n const { sections } = parseDashboardNote(raw);\n const doc = buildDashboardDocument(sections, meta);\n if (!doc.isDashboard) return ['file is not a dashboard note (missing dashboard frontmatter)'];\n if (doc.title === undefined) problems.push('dashboard has no title');\n if (doc.widgets.length < 3) problems.push(`dashboard has only ${doc.widgets.length} widgets (the locked format has ≥ 3)`);\n const declared = new Set(doc.tokens.map((t) => t.name));\n for (const widget of doc.widgets) {\n const where = `widget '${widget.title ?? widget.body.slice(0, 40)}'`;\n if (widget.title === undefined) problems.push(`${where}: missing title`);\n if (!isDashboardWidgetType(resolveWidgetType(widget.type))) {\n problems.push(`${where}: unknown widget type '${widget.type}'`);\n }\n const parsed = parseQuery(widget.body);\n if (parsed.error !== undefined) problems.push(`${where}: WQL parse error: ${parsed.error}`);\n for (const ref of [\n ...widget.body.matchAll(/\\$([A-Za-z][\\w-]*)/g),\n ...Object.values(widget.attributes).flatMap((v) => [...String(v).matchAll(/\\$([A-Za-z][\\w-]*)/g)]),\n ]) {\n if (!declared.has(ref[1])) problems.push(`${where}: undeclared token $${ref[1]}`);\n }\n }\n return problems;\n}\n\n/** Validate every dashboard file in a chunk group; throws on the first invalid file. */\nfunction gateDashboards(groups: ChunkGroup[], corpusRoot: string): void {\n for (const group of groups) {\n if (group.id !== DASHBOARDS_CHUNK_ID) continue;\n for (const p of group.files) {\n const problems = validateDashboardNote(readFileSync(join(corpusRoot, p), 'utf8'));\n if (problems.length > 0) {\n throw new Error(`[generate-seed] Dashboard validation failed for '${p}':\\n - ${problems.join('\\n - ')}`);\n }\n }\n }\n}\n\n// ── Block index derivation ────────────────────────────────────────────────\n\n\n/** Created-at from the corpus repo's git history (collections/guides), 0 fallback when unknown. */\nfunction getFileCreatedAt(file: string, repoRoot: string): number {\n try {\n const out = execSync(`git log -1 --format=%ct -- '${file}'`, { cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n const ts = Number(out) * 1000;\n return Number.isFinite(ts) && ts > 0 ? ts : 0;\n } catch {\n return 0;\n }\n}\n\n/** Created-at timestamp for a feed entry's date key (`yyyy-mm-dd` from the path). */\nexport function feedDateToCreatedAt(dateKey: string): number {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(dateKey)) return 0;\n const ts = Date.parse(`${dateKey}T00:00:00Z`);\n return Number.isNaN(ts) ? 0 : ts;\n}\n\n// canvasRouteSlug lives in contract.ts — one derivation shared by the\n// block plane (here) and the app's importer (note plane).\n\n\nexport function compileSeedRow(path: string, content: string, metadata: { routeId?: string; noteTitle?: string; sourceId?: string; createdAt?: number } = {}): SeedRow {\n let ordinal = 0;\n const blocks = reconcileNoteBlocks(content, { sourceRevision: 1, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${path}:${ordinal++}` });\n const noteId = metadata.routeId ?? path;\n const sections = parseDocumentSections(content);\n const blockIndex: BlockIndexRow[] = Object.values(blocks).map((block, position) => {\n const section = sections.find((section) => section.from === block.from && section.to === block.to);\n return { id: `${noteId}:${block.id}`, noteId, blockId: block.id, blockVersion: 1, sourceRevision: 1, from: block.from, to: block.to, position, dataType: block.kind, ...(block.kind === 'wod' ? { blockContentId: blockContentId(section?.scriptBlock?.content ?? content.slice(block.from, block.to)) } : {}), rawContent: content.slice(block.from, block.to), noteTitle: metadata.noteTitle ?? fileToDisplayName(path.split('/').at(-1) ?? path), createdAt: metadata.createdAt ?? 0, isStatic: true, sourceId: metadata.sourceId, sourcePath: path, routeId: metadata.routeId };\n });\n return { path, content, blocks, blockIndex, blockEfforts: [] };\n}\n\nexport function buildBlockIndexRows(markdownDir: string, corpusRoot: string): BlockIndexRow[] {\n const rows: BlockIndexRow[] = [];\n for (const path of collectCorpusMarkdown(markdownDir)) {\n const chunkId = chunkIdFor(path);\n if (!chunkId) continue;\n const content = readFileSync(join(corpusRoot, path), 'utf8');\n const parts = path.split('/');\n const stem = (parts.at(-1) ?? '').replace(/\\.md$/, '');\n const catalog = parts[2];\n const routeId = chunkId === 'canvas' ? canvasRouteSlug(content) ?? undefined : chunkId.startsWith('collection.') ? (stem.toLowerCase() === 'readme' ? catalog : `${catalog}/${stem}`) : chunkId.startsWith('feed.') ? parts.slice(1).join('/').replace(/\\.md$/, '') : undefined;\n const sourceId = seedNoteSourceId(path, content, chunkId);\n const createdAt = chunkId.startsWith('feed.') ? feedDateToCreatedAt(parts.at(-2) ?? '') : getFileCreatedAt(path, corpusRoot);\n rows.push(...compileSeedRow(path, content, { routeId, sourceId, createdAt }).blockIndex);\n }\n return rows;\n}\n\n/**\n * Uncataloged effort discovered during workout block analysis.\n */\nexport interface DiscoveredEffort {\n slug: string;\n label: string;\n rawLabels: string[];\n}\n\n/** Static lookup — syntax/structure keywords and known non-effort labels that must never scaffold. */\nconst NON_EFFORT_SLUGS: Record<string, true> = {\n 'amrap': true, 'emom': true, 'tabata': true, 'for-time': true, 'rounds': true, 'sets': true,\n 'repetitions': true, 'count-down': true, 'total-reps': true,\n 'warmup': true, 'cooldown': true, 'cool-down': true, 'transition': true, 'rest': true, 'break': true, 'work': true,\n 'i-suck-at-this': true, 'isuckatthis': true, 'movement': true, 'movement-you': true, 'selected-exercise': true,\n 'training-setup': true, 'grade-system': true, 'hangs': true, 'warm-light-wind': true, 'basin': true, 'rock-canyon': true,\n 'b-onsight': true, 'a-redpoint': true, 'd-redpoint': true, 'v0-flash': true, 'v2-flash': true, 'v7-redpoint': true,\n 'avgrpe-mean': true, 'calculate': true, 'totalload-sum': true, 'query': true, 'by': true,\n '': true, '-': true, '...': true, 'x': true, 's': true, 'bw': true,\n};\n\nfunction formatDiscoveredLabel(raw: string): string {\n const clean = raw.replace(/[-_]+/g, ' ').trim();\n return clean\n .split(/\\s+/)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(' ');\n}\n\nexport function loadExistingEffortMap(markdownDir: string): {\n slugs: Set<string>;\n aliasToCanonical: Map<string, string>;\n} {\n const slugs = new Set<string>();\n const aliasToCanonical = new Map<string, string>();\n const effortsDir = join(markdownDir, 'efforts');\n for (const file of listMarkdownFiles(effortsDir)) {\n const raw = readFileSync(join(effortsDir, file), 'utf8');\n const parsed = parseEffortFile(raw);\n if (!parsed) continue;\n\n slugs.add(parsed.slug);\n aliasToCanonical.set(parsed.slug, parsed.slug);\n aliasToCanonical.set(parsed.label.toLowerCase(), parsed.slug);\n aliasToCanonical.set(parsed.slug.replace(/-/g, ' '), parsed.slug);\n\n for (const alias of parsed.aliases) {\n aliasToCanonical.set(alias.toLowerCase(), parsed.slug);\n aliasToCanonical.set(alias.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''), parsed.slug);\n }\n }\n\n return { slugs, aliasToCanonical };\n}\n\nexport function discoverUncatalogedEfforts(markdownDir: string, corpusRoot: string): DiscoveredEffort[] {\n const { slugs, aliasToCanonical } = loadExistingEffortMap(markdownDir);\n const blocks = buildBlockIndexRows(markdownDir, corpusRoot);\n const discovered = new Map<string, { label: string; rawLabels: Set<string> }>();\n\n for (const block of blocks) {\n if (block.dataType !== 'wod' || !block.rawContent) continue;\n const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section) => section.scriptBlock);\n const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);\n for (const statement of script.statements) {\n const effortMetric = statement.metrics?.getMetric?.('effort');\n const effortLabel = (effortMetric && typeof effortMetric.value === 'string' ? effortMetric.value : undefined) ?? statement.exerciseId;\n if (!effortLabel) continue;\n\n const slugified = effortLabel.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\n if (!slugified || NON_EFFORT_SLUGS[slugified]) continue;\n\n const canonical = aliasToCanonical.get(slugified) ?? aliasToCanonical.get(effortLabel.toLowerCase());\n if (canonical && slugs.has(canonical)) continue;\n\n const existing = discovered.get(slugified);\n if (existing) {\n existing.rawLabels.add(effortLabel);\n } else {\n discovered.set(slugified, {\n label: formatDiscoveredLabel(effortLabel),\n rawLabels: new Set([effortLabel]),\n });\n }\n }\n }\n\n return Array.from(discovered.entries()).map(([slug, data]) => ({\n slug,\n label: data.label,\n rawLabels: Array.from(data.rawLabels),\n }));\n}\n\nexport function scaffoldDiscoveredEfforts(\n discovered: DiscoveredEffort[],\n markdownDir: string,\n): string[] {\n const targetDir = join(markdownDir, 'efforts', 'discovered');\n if (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n const createdPaths: string[] = [];\n for (const effort of discovered) {\n const filePath = join(targetDir, `${effort.slug}.md`);\n if (existsSync(filePath)) continue;\n\n const aliases = effort.rawLabels\n .filter((l) => l.toLowerCase() !== effort.label.toLowerCase())\n .map((l) => ` - \"${l.replace(/\"/g, '\\\\\"')}\"`);\n\n const content = [\n '---',\n `id: effort-bundled-${effort.slug}`,\n `slug: ${effort.slug}`,\n `label: \"${effort.label.replace(/\"/g, '\\\\\"')}\"`,\n aliases.length > 0 ? `aliases:\\n${aliases.join('\\n')}` : 'aliases: []',\n 'baseAttributes:',\n ' met: 5.0',\n ' discipline: general',\n ' intensityTier: moderate',\n 'registrySource: bundled',\n '---',\n '',\n ].join('\\n');\n\n writeFileSync(filePath, content, 'utf8');\n createdPaths.push(filePath);\n }\n\n return createdPaths;\n}\n\n/**\n * Derive BlockEffort[] containment rows from the block-index row set.\n * Requires a second pass over the source markdown to parse the script AST,\n * which is safe because the corpus is static and the pass is compile-time.\n */\nexport function buildBlockEffortRows(markdownDir: string, corpusRoot: string, blocks: BlockIndexRow[] = buildBlockIndexRows(markdownDir, corpusRoot)): BlockEffort[] {\n const efforts: BlockEffort[] = [];\n const { aliasToCanonical } = loadExistingEffortMap(markdownDir);\n for (const block of blocks) {\n if (block.dataType !== 'wod' || !block.rawContent) continue;\n const section = parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section) => section.scriptBlock);\n const script = parseScript(section?.scriptBlock?.content ?? block.rawContent);\n const effortSlugs = extractEffortSlugs(script.statements);\n const linked = new Set<string>();\n for (const rawSlug of effortSlugs) {\n const norm = rawSlug.toLowerCase().trim();\n const canonicalSlug =\n aliasToCanonical.get(norm) ??\n aliasToCanonical.get(norm.replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '')) ??\n norm.replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\n if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[norm]) {\n continue;\n }\n if (linked.has(canonicalSlug)) continue;\n linked.add(canonicalSlug);\n efforts.push({\n id: `${block.id}:${canonicalSlug}`,\n noteId: block.noteId,\n blockId: block.blockId,\n blockContentId: block.blockContentId,\n effortSlug: canonicalSlug,\n isStatic: block.isStatic,\n sourcePath: block.sourcePath,\n createdAt: block.createdAt,\n });\n }\n }\n return efforts;\n}\n\n\n// ── Generation ────────────────────────────────────────────────────────────\n\nfunction sha256Hex(data: string | Buffer): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\nexport function generateSeed(options: GenerateSeedOptions = {}): GenerateSeedResult {\n const t0 = Date.now();\n const passMs = { scan: 0, blockIndex: 0, blockEfforts: 0, write: 0 };\n const corpusRoot = options.corpusRoot ?? process.cwd();\n const markdownDir = options.markdownDir ?? join(corpusRoot, 'markdown');\n const outDir = options.outDir ?? join(corpusRoot, 'dist');\n const gitVersion = options.version !== undefined\n ? { ...resolveGitVersion(corpusRoot), version: options.version }\n : resolveGitVersion(corpusRoot);\n if (options.scaffoldDiscoveredEfforts) {\n const discovered = discoverUncatalogedEfforts(markdownDir, corpusRoot);\n if (discovered.length > 0) {\n scaffoldDiscoveredEfforts(discovered, markdownDir);\n }\n }\n\n const tScan = Date.now();\n const paths = collectCorpusMarkdown(markdownDir);\n const groups = partitionIntoChunks(paths);\n gateDashboards(groups, corpusRoot);\n if (options.templateFile && existsSync(options.templateFile)) {\n const templateRowPath = options.templateRowPath\n ?? relative(corpusRoot, options.templateFile).split('\\\\').join('/');\n groups.push({ id: TEMPLATE_CHUNK_ID, files: [templateRowPath] });\n }\n passMs.scan = Date.now() - tScan;\n\n const chunksDir = join(outDir, 'chunks');\n rmSync(outDir, { recursive: true, force: true });\n mkdirSync(chunksDir, { recursive: true });\n\n const manifestChunks: ManifestChunk[] = [];\n const tBlockIndex = Date.now();\n const blockRows = buildBlockIndexRows(markdownDir, corpusRoot);\n passMs.blockIndex = Date.now() - tBlockIndex;\n const tBlockEfforts = Date.now();\n const effortRows = buildBlockEffortRows(markdownDir, corpusRoot, blockRows);\n passMs.blockEfforts = Date.now() - tBlockEfforts;\n const blocksByPath = new Map<string, BlockIndexRow[]>();\n const effortsByPath = new Map<string, BlockEffort[]>();\n for (const row of blockRows) {\n const rows = blocksByPath.get(row.sourcePath!) ?? [];\n rows.push(row); blocksByPath.set(row.sourcePath!, rows);\n }\n for (const row of effortRows) {\n const rows = effortsByPath.get(row.sourcePath!) ?? [];\n rows.push(row); effortsByPath.set(row.sourcePath!, rows);\n }\n let totalFiles = 0;\n\n const tWrite = Date.now();\n for (const { id, files } of groups) {\n // The template chunk's row path is the host-pinned historical identity,\n // not a path inside this repo — read it from the template file itself.\n const templateSource = id === TEMPLATE_CHUNK_ID ? options.templateFile : undefined;\n const rows: SeedRow[] = files.map((p) => {\n const content = readFileSync(templateSource ?? join(corpusRoot, p), 'utf8');\n const compiled = compileSeedRow(p, content);\n compiled.blockIndex = blocksByPath.get(p) ?? compiled.blockIndex;\n compiled.blockEfforts = effortsByPath.get(p) ?? [];\n return compiled;\n });\n const bytes = Buffer.from(JSON.stringify(rows), 'utf8');\n const sha = sha256Hex(bytes);\n const file = `chunks/${id}.${sha.slice(0, 8)}.json`;\n writeFileSync(join(outDir, file), bytes);\n manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: rows.length });\n totalFiles += rows.length;\n }\n\n passMs.write = Date.now() - tWrite;\n\n const manifest: SeedManifest = {\n schema: SEED_SCHEMA,\n version: gitVersion.version,\n versionLabel: gitVersion.versionLabel,\n commit: gitVersion.commit,\n dirty: gitVersion.dirty,\n builtAt: new Date().toISOString(),\n chunks: manifestChunks,\n };\n writeFileSync(join(outDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\\n');\n\n const reportPath = writeBuildReport(outDir, {\n manifest,\n totalMs: Date.now() - t0,\n passMs,\n fileCount: paths.length,\n outDir,\n });\n\n return { manifest, outDir, totalFiles, reportPath, passMs };\n}\n"]}
|