@bitcobblers/wod-wiki-tools 0.36.2136 → 0.37.2139
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 +522 -246
- package/dist/bin/wod.cjs.map +1 -1
- package/dist/bin/wod.mjs +4 -3
- package/dist/bin/wod.mjs.map +1 -1
- package/dist/{chunk-FZKXOYMJ.mjs → chunk-CVFMFD7D.mjs} +153 -188
- package/dist/chunk-CVFMFD7D.mjs.map +1 -0
- package/dist/{chunk-6QNX4FED.mjs → chunk-HRFOKCYI.mjs} +139 -47
- package/dist/chunk-HRFOKCYI.mjs.map +1 -0
- package/dist/{chunk-CYKNTEGY.mjs → chunk-IWT7U3WX.mjs} +8 -3
- package/dist/chunk-IWT7U3WX.mjs.map +1 -0
- package/dist/chunk-JUBKCRNV.mjs +12 -0
- package/dist/chunk-JUBKCRNV.mjs.map +1 -0
- package/dist/{chunk-MA6BBTWC.mjs → chunk-RWNL6APX.mjs} +3 -3
- package/dist/{chunk-MA6BBTWC.mjs.map → chunk-RWNL6APX.mjs.map} +1 -1
- package/dist/compiler-OXEXGIZG.mjs +9 -0
- package/dist/{compiler-CTPLIZJK.mjs.map → compiler-OXEXGIZG.mjs.map} +1 -1
- package/dist/index.cjs +522 -246
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +5 -4
- package/dist/scaffoldCatalog-OIU4FEKJ.mjs +205 -0
- package/dist/scaffoldCatalog-OIU4FEKJ.mjs.map +1 -0
- package/dist/{server-JXECWS4A.mjs → server-2DVBA35J.mjs} +4 -4
- package/dist/{server-JXECWS4A.mjs.map → server-2DVBA35J.mjs.map} +1 -1
- package/dist/web/playground/assets/main-2qCz3_0z.js +194 -0
- package/dist/web/playground/assets/main-BUcjhK2w.css +2 -0
- package/dist/web/playground/assets/{statementBuilderFlow-ugwi0fu0.js → statementBuilderFlow-DKhL-YLz.js} +2 -2
- package/dist/web/playground/assets/{vendor-g-NJOjvC.js → vendor-DaJxNU_W.js} +3 -3
- package/dist/web/playground/assets/{vendor-codemirror-C41SFn-r.js → vendor-codemirror-BtDM22HA.js} +2 -2
- package/dist/web/playground/index.html +4 -4
- package/dist/web/playground/seed/core/chunks/canvas.a45c409e.json +1 -0
- package/dist/web/playground/seed/core/chunks/efforts.e68f2093.json +1 -0
- package/dist/web/playground/seed/core/chunks/equipment.c4fb1d64.json +1 -0
- package/dist/web/playground/seed/core/chunks/{template.e5e0e82d.json → template.0120276c.json} +1 -1
- package/dist/web/playground/seed/core/manifest.json +27 -10
- package/dist/web/playground/seed/core/report.html +15 -13
- package/dist/web/server/assets/main-BUcjhK2w.css +2 -0
- package/dist/web/server/assets/main-DxsZ-yOz.js +193 -0
- package/dist/web/server/assets/{statementBuilderFlow-40WaYjdq.js → statementBuilderFlow-CI5vrd60.js} +2 -2
- package/dist/web/server/assets/{vendor-g-NJOjvC.js → vendor-DaJxNU_W.js} +3 -3
- package/dist/web/server/assets/{vendor-codemirror-C41SFn-r.js → vendor-codemirror-BtDM22HA.js} +2 -2
- package/dist/web/server/index.html +4 -4
- package/dist/web/server/seed/core/chunks/canvas.a45c409e.json +1 -0
- package/dist/web/server/seed/core/chunks/efforts.e68f2093.json +1 -0
- package/dist/web/server/seed/core/chunks/equipment.c4fb1d64.json +1 -0
- package/dist/web/server/seed/core/chunks/{template.e5e0e82d.json → template.0120276c.json} +1 -1
- package/dist/web/server/seed/core/manifest.json +27 -10
- package/dist/web/server/seed/core/report.html +15 -13
- package/package.json +6 -6
- package/dist/chunk-6QNX4FED.mjs.map +0 -1
- package/dist/chunk-CYKNTEGY.mjs.map +0 -1
- package/dist/chunk-FZKXOYMJ.mjs.map +0 -1
- package/dist/compiler-CTPLIZJK.mjs +0 -9
- package/dist/web/playground/assets/main-B-bffxwE.css +0 -2
- package/dist/web/playground/assets/main-iLSeEl2E.js +0 -188
- package/dist/web/playground/seed/core/chunks/canvas.20048125.json +0 -1
- package/dist/web/server/assets/main-B-bffxwE.css +0 -2
- package/dist/web/server/assets/main-CRyLxlPN.js +0 -187
- package/dist/web/server/seed/core/chunks/canvas.20048125.json +0 -1
package/dist/index.cjs
CHANGED
|
@@ -160,7 +160,7 @@ function sqliteEventStore(db) {
|
|
|
160
160
|
getEventsByTimeRange: async (start, end) => (await all()).filter((r) => r.timestamp >= start && r.timestamp <= end),
|
|
161
161
|
getEventsByResult: async (resultId) => (await all()).filter((r) => r.resultId === resultId),
|
|
162
162
|
getEventsForNote: async (noteId) => (await all()).filter((r) => r.noteId === noteId),
|
|
163
|
-
getEventsByContent: async (
|
|
163
|
+
getEventsByContent: async (blockContentId5) => (await all()).filter((r) => r.blockContentId === blockContentId5),
|
|
164
164
|
scanAll: all,
|
|
165
165
|
appendEvents: async (appended) => {
|
|
166
166
|
const sql = await loadSql();
|
|
@@ -266,6 +266,11 @@ async function getEffortBySlug(db, slug) {
|
|
|
266
266
|
const efforts = await allRows(sql, db, "efforts");
|
|
267
267
|
return efforts.find((e) => e.slug === slug);
|
|
268
268
|
}
|
|
269
|
+
async function getEquipmentBySlug(db, slug) {
|
|
270
|
+
const sql = await loadSql();
|
|
271
|
+
const equipment = await allRows(sql, db, "equipment");
|
|
272
|
+
return equipment.find((e) => e.slug === slug);
|
|
273
|
+
}
|
|
269
274
|
async function listTagsByType(db, type) {
|
|
270
275
|
const sql = await loadSql();
|
|
271
276
|
const tags = await allRows(sql, db, "tags");
|
|
@@ -296,6 +301,7 @@ function seedNoteSourceId(path, content, chunkId) {
|
|
|
296
301
|
if (chunkId === CANVAS_CHUNK_ID2) return `guides:${canvasRouteSlug(content) ?? under}`;
|
|
297
302
|
if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
|
|
298
303
|
if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
|
|
304
|
+
if (chunkId === EQUIPMENT_CHUNK_ID) return "equipment";
|
|
299
305
|
if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
|
|
300
306
|
if (chunkId.startsWith("collection.")) {
|
|
301
307
|
const catalog = catalogForChunkId(chunkId);
|
|
@@ -304,40 +310,19 @@ function seedNoteSourceId(path, content, chunkId) {
|
|
|
304
310
|
if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
|
|
305
311
|
return void 0;
|
|
306
312
|
}
|
|
307
|
-
var SEED_SCHEMA2, SEED_REGISTERED_WIDGETS, EFFORTS_CHUNK_ID, CANVAS_CHUNK_ID2, DASHBOARDS_CHUNK_ID, TEMPLATE_CHUNK_ID;
|
|
313
|
+
var SEED_SCHEMA2, SEED_REGISTERED_WIDGETS, EFFORTS_CHUNK_ID, EQUIPMENT_CHUNK_ID, CANVAS_CHUNK_ID2, DASHBOARDS_CHUNK_ID, TEMPLATE_CHUNK_ID;
|
|
308
314
|
var init_contract = __esm({
|
|
309
315
|
"src/seedbuild/contract.ts"() {
|
|
310
|
-
SEED_SCHEMA2 =
|
|
316
|
+
SEED_SCHEMA2 = 9;
|
|
311
317
|
SEED_REGISTERED_WIDGETS = wodWikiCore.KNOWN_SECTION_WIDGETS;
|
|
312
318
|
EFFORTS_CHUNK_ID = "efforts";
|
|
319
|
+
EQUIPMENT_CHUNK_ID = "equipment";
|
|
313
320
|
CANVAS_CHUNK_ID2 = "canvas";
|
|
314
321
|
DASHBOARDS_CHUNK_ID = "dashboards";
|
|
315
322
|
TEMPLATE_CHUNK_ID = "template";
|
|
316
323
|
}
|
|
317
324
|
});
|
|
318
325
|
|
|
319
|
-
// src/seedbuild/effort-slugs.ts
|
|
320
|
-
function extractEffortSlugs2(statements) {
|
|
321
|
-
const slugs = /* @__PURE__ */ new Set();
|
|
322
|
-
for (const statement of statements) {
|
|
323
|
-
if (statement.exerciseId) {
|
|
324
|
-
slugs.add(statement.exerciseId);
|
|
325
|
-
} else {
|
|
326
|
-
const effortMetric = statement.metrics?.getMetric?.("effort");
|
|
327
|
-
const effort = effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0;
|
|
328
|
-
if (effort) {
|
|
329
|
-
const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
330
|
-
if (slug) slugs.add(slug);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return Array.from(slugs);
|
|
335
|
-
}
|
|
336
|
-
var init_effort_slugs = __esm({
|
|
337
|
-
"src/seedbuild/effort-slugs.ts"() {
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
|
|
341
326
|
// src/seedbuild/filename.ts
|
|
342
327
|
function fileToDisplayName(filename) {
|
|
343
328
|
const base = filename.replace(/\.md$/, "");
|
|
@@ -383,149 +368,6 @@ var init_filename = __esm({
|
|
|
383
368
|
};
|
|
384
369
|
}
|
|
385
370
|
});
|
|
386
|
-
|
|
387
|
-
// src/seedbuild/frontmatter.ts
|
|
388
|
-
function unquote(value) {
|
|
389
|
-
return value.replace(
|
|
390
|
-
/^(['"])(.*)\1$/,
|
|
391
|
-
(_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
function parseFrontmatter(raw) {
|
|
395
|
-
const match = raw.match(FRONTMATTER_RE);
|
|
396
|
-
if (!match) return { meta: {}, body: raw };
|
|
397
|
-
return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
|
|
398
|
-
}
|
|
399
|
-
function parseMetaLines(lines) {
|
|
400
|
-
const meta = {};
|
|
401
|
-
for (let i = 0; i < lines.length; i++) {
|
|
402
|
-
const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
|
|
403
|
-
if (!keyMatch) continue;
|
|
404
|
-
const key = keyMatch[1];
|
|
405
|
-
const rawVal = keyMatch[2].trim();
|
|
406
|
-
if (rawVal === "") {
|
|
407
|
-
const items = [];
|
|
408
|
-
while (i + 1 < lines.length) {
|
|
409
|
-
const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
|
|
410
|
-
if (!itemMatch) break;
|
|
411
|
-
items.push(itemMatch[1].trim());
|
|
412
|
-
i++;
|
|
413
|
-
}
|
|
414
|
-
meta[key] = items.length > 0 ? items : "";
|
|
415
|
-
} else {
|
|
416
|
-
const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
|
|
417
|
-
const value = unquote(rawVal);
|
|
418
|
-
const num = Number(value);
|
|
419
|
-
meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return meta;
|
|
423
|
-
}
|
|
424
|
-
function getScalar(meta, key) {
|
|
425
|
-
const value = meta[key];
|
|
426
|
-
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
427
|
-
}
|
|
428
|
-
function getList(meta, key) {
|
|
429
|
-
const value = meta[key];
|
|
430
|
-
return Array.isArray(value) ? value : [];
|
|
431
|
-
}
|
|
432
|
-
var FRONTMATTER_RE;
|
|
433
|
-
var init_frontmatter = __esm({
|
|
434
|
-
"src/seedbuild/frontmatter.ts"() {
|
|
435
|
-
FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
function parseDiscipline(val) {
|
|
439
|
-
if (!val) return void 0;
|
|
440
|
-
const normalized = unquoteYaml(val.trim()).toLowerCase();
|
|
441
|
-
if (wodWikiLang.isEffortDiscipline(normalized)) return normalized;
|
|
442
|
-
console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
|
|
443
|
-
return void 0;
|
|
444
|
-
}
|
|
445
|
-
function unquoteYaml(val) {
|
|
446
|
-
if (val.startsWith('"') && val.endsWith('"')) {
|
|
447
|
-
return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
448
|
-
}
|
|
449
|
-
if (val.startsWith("'") && val.endsWith("'")) {
|
|
450
|
-
return val.slice(1, -1).replace(/\\'/g, "'");
|
|
451
|
-
}
|
|
452
|
-
return val;
|
|
453
|
-
}
|
|
454
|
-
function coerceNestedScalar(raw) {
|
|
455
|
-
if (raw === "true") return true;
|
|
456
|
-
if (raw === "false") return false;
|
|
457
|
-
if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
|
|
458
|
-
return raw;
|
|
459
|
-
}
|
|
460
|
-
function readNestedSection(lines, key) {
|
|
461
|
-
const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
|
|
462
|
-
if (start === -1) return void 0;
|
|
463
|
-
const result = {};
|
|
464
|
-
let subKey = null;
|
|
465
|
-
for (let i = start + 1; i < lines.length; i++) {
|
|
466
|
-
const line = lines[i];
|
|
467
|
-
if (/^\S/.test(line)) break;
|
|
468
|
-
const trimmed = line.trim();
|
|
469
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
470
|
-
const indent = line.length - line.trimStart().length;
|
|
471
|
-
const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
|
|
472
|
-
if (!m) continue;
|
|
473
|
-
const [, k, rawVal] = m;
|
|
474
|
-
if (indent === 2) {
|
|
475
|
-
if (rawVal === "") {
|
|
476
|
-
subKey = k;
|
|
477
|
-
result[k] = {};
|
|
478
|
-
} else {
|
|
479
|
-
subKey = null;
|
|
480
|
-
result[k] = coerceNestedScalar(rawVal);
|
|
481
|
-
}
|
|
482
|
-
} else if (indent >= 4 && subKey !== null) {
|
|
483
|
-
result[subKey][k] = coerceNestedScalar(rawVal);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
487
|
-
}
|
|
488
|
-
function parseEffortFile(raw) {
|
|
489
|
-
const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
490
|
-
if (!blockMatch) return null;
|
|
491
|
-
const { meta, body } = parseFrontmatter(raw);
|
|
492
|
-
const innerLines = blockMatch[1].split(/\r?\n/);
|
|
493
|
-
const id = getScalar(meta, "id");
|
|
494
|
-
const slug = getScalar(meta, "slug");
|
|
495
|
-
const label = getScalar(meta, "label");
|
|
496
|
-
const aliases = getList(meta, "aliases");
|
|
497
|
-
const baseAttrs = readNestedSection(innerLines, "baseAttributes");
|
|
498
|
-
const hints = readNestedSection(innerLines, "hints");
|
|
499
|
-
const derivation = readNestedSection(innerLines, "derivation");
|
|
500
|
-
const metRaw = baseAttrs?.met;
|
|
501
|
-
const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
|
|
502
|
-
if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
|
|
503
|
-
const discipline = parseDiscipline(
|
|
504
|
-
typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
|
|
505
|
-
);
|
|
506
|
-
const intensityTier = baseAttrs?.intensityTier;
|
|
507
|
-
const trimmedBody = body.trim();
|
|
508
|
-
return {
|
|
509
|
-
id: String(id),
|
|
510
|
-
slug: String(slug),
|
|
511
|
-
label: String(label),
|
|
512
|
-
aliases,
|
|
513
|
-
baseAttributes: {
|
|
514
|
-
met,
|
|
515
|
-
...discipline ? { discipline } : {},
|
|
516
|
-
...intensityTier ? { intensityTier } : {}
|
|
517
|
-
},
|
|
518
|
-
registrySource: "bundled",
|
|
519
|
-
...trimmedBody ? { body: trimmedBody } : {},
|
|
520
|
-
...hints ? { hints } : {},
|
|
521
|
-
...derivation ? { derivation } : {}
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
var init_effort_file = __esm({
|
|
525
|
-
"src/seedbuild/effort-file.ts"() {
|
|
526
|
-
init_frontmatter();
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
371
|
function resolveGitVersion(repoRoot) {
|
|
530
372
|
try {
|
|
531
373
|
const run = (args) => child_process.execSync(`git ${args}`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
@@ -614,18 +456,93 @@ var init_report = __esm({
|
|
|
614
456
|
}
|
|
615
457
|
});
|
|
616
458
|
|
|
459
|
+
// src/seedbuild/frontmatter.ts
|
|
460
|
+
function unquote(value) {
|
|
461
|
+
return value.replace(
|
|
462
|
+
/^(['"])(.*)\1$/,
|
|
463
|
+
(_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
function parseFrontmatter(raw) {
|
|
467
|
+
const match = raw.match(FRONTMATTER_RE);
|
|
468
|
+
if (!match) return { meta: {}, body: raw };
|
|
469
|
+
return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
|
|
470
|
+
}
|
|
471
|
+
function parseMetaLines(lines) {
|
|
472
|
+
const meta = {};
|
|
473
|
+
for (let i = 0; i < lines.length; i++) {
|
|
474
|
+
const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
|
|
475
|
+
if (!keyMatch) continue;
|
|
476
|
+
const key = keyMatch[1];
|
|
477
|
+
const rawVal = keyMatch[2].trim();
|
|
478
|
+
if (rawVal === "") {
|
|
479
|
+
const items = [];
|
|
480
|
+
while (i + 1 < lines.length) {
|
|
481
|
+
const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
|
|
482
|
+
if (!itemMatch) break;
|
|
483
|
+
items.push(itemMatch[1].trim());
|
|
484
|
+
i++;
|
|
485
|
+
}
|
|
486
|
+
meta[key] = items.length > 0 ? items : "";
|
|
487
|
+
} else {
|
|
488
|
+
const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
|
|
489
|
+
const value = unquote(rawVal);
|
|
490
|
+
const num = Number(value);
|
|
491
|
+
meta[key] = !wasQuoted && value !== "" && !isNaN(num) ? num : value;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return meta;
|
|
495
|
+
}
|
|
496
|
+
function getScalar(meta, key) {
|
|
497
|
+
const value = meta[key];
|
|
498
|
+
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
499
|
+
}
|
|
500
|
+
var FRONTMATTER_RE;
|
|
501
|
+
var init_frontmatter = __esm({
|
|
502
|
+
"src/seedbuild/frontmatter.ts"() {
|
|
503
|
+
FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
function object(value) {
|
|
507
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
508
|
+
}
|
|
509
|
+
function nonnegative(value) {
|
|
510
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
511
|
+
}
|
|
512
|
+
function assertSeedManifest2(value) {
|
|
513
|
+
if (!object(value) || value.schema !== SEED_SCHEMA2) throw new Error("Incompatible seed schema; rebuild seed artifacts");
|
|
514
|
+
if (!nonnegative(value.version) || typeof value.builtAt !== "string" || !Number.isFinite(Date.parse(value.builtAt)) || !Array.isArray(value.chunks)) throw new Error("seed manifest failed shape validation");
|
|
515
|
+
for (const key of ["versionLabel", "commit", "name", "description"]) if (value[key] !== void 0 && typeof value[key] !== "string") throw new Error("invalid seed manifest metadata");
|
|
516
|
+
if (value.repositoryUrl !== void 0 && (typeof value.repositoryUrl !== "string" || !/^https:\/\//.test(value.repositoryUrl) || new URL(value.repositoryUrl).username || new URL(value.repositoryUrl).password)) throw new Error("invalid seed repository URL");
|
|
517
|
+
if (value.dirty !== void 0 && typeof value.dirty !== "boolean") throw new Error("invalid seed manifest metadata");
|
|
518
|
+
const ids = /* @__PURE__ */ new Set(), paths = /* @__PURE__ */ new Set();
|
|
519
|
+
for (const chunk of value.chunks) {
|
|
520
|
+
if (!object(chunk) || typeof chunk.id !== "string" || !chunk.id || typeof chunk.path !== "string" || !/^chunks\/[a-zA-Z0-9._-]+\.json$/.test(chunk.path) || typeof chunk.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(chunk.sha256) || !nonnegative(chunk.bytes) || !nonnegative(chunk.count) || chunk.kind !== void 0 && chunk.kind !== "notes" || ids.has(chunk.id) || paths.has(chunk.path)) throw new Error("seed manifest failed shape validation");
|
|
521
|
+
ids.add(chunk.id);
|
|
522
|
+
paths.add(chunk.path);
|
|
523
|
+
}
|
|
524
|
+
return value;
|
|
525
|
+
}
|
|
526
|
+
var init_validation = __esm({
|
|
527
|
+
"src/seedbuild/validation.ts"() {
|
|
528
|
+
init_contract();
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
617
532
|
// src/seedbuild/compiler.ts
|
|
618
533
|
var compiler_exports = {};
|
|
619
534
|
__export(compiler_exports, {
|
|
620
535
|
buildBlockEffortRows: () => buildBlockEffortRows,
|
|
536
|
+
buildBlockEquipmentRows: () => buildBlockEquipmentRows,
|
|
621
537
|
buildBlockIndexRows: () => buildBlockIndexRows,
|
|
622
538
|
chunkIdFor: () => chunkIdFor,
|
|
623
539
|
collectCorpusMarkdown: () => collectCorpusMarkdown,
|
|
624
540
|
compileSeedRow: () => compileSeedRow,
|
|
625
541
|
discoverUncatalogedEfforts: () => discoverUncatalogedEfforts,
|
|
626
542
|
feedDateToCreatedAt: () => feedDateToCreatedAt,
|
|
543
|
+
frontmatterDateToCreatedAt: () => frontmatterDateToCreatedAt,
|
|
627
544
|
generateSeed: () => generateSeed,
|
|
628
|
-
|
|
545
|
+
loadCorpusReferenceCatalog: () => loadCorpusReferenceCatalog,
|
|
629
546
|
partitionIntoChunks: () => partitionIntoChunks,
|
|
630
547
|
scaffoldDiscoveredEfforts: () => scaffoldDiscoveredEfforts,
|
|
631
548
|
validateDashboardNote: () => validateDashboardNote,
|
|
@@ -651,6 +568,7 @@ function chunkIdFor(relPath) {
|
|
|
651
568
|
case "canvas":
|
|
652
569
|
case "dashboards":
|
|
653
570
|
case "efforts":
|
|
571
|
+
case "equipment":
|
|
654
572
|
return corpus;
|
|
655
573
|
case "collections":
|
|
656
574
|
return parts.length === 3 ? `collection._root` : `collection.${slug}`;
|
|
@@ -669,7 +587,7 @@ function partitionIntoChunks(paths) {
|
|
|
669
587
|
const id = chunkIdFor(p);
|
|
670
588
|
if (id === null) {
|
|
671
589
|
throw new Error(
|
|
672
|
-
`[generate-seed] Parity failure: '${p}' is not part of any known corpus (canvas | dashboards | efforts | collections | feeds). Extend chunkIdFor() if this is a new corpus.`
|
|
590
|
+
`[generate-seed] Parity failure: '${p}' is not part of any known corpus (canvas | dashboards | efforts | equipment | collections | feeds). Extend chunkIdFor() if this is a new corpus.`
|
|
673
591
|
);
|
|
674
592
|
}
|
|
675
593
|
const list = byId.get(id);
|
|
@@ -725,6 +643,18 @@ function getFileCreatedAt(file, repoRoot) {
|
|
|
725
643
|
return 0;
|
|
726
644
|
}
|
|
727
645
|
}
|
|
646
|
+
function frontmatterDateToCreatedAt(raw, path) {
|
|
647
|
+
const value = getScalar(parseFrontmatter(raw).meta, "date");
|
|
648
|
+
if (value === void 0 || value === "") return void 0;
|
|
649
|
+
const text = String(value);
|
|
650
|
+
const parts = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
651
|
+
const ts = parts ? Date.parse(`${text}T12:00:00Z`) : NaN;
|
|
652
|
+
const valid = !!parts && Number.isFinite(ts) && new Date(ts).getUTCMonth() === Number(parts[2]) - 1 && new Date(ts).getUTCDate() === Number(parts[3]);
|
|
653
|
+
if (!valid) {
|
|
654
|
+
throw new Error(`[wod build] invalid frontmatter date '${text}' in '${path}' \u2014 expected a real calendar day (YYYY-MM-DD)`);
|
|
655
|
+
}
|
|
656
|
+
return ts;
|
|
657
|
+
}
|
|
728
658
|
function feedDateToCreatedAt(dateKey) {
|
|
729
659
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) return 0;
|
|
730
660
|
const ts = Date.parse(`${dateKey}T00:00:00Z`);
|
|
@@ -739,7 +669,7 @@ function compileSeedRow(path, content, metadata = {}) {
|
|
|
739
669
|
const section = sections.find((section2) => section2.from === block.from && section2.to === block.to);
|
|
740
670
|
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: wodWikiCore.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 };
|
|
741
671
|
});
|
|
742
|
-
return { path, content, blocks, blockIndex, blockEfforts: [] };
|
|
672
|
+
return { path, content, blocks, blockIndex, blockEfforts: [], blockEquipment: [] };
|
|
743
673
|
}
|
|
744
674
|
function buildBlockIndexRows(markdownDir, corpusRoot) {
|
|
745
675
|
const rows = [];
|
|
@@ -752,7 +682,7 @@ function buildBlockIndexRows(markdownDir, corpusRoot) {
|
|
|
752
682
|
const catalog = parts[2];
|
|
753
683
|
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;
|
|
754
684
|
const sourceId = seedNoteSourceId(path$1, content, chunkId);
|
|
755
|
-
const createdAt = chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path$1, corpusRoot);
|
|
685
|
+
const createdAt = frontmatterDateToCreatedAt(content, path$1) ?? (chunkId.startsWith("feed.") ? feedDateToCreatedAt(parts.at(-2) ?? "") : getFileCreatedAt(path$1, corpusRoot));
|
|
756
686
|
rows.push(...compileSeedRow(path$1, content, { routeId, sourceId, createdAt }).blockIndex);
|
|
757
687
|
}
|
|
758
688
|
return rows;
|
|
@@ -789,41 +719,37 @@ function formatDiscoveredLabel(raw) {
|
|
|
789
719
|
const clean = raw.replace(/[-_]+/g, " ").trim();
|
|
790
720
|
return clean.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
791
721
|
}
|
|
792
|
-
function
|
|
793
|
-
const
|
|
794
|
-
const
|
|
722
|
+
function loadCorpusReferenceCatalog(markdownDir) {
|
|
723
|
+
const efforts = [];
|
|
724
|
+
const equipment = [];
|
|
795
725
|
const effortsDir = path.join(markdownDir, "efforts");
|
|
796
726
|
for (const file of listMarkdownFiles(effortsDir)) {
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
for (const alias of parsed.aliases) {
|
|
805
|
-
aliasToCanonical.set(alias.toLowerCase(), parsed.slug);
|
|
806
|
-
aliasToCanonical.set(alias.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""), parsed.slug);
|
|
807
|
-
}
|
|
727
|
+
const parsed = wodWikiLang.parseEffortFile(fs.readFileSync(path.join(effortsDir, file), "utf8"));
|
|
728
|
+
if (parsed) efforts.push(parsed);
|
|
729
|
+
}
|
|
730
|
+
const equipmentDir = path.join(markdownDir, "equipment");
|
|
731
|
+
for (const file of listMarkdownFiles(equipmentDir)) {
|
|
732
|
+
const parsed = wodWikiLang.parseEquipmentFile(fs.readFileSync(path.join(equipmentDir, file), "utf8"));
|
|
733
|
+
if (parsed) equipment.push(parsed);
|
|
808
734
|
}
|
|
809
|
-
return {
|
|
735
|
+
return { efforts, equipment };
|
|
810
736
|
}
|
|
811
737
|
function discoverUncatalogedEfforts(markdownDir, corpusRoot) {
|
|
812
|
-
const
|
|
738
|
+
const catalog = loadCorpusReferenceCatalog(markdownDir);
|
|
739
|
+
const index = wodWikiLang.createReferenceIndex(catalog);
|
|
813
740
|
const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
|
|
814
741
|
const discovered = /* @__PURE__ */ new Map();
|
|
815
742
|
for (const block of blocks) {
|
|
816
743
|
if (block.dataType !== "wod" || !block.rawContent) continue;
|
|
817
744
|
const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
|
|
818
|
-
const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent);
|
|
745
|
+
const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
|
|
819
746
|
for (const statement of script.statements) {
|
|
820
747
|
const effortMetric = statement.metrics?.getMetric?.("effort");
|
|
821
748
|
const effortLabel = (effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0) ?? statement.exerciseId;
|
|
822
749
|
if (!effortLabel) continue;
|
|
823
|
-
const slugified =
|
|
750
|
+
const slugified = wodWikiLang.slugifyRefLabel(effortLabel);
|
|
824
751
|
if (!slugified || NON_EFFORT_SLUGS[slugified]) continue;
|
|
825
|
-
|
|
826
|
-
if (canonical && slugs.has(canonical)) continue;
|
|
752
|
+
if (index.resolveEffort(slugified) || index.resolveEffort(effortLabel)) continue;
|
|
827
753
|
const existing = discovered.get(slugified);
|
|
828
754
|
if (existing) {
|
|
829
755
|
existing.rawLabels.add(effortLabel);
|
|
@@ -873,17 +799,16 @@ ${aliases.join("\n")}` : "aliases: []",
|
|
|
873
799
|
}
|
|
874
800
|
function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
|
|
875
801
|
const efforts = [];
|
|
876
|
-
const
|
|
802
|
+
const index = wodWikiLang.createReferenceIndex(loadCorpusReferenceCatalog(markdownDir));
|
|
877
803
|
for (const block of blocks) {
|
|
878
804
|
if (block.dataType !== "wod" || !block.rawContent) continue;
|
|
879
805
|
const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((section2) => section2.scriptBlock);
|
|
880
|
-
const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent);
|
|
881
|
-
const effortSlugs = extractEffortSlugs2(script.statements);
|
|
806
|
+
const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
|
|
882
807
|
const linked = /* @__PURE__ */ new Set();
|
|
883
|
-
for (const rawSlug of
|
|
884
|
-
const
|
|
885
|
-
const canonicalSlug =
|
|
886
|
-
if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[
|
|
808
|
+
for (const rawSlug of wodWikiLang.extractStatementRefs(script.statements).efforts) {
|
|
809
|
+
const canonical = index.resolveEffort(rawSlug);
|
|
810
|
+
const canonicalSlug = canonical?.slug;
|
|
811
|
+
if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[rawSlug]) {
|
|
887
812
|
continue;
|
|
888
813
|
}
|
|
889
814
|
if (linked.has(canonicalSlug)) continue;
|
|
@@ -902,6 +827,32 @@ function buildBlockEffortRows(markdownDir, corpusRoot, blocks = buildBlockIndexR
|
|
|
902
827
|
}
|
|
903
828
|
return efforts;
|
|
904
829
|
}
|
|
830
|
+
function buildBlockEquipmentRows(markdownDir, corpusRoot, blocks = buildBlockIndexRows(markdownDir, corpusRoot)) {
|
|
831
|
+
const links = [];
|
|
832
|
+
const index = wodWikiLang.createReferenceIndex(loadCorpusReferenceCatalog(markdownDir));
|
|
833
|
+
for (const block of blocks) {
|
|
834
|
+
if (block.dataType !== "wod" || !block.rawContent) continue;
|
|
835
|
+
const section = wodWikiCore.parseDocumentSections(block.rawContent, { registeredWidgets: SEED_REGISTERED_WIDGETS }).find((s) => s.scriptBlock);
|
|
836
|
+
const script = wodWikiLang.parseScript(section?.scriptBlock?.content ?? block.rawContent, { references: index });
|
|
837
|
+
const linked = /* @__PURE__ */ new Set();
|
|
838
|
+
for (const rawSlug of wodWikiLang.extractStatementRefs(script.statements).equipment) {
|
|
839
|
+
const canonicalSlug = index.resolveEquipment(rawSlug)?.slug;
|
|
840
|
+
if (!canonicalSlug || linked.has(canonicalSlug)) continue;
|
|
841
|
+
linked.add(canonicalSlug);
|
|
842
|
+
links.push({
|
|
843
|
+
id: `${block.id}:${canonicalSlug}`,
|
|
844
|
+
noteId: block.noteId,
|
|
845
|
+
blockId: block.blockId,
|
|
846
|
+
blockContentId: block.blockContentId,
|
|
847
|
+
equipmentSlug: canonicalSlug,
|
|
848
|
+
isStatic: block.isStatic,
|
|
849
|
+
sourcePath: block.sourcePath,
|
|
850
|
+
createdAt: block.createdAt
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return links;
|
|
855
|
+
}
|
|
905
856
|
function sha256Hex(data) {
|
|
906
857
|
return crypto.createHash("sha256").update(data).digest("hex");
|
|
907
858
|
}
|
|
@@ -912,6 +863,13 @@ function generateSeed(options = {}) {
|
|
|
912
863
|
const markdownDir = options.markdownDir ?? path.join(corpusRoot, "markdown");
|
|
913
864
|
const outDir = options.outDir ?? path.join(corpusRoot, "dist");
|
|
914
865
|
const gitVersion = options.version !== void 0 ? { ...resolveGitVersion(corpusRoot), version: options.version } : resolveGitVersion(corpusRoot);
|
|
866
|
+
const packageFile = path.join(corpusRoot, "package.json");
|
|
867
|
+
const packageMetadata = fs.existsSync(packageFile) ? JSON.parse(fs.readFileSync(packageFile, "utf8")) : {};
|
|
868
|
+
const metadata = packageMetadata && typeof packageMetadata === "object" ? packageMetadata : {};
|
|
869
|
+
const repository = "repository" in metadata ? metadata.repository : void 0;
|
|
870
|
+
const repositoryUrl = typeof repository === "string" ? repository : repository && typeof repository === "object" && "url" in repository && typeof repository.url === "string" ? repository.url : void 0;
|
|
871
|
+
const webRepositoryUrl = repositoryUrl?.replace(/^git\+/, "").replace(/\.git$/, "");
|
|
872
|
+
const repositoryLink = webRepositoryUrl && URL.canParse(webRepositoryUrl) ? new URL(webRepositoryUrl) : void 0;
|
|
915
873
|
if (options.scaffoldDiscoveredEfforts) {
|
|
916
874
|
const discovered = discoverUncatalogedEfforts(markdownDir, corpusRoot);
|
|
917
875
|
if (discovered.length > 0) {
|
|
@@ -946,9 +904,11 @@ ${shown}${more}`);
|
|
|
946
904
|
const manifestChunks = [];
|
|
947
905
|
const tBlockEfforts = Date.now();
|
|
948
906
|
const effortRows = buildBlockEffortRows(markdownDir, corpusRoot, blockRows);
|
|
907
|
+
const equipmentRows = buildBlockEquipmentRows(markdownDir, corpusRoot, blockRows);
|
|
949
908
|
passMs.blockEfforts = Date.now() - tBlockEfforts;
|
|
950
909
|
const blocksByPath = /* @__PURE__ */ new Map();
|
|
951
910
|
const effortsByPath = /* @__PURE__ */ new Map();
|
|
911
|
+
const equipmentByPath = /* @__PURE__ */ new Map();
|
|
952
912
|
for (const row of blockRows) {
|
|
953
913
|
const rows = blocksByPath.get(row.sourcePath) ?? [];
|
|
954
914
|
rows.push(row);
|
|
@@ -959,6 +919,11 @@ ${shown}${more}`);
|
|
|
959
919
|
rows.push(row);
|
|
960
920
|
effortsByPath.set(row.sourcePath, rows);
|
|
961
921
|
}
|
|
922
|
+
for (const row of equipmentRows) {
|
|
923
|
+
const rows = equipmentByPath.get(row.sourcePath) ?? [];
|
|
924
|
+
rows.push(row);
|
|
925
|
+
equipmentByPath.set(row.sourcePath, rows);
|
|
926
|
+
}
|
|
962
927
|
let totalFiles = 0;
|
|
963
928
|
const tWrite = Date.now();
|
|
964
929
|
for (const { id, files } of groups) {
|
|
@@ -969,6 +934,7 @@ ${shown}${more}`);
|
|
|
969
934
|
const compiled = compileSeedRow(p, content);
|
|
970
935
|
compiled.blockIndex = blocksByPath.get(p) ?? compiled.blockIndex;
|
|
971
936
|
compiled.blockEfforts = effortsByPath.get(p) ?? [];
|
|
937
|
+
compiled.blockEquipment = equipmentByPath.get(p) ?? [];
|
|
972
938
|
return compiled;
|
|
973
939
|
});
|
|
974
940
|
const bytes = Buffer.from(JSON.stringify(rows), "utf8");
|
|
@@ -980,6 +946,9 @@ ${shown}${more}`);
|
|
|
980
946
|
}
|
|
981
947
|
passMs.write = Date.now() - tWrite;
|
|
982
948
|
const manifest = {
|
|
949
|
+
name: "name" in metadata && typeof metadata.name === "string" ? metadata.name : void 0,
|
|
950
|
+
description: "description" in metadata && typeof metadata.description === "string" ? metadata.description : void 0,
|
|
951
|
+
repositoryUrl: repositoryLink?.protocol === "https:" && !repositoryLink.username && !repositoryLink.password ? repositoryLink.href : void 0,
|
|
983
952
|
schema: SEED_SCHEMA2,
|
|
984
953
|
version: gitVersion.version,
|
|
985
954
|
versionLabel: gitVersion.versionLabel,
|
|
@@ -988,6 +957,7 @@ ${shown}${more}`);
|
|
|
988
957
|
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
989
958
|
chunks: manifestChunks
|
|
990
959
|
};
|
|
960
|
+
assertSeedManifest2(manifest);
|
|
991
961
|
fs.writeFileSync(path.join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
992
962
|
const reportPath = writeBuildReport(outDir, {
|
|
993
963
|
manifest,
|
|
@@ -1000,12 +970,11 @@ var NON_EFFORT_SLUGS;
|
|
|
1000
970
|
var init_compiler = __esm({
|
|
1001
971
|
"src/seedbuild/compiler.ts"() {
|
|
1002
972
|
init_contract();
|
|
1003
|
-
init_effort_slugs();
|
|
1004
973
|
init_filename();
|
|
1005
|
-
init_effort_file();
|
|
1006
974
|
init_git_version();
|
|
1007
975
|
init_report();
|
|
1008
976
|
init_frontmatter();
|
|
977
|
+
init_validation();
|
|
1009
978
|
NON_EFFORT_SLUGS = {
|
|
1010
979
|
"amrap": true,
|
|
1011
980
|
"emom": true,
|
|
@@ -1055,6 +1024,219 @@ var init_compiler = __esm({
|
|
|
1055
1024
|
}
|
|
1056
1025
|
});
|
|
1057
1026
|
|
|
1027
|
+
// src/version.ts
|
|
1028
|
+
var VERSION;
|
|
1029
|
+
var init_version = __esm({
|
|
1030
|
+
"src/version.ts"() {
|
|
1031
|
+
VERSION = "0.37.2139";
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
// src/cli/scaffoldCatalog.ts
|
|
1036
|
+
var scaffoldCatalog_exports = {};
|
|
1037
|
+
__export(scaffoldCatalog_exports, {
|
|
1038
|
+
scaffoldCatalog: () => scaffoldCatalog,
|
|
1039
|
+
validateCatalogName: () => validateCatalogName
|
|
1040
|
+
});
|
|
1041
|
+
function validateCatalogName(name) {
|
|
1042
|
+
if (name.length === 0 || name === "." || name === "..") {
|
|
1043
|
+
throw new Error("Catalog name must be a single non-empty directory name");
|
|
1044
|
+
}
|
|
1045
|
+
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
|
|
1046
|
+
throw new Error(`Invalid catalog name '${name}': must be a single directory name (no path separators or traversal)`);
|
|
1047
|
+
}
|
|
1048
|
+
if (/^(?:[A-Za-z]:)?[/\\]/.test(name)) {
|
|
1049
|
+
throw new Error(`Invalid catalog name '${name}': absolute paths are not allowed`);
|
|
1050
|
+
}
|
|
1051
|
+
if (name.length > 214 || !/^[a-z0-9][a-z0-9._-]*$/.test(name)) {
|
|
1052
|
+
throw new Error(
|
|
1053
|
+
`Invalid catalog name '${name}': must be a valid npm package name (lowercase letters, digits, dots, hyphens, underscores; no leading dot/hyphen)`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
return name;
|
|
1057
|
+
}
|
|
1058
|
+
function scaffoldCatalog(name, opts = {}) {
|
|
1059
|
+
validateCatalogName(name);
|
|
1060
|
+
const dir = path.resolve(opts.cwd ?? process.cwd(), name);
|
|
1061
|
+
try {
|
|
1062
|
+
fs.mkdirSync(dir);
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
if (error.code === "EEXIST") {
|
|
1065
|
+
throw new Error(`Refusing to overwrite '${dir}': destination already exists`, { cause: error });
|
|
1066
|
+
}
|
|
1067
|
+
throw error;
|
|
1068
|
+
}
|
|
1069
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1070
|
+
const files = [
|
|
1071
|
+
["package.json", `${JSON.stringify(
|
|
1072
|
+
{
|
|
1073
|
+
name,
|
|
1074
|
+
version: "0.1.0",
|
|
1075
|
+
private: true,
|
|
1076
|
+
type: "module",
|
|
1077
|
+
description: `WOD Wiki workout catalog: ${name}`,
|
|
1078
|
+
scripts: {
|
|
1079
|
+
build: "wod build . --output dist"
|
|
1080
|
+
},
|
|
1081
|
+
devDependencies: {
|
|
1082
|
+
"@bitcobblers/wod-wiki-tools": `^${VERSION}`
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
null,
|
|
1086
|
+
2
|
|
1087
|
+
)}
|
|
1088
|
+
`],
|
|
1089
|
+
[".gitignore", "node_modules/\ndist/\n"],
|
|
1090
|
+
[".github/workflows/publish-catalog.yml", publishWorkflow()],
|
|
1091
|
+
["markdown/collections/example/README.md", collectionReadme(name)],
|
|
1092
|
+
[`markdown/collections/example/morning-session.md`, exampleWorkout(date)],
|
|
1093
|
+
["README.md", catalogReadme(name)]
|
|
1094
|
+
];
|
|
1095
|
+
try {
|
|
1096
|
+
for (const [rel, content] of files) {
|
|
1097
|
+
const target = path.join(dir, rel);
|
|
1098
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
1099
|
+
fs.writeFileSync(target, content, "utf-8");
|
|
1100
|
+
}
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
return { dir, files: files.map(([rel]) => rel) };
|
|
1106
|
+
}
|
|
1107
|
+
var publishWorkflow, collectionReadme, exampleWorkout, catalogReadme;
|
|
1108
|
+
var init_scaffoldCatalog = __esm({
|
|
1109
|
+
"src/cli/scaffoldCatalog.ts"() {
|
|
1110
|
+
init_version();
|
|
1111
|
+
publishWorkflow = () => `name: Publish catalog
|
|
1112
|
+
|
|
1113
|
+
# Compile the catalog with the wod CLI and deploy dist/ to GitHub Pages.
|
|
1114
|
+
# Requires Pages enabled: repo Settings \u2192 Pages \u2192 Source: "GitHub Actions".
|
|
1115
|
+
|
|
1116
|
+
on:
|
|
1117
|
+
push:
|
|
1118
|
+
branches: [main]
|
|
1119
|
+
workflow_dispatch:
|
|
1120
|
+
|
|
1121
|
+
permissions:
|
|
1122
|
+
contents: read
|
|
1123
|
+
pages: write
|
|
1124
|
+
id-token: write
|
|
1125
|
+
|
|
1126
|
+
concurrency:
|
|
1127
|
+
group: pages
|
|
1128
|
+
cancel-in-progress: true
|
|
1129
|
+
|
|
1130
|
+
jobs:
|
|
1131
|
+
publish:
|
|
1132
|
+
runs-on: ubuntu-latest
|
|
1133
|
+
environment:
|
|
1134
|
+
name: github-pages
|
|
1135
|
+
url: \${{ steps.deployment.outputs.page_url }}
|
|
1136
|
+
steps:
|
|
1137
|
+
- uses: actions/checkout@v4
|
|
1138
|
+
with:
|
|
1139
|
+
# Full history: seed manifest version stamps derive from git commit count.
|
|
1140
|
+
fetch-depth: 0
|
|
1141
|
+
|
|
1142
|
+
- name: Set up Bun
|
|
1143
|
+
uses: oven-sh/setup-bun@v2
|
|
1144
|
+
with:
|
|
1145
|
+
bun-version: 1.4.x
|
|
1146
|
+
|
|
1147
|
+
- name: Install dependencies
|
|
1148
|
+
run: bun install
|
|
1149
|
+
|
|
1150
|
+
- name: Build seed
|
|
1151
|
+
run: bunx wod build . --output dist
|
|
1152
|
+
|
|
1153
|
+
- uses: actions/configure-pages@v5
|
|
1154
|
+
|
|
1155
|
+
- name: Upload dist
|
|
1156
|
+
uses: actions/upload-pages-artifact@v3
|
|
1157
|
+
with:
|
|
1158
|
+
path: dist
|
|
1159
|
+
|
|
1160
|
+
- id: deployment
|
|
1161
|
+
uses: actions/deploy-pages@v4
|
|
1162
|
+
`;
|
|
1163
|
+
collectionReadme = (name) => `# ${name}
|
|
1164
|
+
|
|
1165
|
+
Example benchmark collection. Each \`.md\` file in this directory becomes part of
|
|
1166
|
+
the \`example\` collection chunk; \`README.md\` is the collection's landing page.
|
|
1167
|
+
|
|
1168
|
+
Add one file per workout:
|
|
1169
|
+
|
|
1170
|
+
\`\`\`\`markdown
|
|
1171
|
+
---
|
|
1172
|
+
title: My workout
|
|
1173
|
+
date: 2026-01-01
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
# My workout
|
|
1177
|
+
|
|
1178
|
+
\`\`\`time
|
|
1179
|
+
(21-15-9)
|
|
1180
|
+
Thrusters @95lb
|
|
1181
|
+
Pull-ups
|
|
1182
|
+
\`\`\`
|
|
1183
|
+
\`\`\`\`
|
|
1184
|
+
|
|
1185
|
+
The \`date:\` frontmatter field sets the workout's chronological placement
|
|
1186
|
+
(regardless of filename or directory).
|
|
1187
|
+
`;
|
|
1188
|
+
exampleWorkout = (date) => `---
|
|
1189
|
+
title: Morning session
|
|
1190
|
+
date: ${date}
|
|
1191
|
+
---
|
|
1192
|
+
|
|
1193
|
+
# Morning session
|
|
1194
|
+
|
|
1195
|
+
\`\`\`time
|
|
1196
|
+
(21-15-9)
|
|
1197
|
+
Thrusters @95lb
|
|
1198
|
+
Pull-ups
|
|
1199
|
+
\`\`\`
|
|
1200
|
+
`;
|
|
1201
|
+
catalogReadme = (name) => `# ${name}
|
|
1202
|
+
|
|
1203
|
+
A [WOD Wiki](https://github.com/bitcobblers/wod-wiki) workout catalog: plain
|
|
1204
|
+
Markdown workouts compiled to static, version-stamped seed chunks and published
|
|
1205
|
+
via GitHub Pages.
|
|
1206
|
+
|
|
1207
|
+
## Develop
|
|
1208
|
+
|
|
1209
|
+
\`\`\`sh
|
|
1210
|
+
bun install
|
|
1211
|
+
bunx wod build . --output dist
|
|
1212
|
+
\`\`\`
|
|
1213
|
+
|
|
1214
|
+
Workouts live under \`markdown/\` \u2014 this scaffold starts with one example
|
|
1215
|
+
collection. Validate everything with \`bunx wod build\`: it gates every file on
|
|
1216
|
+
the Whiteboard grammar and corpus layout before writing \`dist/\`.
|
|
1217
|
+
|
|
1218
|
+
## Publish
|
|
1219
|
+
|
|
1220
|
+
Push to GitHub and enable Pages (**Settings \u2192 Pages \u2192 Source: GitHub
|
|
1221
|
+
Actions**). The bundled workflow (\`.github/workflows/publish-catalog.yml\`)
|
|
1222
|
+
builds the catalog on every push to \`main\` and deploys \`dist/\`.
|
|
1223
|
+
|
|
1224
|
+
## Share
|
|
1225
|
+
|
|
1226
|
+
Once published, anyone can subscribe to this catalog from a WOD Wiki instance
|
|
1227
|
+
at:
|
|
1228
|
+
|
|
1229
|
+
\`\`\`
|
|
1230
|
+
<app-url>/subscribe?source=<url-encoded published catalog URL>
|
|
1231
|
+
\`\`\`
|
|
1232
|
+
|
|
1233
|
+
For \`owner\`/\`repo\` on GitHub Pages the catalog URL is the deployed
|
|
1234
|
+
directory, \`https://owner.github.io/repo/\`, e.g.
|
|
1235
|
+
\`/subscribe?source=https%3A%2F%2Fowner.github.io%2Frepo%2F\`.
|
|
1236
|
+
`;
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1058
1240
|
// src/server/errors.ts
|
|
1059
1241
|
var HttpError;
|
|
1060
1242
|
var init_errors = __esm({
|
|
@@ -1358,13 +1540,13 @@ __export(server_exports, {
|
|
|
1358
1540
|
runCliServer: () => runCliServer
|
|
1359
1541
|
});
|
|
1360
1542
|
function withResolvers() {
|
|
1361
|
-
let
|
|
1543
|
+
let resolve3;
|
|
1362
1544
|
let reject;
|
|
1363
1545
|
const promise = new Promise((res, rej) => {
|
|
1364
|
-
|
|
1546
|
+
resolve3 = res;
|
|
1365
1547
|
reject = rej;
|
|
1366
1548
|
});
|
|
1367
|
-
return { promise, resolve:
|
|
1549
|
+
return { promise, resolve: resolve3, reject };
|
|
1368
1550
|
}
|
|
1369
1551
|
function startHttpServer(handler, host, port) {
|
|
1370
1552
|
const b = globalThis.Bun;
|
|
@@ -1372,7 +1554,7 @@ function startHttpServer(handler, host, port) {
|
|
|
1372
1554
|
const s = b.serve({ port, hostname: host, fetch: handler });
|
|
1373
1555
|
return Promise.resolve({ stop: () => s.stop() });
|
|
1374
1556
|
}
|
|
1375
|
-
const { promise, resolve:
|
|
1557
|
+
const { promise, resolve: resolve3, reject } = withResolvers();
|
|
1376
1558
|
const server = http__default.default.createServer(async (req, res) => {
|
|
1377
1559
|
try {
|
|
1378
1560
|
const url = new URL(req.url || "/", `http://${req.headers.host || `${host}:${port}`}`);
|
|
@@ -1397,7 +1579,7 @@ function startHttpServer(handler, host, port) {
|
|
|
1397
1579
|
});
|
|
1398
1580
|
server.once("error", reject);
|
|
1399
1581
|
server.listen(port, host, () => {
|
|
1400
|
-
|
|
1582
|
+
resolve3({
|
|
1401
1583
|
stop: () => {
|
|
1402
1584
|
const close = withResolvers();
|
|
1403
1585
|
server.close(() => close.resolve());
|
|
@@ -1873,8 +2055,8 @@ async function loadLanguagePack(moduleSpec) {
|
|
|
1873
2055
|
try {
|
|
1874
2056
|
let importPath = moduleSpec;
|
|
1875
2057
|
if (moduleSpec.startsWith(".") || moduleSpec.startsWith("/") || moduleSpec.includes("\\") || moduleSpec.endsWith(".ts") || moduleSpec.endsWith(".js") || moduleSpec.endsWith(".mjs")) {
|
|
1876
|
-
const { resolve:
|
|
1877
|
-
const resolved =
|
|
2058
|
+
const { resolve: resolve3, pathToFileURL } = getPathUtils();
|
|
2059
|
+
const resolved = resolve3(process.cwd(), moduleSpec);
|
|
1878
2060
|
importPath = pathToFileURL(resolved).href;
|
|
1879
2061
|
}
|
|
1880
2062
|
const imported = await import(
|
|
@@ -2144,23 +2326,7 @@ function resolveDatabaseConfig(options = {}, env) {
|
|
|
2144
2326
|
// src/cli/persist.ts
|
|
2145
2327
|
init_db();
|
|
2146
2328
|
var registeredWidgets = wodWikiCore.KNOWN_SECTION_WIDGETS;
|
|
2147
|
-
var saveStores = ["notes", "block_revisions", "block_index", "block_efforts", "tags", "note_tags", "sessions"];
|
|
2148
|
-
function extractEffortSlugs(statements) {
|
|
2149
|
-
const slugs = /* @__PURE__ */ new Set();
|
|
2150
|
-
for (const statement of statements) {
|
|
2151
|
-
if (statement.exerciseId) {
|
|
2152
|
-
slugs.add(statement.exerciseId);
|
|
2153
|
-
} else {
|
|
2154
|
-
const effortMetric = statement.metrics?.getMetric?.("effort");
|
|
2155
|
-
const effort = effortMetric && "effort" in effortMetric ? effortMetric.effort : void 0;
|
|
2156
|
-
if (typeof effort === "string") {
|
|
2157
|
-
const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2158
|
-
if (slug) slugs.add(slug);
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
return [...slugs];
|
|
2163
|
-
}
|
|
2329
|
+
var saveStores = ["notes", "block_revisions", "block_index", "block_efforts", "block_equipment", "efforts", "equipment", "tags", "note_tags", "sessions"];
|
|
2164
2330
|
function titleFromLabel(sourceLabel) {
|
|
2165
2331
|
if (!sourceLabel || sourceLabel === "-") return "stdin";
|
|
2166
2332
|
const stem = sourceLabel.split("/").pop() ?? sourceLabel;
|
|
@@ -2232,9 +2398,19 @@ async function saveSource(db, options, startTime) {
|
|
|
2232
2398
|
};
|
|
2233
2399
|
const priorRows = await tx.readonly("block_index").getAllFromIndex("by-note", noteId);
|
|
2234
2400
|
const priorEfforts = await tx.readonly("block_efforts").getAllFromIndex("by-note", noteId);
|
|
2401
|
+
const priorEquipment = await tx.readonly("block_equipment").getAllFromIndex("by-note", noteId);
|
|
2402
|
+
const storedEfforts = await tx.readonly("efforts").getAll();
|
|
2403
|
+
const effortsBySlug = /* @__PURE__ */ new Map();
|
|
2404
|
+
for (const effort of wodWikiLang.bundledEfforts) effortsBySlug.set(effort.slug, effort);
|
|
2405
|
+
for (const effort of storedEfforts) effortsBySlug.set(effort.slug, effort);
|
|
2406
|
+
const referenceIndex = wodWikiLang.createReferenceIndex({
|
|
2407
|
+
efforts: [...effortsBySlug.values()],
|
|
2408
|
+
equipment: await tx.readonly("equipment").getAll()
|
|
2409
|
+
});
|
|
2235
2410
|
const priorById = new Map(priorRows.map((row) => [row.id, row]));
|
|
2236
2411
|
const nextRows = [];
|
|
2237
2412
|
const nextEfforts = [];
|
|
2413
|
+
const nextEquipment = [];
|
|
2238
2414
|
for (const [position, block] of Object.values(blocks).sort((a, b) => a.from - b.from).entries()) {
|
|
2239
2415
|
const id = `${noteId}:${block.id}`;
|
|
2240
2416
|
const prior = priorById.get(id);
|
|
@@ -2260,7 +2436,20 @@ async function saveSource(db, options, startTime) {
|
|
|
2260
2436
|
...note.sourceId ? { sourceId: note.sourceId } : {}
|
|
2261
2437
|
});
|
|
2262
2438
|
if (block.kind === "wod") {
|
|
2263
|
-
|
|
2439
|
+
const statements = wodWikiLang.parseScript(body, { sport: block.sport, dialect: block.dialect, references: referenceIndex }).statements;
|
|
2440
|
+
const refs = wodWikiLang.extractStatementRefs(statements);
|
|
2441
|
+
const effortSlugs = new Set(refs.efforts);
|
|
2442
|
+
if (effortSlugs.size === 0) {
|
|
2443
|
+
for (const s of statements) {
|
|
2444
|
+
const m = s.metrics?.getMetric?.(wodWikiCore.MetricType.Effort);
|
|
2445
|
+
const val = typeof m?.value === "string" ? m.value : m && "effort" in m ? String(m.effort) : "";
|
|
2446
|
+
const slug = val.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2447
|
+
if (slug && !["rest", "amrap", "emom", "tabata", "for-time"].includes(slug)) {
|
|
2448
|
+
effortSlugs.add(slug);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
for (const effortSlug of effortSlugs) {
|
|
2264
2453
|
const effortId = `${noteId}:${block.id}:${effortSlug}`;
|
|
2265
2454
|
const previous = priorEfforts.find((row) => row.id === effortId);
|
|
2266
2455
|
nextEfforts.push({
|
|
@@ -2273,12 +2462,26 @@ async function saveSource(db, options, startTime) {
|
|
|
2273
2462
|
createdAt: previous?.createdAt ?? now
|
|
2274
2463
|
});
|
|
2275
2464
|
}
|
|
2465
|
+
for (const equipmentSlug of refs.equipment) {
|
|
2466
|
+
const equipmentId = `${noteId}:${block.id}:${equipmentSlug}`;
|
|
2467
|
+
const previous = priorEquipment.find((row) => row.id === equipmentId);
|
|
2468
|
+
nextEquipment.push({
|
|
2469
|
+
...previous,
|
|
2470
|
+
id: equipmentId,
|
|
2471
|
+
noteId,
|
|
2472
|
+
blockId: block.id,
|
|
2473
|
+
blockContentId: fingerprint,
|
|
2474
|
+
equipmentSlug,
|
|
2475
|
+
createdAt: previous?.createdAt ?? now
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2276
2478
|
}
|
|
2277
2479
|
}
|
|
2278
2480
|
if (!wodWikiStorage.rowsEqual(existing, note)) await tx.readwrite("notes").put(note);
|
|
2279
2481
|
if (revisions.length) await tx.readwrite("block_revisions").putMany(revisions);
|
|
2280
2482
|
await diffProjection(tx, "block_index", priorRows, nextRows);
|
|
2281
2483
|
await diffProjection(tx, "block_efforts", priorEfforts, nextEfforts);
|
|
2484
|
+
await diffProjection(tx, "block_equipment", priorEquipment, nextEquipment);
|
|
2282
2485
|
const tags = await saveTags(tx, noteId, options.tags, now);
|
|
2283
2486
|
let session;
|
|
2284
2487
|
let revision;
|
|
@@ -2314,6 +2517,7 @@ async function diffProjection(tx, store, previous, next) {
|
|
|
2314
2517
|
if (!wodWikiStorage.rowsEqual(old.get(row.id), row)) {
|
|
2315
2518
|
if (store === "block_index" && "blockVersion" in row) await tx.readwrite("block_index").put(row);
|
|
2316
2519
|
if (store === "block_efforts" && "effortSlug" in row) await tx.readwrite("block_efforts").put(row);
|
|
2520
|
+
if (store === "block_equipment" && "equipmentSlug" in row) await tx.readwrite("block_equipment").put(row);
|
|
2317
2521
|
}
|
|
2318
2522
|
}
|
|
2319
2523
|
}
|
|
@@ -2405,6 +2609,12 @@ async function persistEffort(db, effort) {
|
|
|
2405
2609
|
await sql.putRow(tx, sql.storeDefinition("efforts"), effort);
|
|
2406
2610
|
});
|
|
2407
2611
|
}
|
|
2612
|
+
async function persistEquipment(db, equipment) {
|
|
2613
|
+
const sql = await loadSql();
|
|
2614
|
+
await db.db.transaction(async (tx) => {
|
|
2615
|
+
await sql.putRow(tx, sql.storeDefinition("equipment"), equipment);
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2408
2618
|
async function persistTypedTags(db, labels, type) {
|
|
2409
2619
|
return db.storage.transaction(["tags"], "readwrite", async (tx) => {
|
|
2410
2620
|
let added = 0;
|
|
@@ -2430,17 +2640,18 @@ async function importSeedDir(db, dir, sourceId) {
|
|
|
2430
2640
|
const source = sourceId || path.basename(dir);
|
|
2431
2641
|
const manifest = wodWikiEngine.assertSeedManifest(JSON.parse(fs.readFileSync(path.resolve(dir, "manifest.json"), "utf-8")));
|
|
2432
2642
|
const key = wodWikiEngine.seedMetaKey(source);
|
|
2433
|
-
|
|
2434
|
-
|
|
2643
|
+
const stored = await db.storage.readonly("meta").get(key);
|
|
2644
|
+
let expected = stored?.schema === wodWikiEngine.SEED_SCHEMA ? stored : void 0;
|
|
2435
2645
|
let meta = expected ?? wodWikiEngine.emptySeedMeta(key);
|
|
2436
2646
|
if (manifest.version < meta.seedVersion) throw new Error("Seed manifest is older than stored checkpoint");
|
|
2437
|
-
const summary = { sourceId: source, version: manifest.version, notes: 0, efforts: 0, blocks: 0, blockEfforts: 0, deleted: 0, skippedUserOwned: 0 };
|
|
2647
|
+
const summary = { sourceId: source, version: manifest.version, notes: 0, efforts: 0, equipment: 0, blocks: 0, blockEfforts: 0, deleted: 0, skippedUserOwned: 0 };
|
|
2438
2648
|
const metadataChanged = manifest.version !== meta.seedVersion || manifest.builtAt !== meta.builtAt;
|
|
2439
2649
|
const plan = manifest.chunks.filter((c) => metadataChanged || meta.chunks[c.id]?.sha256 !== c.sha256);
|
|
2440
2650
|
plan.sort((a, b) => Number(b.id === wodWikiEngine.CANVAS_CHUNK_ID) - Number(a.id === wodWikiEngine.CANVAS_CHUNK_ID));
|
|
2441
2651
|
const accumulate = (result) => {
|
|
2442
2652
|
summary.notes += result.totalNotes;
|
|
2443
2653
|
summary.efforts += result.totalEfforts;
|
|
2654
|
+
summary.equipment += result.totalEquipment;
|
|
2444
2655
|
summary.blocks += result.totalBlocks;
|
|
2445
2656
|
summary.deleted += result.deleted;
|
|
2446
2657
|
summary.skippedUserOwned += result.skippedUserOwned;
|
|
@@ -2516,6 +2727,7 @@ async function publishCatalogSeed(db, options) {
|
|
|
2516
2727
|
}
|
|
2517
2728
|
const allIndexRows = await db.storage.readonly("block_index").getAll();
|
|
2518
2729
|
const allEffortRows = await db.storage.readonly("block_efforts").getAll();
|
|
2730
|
+
const allEquipmentRows = await db.storage.readonly("block_equipment").getAll();
|
|
2519
2731
|
const indexByNote = /* @__PURE__ */ new Map();
|
|
2520
2732
|
for (const row of allIndexRows) {
|
|
2521
2733
|
const list = indexByNote.get(row.noteId) ?? [];
|
|
@@ -2528,6 +2740,12 @@ async function publishCatalogSeed(db, options) {
|
|
|
2528
2740
|
list.push(row);
|
|
2529
2741
|
effortsByNote.set(row.noteId, list);
|
|
2530
2742
|
}
|
|
2743
|
+
const equipmentByNote = /* @__PURE__ */ new Map();
|
|
2744
|
+
for (const row of allEquipmentRows) {
|
|
2745
|
+
const list = equipmentByNote.get(row.noteId) ?? [];
|
|
2746
|
+
list.push(row);
|
|
2747
|
+
equipmentByNote.set(row.noteId, list);
|
|
2748
|
+
}
|
|
2531
2749
|
const outDir = path.resolve(process.cwd(), options.outDir);
|
|
2532
2750
|
const chunksDir = path.join(outDir, "chunks");
|
|
2533
2751
|
fs.mkdirSync(chunksDir, { recursive: true });
|
|
@@ -2542,6 +2760,7 @@ async function publishCatalogSeed(db, options) {
|
|
|
2542
2760
|
const notePath = note.sourcePath ?? `markdown/collections/${catalogName}/${stem}.md`;
|
|
2543
2761
|
const noteBlockIndex = indexByNote.get(note.id) ?? [];
|
|
2544
2762
|
const noteBlockEfforts = effortsByNote.get(note.id) ?? [];
|
|
2763
|
+
const noteBlockEquipment = equipmentByNote.get(note.id) ?? [];
|
|
2545
2764
|
const blockIndex = noteBlockIndex.length > 0 ? noteBlockIndex.map((b) => ({
|
|
2546
2765
|
...b,
|
|
2547
2766
|
isStatic: true,
|
|
@@ -2551,7 +2770,9 @@ async function publishCatalogSeed(db, options) {
|
|
|
2551
2770
|
})) : compileSeedRow(notePath, note.rawContent, {
|
|
2552
2771
|
routeId: `${catalogName}/${stem}`,
|
|
2553
2772
|
sourceId: `collection:${catalogName}/${stem}`,
|
|
2554
|
-
|
|
2773
|
+
// Authored frontmatter date first — exports keep the post's own
|
|
2774
|
+
// day, never the import/publish time.
|
|
2775
|
+
createdAt: note.date ?? note.createdAt,
|
|
2555
2776
|
noteTitle: note.title
|
|
2556
2777
|
}).blockIndex;
|
|
2557
2778
|
const blockEfforts = noteBlockEfforts.map((e) => ({
|
|
@@ -2559,12 +2780,18 @@ async function publishCatalogSeed(db, options) {
|
|
|
2559
2780
|
isStatic: true,
|
|
2560
2781
|
sourcePath: e.sourcePath ?? notePath
|
|
2561
2782
|
}));
|
|
2783
|
+
const blockEquipment = noteBlockEquipment.map((e) => ({
|
|
2784
|
+
...e,
|
|
2785
|
+
isStatic: true,
|
|
2786
|
+
sourcePath: e.sourcePath ?? notePath
|
|
2787
|
+
}));
|
|
2562
2788
|
chunkRows.push({
|
|
2563
2789
|
path: notePath,
|
|
2564
2790
|
content: note.rawContent,
|
|
2565
2791
|
blocks: note.blocks,
|
|
2566
2792
|
blockIndex,
|
|
2567
|
-
blockEfforts
|
|
2793
|
+
blockEfforts,
|
|
2794
|
+
blockEquipment
|
|
2568
2795
|
});
|
|
2569
2796
|
}
|
|
2570
2797
|
const chunkId = `collection.${catalogName}`;
|
|
@@ -2893,10 +3120,8 @@ ${escapeCsvField(data.parsed.metric)},${data.scalar},${escapeCsvField(data.unit
|
|
|
2893
3120
|
return lines.join("\n");
|
|
2894
3121
|
}
|
|
2895
3122
|
|
|
2896
|
-
// src/version.ts
|
|
2897
|
-
var VERSION = "0.36.2136";
|
|
2898
|
-
|
|
2899
3123
|
// src/cli/runner.ts
|
|
3124
|
+
init_version();
|
|
2900
3125
|
function getFs2() {
|
|
2901
3126
|
if (typeof globalThis.process?.versions?.node === "undefined" && typeof globalThis.Bun === "undefined") {
|
|
2902
3127
|
throw new Error("File system operations are only supported in Node / Bun environments");
|
|
@@ -3048,7 +3273,7 @@ function parseCliArgs(args) {
|
|
|
3048
3273
|
if (Number.isSafeInteger(v) && v >= 0) result.seedVersion = v;
|
|
3049
3274
|
i++;
|
|
3050
3275
|
} else if (!result.subcommand) {
|
|
3051
|
-
if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "export" || arg === "import" || arg === "publish" || arg === "build" || arg === "seed" || arg === "reset" || arg === "init" || arg === "server" || arg === "serve" || arg === "playground" || arg === "help" || arg === "version") {
|
|
3276
|
+
if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "export" || arg === "import" || arg === "publish" || arg === "build" || arg === "seed" || arg === "reset" || arg === "init" || arg === "server" || arg === "serve" || arg === "playground" || arg === "init-catalog" || arg === "help" || arg === "version") {
|
|
3052
3277
|
result.subcommand = arg;
|
|
3053
3278
|
} else {
|
|
3054
3279
|
result.target = arg;
|
|
@@ -3088,13 +3313,13 @@ function readBunStdinText() {
|
|
|
3088
3313
|
async function readStdin() {
|
|
3089
3314
|
const bunText = readBunStdinText();
|
|
3090
3315
|
if (bunText) return bunText();
|
|
3091
|
-
return new Promise((
|
|
3316
|
+
return new Promise((resolve3, reject) => {
|
|
3092
3317
|
const chunks = [];
|
|
3093
3318
|
process.stdin.on("data", (chunk) => {
|
|
3094
3319
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
3095
3320
|
});
|
|
3096
3321
|
process.stdin.on("end", () => {
|
|
3097
|
-
|
|
3322
|
+
resolve3(Buffer.concat(chunks).toString("utf-8"));
|
|
3098
3323
|
});
|
|
3099
3324
|
process.stdin.on("error", (err) => {
|
|
3100
3325
|
reject(err);
|
|
@@ -3114,6 +3339,10 @@ USAGE:
|
|
|
3114
3339
|
wod import <note|playground|session|effort|equipment> <file> [options]
|
|
3115
3340
|
wod publish [catalog] [options]
|
|
3116
3341
|
wod build [dir] [options]
|
|
3342
|
+
wod init-catalog <name>
|
|
3343
|
+
Scaffold a new catalog repository (package.json, example
|
|
3344
|
+
collection, GitHub Actions Pages workflow). Refuses an
|
|
3345
|
+
existing destination.
|
|
3117
3346
|
wod seed [options]
|
|
3118
3347
|
wod reset [options]
|
|
3119
3348
|
wod init Initialize the configured database (idempotent)
|
|
@@ -3316,6 +3545,22 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3316
3545
|
}
|
|
3317
3546
|
};
|
|
3318
3547
|
try {
|
|
3548
|
+
if (parsed.subcommand === "init-catalog") {
|
|
3549
|
+
if (!parsed.target) {
|
|
3550
|
+
writeErr("[wod init-catalog] Error: Missing catalog name (a single new directory name)");
|
|
3551
|
+
return 1;
|
|
3552
|
+
}
|
|
3553
|
+
const { scaffoldCatalog: scaffoldCatalog2 } = await Promise.resolve().then(() => (init_scaffoldCatalog(), scaffoldCatalog_exports));
|
|
3554
|
+
try {
|
|
3555
|
+
const result = scaffoldCatalog2(parsed.target);
|
|
3556
|
+
writeOut(`[wod init-catalog] Created ${result.dir} (${result.files.length} files)`);
|
|
3557
|
+
writeOut(`Next: cd ${parsed.target} && bun install && bunx wod build . --output dist`);
|
|
3558
|
+
return 0;
|
|
3559
|
+
} catch (error) {
|
|
3560
|
+
writeErr(`[wod init-catalog] ${error.message}`);
|
|
3561
|
+
return 1;
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3319
3564
|
if (parsed.subcommand === "parse") {
|
|
3320
3565
|
if (!parsed.target) {
|
|
3321
3566
|
writeErr('[wod parse] Error: Missing input file or "-" for stdin');
|
|
@@ -3516,7 +3761,9 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3516
3761
|
writeErr(`[wod ${verb}] Effort not found in ${db.path}: ${id}`);
|
|
3517
3762
|
return 1;
|
|
3518
3763
|
}
|
|
3519
|
-
const
|
|
3764
|
+
const ownerNoteId = typeof effort.ownerNoteId === "string" ? effort.ownerNoteId : `effort/${effort.slug}`;
|
|
3765
|
+
const owner = await db.storage.readonly("notes").get(ownerNoteId);
|
|
3766
|
+
const markdown = owner?.rawContent ?? wodWikiEngine.effortToMarkdown(effort);
|
|
3520
3767
|
const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
|
|
3521
3768
|
if (parsed.output === "-") {
|
|
3522
3769
|
writeOut(markdown);
|
|
@@ -3525,15 +3772,29 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3525
3772
|
}
|
|
3526
3773
|
writeOut(`[wod ${verb}] effort ${id} \u2192 ${target}`);
|
|
3527
3774
|
} else {
|
|
3528
|
-
const
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3775
|
+
const equipment = id ? await getEquipmentBySlug(db.db, targetId) : void 0;
|
|
3776
|
+
if (equipment) {
|
|
3777
|
+
const ownerNoteId = typeof equipment.ownerNoteId === "string" ? equipment.ownerNoteId : `equipment/${equipment.slug}`;
|
|
3778
|
+
const owner = await db.storage.readonly("notes").get(ownerNoteId);
|
|
3779
|
+
const markdown = owner?.rawContent ?? wodWikiEngine.equipmentToMarkdown(equipment);
|
|
3780
|
+
const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
|
|
3781
|
+
if (parsed.output === "-") {
|
|
3782
|
+
writeOut(markdown);
|
|
3783
|
+
} else {
|
|
3784
|
+
nodeFs().writeFileSync(target, markdown, "utf-8");
|
|
3785
|
+
}
|
|
3786
|
+
writeOut(`[wod ${verb}] equipment ${id} \u2192 ${target}`);
|
|
3533
3787
|
} else {
|
|
3534
|
-
|
|
3788
|
+
const labels = (await listTagsByType(db.db, "equipment")).map((t) => t.label).sort();
|
|
3789
|
+
const csv = wodWikiEngine.equipmentToCsv(labels);
|
|
3790
|
+
const target = parsed.output && parsed.output !== "-" ? parsed.output : "equipment.csv";
|
|
3791
|
+
if (parsed.output === "-") {
|
|
3792
|
+
writeOut(csv);
|
|
3793
|
+
} else {
|
|
3794
|
+
nodeFs().writeFileSync(target, csv, "utf-8");
|
|
3795
|
+
}
|
|
3796
|
+
writeOut(`[wod ${verb}] equipment \u2192 ${target} (${labels.length} labels)`);
|
|
3535
3797
|
}
|
|
3536
|
-
writeOut(`[wod ${verb}] equipment \u2192 ${target} (${labels.length} labels)`);
|
|
3537
3798
|
}
|
|
3538
3799
|
} finally {
|
|
3539
3800
|
await db.close();
|
|
@@ -3575,7 +3836,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3575
3836
|
return 0;
|
|
3576
3837
|
}
|
|
3577
3838
|
if (kind === "effort") {
|
|
3578
|
-
const effort =
|
|
3839
|
+
const effort = wodWikiLang.parseEffortFile(fs.readFileSync(file, "utf-8"));
|
|
3579
3840
|
if (!effort) {
|
|
3580
3841
|
writeErr(`[wod import] Unparseable effort file (need frontmatter id/slug/label + numeric baseAttributes.met): ${file}`);
|
|
3581
3842
|
return 1;
|
|
@@ -3589,6 +3850,21 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3589
3850
|
}
|
|
3590
3851
|
return 0;
|
|
3591
3852
|
}
|
|
3853
|
+
if (kind === "equipment" && /\.md$/i.test(file)) {
|
|
3854
|
+
const equipment = wodWikiLang.parseEquipmentFile(fs.readFileSync(file, "utf-8"));
|
|
3855
|
+
if (!equipment) {
|
|
3856
|
+
writeErr(`[wod import] Unparseable equipment file (need frontmatter type: equipment + id/slug/label): ${file}`);
|
|
3857
|
+
return 1;
|
|
3858
|
+
}
|
|
3859
|
+
const db2 = await openCliDb(cliDb());
|
|
3860
|
+
try {
|
|
3861
|
+
await persistEquipment(db2, { ...equipment, registrySource: "user" });
|
|
3862
|
+
writeOut(`[wod import] equipment ${equipment.slug} \u2014 ${equipment.label} \u2192 ${db2.path}`);
|
|
3863
|
+
} finally {
|
|
3864
|
+
await db2.close();
|
|
3865
|
+
}
|
|
3866
|
+
return 0;
|
|
3867
|
+
}
|
|
3592
3868
|
if (kind === "equipment") {
|
|
3593
3869
|
const labels = wodWikiEngine.csvToEquipmentLabels(fs.readFileSync(file, "utf-8"));
|
|
3594
3870
|
if (labels.length === 0) {
|