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