@bitcobblers/wod-wiki-engine 0.11.43 → 0.35.2116
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 +2761 -232
- package/dist/bin/wod.cjs.map +1 -1
- package/dist/bin/wod.mjs +7 -1
- package/dist/bin/wod.mjs.map +1 -1
- package/dist/chunk-6L5ZDZJ7.mjs +15 -0
- package/dist/chunk-6L5ZDZJ7.mjs.map +1 -0
- package/dist/chunk-B2OYLNEZ.mjs +2722 -0
- package/dist/chunk-B2OYLNEZ.mjs.map +1 -0
- package/dist/compiler-KTBR6OEL.mjs +787 -0
- package/dist/compiler-KTBR6OEL.mjs.map +1 -0
- package/dist/index.cjs +2816 -251
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -29
- package/dist/index.d.ts +146 -29
- package/dist/index.mjs +15 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -4
- package/dist/chunk-QFWJKM45.mjs +0 -1041
- package/dist/chunk-QFWJKM45.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,16 +1,859 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var wodWikiCore = require('@bitcobblers/wod-wiki-core');
|
|
4
3
|
var wodWikiLang = require('@bitcobblers/wod-wiki-lang');
|
|
5
|
-
var
|
|
4
|
+
var child_process = require('child_process');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var wodWikiCore = require('@bitcobblers/wod-wiki-core');
|
|
6
8
|
var wodWikiWql = require('@bitcobblers/wod-wiki-wql');
|
|
9
|
+
var crypto = require('crypto');
|
|
10
|
+
var react = require('@bitcobblers/wod-wiki-lang/react');
|
|
7
11
|
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
14
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
15
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
16
|
}) : x)(function(x) {
|
|
11
17
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
18
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
19
|
});
|
|
20
|
+
var __esm = (fn, res) => function __init() {
|
|
21
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
22
|
+
};
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/cli/seedbuild/contract.ts
|
|
29
|
+
function canvasRouteSlug2(content) {
|
|
30
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
31
|
+
if (fm?.[1] && !/^template:\s*canvas\s*$/m.test(fm[1])) return null;
|
|
32
|
+
const route = fm?.[1]?.match(/^route:\s*(\S+)\s*$/m)?.[1];
|
|
33
|
+
if (!route) return null;
|
|
34
|
+
return route.replace(/^\//, "") || null;
|
|
35
|
+
}
|
|
36
|
+
var SEED_SCHEMA2, TEMPLATE_CHUNK_ID2, DASHBOARDS_CHUNK_ID2, BLOCK_INDEX_CHUNK_PREFIX, BLOCK_EFFORTS_CHUNK_PREFIX;
|
|
37
|
+
var init_contract = __esm({
|
|
38
|
+
"src/cli/seedbuild/contract.ts"() {
|
|
39
|
+
SEED_SCHEMA2 = 7;
|
|
40
|
+
TEMPLATE_CHUNK_ID2 = "template";
|
|
41
|
+
DASHBOARDS_CHUNK_ID2 = "dashboards";
|
|
42
|
+
BLOCK_INDEX_CHUNK_PREFIX = "block-index.";
|
|
43
|
+
BLOCK_EFFORTS_CHUNK_PREFIX = "block-efforts.";
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/cli/seedbuild/effort-slugs.ts
|
|
48
|
+
function extractEffortSlugs2(statements) {
|
|
49
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
50
|
+
for (const statement of statements) {
|
|
51
|
+
if (statement.exerciseId) {
|
|
52
|
+
slugs.add(statement.exerciseId);
|
|
53
|
+
} else {
|
|
54
|
+
const effortMetric = statement.metrics?.getMetric?.("effort");
|
|
55
|
+
const effort = effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0;
|
|
56
|
+
if (effort) {
|
|
57
|
+
const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
58
|
+
if (slug) slugs.add(slug);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return Array.from(slugs);
|
|
63
|
+
}
|
|
64
|
+
var init_effort_slugs = __esm({
|
|
65
|
+
"src/cli/seedbuild/effort-slugs.ts"() {
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// src/cli/seedbuild/filename.ts
|
|
70
|
+
function fileToDisplayName(filename) {
|
|
71
|
+
const base = filename.replace(/\.md$/, "");
|
|
72
|
+
if (base.toUpperCase() === "README") return "Overview";
|
|
73
|
+
const cleaned = base.replace(/^day-\d+-/, "");
|
|
74
|
+
const tokens = cleaned.split(/[-_]/);
|
|
75
|
+
const titled = [];
|
|
76
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
77
|
+
const token = tokens[i];
|
|
78
|
+
const next = tokens[i + 1];
|
|
79
|
+
const after = tokens[i + 2];
|
|
80
|
+
const prev = tokens[i - 1];
|
|
81
|
+
if (/^\d{1,2}$/.test(token) && /^\d{1,2}$/.test(next ?? "") && !/^\d+$/.test(prev ?? "") && !/^\d+$/.test(after ?? "")) {
|
|
82
|
+
titled.push(`${token}.${next}`);
|
|
83
|
+
i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (titled.length > 0 && FILE_TITLE_STOP_WORDS[token.toLowerCase()]) {
|
|
87
|
+
titled.push(token.toLowerCase());
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
titled.push(token.charAt(0).toUpperCase() + token.slice(1));
|
|
91
|
+
}
|
|
92
|
+
return titled.join(" ");
|
|
93
|
+
}
|
|
94
|
+
var FILE_TITLE_STOP_WORDS;
|
|
95
|
+
var init_filename = __esm({
|
|
96
|
+
"src/cli/seedbuild/filename.ts"() {
|
|
97
|
+
FILE_TITLE_STOP_WORDS = {
|
|
98
|
+
and: true,
|
|
99
|
+
or: true,
|
|
100
|
+
of: true,
|
|
101
|
+
for: true,
|
|
102
|
+
to: true,
|
|
103
|
+
in: true,
|
|
104
|
+
on: true,
|
|
105
|
+
at: true,
|
|
106
|
+
by: true,
|
|
107
|
+
with: true,
|
|
108
|
+
the: true,
|
|
109
|
+
a: true,
|
|
110
|
+
an: true
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// src/cli/seedbuild/frontmatter.ts
|
|
116
|
+
function unquote(value) {
|
|
117
|
+
return value.replace(
|
|
118
|
+
/^(['"])(.*)\1$/,
|
|
119
|
+
(_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
function parseFrontmatter(raw) {
|
|
123
|
+
const match = raw.match(FRONTMATTER_RE);
|
|
124
|
+
if (!match) return { meta: {}, body: raw };
|
|
125
|
+
return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
|
|
126
|
+
}
|
|
127
|
+
function parseMetaLines(lines) {
|
|
128
|
+
const meta = {};
|
|
129
|
+
for (let i = 0; i < lines.length; i++) {
|
|
130
|
+
const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
|
|
131
|
+
if (!keyMatch) continue;
|
|
132
|
+
const key = keyMatch[1];
|
|
133
|
+
const rawVal = keyMatch[2].trim();
|
|
134
|
+
if (rawVal === "") {
|
|
135
|
+
const items = [];
|
|
136
|
+
while (i + 1 < lines.length) {
|
|
137
|
+
const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
|
|
138
|
+
if (!itemMatch) break;
|
|
139
|
+
items.push(itemMatch[1].trim());
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
142
|
+
meta[key] = items.length > 0 ? items : "";
|
|
143
|
+
} else {
|
|
144
|
+
const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
|
|
145
|
+
const value = unquote(rawVal);
|
|
146
|
+
const num2 = Number(value);
|
|
147
|
+
meta[key] = !wasQuoted && value !== "" && !isNaN(num2) ? num2 : value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return meta;
|
|
151
|
+
}
|
|
152
|
+
function getScalar(meta, key) {
|
|
153
|
+
const value = meta[key];
|
|
154
|
+
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
155
|
+
}
|
|
156
|
+
function getList(meta, key) {
|
|
157
|
+
const value = meta[key];
|
|
158
|
+
return Array.isArray(value) ? value : [];
|
|
159
|
+
}
|
|
160
|
+
var FRONTMATTER_RE;
|
|
161
|
+
var init_frontmatter = __esm({
|
|
162
|
+
"src/cli/seedbuild/frontmatter.ts"() {
|
|
163
|
+
FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
function parseDiscipline(val) {
|
|
167
|
+
if (!val) return void 0;
|
|
168
|
+
const normalized = unquoteYaml(val.trim()).toLowerCase();
|
|
169
|
+
if (wodWikiLang.isEffortDiscipline(normalized)) return normalized;
|
|
170
|
+
console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
function unquoteYaml(val) {
|
|
174
|
+
if (val.startsWith('"') && val.endsWith('"')) {
|
|
175
|
+
return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
176
|
+
}
|
|
177
|
+
if (val.startsWith("'") && val.endsWith("'")) {
|
|
178
|
+
return val.slice(1, -1).replace(/\\'/g, "'");
|
|
179
|
+
}
|
|
180
|
+
return val;
|
|
181
|
+
}
|
|
182
|
+
function coerceNestedScalar(raw) {
|
|
183
|
+
if (raw === "true") return true;
|
|
184
|
+
if (raw === "false") return false;
|
|
185
|
+
if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
|
|
186
|
+
return raw;
|
|
187
|
+
}
|
|
188
|
+
function readNestedSection(lines, key) {
|
|
189
|
+
const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
|
|
190
|
+
if (start === -1) return void 0;
|
|
191
|
+
const result = {};
|
|
192
|
+
let subKey = null;
|
|
193
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
194
|
+
const line = lines[i];
|
|
195
|
+
if (/^\S/.test(line)) break;
|
|
196
|
+
const trimmed = line.trim();
|
|
197
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
198
|
+
const indent = line.length - line.trimStart().length;
|
|
199
|
+
const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
|
|
200
|
+
if (!m) continue;
|
|
201
|
+
const [, k, rawVal] = m;
|
|
202
|
+
if (indent === 2) {
|
|
203
|
+
if (rawVal === "") {
|
|
204
|
+
subKey = k;
|
|
205
|
+
result[k] = {};
|
|
206
|
+
} else {
|
|
207
|
+
subKey = null;
|
|
208
|
+
result[k] = coerceNestedScalar(rawVal);
|
|
209
|
+
}
|
|
210
|
+
} else if (indent >= 4 && subKey !== null) {
|
|
211
|
+
result[subKey][k] = coerceNestedScalar(rawVal);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
215
|
+
}
|
|
216
|
+
function parseEffortFile(raw) {
|
|
217
|
+
const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
218
|
+
if (!blockMatch) return null;
|
|
219
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
220
|
+
const innerLines = blockMatch[1].split(/\r?\n/);
|
|
221
|
+
const id = getScalar(meta, "id");
|
|
222
|
+
const slug = getScalar(meta, "slug");
|
|
223
|
+
const label = getScalar(meta, "label");
|
|
224
|
+
const aliases = getList(meta, "aliases");
|
|
225
|
+
const baseAttrs = readNestedSection(innerLines, "baseAttributes");
|
|
226
|
+
const hints = readNestedSection(innerLines, "hints");
|
|
227
|
+
const derivation = readNestedSection(innerLines, "derivation");
|
|
228
|
+
const metRaw = baseAttrs?.met;
|
|
229
|
+
const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
|
|
230
|
+
if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
|
|
231
|
+
const discipline = parseDiscipline(
|
|
232
|
+
typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
|
|
233
|
+
);
|
|
234
|
+
const intensityTier = baseAttrs?.intensityTier;
|
|
235
|
+
const trimmedBody = body.trim();
|
|
236
|
+
return {
|
|
237
|
+
id: String(id),
|
|
238
|
+
slug: String(slug),
|
|
239
|
+
label: String(label),
|
|
240
|
+
aliases,
|
|
241
|
+
baseAttributes: {
|
|
242
|
+
met,
|
|
243
|
+
...discipline ? { discipline } : {},
|
|
244
|
+
...intensityTier ? { intensityTier } : {}
|
|
245
|
+
},
|
|
246
|
+
registrySource: "bundled",
|
|
247
|
+
...trimmedBody ? { body: trimmedBody } : {},
|
|
248
|
+
...hints ? { hints } : {},
|
|
249
|
+
...derivation ? { derivation } : {}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
var init_effort_file = __esm({
|
|
253
|
+
"src/cli/seedbuild/effort-file.ts"() {
|
|
254
|
+
init_frontmatter();
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
function resolveGitVersion(repoRoot) {
|
|
258
|
+
try {
|
|
259
|
+
const run = (args) => child_process.execSync(`git ${args}`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
260
|
+
const count = Number(run("rev-list --count HEAD"));
|
|
261
|
+
return {
|
|
262
|
+
version: Number.isFinite(count) && count > 0 ? count : 0,
|
|
263
|
+
versionLabel: run("describe --tags --long --always --dirty"),
|
|
264
|
+
commit: run("rev-parse HEAD"),
|
|
265
|
+
dirty: run("status --porcelain") !== ""
|
|
266
|
+
};
|
|
267
|
+
} catch {
|
|
268
|
+
return UNKNOWN_GIT_VERSION;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
var UNKNOWN_GIT_VERSION;
|
|
272
|
+
var init_git_version = __esm({
|
|
273
|
+
"src/cli/seedbuild/git-version.ts"() {
|
|
274
|
+
UNKNOWN_GIT_VERSION = {
|
|
275
|
+
version: 0,
|
|
276
|
+
versionLabel: "unknown",
|
|
277
|
+
commit: "",
|
|
278
|
+
dirty: false
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
function escapeHtml(s) {
|
|
283
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
284
|
+
}
|
|
285
|
+
function rowHtml(c) {
|
|
286
|
+
const kind = c.kind ?? "notes";
|
|
287
|
+
return `<tr><td>${escapeHtml(c.id)}</td><td>${kind}</td><td>${c.count}</td><td>${(c.bytes / 1024).toFixed(1)} KB</td><td><code>${c.sha256.slice(0, 8)}</code></td></tr>`;
|
|
288
|
+
}
|
|
289
|
+
function writeBuildReport(outDir, data) {
|
|
290
|
+
const { manifest, totalMs, passMs, fileCount } = data;
|
|
291
|
+
const totalBytes = manifest.chunks.reduce((s, c) => s + c.bytes, 0);
|
|
292
|
+
const dirtyBadge = manifest.dirty ? ' <strong style="color:#b45309">(dirty working tree \u2014 not reproducible from <code>' + escapeHtml(manifest.commit ?? "") + "</code>)</strong>" : "";
|
|
293
|
+
const html = `<!doctype html>
|
|
294
|
+
<html lang="en">
|
|
295
|
+
<head>
|
|
296
|
+
<meta charset="utf-8" />
|
|
297
|
+
<title>Seed build report</title>
|
|
298
|
+
<style>
|
|
299
|
+
body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #1f2937; }
|
|
300
|
+
h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }
|
|
301
|
+
table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
|
|
302
|
+
th, td { text-align: left; padding: 0.3rem 0.6rem; border-bottom: 1px solid #e5e7eb; }
|
|
303
|
+
code { font-size: 0.85em; }
|
|
304
|
+
dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 1rem; font-size: 0.95rem; }
|
|
305
|
+
dt { font-weight: 600; }
|
|
306
|
+
</style>
|
|
307
|
+
</head>
|
|
308
|
+
<body>
|
|
309
|
+
<h1>Seed build report</h1>
|
|
310
|
+
<h2>Version</h2>
|
|
311
|
+
<dl>
|
|
312
|
+
<dt>version</dt><dd>${manifest.version} <code>${escapeHtml(manifest.versionLabel ?? "")}</code>${dirtyBadge}</dd>
|
|
313
|
+
<dt>commit</dt><dd><code>${escapeHtml(manifest.commit ?? "unknown")}</code></dd>
|
|
314
|
+
<dt>built at</dt><dd>${escapeHtml(manifest.builtAt)}</dd>
|
|
315
|
+
<dt>schema</dt><dd>v${manifest.schema}</dd>
|
|
316
|
+
</dl>
|
|
317
|
+
<h2>Benchmarks</h2>
|
|
318
|
+
<dl>
|
|
319
|
+
<dt>total</dt><dd>${totalMs} ms</dd>
|
|
320
|
+
<dt>corpus scan + partition</dt><dd>${passMs.scan} ms (${fileCount} files)</dd>
|
|
321
|
+
<dt>block-index derivation</dt><dd>${passMs.blockIndex} ms</dd>
|
|
322
|
+
<dt>block-efforts derivation</dt><dd>${passMs.blockEfforts} ms</dd>
|
|
323
|
+
<dt>chunk writes</dt><dd>${passMs.write} ms</dd>
|
|
324
|
+
</dl>
|
|
325
|
+
<h2>Chunks (${manifest.chunks.length}, ${(totalBytes / 1024).toFixed(0)} KB total)</h2>
|
|
326
|
+
<table>
|
|
327
|
+
<thead><tr><th>id</th><th>kind</th><th>rows</th><th>size</th><th>sha256</th></tr></thead>
|
|
328
|
+
<tbody>
|
|
329
|
+
${manifest.chunks.map(rowHtml).join("\n")}
|
|
330
|
+
</tbody>
|
|
331
|
+
</table>
|
|
332
|
+
</body>
|
|
333
|
+
</html>
|
|
334
|
+
`;
|
|
335
|
+
const file = path.join(outDir, "report.html");
|
|
336
|
+
fs.writeFileSync(file, html, "utf8");
|
|
337
|
+
return file;
|
|
338
|
+
}
|
|
339
|
+
var init_report = __esm({
|
|
340
|
+
"src/cli/seedbuild/report.ts"() {
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// src/cli/seedbuild/compiler.ts
|
|
345
|
+
var compiler_exports = {};
|
|
346
|
+
__export(compiler_exports, {
|
|
347
|
+
buildBlockEffortRows: () => buildBlockEffortRows,
|
|
348
|
+
buildBlockIndexRows: () => buildBlockIndexRows,
|
|
349
|
+
buildStaticParentMap: () => buildStaticParentMap,
|
|
350
|
+
chunkIdFor: () => chunkIdFor,
|
|
351
|
+
collectCorpusMarkdown: () => collectCorpusMarkdown,
|
|
352
|
+
discoverUncatalogedEfforts: () => discoverUncatalogedEfforts,
|
|
353
|
+
feedDateToCreatedAt: () => feedDateToCreatedAt,
|
|
354
|
+
generateSeed: () => generateSeed,
|
|
355
|
+
loadExistingEffortMap: () => loadExistingEffortMap,
|
|
356
|
+
partitionIntoChunks: () => partitionIntoChunks,
|
|
357
|
+
scaffoldDiscoveredEfforts: () => scaffoldDiscoveredEfforts,
|
|
358
|
+
validateDashboardNote: () => validateDashboardNote
|
|
359
|
+
});
|
|
360
|
+
function listMarkdownFiles(dir, prefix = "") {
|
|
361
|
+
if (!fs.existsSync(dir)) return [];
|
|
362
|
+
const out = [];
|
|
363
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
364
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
365
|
+
if (entry.isDirectory()) out.push(...listMarkdownFiles(path.join(dir, entry.name), rel));
|
|
366
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) out.push(rel);
|
|
367
|
+
}
|
|
368
|
+
return out.sort();
|
|
369
|
+
}
|
|
370
|
+
function chunkIdFor(relPath) {
|
|
371
|
+
const parts = relPath.split("/");
|
|
372
|
+
if (parts[0] !== "markdown" || parts.length < 2) return null;
|
|
373
|
+
const corpus = parts[1];
|
|
374
|
+
if (parts.length === 2) return null;
|
|
375
|
+
const slug = parts[2]?.replace(/\.md$/, "");
|
|
376
|
+
switch (corpus) {
|
|
377
|
+
case "canvas":
|
|
378
|
+
case "dashboards":
|
|
379
|
+
case "efforts":
|
|
380
|
+
return corpus;
|
|
381
|
+
case "collections":
|
|
382
|
+
return parts.length === 3 ? `collection._root` : `collection.${slug}`;
|
|
383
|
+
case "feeds":
|
|
384
|
+
return parts.length === 3 ? `feed._root` : `feed.${slug}`;
|
|
385
|
+
default:
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function collectCorpusMarkdown(markdownDir) {
|
|
390
|
+
return listMarkdownFiles(markdownDir).map((p) => `markdown/${p}`);
|
|
391
|
+
}
|
|
392
|
+
function partitionIntoChunks(paths) {
|
|
393
|
+
const byId = /* @__PURE__ */ new Map();
|
|
394
|
+
for (const p of paths) {
|
|
395
|
+
const id = chunkIdFor(p);
|
|
396
|
+
if (id === null) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`[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.`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const list = byId.get(id);
|
|
402
|
+
if (list) list.push(p);
|
|
403
|
+
else byId.set(id, [p]);
|
|
404
|
+
}
|
|
405
|
+
return [...byId.entries()].sort(([a], [b]) => a < b ? -1 : 1).map(([id, files]) => ({ id, files }));
|
|
406
|
+
}
|
|
407
|
+
function validateDashboardNote(raw) {
|
|
408
|
+
const problems = [];
|
|
409
|
+
const { meta } = parseFrontmatter(raw);
|
|
410
|
+
const { sections } = wodWikiWql.parseDashboardNote(raw);
|
|
411
|
+
const doc = wodWikiWql.buildDashboardDocument(sections, meta);
|
|
412
|
+
if (!doc.isDashboard) return ["file is not a dashboard note (missing dashboard frontmatter)"];
|
|
413
|
+
if (doc.title === void 0) problems.push("dashboard has no title");
|
|
414
|
+
if (doc.widgets.length < 3) problems.push(`dashboard has only ${doc.widgets.length} widgets (the locked format has \u2265 3)`);
|
|
415
|
+
const declared = new Set(doc.tokens.map((t) => t.name));
|
|
416
|
+
for (const widget of doc.widgets) {
|
|
417
|
+
const where = `widget '${widget.title ?? widget.body.slice(0, 40)}'`;
|
|
418
|
+
if (widget.title === void 0) problems.push(`${where}: missing title`);
|
|
419
|
+
if (!wodWikiWql.isDashboardWidgetType(wodWikiWql.resolveWidgetType(widget.type))) {
|
|
420
|
+
problems.push(`${where}: unknown widget type '${widget.type}'`);
|
|
421
|
+
}
|
|
422
|
+
const parsed = wodWikiWql.parseQuery(widget.body);
|
|
423
|
+
if (parsed.error !== void 0) problems.push(`${where}: WQL parse error: ${parsed.error}`);
|
|
424
|
+
for (const ref of [
|
|
425
|
+
...widget.body.matchAll(/\$([A-Za-z][\w-]*)/g),
|
|
426
|
+
...Object.values(widget.attributes).flatMap((v) => [...String(v).matchAll(/\$([A-Za-z][\w-]*)/g)])
|
|
427
|
+
]) {
|
|
428
|
+
if (!declared.has(ref[1])) problems.push(`${where}: undeclared token $${ref[1]}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return problems;
|
|
432
|
+
}
|
|
433
|
+
function gateDashboards(groups, corpusRoot) {
|
|
434
|
+
for (const group of groups) {
|
|
435
|
+
if (group.id !== DASHBOARDS_CHUNK_ID2) continue;
|
|
436
|
+
for (const p of group.files) {
|
|
437
|
+
const problems = validateDashboardNote(fs.readFileSync(path.join(corpusRoot, p), "utf8"));
|
|
438
|
+
if (problems.length > 0) {
|
|
439
|
+
throw new Error(`[generate-seed] Dashboard validation failed for '${p}':
|
|
440
|
+
- ${problems.join("\n - ")}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function toSegmentDataType2(section) {
|
|
446
|
+
switch (section.type) {
|
|
447
|
+
case "time":
|
|
448
|
+
case "log":
|
|
449
|
+
return "wod";
|
|
450
|
+
case "title": {
|
|
451
|
+
const l = section.level ?? 1;
|
|
452
|
+
return `h${Math.min(Math.max(l, 1), 6)}`;
|
|
453
|
+
}
|
|
454
|
+
case "frontmatter":
|
|
455
|
+
return "frontmatter";
|
|
456
|
+
case "markdown":
|
|
457
|
+
case "widget":
|
|
458
|
+
case "embed":
|
|
459
|
+
case "code":
|
|
460
|
+
return "markdown";
|
|
461
|
+
default:
|
|
462
|
+
return "markdown";
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function getFileCreatedAt(file, repoRoot) {
|
|
466
|
+
try {
|
|
467
|
+
const out = child_process.execSync(`git log -1 --format=%ct -- '${file}'`, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
468
|
+
const ts = Number(out) * 1e3;
|
|
469
|
+
return Number.isFinite(ts) && ts > 0 ? ts : 0;
|
|
470
|
+
} catch {
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function feedDateToCreatedAt(dateKey) {
|
|
475
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) return 0;
|
|
476
|
+
const ts = Date.parse(`${dateKey}T00:00:00Z`);
|
|
477
|
+
return Number.isNaN(ts) ? 0 : ts;
|
|
478
|
+
}
|
|
479
|
+
function buildBlockIndexRows(markdownDir, corpusRoot) {
|
|
480
|
+
const index = [];
|
|
481
|
+
for (const file of listMarkdownFiles(path.join(markdownDir, "collections"))) {
|
|
482
|
+
const rel = `markdown/collections/${file}`;
|
|
483
|
+
const parts = rel.split("/");
|
|
484
|
+
if (parts.length < 4) continue;
|
|
485
|
+
const dirName = parts[parts.length - 2];
|
|
486
|
+
const fileNameExt = parts[parts.length - 1];
|
|
487
|
+
const isReadme = fileNameExt.toLowerCase() === "readme.md";
|
|
488
|
+
const fileName = fileNameExt.replace(/\.md$/, "");
|
|
489
|
+
const noteId = isReadme ? dirName : `${dirName}/${fileName}`;
|
|
490
|
+
const content = fs.readFileSync(path.join(corpusRoot, rel), "utf8");
|
|
491
|
+
const headingMatch = isReadme ? content.match(/^#\s+(.+)$/m) : null;
|
|
492
|
+
const noteTitle = headingMatch?.[1]?.trim() || fileToDisplayName(isReadme ? dirName : fileNameExt);
|
|
493
|
+
const createdAt = getFileCreatedAt(rel, corpusRoot);
|
|
494
|
+
const sections = wodWikiCore.parseDocumentSections(content);
|
|
495
|
+
let position = 0;
|
|
496
|
+
for (const section of sections) {
|
|
497
|
+
const blockContentId = section.scriptBlock?.contentId ?? void 0;
|
|
498
|
+
index.push({
|
|
499
|
+
id: `static:${noteId}:${section.id}:1`,
|
|
500
|
+
noteId,
|
|
501
|
+
segmentId: section.id,
|
|
502
|
+
segmentVersion: 1,
|
|
503
|
+
position: position++,
|
|
504
|
+
dataType: toSegmentDataType2(section),
|
|
505
|
+
blockContentId,
|
|
506
|
+
rawContent: section.displayContent,
|
|
507
|
+
noteTitle,
|
|
508
|
+
createdAt,
|
|
509
|
+
isStatic: true,
|
|
510
|
+
sourceId: isReadme ? `collection:${dirName}` : `collection:${noteId}`,
|
|
511
|
+
sourcePath: rel
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
for (const file of listMarkdownFiles(path.join(markdownDir, "feeds"))) {
|
|
516
|
+
const rel = `markdown/feeds/${file}`;
|
|
517
|
+
const parts = rel.split("/");
|
|
518
|
+
if (parts.length < 5) continue;
|
|
519
|
+
const dirName = parts[parts.length - 3];
|
|
520
|
+
const dateKey = parts[parts.length - 2];
|
|
521
|
+
const fileNameExt = parts[parts.length - 1];
|
|
522
|
+
if (fileNameExt.toLowerCase() === "readme.md") continue;
|
|
523
|
+
const fileName = fileNameExt.replace(/\.md$/, "");
|
|
524
|
+
const noteId = `feeds/${dirName}/${dateKey}/${fileName}`;
|
|
525
|
+
const noteTitle = fileToDisplayName(fileNameExt);
|
|
526
|
+
const content = fs.readFileSync(path.join(corpusRoot, rel), "utf8");
|
|
527
|
+
const sections = wodWikiCore.parseDocumentSections(content);
|
|
528
|
+
let position = 0;
|
|
529
|
+
for (const section of sections) {
|
|
530
|
+
const blockContentId = section.scriptBlock?.contentId ?? void 0;
|
|
531
|
+
index.push({
|
|
532
|
+
id: `static:${noteId}:${section.id}:1`,
|
|
533
|
+
noteId,
|
|
534
|
+
segmentId: section.id,
|
|
535
|
+
segmentVersion: 1,
|
|
536
|
+
position: position++,
|
|
537
|
+
dataType: toSegmentDataType2(section),
|
|
538
|
+
blockContentId,
|
|
539
|
+
rawContent: section.displayContent,
|
|
540
|
+
noteTitle,
|
|
541
|
+
createdAt: feedDateToCreatedAt(dateKey),
|
|
542
|
+
isStatic: true,
|
|
543
|
+
sourceId: `feed:${noteId}`,
|
|
544
|
+
sourcePath: rel
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
for (const file of listMarkdownFiles(path.join(markdownDir, "canvas"))) {
|
|
549
|
+
const rel = `markdown/canvas/${file}`;
|
|
550
|
+
const content = fs.readFileSync(path.join(corpusRoot, rel), "utf8");
|
|
551
|
+
const noteId = canvasRouteSlug2(content);
|
|
552
|
+
if (!noteId) continue;
|
|
553
|
+
const fileNameExt = file.split("/").pop() ?? file;
|
|
554
|
+
const noteTitle = fileToDisplayName(fileNameExt);
|
|
555
|
+
const createdAt = getFileCreatedAt(rel, corpusRoot);
|
|
556
|
+
const sections = wodWikiCore.parseDocumentSections(content);
|
|
557
|
+
let position = 0;
|
|
558
|
+
for (const section of sections) {
|
|
559
|
+
const blockContentId = section.scriptBlock?.contentId ?? void 0;
|
|
560
|
+
index.push({
|
|
561
|
+
id: `static:${noteId}:${section.id}:1`,
|
|
562
|
+
noteId,
|
|
563
|
+
segmentId: section.id,
|
|
564
|
+
segmentVersion: 1,
|
|
565
|
+
position: position++,
|
|
566
|
+
dataType: toSegmentDataType2(section),
|
|
567
|
+
blockContentId,
|
|
568
|
+
rawContent: section.displayContent,
|
|
569
|
+
noteTitle,
|
|
570
|
+
createdAt,
|
|
571
|
+
isStatic: true,
|
|
572
|
+
sourceId: `guides:${noteId}`,
|
|
573
|
+
sourcePath: rel
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return index;
|
|
578
|
+
}
|
|
579
|
+
function formatDiscoveredLabel(raw) {
|
|
580
|
+
const clean = raw.replace(/[-_]+/g, " ").trim();
|
|
581
|
+
return clean.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
|
|
582
|
+
}
|
|
583
|
+
function loadExistingEffortMap(markdownDir) {
|
|
584
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
585
|
+
const aliasToCanonical = /* @__PURE__ */ new Map();
|
|
586
|
+
const effortsDir = path.join(markdownDir, "efforts");
|
|
587
|
+
for (const file of listMarkdownFiles(effortsDir)) {
|
|
588
|
+
const raw = fs.readFileSync(path.join(effortsDir, file), "utf8");
|
|
589
|
+
const parsed = parseEffortFile(raw);
|
|
590
|
+
if (!parsed) continue;
|
|
591
|
+
slugs.add(parsed.slug);
|
|
592
|
+
aliasToCanonical.set(parsed.slug, parsed.slug);
|
|
593
|
+
aliasToCanonical.set(parsed.label.toLowerCase(), parsed.slug);
|
|
594
|
+
aliasToCanonical.set(parsed.slug.replace(/-/g, " "), parsed.slug);
|
|
595
|
+
for (const alias of parsed.aliases) {
|
|
596
|
+
aliasToCanonical.set(alias.toLowerCase(), parsed.slug);
|
|
597
|
+
aliasToCanonical.set(alias.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""), parsed.slug);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return { slugs, aliasToCanonical };
|
|
601
|
+
}
|
|
602
|
+
function discoverUncatalogedEfforts(markdownDir, corpusRoot) {
|
|
603
|
+
const { slugs, aliasToCanonical } = loadExistingEffortMap(markdownDir);
|
|
604
|
+
const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
|
|
605
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
606
|
+
for (const block of blocks) {
|
|
607
|
+
if (block.dataType !== "wod" || !block.rawContent) continue;
|
|
608
|
+
const script = wodWikiLang.parseScript(block.rawContent);
|
|
609
|
+
for (const statement of script.statements) {
|
|
610
|
+
const effortMetric = statement.metrics?.getMetric?.("effort");
|
|
611
|
+
const effortLabel = (effortMetric && typeof effortMetric.value === "string" ? effortMetric.value : void 0) ?? statement.exerciseId;
|
|
612
|
+
if (!effortLabel) continue;
|
|
613
|
+
const slugified = effortLabel.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
614
|
+
if (!slugified || NON_EFFORT_SLUGS[slugified]) continue;
|
|
615
|
+
const canonical = aliasToCanonical.get(slugified) ?? aliasToCanonical.get(effortLabel.toLowerCase());
|
|
616
|
+
if (canonical && slugs.has(canonical)) continue;
|
|
617
|
+
const existing = discovered.get(slugified);
|
|
618
|
+
if (existing) {
|
|
619
|
+
existing.rawLabels.add(effortLabel);
|
|
620
|
+
} else {
|
|
621
|
+
discovered.set(slugified, {
|
|
622
|
+
label: formatDiscoveredLabel(effortLabel),
|
|
623
|
+
rawLabels: /* @__PURE__ */ new Set([effortLabel])
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return Array.from(discovered.entries()).map(([slug, data]) => ({
|
|
629
|
+
slug,
|
|
630
|
+
label: data.label,
|
|
631
|
+
rawLabels: Array.from(data.rawLabels)
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
function scaffoldDiscoveredEfforts(discovered, markdownDir) {
|
|
635
|
+
const targetDir = path.join(markdownDir, "efforts", "discovered");
|
|
636
|
+
if (!fs.existsSync(targetDir)) {
|
|
637
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
638
|
+
}
|
|
639
|
+
const createdPaths = [];
|
|
640
|
+
for (const effort of discovered) {
|
|
641
|
+
const filePath = path.join(targetDir, `${effort.slug}.md`);
|
|
642
|
+
if (fs.existsSync(filePath)) continue;
|
|
643
|
+
const aliases = effort.rawLabels.filter((l) => l.toLowerCase() !== effort.label.toLowerCase()).map((l) => ` - "${l.replace(/"/g, '\\"')}"`);
|
|
644
|
+
const content = [
|
|
645
|
+
"---",
|
|
646
|
+
`id: effort-bundled-${effort.slug}`,
|
|
647
|
+
`slug: ${effort.slug}`,
|
|
648
|
+
`label: "${effort.label.replace(/"/g, '\\"')}"`,
|
|
649
|
+
aliases.length > 0 ? `aliases:
|
|
650
|
+
${aliases.join("\n")}` : "aliases: []",
|
|
651
|
+
"baseAttributes:",
|
|
652
|
+
" met: 5.0",
|
|
653
|
+
" discipline: general",
|
|
654
|
+
" intensityTier: moderate",
|
|
655
|
+
"registrySource: bundled",
|
|
656
|
+
"---",
|
|
657
|
+
""
|
|
658
|
+
].join("\n");
|
|
659
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
660
|
+
createdPaths.push(filePath);
|
|
661
|
+
}
|
|
662
|
+
return createdPaths;
|
|
663
|
+
}
|
|
664
|
+
function buildBlockEffortRows(markdownDir, corpusRoot) {
|
|
665
|
+
const efforts = [];
|
|
666
|
+
const { aliasToCanonical } = loadExistingEffortMap(markdownDir);
|
|
667
|
+
const blocks = buildBlockIndexRows(markdownDir, corpusRoot);
|
|
668
|
+
for (const block of blocks) {
|
|
669
|
+
if (block.dataType !== "wod" || !block.rawContent) continue;
|
|
670
|
+
const script = wodWikiLang.parseScript(block.rawContent);
|
|
671
|
+
const effortSlugs = extractEffortSlugs2(script.statements);
|
|
672
|
+
for (const rawSlug of effortSlugs) {
|
|
673
|
+
const norm = rawSlug.toLowerCase().trim();
|
|
674
|
+
const canonicalSlug = aliasToCanonical.get(norm) ?? aliasToCanonical.get(norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")) ?? norm.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
675
|
+
if (!canonicalSlug || NON_EFFORT_SLUGS[canonicalSlug] || NON_EFFORT_SLUGS[norm]) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
efforts.push({
|
|
679
|
+
id: `${block.id}:${canonicalSlug}`,
|
|
680
|
+
noteId: block.noteId,
|
|
681
|
+
blockId: block.segmentId,
|
|
682
|
+
blockContentId: block.blockContentId,
|
|
683
|
+
effortSlug: canonicalSlug,
|
|
684
|
+
isStatic: block.isStatic,
|
|
685
|
+
sourcePath: block.sourcePath,
|
|
686
|
+
createdAt: block.createdAt
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return efforts;
|
|
691
|
+
}
|
|
692
|
+
function buildStaticParentMap(blockRows) {
|
|
693
|
+
const map = {};
|
|
694
|
+
for (const row of blockRows) {
|
|
695
|
+
if (!row.sourcePath) continue;
|
|
696
|
+
const prev = map[row.noteId];
|
|
697
|
+
if (prev && prev.sourcePath !== row.sourcePath) {
|
|
698
|
+
throw new Error(
|
|
699
|
+
`[generate-seed] ambiguous static parent '${row.noteId}' maps to both '${prev.sourcePath}' and '${row.sourcePath}'`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
map[row.noteId] = { sourcePath: row.sourcePath, sourceId: row.sourceId };
|
|
703
|
+
}
|
|
704
|
+
return map;
|
|
705
|
+
}
|
|
706
|
+
function sha256Hex(data) {
|
|
707
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
|
708
|
+
}
|
|
709
|
+
function generateSeed(options = {}) {
|
|
710
|
+
const t0 = Date.now();
|
|
711
|
+
const passMs = { scan: 0, blockIndex: 0, blockEfforts: 0, write: 0 };
|
|
712
|
+
const corpusRoot = options.corpusRoot ?? process.cwd();
|
|
713
|
+
const markdownDir = options.markdownDir ?? path.join(corpusRoot, "markdown");
|
|
714
|
+
const outDir = options.outDir ?? path.join(corpusRoot, "dist");
|
|
715
|
+
const gitVersion = options.version !== void 0 ? { ...resolveGitVersion(corpusRoot), version: options.version } : resolveGitVersion(corpusRoot);
|
|
716
|
+
if (options.scaffoldDiscoveredEfforts) {
|
|
717
|
+
const discovered = discoverUncatalogedEfforts(markdownDir, corpusRoot);
|
|
718
|
+
if (discovered.length > 0) {
|
|
719
|
+
scaffoldDiscoveredEfforts(discovered, markdownDir);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const tScan = Date.now();
|
|
723
|
+
const paths = collectCorpusMarkdown(markdownDir);
|
|
724
|
+
const groups = partitionIntoChunks(paths);
|
|
725
|
+
gateDashboards(groups, corpusRoot);
|
|
726
|
+
if (options.templateFile && fs.existsSync(options.templateFile)) {
|
|
727
|
+
const templateRowPath = options.templateRowPath ?? path.relative(corpusRoot, options.templateFile).split("\\").join("/");
|
|
728
|
+
groups.push({ id: TEMPLATE_CHUNK_ID2, files: [templateRowPath] });
|
|
729
|
+
}
|
|
730
|
+
passMs.scan = Date.now() - tScan;
|
|
731
|
+
const chunksDir = path.join(outDir, "chunks");
|
|
732
|
+
fs.rmSync(outDir, { recursive: true, force: true });
|
|
733
|
+
fs.mkdirSync(chunksDir, { recursive: true });
|
|
734
|
+
const manifestChunks = [];
|
|
735
|
+
let totalFiles = 0;
|
|
736
|
+
const tWrite = Date.now();
|
|
737
|
+
for (const { id, files } of groups) {
|
|
738
|
+
const templateSource = id === TEMPLATE_CHUNK_ID2 ? options.templateFile : void 0;
|
|
739
|
+
const rows = files.map((p) => ({
|
|
740
|
+
path: p,
|
|
741
|
+
content: fs.readFileSync(templateSource ?? path.join(corpusRoot, p), "utf8")
|
|
742
|
+
}));
|
|
743
|
+
const bytes = Buffer.from(JSON.stringify(rows), "utf8");
|
|
744
|
+
const sha = sha256Hex(bytes);
|
|
745
|
+
const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
|
|
746
|
+
fs.writeFileSync(path.join(outDir, file), bytes);
|
|
747
|
+
manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: rows.length });
|
|
748
|
+
totalFiles += rows.length;
|
|
749
|
+
}
|
|
750
|
+
const tBlockIndex = Date.now();
|
|
751
|
+
const blockRows = buildBlockIndexRows(markdownDir, corpusRoot);
|
|
752
|
+
passMs.blockIndex = Date.now() - tBlockIndex;
|
|
753
|
+
fs.writeFileSync(
|
|
754
|
+
path.join(outDir, "block-parent-map.json"),
|
|
755
|
+
JSON.stringify(buildStaticParentMap(blockRows), null, 2) + "\n"
|
|
756
|
+
);
|
|
757
|
+
for (let i = 0; blockRows.length > 0 && i * BLOCK_INDEX_ROWS_PER_CHUNK < blockRows.length; i++) {
|
|
758
|
+
const part = blockRows.slice(i * BLOCK_INDEX_ROWS_PER_CHUNK, (i + 1) * BLOCK_INDEX_ROWS_PER_CHUNK);
|
|
759
|
+
if (part.length === 0) break;
|
|
760
|
+
const id = `${BLOCK_INDEX_CHUNK_PREFIX}${i}`;
|
|
761
|
+
const bytes = Buffer.from(JSON.stringify(part), "utf8");
|
|
762
|
+
const sha = sha256Hex(bytes);
|
|
763
|
+
const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
|
|
764
|
+
fs.writeFileSync(path.join(outDir, file), bytes);
|
|
765
|
+
manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: part.length, kind: "block-index" });
|
|
766
|
+
}
|
|
767
|
+
const tBlockEfforts = Date.now();
|
|
768
|
+
const effortRows = buildBlockEffortRows(markdownDir, corpusRoot);
|
|
769
|
+
passMs.blockEfforts = Date.now() - tBlockEfforts;
|
|
770
|
+
for (let i = 0; effortRows.length > 0 && i * BLOCK_INDEX_ROWS_PER_CHUNK < effortRows.length; i++) {
|
|
771
|
+
const part = effortRows.slice(i * BLOCK_INDEX_ROWS_PER_CHUNK, (i + 1) * BLOCK_INDEX_ROWS_PER_CHUNK);
|
|
772
|
+
if (part.length === 0) break;
|
|
773
|
+
const id = `${BLOCK_EFFORTS_CHUNK_PREFIX}${i}`;
|
|
774
|
+
const bytes = Buffer.from(JSON.stringify(part), "utf8");
|
|
775
|
+
const sha = sha256Hex(bytes);
|
|
776
|
+
const file = `chunks/${id}.${sha.slice(0, 8)}.json`;
|
|
777
|
+
fs.writeFileSync(path.join(outDir, file), bytes);
|
|
778
|
+
manifestChunks.push({ id, path: file, sha256: sha, bytes: bytes.length, count: part.length, kind: "block-efforts" });
|
|
779
|
+
}
|
|
780
|
+
passMs.write = Date.now() - tWrite;
|
|
781
|
+
const manifest = {
|
|
782
|
+
schema: SEED_SCHEMA2,
|
|
783
|
+
version: gitVersion.version,
|
|
784
|
+
versionLabel: gitVersion.versionLabel,
|
|
785
|
+
commit: gitVersion.commit,
|
|
786
|
+
dirty: gitVersion.dirty,
|
|
787
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
788
|
+
chunks: manifestChunks
|
|
789
|
+
};
|
|
790
|
+
fs.writeFileSync(path.join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
791
|
+
const reportPath = writeBuildReport(outDir, {
|
|
792
|
+
manifest,
|
|
793
|
+
totalMs: Date.now() - t0,
|
|
794
|
+
passMs,
|
|
795
|
+
fileCount: paths.length});
|
|
796
|
+
return { manifest, outDir, totalFiles, reportPath, passMs };
|
|
797
|
+
}
|
|
798
|
+
var BLOCK_INDEX_ROWS_PER_CHUNK, NON_EFFORT_SLUGS;
|
|
799
|
+
var init_compiler = __esm({
|
|
800
|
+
"src/cli/seedbuild/compiler.ts"() {
|
|
801
|
+
init_contract();
|
|
802
|
+
init_effort_slugs();
|
|
803
|
+
init_filename();
|
|
804
|
+
init_effort_file();
|
|
805
|
+
init_git_version();
|
|
806
|
+
init_report();
|
|
807
|
+
init_frontmatter();
|
|
808
|
+
BLOCK_INDEX_ROWS_PER_CHUNK = 4e3;
|
|
809
|
+
NON_EFFORT_SLUGS = {
|
|
810
|
+
"amrap": true,
|
|
811
|
+
"emom": true,
|
|
812
|
+
"tabata": true,
|
|
813
|
+
"for-time": true,
|
|
814
|
+
"rounds": true,
|
|
815
|
+
"sets": true,
|
|
816
|
+
"repetitions": true,
|
|
817
|
+
"count-down": true,
|
|
818
|
+
"total-reps": true,
|
|
819
|
+
"warmup": true,
|
|
820
|
+
"cooldown": true,
|
|
821
|
+
"cool-down": true,
|
|
822
|
+
"transition": true,
|
|
823
|
+
"rest": true,
|
|
824
|
+
"break": true,
|
|
825
|
+
"work": true,
|
|
826
|
+
"i-suck-at-this": true,
|
|
827
|
+
"isuckatthis": true,
|
|
828
|
+
"movement": true,
|
|
829
|
+
"movement-you": true,
|
|
830
|
+
"selected-exercise": true,
|
|
831
|
+
"training-setup": true,
|
|
832
|
+
"grade-system": true,
|
|
833
|
+
"hangs": true,
|
|
834
|
+
"warm-light-wind": true,
|
|
835
|
+
"basin": true,
|
|
836
|
+
"rock-canyon": true,
|
|
837
|
+
"b-onsight": true,
|
|
838
|
+
"a-redpoint": true,
|
|
839
|
+
"d-redpoint": true,
|
|
840
|
+
"v0-flash": true,
|
|
841
|
+
"v2-flash": true,
|
|
842
|
+
"v7-redpoint": true,
|
|
843
|
+
"avgrpe-mean": true,
|
|
844
|
+
"calculate": true,
|
|
845
|
+
"totalload-sum": true,
|
|
846
|
+
"query": true,
|
|
847
|
+
"by": true,
|
|
848
|
+
"": true,
|
|
849
|
+
"-": true,
|
|
850
|
+
"...": true,
|
|
851
|
+
"x": true,
|
|
852
|
+
"s": true,
|
|
853
|
+
"bw": true
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
});
|
|
14
857
|
var activePacks = /* @__PURE__ */ new Map();
|
|
15
858
|
function defineLanguagePack(pack) {
|
|
16
859
|
return pack;
|
|
@@ -65,36 +908,6 @@ function getRegisteredLanguagePacks() {
|
|
|
65
908
|
}
|
|
66
909
|
|
|
67
910
|
// src/store.ts
|
|
68
|
-
function factRowsToEventRows(facts) {
|
|
69
|
-
return facts.map((f, i) => {
|
|
70
|
-
const metricKey = f.metricKey ?? f.type;
|
|
71
|
-
return {
|
|
72
|
-
id: `fact:${f.resultId}:${metricKey}:${i}`,
|
|
73
|
-
resultId: f.resultId,
|
|
74
|
-
noteId: f.noteId,
|
|
75
|
-
blockContentId: f.blockContentId,
|
|
76
|
-
pageId: f.pageId,
|
|
77
|
-
origin: f.origin,
|
|
78
|
-
timestamp: f.timestamp,
|
|
79
|
-
grain: f.grain === "event" ? "event" : "summary",
|
|
80
|
-
outputType: "analytics",
|
|
81
|
-
effortSlug: f.effortSlug,
|
|
82
|
-
metrics: [{
|
|
83
|
-
type: metricKey,
|
|
84
|
-
value: f.value,
|
|
85
|
-
...f.unit ? { unit: f.unit } : {},
|
|
86
|
-
metadata: {
|
|
87
|
-
canonicalKey: metricKey,
|
|
88
|
-
...f.effortSlug ? { effortSlug: f.effortSlug } : {},
|
|
89
|
-
...f.discipline ? { effortDiscipline: f.discipline } : {},
|
|
90
|
-
...f.intensityTier ? { effortIntensityTier: f.intensityTier } : {}
|
|
91
|
-
}
|
|
92
|
-
}],
|
|
93
|
-
segmentId: f.segmentId,
|
|
94
|
-
segmentVersion: f.segmentVersion
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
911
|
function inMemoryEventStore(events) {
|
|
99
912
|
const rows = [...events];
|
|
100
913
|
return {
|
|
@@ -121,10 +934,6 @@ function inMemoryEventStore(events) {
|
|
|
121
934
|
}
|
|
122
935
|
};
|
|
123
936
|
}
|
|
124
|
-
function inMemoryEventStoreFromFacts(facts) {
|
|
125
|
-
return inMemoryEventStore(factRowsToEventRows(facts));
|
|
126
|
-
}
|
|
127
|
-
var inMemoryFactStore = inMemoryEventStoreFromFacts;
|
|
128
937
|
function createIRFile(kind, data, optionsOrSource, generatedAt) {
|
|
129
938
|
let source = "wod:headless";
|
|
130
939
|
let genAt = Date.now();
|
|
@@ -297,33 +1106,9 @@ var WqlSyntaxError = class extends Error {
|
|
|
297
1106
|
query;
|
|
298
1107
|
syntaxError;
|
|
299
1108
|
};
|
|
300
|
-
function
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
const started = s.timeSpan?.started ?? s.timestamp ?? Date.now();
|
|
304
|
-
for (const m of s.metrics) {
|
|
305
|
-
const mType = m.type || "metric";
|
|
306
|
-
const numVal = typeof m.value === "number" ? m.value : Number(m.value);
|
|
307
|
-
facts.push({
|
|
308
|
-
id: `fact-${s.id ?? 0}-${mType}-${facts.length + 1}`,
|
|
309
|
-
noteId: "stdin-note-1",
|
|
310
|
-
blockContentId: s.sourceBlockKey || "stdin-block-1",
|
|
311
|
-
segmentId: s.sourceBlockKey || "stdin-seg-1",
|
|
312
|
-
segmentVersion: 1,
|
|
313
|
-
resultId,
|
|
314
|
-
grain: s.outputType === "analytics" ? "summary" : "event",
|
|
315
|
-
type: mType,
|
|
316
|
-
value: Number.isNaN(numVal) ? 0 : numVal,
|
|
317
|
-
unit: m.unit,
|
|
318
|
-
label: m.label || mType,
|
|
319
|
-
metricKey: mType,
|
|
320
|
-
metricLabel: m.label || mType,
|
|
321
|
-
timestamp: started,
|
|
322
|
-
createdAt: started
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return facts;
|
|
1109
|
+
function legacyLogsOf(result) {
|
|
1110
|
+
const legacy = result;
|
|
1111
|
+
return legacy.data?.logs ?? [];
|
|
327
1112
|
}
|
|
328
1113
|
function extractPayload(rawJson) {
|
|
329
1114
|
try {
|
|
@@ -333,7 +1118,7 @@ function extractPayload(rawJson) {
|
|
|
333
1118
|
}
|
|
334
1119
|
return parsed;
|
|
335
1120
|
} catch (err) {
|
|
336
|
-
throw new Error(`Failed to parse JSON input: ${err instanceof Error ? err.message : String(err)}
|
|
1121
|
+
throw new Error(`Failed to parse JSON input: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
337
1122
|
}
|
|
338
1123
|
}
|
|
339
1124
|
function buildStoresFromData(data) {
|
|
@@ -348,9 +1133,29 @@ function buildStoresFromData(data) {
|
|
|
348
1133
|
workoutTimestamp: r.createdAt
|
|
349
1134
|
});
|
|
350
1135
|
const events = [
|
|
351
|
-
...
|
|
1136
|
+
...data.eventRecords,
|
|
1137
|
+
...data.facts.map((f, i) => ({
|
|
1138
|
+
id: `fact:${f.resultId ?? "stdin"}:${f.metricKey ?? f.type}:${i}`,
|
|
1139
|
+
resultId: f.resultId ?? "stdin-result-1",
|
|
1140
|
+
noteId: f.noteId ?? "stdin-note-1",
|
|
1141
|
+
blockContentId: f.blockContentId,
|
|
1142
|
+
pageId: f.pageId,
|
|
1143
|
+
origin: f.origin,
|
|
1144
|
+
timestamp: f.timestamp,
|
|
1145
|
+
grain: f.grain === "event" ? "event" : "summary",
|
|
1146
|
+
outputType: "analytics",
|
|
1147
|
+
effortSlug: f.effortSlug,
|
|
1148
|
+
metrics: [{
|
|
1149
|
+
type: f.metricKey ?? f.type,
|
|
1150
|
+
value: f.value,
|
|
1151
|
+
...f.unit ? { unit: f.unit } : {},
|
|
1152
|
+
origin: "engine"
|
|
1153
|
+
}],
|
|
1154
|
+
segmentId: f.segmentId,
|
|
1155
|
+
segmentVersion: f.segmentVersion
|
|
1156
|
+
})),
|
|
352
1157
|
...data.results.flatMap((r) => {
|
|
353
|
-
const logs = r
|
|
1158
|
+
const logs = legacyLogsOf(r);
|
|
354
1159
|
return [...wodWikiWql.toEventRows(logs, identity(r)), ...wodWikiWql.toSummaryEventRows(logs, identity(r))];
|
|
355
1160
|
})
|
|
356
1161
|
];
|
|
@@ -376,6 +1181,7 @@ function buildStoresFromData(data) {
|
|
|
376
1181
|
}
|
|
377
1182
|
function loadQueryData(options) {
|
|
378
1183
|
const data = {
|
|
1184
|
+
eventRecords: [],
|
|
379
1185
|
facts: [],
|
|
380
1186
|
results: [],
|
|
381
1187
|
notes: [],
|
|
@@ -393,35 +1199,49 @@ function loadQueryData(options) {
|
|
|
393
1199
|
if (Array.isArray(payload)) {
|
|
394
1200
|
data.facts = payload;
|
|
395
1201
|
} else if (payload && typeof payload === "object") {
|
|
396
|
-
const
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1202
|
+
const obj = payload;
|
|
1203
|
+
if (obj.kind === "event-journal" || Array.isArray(obj.records)) {
|
|
1204
|
+
if (Array.isArray(obj.records)) {
|
|
1205
|
+
data.eventRecords = obj.records;
|
|
1206
|
+
}
|
|
1207
|
+
if (Array.isArray(obj.notes)) {
|
|
1208
|
+
data.notes = obj.notes;
|
|
1209
|
+
for (const n of obj.notes) {
|
|
1210
|
+
if (n && typeof n === "object" && n.id && Array.isArray(n.tags)) {
|
|
1211
|
+
data.noteTags[n.id] = n.tags;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} else if (obj.facts || obj.results || obj.notes || obj.blocks || obj.efforts || obj.tags || obj.logs) {
|
|
1216
|
+
const corpus = payload;
|
|
1217
|
+
if (corpus.facts) data.facts = corpus.facts;
|
|
1218
|
+
if (corpus.results) data.results = corpus.results;
|
|
1219
|
+
if (corpus.notes) data.notes = corpus.notes;
|
|
1220
|
+
if (corpus.blocks) data.blocks = corpus.blocks;
|
|
1221
|
+
if (corpus.efforts) data.efforts = corpus.efforts;
|
|
1222
|
+
if (corpus.tags) data.noteTags = corpus.tags;
|
|
1223
|
+
if (corpus.logs && (!corpus.results || corpus.results.length === 0)) {
|
|
1224
|
+
const syntheticResult = {
|
|
1225
|
+
id: "corpus-result-1",
|
|
1226
|
+
noteId: "corpus-note-1",
|
|
1227
|
+
blockContentId: "corpus-block-1",
|
|
1228
|
+
origin: "journal",
|
|
1229
|
+
createdAt: corpus.logs[0]?.timeSpan?.started ?? Date.now(),
|
|
411
1230
|
startTime: corpus.logs[0]?.timeSpan?.started ?? Date.now(),
|
|
412
1231
|
endTime: corpus.logs[corpus.logs.length - 1]?.timeSpan?.ended ?? Date.now(),
|
|
413
1232
|
duration: 0,
|
|
414
|
-
completed: true
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
1233
|
+
completed: true
|
|
1234
|
+
};
|
|
1235
|
+
data.results = [syntheticResult];
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
throw new Error(`Unrecognized corpus payload shape in "${options.corpusPath}" (expected event-journal, fact-set, or CorpusIRData)`);
|
|
419
1239
|
}
|
|
420
1240
|
}
|
|
421
1241
|
} else if (options.stdinLog) {
|
|
422
1242
|
const payload = extractPayload(options.stdinLog);
|
|
423
1243
|
let executionLog;
|
|
424
|
-
const rawLogs = payload.logs ?? payload.statements ?? [];
|
|
1244
|
+
const rawLogs = payload.logs ?? ("statements" in payload ? payload.statements : void 0) ?? [];
|
|
425
1245
|
if ("results" in payload) {
|
|
426
1246
|
executionLog = { results: payload.results, logs: rawLogs, statements: rawLogs };
|
|
427
1247
|
} else {
|
|
@@ -434,13 +1254,22 @@ function loadQueryData(options) {
|
|
|
434
1254
|
blockContentId: "stdin-block-1",
|
|
435
1255
|
origin: "journal",
|
|
436
1256
|
createdAt: executionLog.results.startTime,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
1257
|
+
startTime: executionLog.results.startTime,
|
|
1258
|
+
endTime: executionLog.results.endTime,
|
|
1259
|
+
duration: executionLog.results.duration ?? 0,
|
|
1260
|
+
completed: executionLog.results.completed
|
|
441
1261
|
};
|
|
442
1262
|
data.results = [workoutResult];
|
|
443
|
-
data.
|
|
1263
|
+
data.eventRecords = wodWikiWql.toEventRows(executionLog.logs, {
|
|
1264
|
+
noteId: workoutResult.noteId,
|
|
1265
|
+
resultId: workoutResult.id,
|
|
1266
|
+
segmentId: workoutResult.segmentId,
|
|
1267
|
+
segmentVersion: workoutResult.segmentVersion,
|
|
1268
|
+
blockContentId: workoutResult.blockContentId,
|
|
1269
|
+
origin: workoutResult.origin,
|
|
1270
|
+
pageId: workoutResult.pageId,
|
|
1271
|
+
workoutTimestamp: workoutResult.createdAt
|
|
1272
|
+
});
|
|
444
1273
|
} else if (options.stdinFacts) {
|
|
445
1274
|
const payload = extractPayload(options.stdinFacts);
|
|
446
1275
|
if (Array.isArray(payload)) {
|
|
@@ -458,20 +1287,18 @@ async function runQueryCli(wqlString, options = {}) {
|
|
|
458
1287
|
if (parsed.error) {
|
|
459
1288
|
throw new WqlSyntaxError(wqlString, parsed.error);
|
|
460
1289
|
}
|
|
461
|
-
const stores = buildStoresFromData(loadQueryData(options));
|
|
462
|
-
const service = new wodWikiWql.QueryService(
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
1290
|
+
const stores = options.stores ?? buildStoresFromData(loadQueryData(options));
|
|
1291
|
+
const service = new wodWikiWql.QueryService(stores);
|
|
1292
|
+
if (wodWikiWql.isPipelineQuery(parsed)) {
|
|
1293
|
+
const result2 = await service.runPipeline(wqlString, {
|
|
1294
|
+
preferredUnit: options.preferredUnit
|
|
1295
|
+
});
|
|
1296
|
+
return createIRFile("pipeline-result", result2, { source: options.sourceLabel ?? "cli:wod query" });
|
|
1297
|
+
}
|
|
468
1298
|
if (wodWikiWql.isFindQuery(parsed)) {
|
|
469
1299
|
const result2 = await service.runFind(parsed);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (wodWikiWql.isRowsQuery(parsed)) {
|
|
473
|
-
const result2 = await service.runRows(parsed);
|
|
474
|
-
return createIRFile("rows-result", result2, { source: options.sourceLabel ?? "cli:wod query" });
|
|
1300
|
+
const kind = parsed.target === "session" ? "rows-result" : "find-result";
|
|
1301
|
+
return createIRFile(kind, result2, { source: options.sourceLabel ?? "cli:wod query" });
|
|
475
1302
|
}
|
|
476
1303
|
const result = await service.runQuery(wqlString, {
|
|
477
1304
|
preferredUnit: options.preferredUnit
|
|
@@ -481,11 +1308,13 @@ async function runQueryCli(wqlString, options = {}) {
|
|
|
481
1308
|
|
|
482
1309
|
// src/cli/loader.ts
|
|
483
1310
|
function getPathUtils() {
|
|
484
|
-
if (typeof globalThis.process?.versions?.node === "undefined" &&
|
|
1311
|
+
if (typeof globalThis.process?.versions?.node === "undefined" && !("Bun" in globalThis)) {
|
|
485
1312
|
throw new Error("Path resolution is only supported in Node / Bun environments");
|
|
486
1313
|
}
|
|
487
1314
|
return {
|
|
1315
|
+
// eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
|
|
488
1316
|
resolve: __require("path").resolve,
|
|
1317
|
+
// eslint-disable-next-line no-restricted-syntax -- lazy CJS require keeps node builtins out of browser bundles
|
|
489
1318
|
pathToFileURL: __require("url").pathToFileURL
|
|
490
1319
|
};
|
|
491
1320
|
}
|
|
@@ -524,9 +1353,1257 @@ async function loadLanguagePack(moduleSpec) {
|
|
|
524
1353
|
throw new PackLoadError(moduleSpec, error);
|
|
525
1354
|
}
|
|
526
1355
|
}
|
|
1356
|
+
var nodePath = () => nodeRequire()("node:path");
|
|
1357
|
+
var nodeFs = () => nodeRequire()("node:fs");
|
|
1358
|
+
var nodeCrypto = () => nodeRequire()("node:crypto");
|
|
1359
|
+
function nodeRequire() {
|
|
1360
|
+
const g = globalThis;
|
|
1361
|
+
if (typeof g.process?.versions?.node === "undefined" && typeof g.Bun === "undefined") {
|
|
1362
|
+
throw new Error("SQLite operations are only supported in Node / Bun environments");
|
|
1363
|
+
}
|
|
1364
|
+
const req = g.require ?? (typeof __require === "function" ? __require : void 0);
|
|
1365
|
+
if (!req) throw new Error("SQLite operations require a CJS require shim (missing tsup banner?)");
|
|
1366
|
+
return req;
|
|
1367
|
+
}
|
|
1368
|
+
var sqlModulePromise;
|
|
1369
|
+
function loadSql() {
|
|
1370
|
+
sqlModulePromise ??= import(
|
|
1371
|
+
/* @vite-ignore */
|
|
1372
|
+
'@bitcobblers/wod-wiki-storage/sql'
|
|
1373
|
+
);
|
|
1374
|
+
return sqlModulePromise;
|
|
1375
|
+
}
|
|
1376
|
+
function resolveDbPath(dbPath) {
|
|
1377
|
+
const chosen = dbPath ?? process.env.WOD_WIKI_DB ?? "./wodwiki.db";
|
|
1378
|
+
return nodePath().resolve(process.cwd(), chosen);
|
|
1379
|
+
}
|
|
1380
|
+
async function openCliDb(dbPath) {
|
|
1381
|
+
const [{ createClient }, sql] = await Promise.all([
|
|
1382
|
+
import(
|
|
1383
|
+
/* @vite-ignore */
|
|
1384
|
+
'@libsql/client'
|
|
1385
|
+
),
|
|
1386
|
+
loadSql()
|
|
1387
|
+
]);
|
|
1388
|
+
const resolved = resolveDbPath(dbPath);
|
|
1389
|
+
nodeFs().mkdirSync(nodePath().dirname(resolved), { recursive: true });
|
|
1390
|
+
const db = sql.libsqlDatabase(createClient({ url: `file:${resolved}` }));
|
|
1391
|
+
await sql.initializeStore(db);
|
|
1392
|
+
await sql.initializeDomain(db);
|
|
1393
|
+
return { db, path: resolved, close: () => db.close() };
|
|
1394
|
+
}
|
|
1395
|
+
function formatV5Digest(digest) {
|
|
1396
|
+
const bytes = digest.slice(0, 16);
|
|
1397
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
1398
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1399
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0"));
|
|
1400
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
|
1401
|
+
}
|
|
1402
|
+
function uuid5(namespace, name) {
|
|
1403
|
+
return formatV5Digest(nodeCrypto().createHash("sha1").update(namespace).update(name).digest());
|
|
1404
|
+
}
|
|
1405
|
+
var CLI_UUID_NAMESPACE = "c0ffee00-8b1a-4e2c-9d3f-5a6b7c8d9e0f";
|
|
1406
|
+
function stableNoteId(sourceLabel, content) {
|
|
1407
|
+
if (sourceLabel && sourceLabel !== "-") {
|
|
1408
|
+
return uuid5(CLI_UUID_NAMESPACE, `wod-cli:${nodePath().resolve(process.cwd(), sourceLabel)}`);
|
|
1409
|
+
}
|
|
1410
|
+
return uuid5(CLI_UUID_NAMESPACE, `wod-cli:stdin:${content.length}:${content.slice(0, 256)}`);
|
|
1411
|
+
}
|
|
1412
|
+
function randomId() {
|
|
1413
|
+
return nodeCrypto().randomUUID();
|
|
1414
|
+
}
|
|
1415
|
+
async function allRows(sql, db, store) {
|
|
1416
|
+
return sql.listRows(db, sql.storeDefinition(store));
|
|
1417
|
+
}
|
|
1418
|
+
async function rowsByIndex(sql, db, store, index, only) {
|
|
1419
|
+
return sql.listRows(db, sql.storeDefinition(store), { only }, void 0, index);
|
|
1420
|
+
}
|
|
1421
|
+
function sqliteEventStore(db) {
|
|
1422
|
+
const all = async () => await allRows(await loadSql(), db, "events");
|
|
1423
|
+
return {
|
|
1424
|
+
getEventsByTimeRange: async (start, end) => (await all()).filter((r) => r.timestamp >= start && r.timestamp <= end),
|
|
1425
|
+
getEventsByResult: async (resultId) => (await all()).filter((r) => r.resultId === resultId),
|
|
1426
|
+
getEventsForNote: async (noteId) => (await all()).filter((r) => r.noteId === noteId),
|
|
1427
|
+
getEventsByContent: async (blockContentId) => (await all()).filter((r) => r.blockContentId === blockContentId),
|
|
1428
|
+
scanAll: all,
|
|
1429
|
+
appendEvents: async (appended) => {
|
|
1430
|
+
const sql = await loadSql();
|
|
1431
|
+
await db.transaction(async (tx) => {
|
|
1432
|
+
const eventsDef = sql.storeDefinition("events");
|
|
1433
|
+
for (const row of appended) await sql.putRow(tx, eventsDef, row);
|
|
1434
|
+
});
|
|
1435
|
+
},
|
|
1436
|
+
finalizeSummaries: async (resultId, finals) => {
|
|
1437
|
+
const sql = await loadSql();
|
|
1438
|
+
await db.transaction(async (tx) => {
|
|
1439
|
+
const eventsDef = sql.storeDefinition("events");
|
|
1440
|
+
const existing = (await sql.listRows(tx, eventsDef)).filter(
|
|
1441
|
+
(r) => r.resultId === resultId && r.grain === "summary" && r.origin !== "user"
|
|
1442
|
+
);
|
|
1443
|
+
for (const row of existing) await sql.deleteRows(tx, eventsDef, row.id);
|
|
1444
|
+
for (const row of finals) await sql.putRow(tx, eventsDef, row);
|
|
1445
|
+
});
|
|
1446
|
+
},
|
|
1447
|
+
deleteEvents: async (ids) => {
|
|
1448
|
+
const sql = await loadSql();
|
|
1449
|
+
await db.transaction(async (tx) => {
|
|
1450
|
+
const eventsDef = sql.storeDefinition("events");
|
|
1451
|
+
for (const id of ids) await sql.deleteRows(tx, eventsDef, id);
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
function sqliteNoteStore(db) {
|
|
1457
|
+
return {
|
|
1458
|
+
getAllNotes: async () => await allRows(await loadSql(), db, "notes"),
|
|
1459
|
+
getNoteIdsForTag: async (label) => {
|
|
1460
|
+
const sql = await loadSql();
|
|
1461
|
+
const tags = (await allRows(sql, db, "tags")).filter((t) => t.label === label);
|
|
1462
|
+
const ids = {};
|
|
1463
|
+
for (const tag of tags) {
|
|
1464
|
+
for (const row of await rowsByIndex(sql, db, "note_tags", "by-tag", tag.id)) {
|
|
1465
|
+
ids[row.noteId] = true;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return new Set(Object.keys(ids));
|
|
1469
|
+
},
|
|
1470
|
+
getNoteTagLabels: async (noteId) => {
|
|
1471
|
+
const sql = await loadSql();
|
|
1472
|
+
const tagRows = await allRows(sql, db, "tags");
|
|
1473
|
+
const labelById = {};
|
|
1474
|
+
for (const t of tagRows) labelById[t.id] = t.label;
|
|
1475
|
+
const links = await rowsByIndex(sql, db, "note_tags", "by-note", noteId);
|
|
1476
|
+
return links.map((l) => labelById[l.tagId]).filter((l) => typeof l === "string");
|
|
1477
|
+
},
|
|
1478
|
+
queryDomain: async (request) => {
|
|
1479
|
+
const result = await (await loadSql()).handleDomainQuery(db, request);
|
|
1480
|
+
return result;
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
function sqliteBlockStore(db) {
|
|
1485
|
+
return {
|
|
1486
|
+
getAllBlocks: async () => await allRows(await loadSql(), db, "block_index"),
|
|
1487
|
+
queryDomain: async (request) => {
|
|
1488
|
+
const result = await (await loadSql()).handleDomainQuery(db, { ...request, plan: "blocks" });
|
|
1489
|
+
return result;
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function sqliteEffortStore(db) {
|
|
1494
|
+
return {
|
|
1495
|
+
getAllEfforts: async () => {
|
|
1496
|
+
const stored = await allRows(await loadSql(), db, "efforts");
|
|
1497
|
+
const bySlug = {};
|
|
1498
|
+
for (const effort of stored) bySlug[effort.slug] = effort;
|
|
1499
|
+
for (const effort of wodWikiLang.bundledEfforts) {
|
|
1500
|
+
bySlug[effort.slug] ??= effort;
|
|
1501
|
+
}
|
|
1502
|
+
return Object.values(bySlug);
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
function sqliteIndexStore(db, store) {
|
|
1507
|
+
return {
|
|
1508
|
+
getAllFromIndex: async (index, key) => rowsByIndex(await loadSql(), db, store, index, key)
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
async function getNoteBundle(db, noteId) {
|
|
1512
|
+
const sql = await loadSql();
|
|
1513
|
+
const notes = await allRows(sql, db, "notes");
|
|
1514
|
+
const note = notes.find((n) => n.id === noteId);
|
|
1515
|
+
const segments = await rowsByIndex(sql, db, "segments", "by-note", noteId);
|
|
1516
|
+
return { note, segments };
|
|
1517
|
+
}
|
|
1518
|
+
async function getSessionBundle(db, resultId) {
|
|
1519
|
+
const sql = await loadSql();
|
|
1520
|
+
const sessions = await allRows(sql, db, "sessions");
|
|
1521
|
+
const session = sessions.find((s) => s.id === resultId);
|
|
1522
|
+
if (!session) return void 0;
|
|
1523
|
+
const events = (await allRows(sql, db, "events")).filter((e) => e.resultId === resultId);
|
|
1524
|
+
return { session, events };
|
|
1525
|
+
}
|
|
1526
|
+
async function getEffortBySlug(db, slug) {
|
|
1527
|
+
const sql = await loadSql();
|
|
1528
|
+
const efforts = await allRows(sql, db, "efforts");
|
|
1529
|
+
return efforts.find((e) => e.slug === slug);
|
|
1530
|
+
}
|
|
1531
|
+
async function listTagsByType(db, type) {
|
|
1532
|
+
const sql = await loadSql();
|
|
1533
|
+
const tags = await allRows(sql, db, "tags");
|
|
1534
|
+
return tags.filter((t) => t.type === type);
|
|
1535
|
+
}
|
|
1536
|
+
function toSegmentDataType(section) {
|
|
1537
|
+
switch (section.type) {
|
|
1538
|
+
case "time":
|
|
1539
|
+
case "log":
|
|
1540
|
+
return "wod";
|
|
1541
|
+
case "title": {
|
|
1542
|
+
const level = Math.min(6, Math.max(1, section.level ?? 1));
|
|
1543
|
+
return `h${level}`;
|
|
1544
|
+
}
|
|
1545
|
+
case "frontmatter":
|
|
1546
|
+
return "frontmatter";
|
|
1547
|
+
case "markdown":
|
|
1548
|
+
default:
|
|
1549
|
+
return "markdown";
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function sectionSourceFragments(content, sections) {
|
|
1553
|
+
const starts = [0];
|
|
1554
|
+
for (let i = 0; i < content.length; i++) {
|
|
1555
|
+
if (content[i] === "\n") starts.push(i + 1);
|
|
1556
|
+
}
|
|
1557
|
+
return sections.map((section, index) => content.slice(
|
|
1558
|
+
starts[section.startLine],
|
|
1559
|
+
index + 1 < sections.length ? starts[sections[index + 1].startLine] : content.length
|
|
1560
|
+
));
|
|
1561
|
+
}
|
|
1562
|
+
function scriptBlockContentId(data) {
|
|
1563
|
+
if (data && typeof data === "object" && "contentId" in data && typeof data.contentId === "string") {
|
|
1564
|
+
return data.contentId;
|
|
1565
|
+
}
|
|
1566
|
+
return void 0;
|
|
1567
|
+
}
|
|
1568
|
+
function extractEffortSlugs(statements) {
|
|
1569
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
1570
|
+
for (const statement of statements) {
|
|
1571
|
+
if (statement.exerciseId) {
|
|
1572
|
+
slugs.add(statement.exerciseId);
|
|
1573
|
+
} else {
|
|
1574
|
+
const effortMetric = statement.metrics?.getMetric?.("effort");
|
|
1575
|
+
const effort = effortMetric && "effort" in effortMetric ? effortMetric.effort : void 0;
|
|
1576
|
+
if (typeof effort === "string") {
|
|
1577
|
+
const slug = effort.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1578
|
+
if (slug) slugs.add(slug);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return [...slugs];
|
|
1583
|
+
}
|
|
1584
|
+
async function rowsByNote(sql, db, store, noteId) {
|
|
1585
|
+
return sql.listRows(db, sql.storeDefinition(store), { only: noteId }, void 0, "by-note");
|
|
1586
|
+
}
|
|
1587
|
+
function titleFromLabel(sourceLabel) {
|
|
1588
|
+
if (!sourceLabel || sourceLabel === "-") return "stdin";
|
|
1589
|
+
const stem = sourceLabel.split("/").pop() ?? sourceLabel;
|
|
1590
|
+
return stem.replace(/\.[^.]+$/, "") || sourceLabel;
|
|
1591
|
+
}
|
|
1592
|
+
async function persistNote(db, options) {
|
|
1593
|
+
const sql = await loadSql();
|
|
1594
|
+
const now = options.now ?? Date.now();
|
|
1595
|
+
const noteId = options.noteId ?? stableNoteId(options.sourceLabel, options.source);
|
|
1596
|
+
const title = options.title ?? titleFromLabel(options.sourceLabel);
|
|
1597
|
+
const sections = wodWikiCore.parseDocumentSections(options.source);
|
|
1598
|
+
const fragments = sectionSourceFragments(options.source, sections);
|
|
1599
|
+
const existingNote = await noteById(sql, db.db, noteId);
|
|
1600
|
+
const priorSegments = await rowsByNote(sql, db.db, "segments", noteId);
|
|
1601
|
+
const versionBySegmentId = {};
|
|
1602
|
+
for (const segment of priorSegments) {
|
|
1603
|
+
versionBySegmentId[segment.id] = Math.max(versionBySegmentId[segment.id] ?? 0, segment.version);
|
|
1604
|
+
}
|
|
1605
|
+
const createdAt = existingNote?.createdAt ?? now;
|
|
1606
|
+
const note = {
|
|
1607
|
+
id: noteId,
|
|
1608
|
+
title,
|
|
1609
|
+
type: existingNote?.type ?? "note",
|
|
1610
|
+
date: existingNote?.date ?? now,
|
|
1611
|
+
createdAt
|
|
1612
|
+
};
|
|
1613
|
+
const ops = [];
|
|
1614
|
+
const segments = sections.map((section, position) => {
|
|
1615
|
+
const id = `${noteId}:${section.id}`;
|
|
1616
|
+
const version = (versionBySegmentId[id] ?? 0) + 1;
|
|
1617
|
+
return {
|
|
1618
|
+
id,
|
|
1619
|
+
version,
|
|
1620
|
+
noteId,
|
|
1621
|
+
position,
|
|
1622
|
+
dataType: toSegmentDataType(section),
|
|
1623
|
+
data: section.scriptBlock || null,
|
|
1624
|
+
rawContent: section.displayContent,
|
|
1625
|
+
sourceContent: fragments[position],
|
|
1626
|
+
createdAt: version === 1 ? createdAt : now,
|
|
1627
|
+
updatedAt: now,
|
|
1628
|
+
isHistory: false
|
|
1629
|
+
};
|
|
1630
|
+
});
|
|
1631
|
+
for (const segment of priorSegments) {
|
|
1632
|
+
if (segment.isHistory !== true) {
|
|
1633
|
+
ops.push({ op: "put", store: "segments", value: { ...segment, isHistory: true, updatedAt: now } });
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
ops.push({ op: "put", store: "notes", value: note });
|
|
1637
|
+
for (const segment of segments) ops.push({ op: "put", store: "segments", value: segment });
|
|
1638
|
+
const priorBlocks = await rowsByNote(sql, db.db, "block_index", noteId);
|
|
1639
|
+
const priorBlockEfforts = await rowsByNote(sql, db.db, "block_efforts", noteId);
|
|
1640
|
+
for (const row of priorBlocks) ops.push({ op: "delete", store: "block_index", key: row.id });
|
|
1641
|
+
for (const row of priorBlockEfforts) ops.push({ op: "delete", store: "block_efforts", key: row.id });
|
|
1642
|
+
let blockCount = 0;
|
|
1643
|
+
let effortCount = 0;
|
|
1644
|
+
for (const segment of segments) {
|
|
1645
|
+
const blockContentId = segment.dataType === "wod" ? scriptBlockContentId(segment.data) : void 0;
|
|
1646
|
+
ops.push({
|
|
1647
|
+
op: "put",
|
|
1648
|
+
store: "block_index",
|
|
1649
|
+
value: {
|
|
1650
|
+
id: `${noteId}:${segment.id}:${segment.version}`,
|
|
1651
|
+
noteId,
|
|
1652
|
+
segmentId: segment.id,
|
|
1653
|
+
segmentVersion: segment.version,
|
|
1654
|
+
position: segment.position,
|
|
1655
|
+
dataType: segment.dataType,
|
|
1656
|
+
blockContentId,
|
|
1657
|
+
rawContent: segment.rawContent,
|
|
1658
|
+
noteTitle: title,
|
|
1659
|
+
createdAt: segment.createdAt
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
blockCount += 1;
|
|
1663
|
+
if (segment.dataType === "wod" && segment.rawContent) {
|
|
1664
|
+
const script = wodWikiLang.parseScript(segment.rawContent);
|
|
1665
|
+
for (const effortSlug of extractEffortSlugs(script.statements)) {
|
|
1666
|
+
ops.push({
|
|
1667
|
+
op: "put",
|
|
1668
|
+
store: "block_efforts",
|
|
1669
|
+
value: {
|
|
1670
|
+
id: `${noteId}:${segment.id}:${effortSlug}`,
|
|
1671
|
+
noteId,
|
|
1672
|
+
blockId: segment.id,
|
|
1673
|
+
blockContentId,
|
|
1674
|
+
effortSlug,
|
|
1675
|
+
createdAt: segment.createdAt
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
effortCount += 1;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
let tagCount = 0;
|
|
1683
|
+
if (options.tags) {
|
|
1684
|
+
const priorLinks = await rowsByNote(sql, db.db, "note_tags", noteId);
|
|
1685
|
+
for (const link of priorLinks) ops.push({ op: "delete", store: "note_tags", key: link.id });
|
|
1686
|
+
const tagByLabel = new Map(
|
|
1687
|
+
(await sql.listRows(db.db, sql.storeDefinition("tags"))).map((t) => [t.label, t])
|
|
1688
|
+
);
|
|
1689
|
+
for (const item of options.tags) {
|
|
1690
|
+
const label = item.label.trim();
|
|
1691
|
+
if (!label) continue;
|
|
1692
|
+
let tag = tagByLabel.get(label);
|
|
1693
|
+
if (!tag) {
|
|
1694
|
+
tag = { id: randomId(), label, type: item.type, createdAt: now };
|
|
1695
|
+
tagByLabel.set(label, tag);
|
|
1696
|
+
ops.push({ op: "put", store: "tags", value: tag });
|
|
1697
|
+
} else if (item.type && !tag.type) {
|
|
1698
|
+
tag = { ...tag, type: item.type };
|
|
1699
|
+
tagByLabel.set(label, tag);
|
|
1700
|
+
ops.push({ op: "put", store: "tags", value: tag });
|
|
1701
|
+
}
|
|
1702
|
+
tagCount += 1;
|
|
1703
|
+
ops.push({
|
|
1704
|
+
op: "put",
|
|
1705
|
+
store: "note_tags",
|
|
1706
|
+
// Deterministic junction id (unique [noteId, tagId] index).
|
|
1707
|
+
value: { id: uuid5(CLI_UUID_NAMESPACE, `note-tag:${noteId}:${tag.id}`), noteId, tagId: tag.id }
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
await db.db.transaction(async (tx) => {
|
|
1712
|
+
await sql.refreshDomain(tx, ops);
|
|
1713
|
+
});
|
|
1714
|
+
return {
|
|
1715
|
+
noteId,
|
|
1716
|
+
note,
|
|
1717
|
+
segments: segments.length,
|
|
1718
|
+
blocks: blockCount,
|
|
1719
|
+
efforts: effortCount,
|
|
1720
|
+
tags: tagCount,
|
|
1721
|
+
created: !existingNote
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
async function noteById(sql, db, noteId) {
|
|
1725
|
+
const rows = await sql.listRows(db, sql.storeDefinition("notes"));
|
|
1726
|
+
return rows.find((row) => row.id === noteId);
|
|
1727
|
+
}
|
|
1728
|
+
async function persistRun(db, options) {
|
|
1729
|
+
const sql = await loadSql();
|
|
1730
|
+
const results = options.executionLog.results;
|
|
1731
|
+
const logs = options.executionLog.logs ?? [];
|
|
1732
|
+
const noteState = await persistNote(db, {
|
|
1733
|
+
source: options.source,
|
|
1734
|
+
sourceLabel: options.sourceLabel,
|
|
1735
|
+
title: options.title,
|
|
1736
|
+
now: options.now
|
|
1737
|
+
});
|
|
1738
|
+
const segments = (await rowsByNote(sql, db.db, "segments", noteState.noteId)).filter((s) => s.isHistory !== true && s.dataType === "wod").sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
|
1739
|
+
const runSegment = segments[0];
|
|
1740
|
+
const blockContentId = runSegment ? scriptBlockContentId(runSegment.data) : void 0;
|
|
1741
|
+
const resultId = randomId();
|
|
1742
|
+
const session = {
|
|
1743
|
+
id: resultId,
|
|
1744
|
+
segmentId: runSegment?.id,
|
|
1745
|
+
segmentVersion: runSegment?.version,
|
|
1746
|
+
noteId: noteState.noteId,
|
|
1747
|
+
blockId: runSegment?.id,
|
|
1748
|
+
blockContentId,
|
|
1749
|
+
origin: "journal",
|
|
1750
|
+
status: results.completed === false ? "in-progress" : "completed",
|
|
1751
|
+
startTime: results.startTime,
|
|
1752
|
+
endTime: results.endTime,
|
|
1753
|
+
duration: results.duration ?? 0,
|
|
1754
|
+
completed: results.completed,
|
|
1755
|
+
createdAt: results.endTime || Date.now()
|
|
1756
|
+
};
|
|
1757
|
+
const identity = {
|
|
1758
|
+
noteId: noteState.noteId,
|
|
1759
|
+
resultId,
|
|
1760
|
+
segmentId: session.segmentId,
|
|
1761
|
+
segmentVersion: session.segmentVersion,
|
|
1762
|
+
blockContentId,
|
|
1763
|
+
origin: session.origin,
|
|
1764
|
+
pageId: session.pageId,
|
|
1765
|
+
workoutTimestamp: session.createdAt
|
|
1766
|
+
};
|
|
1767
|
+
const eventRows = wodWikiWql.toEventRows(logs, identity);
|
|
1768
|
+
const summaryRows = wodWikiWql.toSummaryEventRows(logs, identity);
|
|
1769
|
+
await db.db.transaction(async (tx) => {
|
|
1770
|
+
await sql.putRow(tx, sql.storeDefinition("sessions"), session);
|
|
1771
|
+
const eventsDef = sql.storeDefinition("events");
|
|
1772
|
+
for (const row of [...eventRows, ...summaryRows]) await sql.putRow(tx, eventsDef, row);
|
|
1773
|
+
});
|
|
1774
|
+
return {
|
|
1775
|
+
noteId: noteState.noteId,
|
|
1776
|
+
resultId,
|
|
1777
|
+
session,
|
|
1778
|
+
events: eventRows.length,
|
|
1779
|
+
summaries: summaryRows.length,
|
|
1780
|
+
noteCreated: noteState.created
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
async function persistSessionBundle(db, session, events) {
|
|
1784
|
+
const sql = await loadSql();
|
|
1785
|
+
const existing = await noteById(sql, db.db, session.noteId);
|
|
1786
|
+
let noteCreated = false;
|
|
1787
|
+
if (!existing) {
|
|
1788
|
+
noteCreated = true;
|
|
1789
|
+
const now = session.createdAt || Date.now();
|
|
1790
|
+
await db.db.transaction(async (tx) => {
|
|
1791
|
+
await sql.putRow(tx, sql.storeDefinition("notes"), {
|
|
1792
|
+
id: session.noteId,
|
|
1793
|
+
title: `imported-${session.noteId.slice(0, 8)}`,
|
|
1794
|
+
createdAt: now,
|
|
1795
|
+
type: "note"
|
|
1796
|
+
});
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
await db.db.transaction(async (tx) => {
|
|
1800
|
+
await sql.putRow(tx, sql.storeDefinition("sessions"), session);
|
|
1801
|
+
const eventsDef = sql.storeDefinition("events");
|
|
1802
|
+
for (const row of events) await sql.putRow(tx, eventsDef, row);
|
|
1803
|
+
});
|
|
1804
|
+
return { events: events.length, noteCreated };
|
|
1805
|
+
}
|
|
1806
|
+
async function persistEffort(db, effort) {
|
|
1807
|
+
const sql = await loadSql();
|
|
1808
|
+
await db.db.transaction(async (tx) => {
|
|
1809
|
+
await sql.putRow(tx, sql.storeDefinition("efforts"), effort);
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
async function persistTypedTags(db, labels, type) {
|
|
1813
|
+
const sql = await loadSql();
|
|
1814
|
+
const now = Date.now();
|
|
1815
|
+
const tagByLabel = new Map(
|
|
1816
|
+
(await sql.listRows(db.db, sql.storeDefinition("tags"))).map((t) => [t.label, t])
|
|
1817
|
+
);
|
|
1818
|
+
const ops = [];
|
|
1819
|
+
let added = 0;
|
|
1820
|
+
for (const raw of labels) {
|
|
1821
|
+
const label = raw.trim();
|
|
1822
|
+
if (!label) continue;
|
|
1823
|
+
const tag = tagByLabel.get(label);
|
|
1824
|
+
if (!tag) {
|
|
1825
|
+
ops.push({ op: "put", store: "tags", value: { id: randomId(), label, type, createdAt: now } });
|
|
1826
|
+
added += 1;
|
|
1827
|
+
} else if (!tag.type) {
|
|
1828
|
+
ops.push({ op: "put", store: "tags", value: { ...tag, type } });
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
if (ops.length > 0) {
|
|
1832
|
+
await db.db.transaction(async (tx) => {
|
|
1833
|
+
await sql.refreshDomain(tx, ops);
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
return added;
|
|
1837
|
+
}
|
|
1838
|
+
var NOTE_ID_FRONTMATTER_KEY = "wod-note-id";
|
|
1839
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1840
|
+
function extractNoteIdentity(markdown) {
|
|
1841
|
+
const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
|
|
1842
|
+
if (!block) return void 0;
|
|
1843
|
+
let noteId;
|
|
1844
|
+
let title;
|
|
1845
|
+
for (const line of block[1].split(/\r?\n/)) {
|
|
1846
|
+
const kv = line.match(/^([^:]+):\s*(.*)$/);
|
|
1847
|
+
if (!kv) continue;
|
|
1848
|
+
const key = kv[1].trim();
|
|
1849
|
+
const value = kv[2].trim();
|
|
1850
|
+
if (key === NOTE_ID_FRONTMATTER_KEY && UUID_RE.test(value)) noteId = value;
|
|
1851
|
+
if (key === "title" && value) title = value;
|
|
1852
|
+
}
|
|
1853
|
+
return noteId ? { noteId, title } : void 0;
|
|
1854
|
+
}
|
|
1855
|
+
var FRONTMATTER_SAFE = /^[^:#\[\]{}'"*&,!?|>%@`\s][^:#\[\]{}"']*$/;
|
|
1856
|
+
function injectIdentityFrontmatter(markdown, identity) {
|
|
1857
|
+
const stamp = (lines2) => {
|
|
1858
|
+
const replaceOrInsert = (key, value) => {
|
|
1859
|
+
if (value === void 0 || !FRONTMATTER_SAFE.test(value)) return;
|
|
1860
|
+
const idx = lines2.findIndex((l) => l.startsWith(`${key}:`));
|
|
1861
|
+
if (idx >= 0) lines2[idx] = `${key}: ${value}`;
|
|
1862
|
+
else lines2.push(`${key}: ${value}`);
|
|
1863
|
+
};
|
|
1864
|
+
replaceOrInsert("title", identity.title);
|
|
1865
|
+
replaceOrInsert(NOTE_ID_FRONTMATTER_KEY, identity.noteId);
|
|
1866
|
+
return lines2;
|
|
1867
|
+
};
|
|
1868
|
+
const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
|
|
1869
|
+
if (block) {
|
|
1870
|
+
const lines2 = stamp(block[1].split(/\r?\n/));
|
|
1871
|
+
return `---
|
|
1872
|
+
${lines2.join("\n")}
|
|
1873
|
+
---${markdown.slice(block[0].length)}`;
|
|
1874
|
+
}
|
|
1875
|
+
const lines = stamp([]);
|
|
1876
|
+
return `---
|
|
1877
|
+
${lines.join("\n")}
|
|
1878
|
+
---
|
|
527
1879
|
|
|
528
|
-
|
|
1880
|
+
${markdown}`;
|
|
1881
|
+
}
|
|
1882
|
+
function segmentToFragment(segment) {
|
|
1883
|
+
if (segment.sourceContent !== void 0) return segment.sourceContent;
|
|
1884
|
+
if (segment.dataType === "wod") {
|
|
1885
|
+
const block = segment.data;
|
|
1886
|
+
const dialect = block?.dialect ?? "time";
|
|
1887
|
+
const fenceTag = block?.sport ? `${dialect}:${block.sport}` : dialect;
|
|
1888
|
+
return `\`\`\`${fenceTag}
|
|
1889
|
+
${segment.rawContent}
|
|
1890
|
+
\`\`\``;
|
|
1891
|
+
}
|
|
1892
|
+
if (segment.dataType === "frontmatter") {
|
|
1893
|
+
return `---
|
|
1894
|
+
${segment.rawContent}
|
|
1895
|
+
---`;
|
|
1896
|
+
}
|
|
1897
|
+
return segment.rawContent;
|
|
1898
|
+
}
|
|
1899
|
+
function composeNoteMarkdown(note, segments, version) {
|
|
1900
|
+
const pool = version === void 0 ? segments.filter((s) => s.isHistory !== true) : segments;
|
|
1901
|
+
const cap = version ?? Number.MAX_SAFE_INTEGER;
|
|
1902
|
+
const bySegmentId = {};
|
|
1903
|
+
for (const segment of pool) {
|
|
1904
|
+
if (segment.version > cap) continue;
|
|
1905
|
+
const current = bySegmentId[segment.id];
|
|
1906
|
+
if (!current || segment.version > current.version) bySegmentId[segment.id] = segment;
|
|
1907
|
+
}
|
|
1908
|
+
const picked = Object.values(bySegmentId).sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
|
1909
|
+
const markdown = picked.map(segmentToFragment).join("");
|
|
1910
|
+
return injectIdentityFrontmatter(markdown, { noteId: note.id, title: note.title });
|
|
1911
|
+
}
|
|
1912
|
+
var FRONTMATTER_BLOCK_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
1913
|
+
function readFrontmatterMeta(markdown) {
|
|
1914
|
+
const match = FRONTMATTER_BLOCK_RE.exec(markdown);
|
|
1915
|
+
if (!match) return {};
|
|
1916
|
+
const meta = {};
|
|
1917
|
+
const lines = match[1].split(/\r?\n/);
|
|
1918
|
+
let currentListKey;
|
|
1919
|
+
for (const rawLine of lines) {
|
|
1920
|
+
const line = rawLine.trimEnd();
|
|
1921
|
+
const item = line.match(/^\s+-\s+(.*)$/);
|
|
1922
|
+
if (item && currentListKey) {
|
|
1923
|
+
meta[currentListKey].push(item[1].trim());
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
const kv = line.match(/^([^:]+):\s*(.*)$/);
|
|
1927
|
+
if (!kv) continue;
|
|
1928
|
+
const key = kv[1].trim().toLowerCase();
|
|
1929
|
+
const value = kv[2].trim();
|
|
1930
|
+
if (value === "") {
|
|
1931
|
+
meta[key] = [];
|
|
1932
|
+
currentListKey = key;
|
|
1933
|
+
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
1934
|
+
meta[key] = value.slice(1, -1).split(",").map((v) => v.trim().replace(/^['"]|['"]$/g, "")).filter((v) => v.length > 0);
|
|
1935
|
+
currentListKey = void 0;
|
|
1936
|
+
} else {
|
|
1937
|
+
meta[key] = value;
|
|
1938
|
+
currentListKey = void 0;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return meta;
|
|
1942
|
+
}
|
|
1943
|
+
var asList = (value) => Array.isArray(value) ? value : typeof value === "string" ? value.split(",").map((v) => v.trim()).filter((v) => v.length > 0) : [];
|
|
1944
|
+
function extractTypedTags(markdown, typedKeys = wodWikiWql.WQL_TYPED_TAG_KEYS) {
|
|
1945
|
+
const meta = readFrontmatterMeta(markdown);
|
|
1946
|
+
const results = asList(meta["tags"]).map((label) => ({ label }));
|
|
1947
|
+
const metaKeys = Object.keys(meta);
|
|
1948
|
+
for (const typeName of typedKeys) {
|
|
1949
|
+
if (typeName === "tags") continue;
|
|
1950
|
+
const matchedKey = metaKeys.find((k) => k === typeName.toLowerCase());
|
|
1951
|
+
if (!matchedKey) continue;
|
|
1952
|
+
for (const label of asList(meta[matchedKey])) {
|
|
1953
|
+
results.push({ label, type: typeName });
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
return results.filter((t) => t.label.length > 0);
|
|
1957
|
+
}
|
|
1958
|
+
function markdownToEffort(raw) {
|
|
1959
|
+
const match = FRONTMATTER_BLOCK_RE.exec(raw);
|
|
1960
|
+
if (!match) return null;
|
|
1961
|
+
const meta = readFrontmatterMeta(raw);
|
|
1962
|
+
const body = raw.slice(match[0].length).trim();
|
|
1963
|
+
const id = scalarOf(meta["id"]);
|
|
1964
|
+
const slug = scalarOf(meta["slug"]);
|
|
1965
|
+
const label = scalarOf(meta["label"]);
|
|
1966
|
+
if (!id || !slug || !label) return null;
|
|
1967
|
+
const baseAttributes = {};
|
|
1968
|
+
const blockRe = /^\s{2,}([^:]+):\s*(.+)$/;
|
|
1969
|
+
let inAttrs = false;
|
|
1970
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
1971
|
+
if (/^baseAttributes:/.test(line)) {
|
|
1972
|
+
inAttrs = true;
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
if (inAttrs) {
|
|
1976
|
+
const attr = blockRe.exec(line);
|
|
1977
|
+
if (attr && /^ {2,}/.test(line)) {
|
|
1978
|
+
const v = attr[2].trim();
|
|
1979
|
+
baseAttributes[attr[1].trim()] = /^\d+(\.\d+)?$/.test(v) ? Number(v) : v;
|
|
1980
|
+
} else if (/^\S/.test(line)) {
|
|
1981
|
+
inAttrs = false;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (typeof baseAttributes["met"] !== "number") return null;
|
|
1986
|
+
return {
|
|
1987
|
+
id,
|
|
1988
|
+
slug,
|
|
1989
|
+
label,
|
|
1990
|
+
aliases: asList(meta["aliases"]),
|
|
1991
|
+
baseAttributes,
|
|
1992
|
+
...body ? { body } : {}
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function effortToMarkdown(effort) {
|
|
1996
|
+
const lines = ["---", `id: ${effort.id}`, `slug: ${effort.slug}`, `label: ${effort.label}`];
|
|
1997
|
+
if (effort.aliases && effort.aliases.length > 0) {
|
|
1998
|
+
lines.push("aliases:");
|
|
1999
|
+
for (const alias of effort.aliases) lines.push(` - ${alias}`);
|
|
2000
|
+
}
|
|
2001
|
+
const attrs = Object.entries(effort.baseAttributes).filter(([, v]) => typeof v === "number" || typeof v === "string");
|
|
2002
|
+
if (attrs.length > 0) {
|
|
2003
|
+
lines.push("baseAttributes:");
|
|
2004
|
+
for (const [k, v] of attrs) lines.push(` ${k}: ${String(v)}`);
|
|
2005
|
+
}
|
|
2006
|
+
lines.push("---", "");
|
|
2007
|
+
return `${lines.join("\n")}
|
|
2008
|
+
${(effort.body ?? "").trim()}`.trimEnd() + "\n";
|
|
2009
|
+
}
|
|
2010
|
+
function scalarOf(value) {
|
|
2011
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2012
|
+
}
|
|
2013
|
+
function equipmentToCsv(labels) {
|
|
2014
|
+
return ["label", ...labels].map((l) => escapeCsvField(l)).join("\n") + "\n";
|
|
2015
|
+
}
|
|
2016
|
+
function csvToEquipmentLabels(csv) {
|
|
2017
|
+
const lines = splitCsvRecords(csv);
|
|
2018
|
+
if (lines.length === 0) return [];
|
|
2019
|
+
const header = parseCsvLine(lines[0]);
|
|
2020
|
+
const labelIdx = header[0] === "label" ? 0 : -1;
|
|
2021
|
+
const start = labelIdx >= 0 ? 1 : 0;
|
|
2022
|
+
const labels = [];
|
|
2023
|
+
for (const line of lines.slice(start)) {
|
|
2024
|
+
const cells = parseCsvLine(line);
|
|
2025
|
+
const label = (labelIdx >= 0 ? cells[0] : cells.join(",")).trim();
|
|
2026
|
+
if (label) labels.push(label);
|
|
2027
|
+
}
|
|
2028
|
+
return [...new Set(labels)];
|
|
2029
|
+
}
|
|
2030
|
+
var SESSION_HEADER = [
|
|
2031
|
+
"sessionId",
|
|
2032
|
+
"noteId",
|
|
2033
|
+
"segmentId",
|
|
2034
|
+
"segmentVersion",
|
|
2035
|
+
"blockId",
|
|
2036
|
+
"blockContentId",
|
|
2037
|
+
"origin",
|
|
2038
|
+
"status",
|
|
2039
|
+
"startTime",
|
|
2040
|
+
"endTime",
|
|
2041
|
+
"duration",
|
|
2042
|
+
"completed",
|
|
2043
|
+
"sessionCreatedAt"
|
|
2044
|
+
];
|
|
2045
|
+
var EVENT_HEADER = [
|
|
2046
|
+
"eventId",
|
|
2047
|
+
"grain",
|
|
2048
|
+
"outputType",
|
|
2049
|
+
"timestamp",
|
|
2050
|
+
"pageId",
|
|
2051
|
+
"effortSlug",
|
|
2052
|
+
"eventSegmentId",
|
|
2053
|
+
"eventSegmentVersion",
|
|
2054
|
+
"eventBlockContentId",
|
|
2055
|
+
"eventOrigin",
|
|
2056
|
+
"timeSpan",
|
|
2057
|
+
"sourceBlockKey",
|
|
2058
|
+
"sourceStatementId",
|
|
2059
|
+
"line",
|
|
2060
|
+
"text",
|
|
2061
|
+
"dialect",
|
|
2062
|
+
"stackLevel",
|
|
2063
|
+
"completionReason",
|
|
2064
|
+
"representationKind",
|
|
2065
|
+
"metricType",
|
|
2066
|
+
"metricValue",
|
|
2067
|
+
"metricUnit",
|
|
2068
|
+
"metricImage",
|
|
2069
|
+
"metricOrigin",
|
|
2070
|
+
"metricMetadata",
|
|
2071
|
+
"metricTemporal",
|
|
2072
|
+
// Verbatim JSON backstops: make the machine round-trip lossless across
|
|
2073
|
+
// schema fields that have no dedicated spreadsheet column. Explicit cells
|
|
2074
|
+
// above still win on import, so hand-edited values are honored.
|
|
2075
|
+
"metricJson",
|
|
2076
|
+
"eventExtraJson"
|
|
2077
|
+
];
|
|
2078
|
+
var SESSION_CSV_HEADER = [...SESSION_HEADER, ...EVENT_HEADER];
|
|
529
2079
|
function escapeCsvField(field) {
|
|
2080
|
+
if (field === void 0 || field === null) return "";
|
|
2081
|
+
const str = typeof field === "object" ? JSON.stringify(field) : String(field);
|
|
2082
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
|
|
2083
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
2084
|
+
}
|
|
2085
|
+
return str;
|
|
2086
|
+
}
|
|
2087
|
+
var metricValueToCell = (value) => {
|
|
2088
|
+
if (value === void 0 || value === null) return "";
|
|
2089
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
2090
|
+
return String(value);
|
|
2091
|
+
};
|
|
2092
|
+
var metricValueFromCell = (cell) => {
|
|
2093
|
+
if (cell === "") return void 0;
|
|
2094
|
+
if (/^-?\d+(\.\d+)?$/.test(cell)) return Number(cell);
|
|
2095
|
+
if (cell === "true" || cell === "false") return cell === "true";
|
|
2096
|
+
if (cell.startsWith("{") || cell.startsWith("[")) {
|
|
2097
|
+
try {
|
|
2098
|
+
return JSON.parse(cell);
|
|
2099
|
+
} catch {
|
|
2100
|
+
return cell;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
return cell;
|
|
2104
|
+
};
|
|
2105
|
+
function sessionCells(session) {
|
|
2106
|
+
return [
|
|
2107
|
+
session.id,
|
|
2108
|
+
session.noteId,
|
|
2109
|
+
session.segmentId,
|
|
2110
|
+
session.segmentVersion,
|
|
2111
|
+
session.blockId,
|
|
2112
|
+
session.blockContentId,
|
|
2113
|
+
session.origin,
|
|
2114
|
+
session.status,
|
|
2115
|
+
session.startTime,
|
|
2116
|
+
session.endTime,
|
|
2117
|
+
session.duration,
|
|
2118
|
+
session.completed,
|
|
2119
|
+
session.createdAt
|
|
2120
|
+
].map(escapeCsvField);
|
|
2121
|
+
}
|
|
2122
|
+
function metricCells(event, metric, index) {
|
|
2123
|
+
return [
|
|
2124
|
+
event.id,
|
|
2125
|
+
event.grain,
|
|
2126
|
+
event.outputType,
|
|
2127
|
+
event.timestamp,
|
|
2128
|
+
event.pageId,
|
|
2129
|
+
event.effortSlug,
|
|
2130
|
+
event.segmentId,
|
|
2131
|
+
event.segmentVersion,
|
|
2132
|
+
event.blockContentId,
|
|
2133
|
+
event.origin,
|
|
2134
|
+
event.timeSpan,
|
|
2135
|
+
event.sourceBlockKey,
|
|
2136
|
+
event.sourceStatementId,
|
|
2137
|
+
event.line,
|
|
2138
|
+
event.text,
|
|
2139
|
+
event.dialect,
|
|
2140
|
+
event.stackLevel,
|
|
2141
|
+
event.completionReason,
|
|
2142
|
+
event.representationKind,
|
|
2143
|
+
metric.type,
|
|
2144
|
+
metricValueToCell(metric.value),
|
|
2145
|
+
metric.unit,
|
|
2146
|
+
metric.image,
|
|
2147
|
+
metric.origin,
|
|
2148
|
+
metric.metadata === void 0 ? "" : JSON.stringify(metric.metadata),
|
|
2149
|
+
event.metricTemporal?.[index] === void 0 ? "" : JSON.stringify(event.metricTemporal[index]),
|
|
2150
|
+
JSON.stringify(metric)
|
|
2151
|
+
].map(escapeCsvField);
|
|
2152
|
+
}
|
|
2153
|
+
function eventExtraCells(event) {
|
|
2154
|
+
const { metrics: _metrics, ...rest } = event;
|
|
2155
|
+
return escapeCsvField(JSON.stringify(rest));
|
|
2156
|
+
}
|
|
2157
|
+
function sessionToCsv(session, events) {
|
|
2158
|
+
const lines = [SESSION_CSV_HEADER.join(",")];
|
|
2159
|
+
lines.push([...sessionCells(session), ...EVENT_HEADER.map(() => "")].join(","));
|
|
2160
|
+
const ordered = [...events].sort((a, b) => a.id.localeCompare(b.id));
|
|
2161
|
+
for (const event of ordered) {
|
|
2162
|
+
const extra = eventExtraCells(event);
|
|
2163
|
+
const metrics = event.metrics ?? [];
|
|
2164
|
+
if (metrics.length === 0) {
|
|
2165
|
+
lines.push([...sessionCells(session), ...metricCells(event, { type: "" }, 0), extra].join(","));
|
|
2166
|
+
continue;
|
|
2167
|
+
}
|
|
2168
|
+
for (let i = 0; i < metrics.length; i++) {
|
|
2169
|
+
lines.push([...sessionCells(session), ...metricCells(event, metrics[i], i), extra].join(","));
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return lines.join("\n") + "\n";
|
|
2173
|
+
}
|
|
2174
|
+
function parseCsvLine(line) {
|
|
2175
|
+
const cells = [];
|
|
2176
|
+
let current = "";
|
|
2177
|
+
let quoted = false;
|
|
2178
|
+
for (let i = 0; i < line.length; i++) {
|
|
2179
|
+
const ch = line[i];
|
|
2180
|
+
if (quoted) {
|
|
2181
|
+
if (ch === '"') {
|
|
2182
|
+
if (line[i + 1] === '"') {
|
|
2183
|
+
current += '"';
|
|
2184
|
+
i += 1;
|
|
2185
|
+
} else {
|
|
2186
|
+
quoted = false;
|
|
2187
|
+
}
|
|
2188
|
+
} else {
|
|
2189
|
+
current += ch;
|
|
2190
|
+
}
|
|
2191
|
+
} else if (ch === '"' && current === "") {
|
|
2192
|
+
quoted = true;
|
|
2193
|
+
} else if (ch === ",") {
|
|
2194
|
+
cells.push(current);
|
|
2195
|
+
current = "";
|
|
2196
|
+
} else {
|
|
2197
|
+
current += ch;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
cells.push(current);
|
|
2201
|
+
return cells;
|
|
2202
|
+
}
|
|
2203
|
+
function splitCsvRecords(csv) {
|
|
2204
|
+
const records = [];
|
|
2205
|
+
let current = "";
|
|
2206
|
+
let quoted = false;
|
|
2207
|
+
for (let i = 0; i < csv.length; i++) {
|
|
2208
|
+
const ch = csv[i];
|
|
2209
|
+
if (ch === '"') {
|
|
2210
|
+
if (quoted && csv[i + 1] === '"') {
|
|
2211
|
+
current += '""';
|
|
2212
|
+
i += 1;
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
quoted = !quoted;
|
|
2216
|
+
current += ch;
|
|
2217
|
+
} else if (!quoted && (ch === "\n" || ch === "\r")) {
|
|
2218
|
+
if (ch === "\r" && csv[i + 1] === "\n") i += 1;
|
|
2219
|
+
if (current.length > 0) records.push(current);
|
|
2220
|
+
current = "";
|
|
2221
|
+
} else {
|
|
2222
|
+
current += ch;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
if (current.length > 0) records.push(current);
|
|
2226
|
+
return records;
|
|
2227
|
+
}
|
|
2228
|
+
var num = (cell) => cell === "" ? void 0 : Number(cell);
|
|
2229
|
+
var nonempty = (cell) => cell === "" ? void 0 : cell;
|
|
2230
|
+
function jsonCell(cell) {
|
|
2231
|
+
if (!cell) return void 0;
|
|
2232
|
+
try {
|
|
2233
|
+
return JSON.parse(cell);
|
|
2234
|
+
} catch {
|
|
2235
|
+
return void 0;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
function csvToSessionRows(csv) {
|
|
2239
|
+
const lines = splitCsvRecords(csv);
|
|
2240
|
+
if (lines.length < 2) throw new Error("[wod import] Session CSV has no data rows");
|
|
2241
|
+
const header = parseCsvLine(lines[0]);
|
|
2242
|
+
const col = (name) => {
|
|
2243
|
+
const idx = header.indexOf(name);
|
|
2244
|
+
if (idx < 0) throw new Error(`[wod import] Session CSV missing column "${name}"`);
|
|
2245
|
+
return idx;
|
|
2246
|
+
};
|
|
2247
|
+
const at = (cells, name) => cells[col(name)] ?? "";
|
|
2248
|
+
const eventRows = /* @__PURE__ */ new Map();
|
|
2249
|
+
let session;
|
|
2250
|
+
const ensureSession = (cells) => {
|
|
2251
|
+
if (session) return session;
|
|
2252
|
+
session = {
|
|
2253
|
+
id: at(cells, "sessionId"),
|
|
2254
|
+
noteId: at(cells, "noteId"),
|
|
2255
|
+
segmentId: at(cells, "segmentId") || void 0,
|
|
2256
|
+
segmentVersion: num(at(cells, "segmentVersion")),
|
|
2257
|
+
blockId: at(cells, "blockId") || void 0,
|
|
2258
|
+
blockContentId: at(cells, "blockContentId") || void 0,
|
|
2259
|
+
origin: at(cells, "origin") || void 0,
|
|
2260
|
+
status: at(cells, "status") || void 0,
|
|
2261
|
+
startTime: Number(at(cells, "startTime") || 0),
|
|
2262
|
+
endTime: Number(at(cells, "endTime") || 0),
|
|
2263
|
+
duration: Number(at(cells, "duration") || 0),
|
|
2264
|
+
completed: at(cells, "completed") === "true",
|
|
2265
|
+
createdAt: Number(at(cells, "sessionCreatedAt") || 0)
|
|
2266
|
+
};
|
|
2267
|
+
return session;
|
|
2268
|
+
};
|
|
2269
|
+
for (const line of lines.slice(1)) {
|
|
2270
|
+
const cells = parseCsvLine(line);
|
|
2271
|
+
if (cells.length === 1 && cells[0] === "") continue;
|
|
2272
|
+
const currentSession = ensureSession(cells);
|
|
2273
|
+
const eventId = at(cells, "eventId");
|
|
2274
|
+
if (!eventId) continue;
|
|
2275
|
+
let event = eventRows.get(eventId);
|
|
2276
|
+
if (!event) {
|
|
2277
|
+
const extra = jsonCell(at(cells, "eventExtraJson")) ?? {};
|
|
2278
|
+
event = {
|
|
2279
|
+
...extra,
|
|
2280
|
+
id: eventId,
|
|
2281
|
+
resultId: currentSession.id,
|
|
2282
|
+
noteId: nonempty(at(cells, "noteId")) ?? extra.noteId ?? currentSession.noteId,
|
|
2283
|
+
blockContentId: nonempty(at(cells, "eventBlockContentId")) ?? extra.blockContentId,
|
|
2284
|
+
pageId: nonempty(at(cells, "pageId")) ?? extra.pageId,
|
|
2285
|
+
origin: nonempty(at(cells, "eventOrigin")) ?? extra.origin,
|
|
2286
|
+
timestamp: Number(at(cells, "timestamp") || extra.timestamp || 0),
|
|
2287
|
+
grain: nonempty(at(cells, "grain")) ?? extra.grain ?? "event",
|
|
2288
|
+
effortSlug: nonempty(at(cells, "effortSlug")) ?? extra.effortSlug,
|
|
2289
|
+
outputType: nonempty(at(cells, "outputType")) ?? extra.outputType ?? "event",
|
|
2290
|
+
segmentId: nonempty(at(cells, "eventSegmentId")) ?? extra.segmentId,
|
|
2291
|
+
segmentVersion: num(at(cells, "eventSegmentVersion")) ?? extra.segmentVersion,
|
|
2292
|
+
timeSpan: jsonCell(at(cells, "timeSpan")) ?? extra.timeSpan,
|
|
2293
|
+
sourceBlockKey: nonempty(at(cells, "sourceBlockKey")) ?? extra.sourceBlockKey,
|
|
2294
|
+
sourceStatementId: num(at(cells, "sourceStatementId")) ?? extra.sourceStatementId,
|
|
2295
|
+
line: num(at(cells, "line")) ?? extra.line,
|
|
2296
|
+
text: nonempty(at(cells, "text")) ?? extra.text,
|
|
2297
|
+
dialect: nonempty(at(cells, "dialect")) ?? extra.dialect,
|
|
2298
|
+
stackLevel: num(at(cells, "stackLevel")) ?? extra.stackLevel,
|
|
2299
|
+
completionReason: nonempty(at(cells, "completionReason")) ?? extra.completionReason,
|
|
2300
|
+
representationKind: nonempty(at(cells, "representationKind")) ?? extra.representationKind,
|
|
2301
|
+
metrics: [],
|
|
2302
|
+
// Rebuilt per-row from the metricTemporal column below — never
|
|
2303
|
+
// inherited from the backstop, or rows would double-collect.
|
|
2304
|
+
metricTemporal: void 0
|
|
2305
|
+
};
|
|
2306
|
+
eventRows.set(eventId, event);
|
|
2307
|
+
}
|
|
2308
|
+
const metricType = at(cells, "metricType");
|
|
2309
|
+
if (!metricType) continue;
|
|
2310
|
+
const baseMetric = jsonCell(at(cells, "metricJson")) ?? {};
|
|
2311
|
+
const metric = {
|
|
2312
|
+
...baseMetric,
|
|
2313
|
+
type: metricType,
|
|
2314
|
+
...at(cells, "metricValue") !== "" ? { value: metricValueFromCell(at(cells, "metricValue")) } : {},
|
|
2315
|
+
...nonempty(at(cells, "metricUnit")) ? { unit: at(cells, "metricUnit") } : {},
|
|
2316
|
+
...nonempty(at(cells, "metricImage")) ? { image: at(cells, "metricImage") } : {},
|
|
2317
|
+
...nonempty(at(cells, "metricOrigin")) ? { origin: at(cells, "metricOrigin") } : {},
|
|
2318
|
+
...at(cells, "metricMetadata") !== "" ? { metadata: jsonCell(at(cells, "metricMetadata")) } : {}
|
|
2319
|
+
};
|
|
2320
|
+
event.metrics.push(metric);
|
|
2321
|
+
const temporal = jsonCell(at(cells, "metricTemporal"));
|
|
2322
|
+
if (temporal !== void 0) {
|
|
2323
|
+
(event.metricTemporal ??= []).push(temporal);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (!session) throw new Error("[wod import] Session CSV did not contain session columns");
|
|
2327
|
+
return { session, events: [...eventRows.values()] };
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// src/cli/seed.ts
|
|
2331
|
+
var SEED_NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341";
|
|
2332
|
+
var SEED_SCHEMA = 7;
|
|
2333
|
+
var CANVAS_CHUNK_ID = "canvas";
|
|
2334
|
+
var DASHBOARDS_CHUNK_ID = "dashboards";
|
|
2335
|
+
var EFFORTS_CHUNK_ID = "efforts";
|
|
2336
|
+
var TEMPLATE_CHUNK_ID = "template";
|
|
2337
|
+
var scalarOf2 = (value) => typeof value === "string" ? value : void 0;
|
|
2338
|
+
function seedNoteId(path) {
|
|
2339
|
+
return formatV5Digest(nodeCrypto().createHash("sha1").update(`${SEED_NAMESPACE}:${path}`).digest());
|
|
2340
|
+
}
|
|
2341
|
+
function titleFromPath(path) {
|
|
2342
|
+
const stem = path.split("/").pop() ?? path;
|
|
2343
|
+
return stem.replace(/\.md$/, "").replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2344
|
+
}
|
|
2345
|
+
function catalogForChunkId(chunkId) {
|
|
2346
|
+
const match = /^(?:collection|feed)\.(.+)$/.exec(chunkId);
|
|
2347
|
+
const value = match?.[1];
|
|
2348
|
+
return value && value !== "_root" ? value : void 0;
|
|
2349
|
+
}
|
|
2350
|
+
function canvasRouteSlug(content) {
|
|
2351
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
2352
|
+
if (fm?.[1] && !/^template:\s*canvas\s*$/m.test(fm[1])) return null;
|
|
2353
|
+
const route = fm?.[1]?.match(/^route:\s*(\S+)\s*$/m)?.[1];
|
|
2354
|
+
if (!route) return null;
|
|
2355
|
+
return route.replace(/^\//, "") || null;
|
|
2356
|
+
}
|
|
2357
|
+
function seedNoteSourceId(path, content, chunkId) {
|
|
2358
|
+
const parts = path.split("/");
|
|
2359
|
+
const stem = (parts[parts.length - 1] ?? "").replace(/\.md$/i, "");
|
|
2360
|
+
const under = parts.slice(2).map((p) => p.replace(/\.md$/i, "")).join("/");
|
|
2361
|
+
if (chunkId === CANVAS_CHUNK_ID) {
|
|
2362
|
+
const route = canvasRouteSlug(content);
|
|
2363
|
+
return route ? `guides:${route}` : `guides:${under}`;
|
|
2364
|
+
}
|
|
2365
|
+
if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
|
|
2366
|
+
if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
|
|
2367
|
+
if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
|
|
2368
|
+
if (chunkId.startsWith("collection.")) {
|
|
2369
|
+
const catalog = catalogForChunkId(chunkId);
|
|
2370
|
+
if (!catalog) return "collection:_root";
|
|
2371
|
+
return stem.toLowerCase() === "readme" ? `collection:${catalog}` : `collection:${catalog}/${stem}`;
|
|
2372
|
+
}
|
|
2373
|
+
if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
|
|
2374
|
+
return void 0;
|
|
2375
|
+
}
|
|
2376
|
+
function buildNoteRecord(row, chunkId, seedVersion) {
|
|
2377
|
+
const id = seedNoteId(row.path);
|
|
2378
|
+
const createdAt = seedVersion;
|
|
2379
|
+
const meta = readFrontmatterMeta(row.content);
|
|
2380
|
+
const tags = extractTypedTags(row.content);
|
|
2381
|
+
let date;
|
|
2382
|
+
let page;
|
|
2383
|
+
let pageNote;
|
|
2384
|
+
const rawDate = scalarOf2(meta["date"]);
|
|
2385
|
+
if (rawDate && /^\d{4}-\d{2}-\d{2}$/.test(rawDate)) {
|
|
2386
|
+
date = (/* @__PURE__ */ new Date(`${rawDate}T12:00:00Z`)).getTime();
|
|
2387
|
+
}
|
|
2388
|
+
const slug = scalarOf2(meta["slug"]);
|
|
2389
|
+
if (slug) {
|
|
2390
|
+
const pageId = seedNoteId(`page:${slug}`);
|
|
2391
|
+
page = { id: pageId, slug, title: titleFromPath(row.path), createdAt };
|
|
2392
|
+
pageNote = { id: seedNoteId(`pagenote:${slug}:${row.path}`), pageId, noteId: id, createdAt };
|
|
2393
|
+
}
|
|
2394
|
+
const sourceId = seedNoteSourceId(row.path, row.content, chunkId);
|
|
2395
|
+
if (!sourceId) {
|
|
2396
|
+
throw new Error(`[wod seed] chunk "${chunkId}" row "${row.path}" has no source family \u2014 refusing to import it as journal`);
|
|
2397
|
+
}
|
|
2398
|
+
const metaTitle = scalarOf2(meta["title"]);
|
|
2399
|
+
return {
|
|
2400
|
+
note: {
|
|
2401
|
+
id,
|
|
2402
|
+
title: metaTitle?.trim() || titleFromPath(row.path),
|
|
2403
|
+
date,
|
|
2404
|
+
createdAt,
|
|
2405
|
+
type: chunkId === DASHBOARDS_CHUNK_ID ? "dashboard" : "note",
|
|
2406
|
+
sourceId,
|
|
2407
|
+
catalog: catalogForChunkId(chunkId),
|
|
2408
|
+
seedOrigin: "seed",
|
|
2409
|
+
seedVersion,
|
|
2410
|
+
seedChunkId: chunkId,
|
|
2411
|
+
sourcePath: row.path
|
|
2412
|
+
},
|
|
2413
|
+
segment: {
|
|
2414
|
+
id: `seed:${id}`,
|
|
2415
|
+
version: 1,
|
|
2416
|
+
noteId: id,
|
|
2417
|
+
position: 0,
|
|
2418
|
+
dataType: "markdown",
|
|
2419
|
+
data: null,
|
|
2420
|
+
rawContent: row.content,
|
|
2421
|
+
createdAt
|
|
2422
|
+
},
|
|
2423
|
+
tags,
|
|
2424
|
+
page,
|
|
2425
|
+
pageNote,
|
|
2426
|
+
rawContent: row.content,
|
|
2427
|
+
sourcePath: row.path
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
async function metaRecord(sql, db, sourceId) {
|
|
2431
|
+
const rows = await sql.listRows(db, sql.storeDefinition("meta"));
|
|
2432
|
+
const existing = rows.find((r) => r.key === `seed:${sourceId}`);
|
|
2433
|
+
return existing ?? {
|
|
2434
|
+
key: `seed:${sourceId}`,
|
|
2435
|
+
schema: SEED_SCHEMA,
|
|
2436
|
+
seedVersion: 0,
|
|
2437
|
+
builtAt: "",
|
|
2438
|
+
importedAt: 0,
|
|
2439
|
+
chunks: {},
|
|
2440
|
+
claim: null
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
async function applyOps(db, ops) {
|
|
2444
|
+
if (ops.length === 0) return;
|
|
2445
|
+
const sql = await loadSql();
|
|
2446
|
+
await db.db.transaction(async (tx) => {
|
|
2447
|
+
await sql.refreshDomain(tx, ops);
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
async function importSeedDir(db, dir, sourceId) {
|
|
2451
|
+
const sql = await loadSql();
|
|
2452
|
+
const fs = nodeFs();
|
|
2453
|
+
const source = sourceId || nodePath().basename(dir);
|
|
2454
|
+
const manifest = JSON.parse(fs.readFileSync(nodePath().resolve(dir, "manifest.json"), "utf-8"));
|
|
2455
|
+
const prior = await metaRecord(sql, db.db, source);
|
|
2456
|
+
const notesById = new Map(
|
|
2457
|
+
(await sql.listRows(db.db, sql.storeDefinition("notes"))).map((row) => [row.id, row])
|
|
2458
|
+
);
|
|
2459
|
+
const tagRowByLabel = new Map(
|
|
2460
|
+
(await sql.listRows(db.db, sql.storeDefinition("tags"))).map((t) => [t.label, t])
|
|
2461
|
+
);
|
|
2462
|
+
const summary = {
|
|
2463
|
+
sourceId: source,
|
|
2464
|
+
version: manifest.version,
|
|
2465
|
+
notes: 0,
|
|
2466
|
+
efforts: 0,
|
|
2467
|
+
blocks: 0,
|
|
2468
|
+
blockEfforts: 0,
|
|
2469
|
+
deleted: 0,
|
|
2470
|
+
skippedUserOwned: 0
|
|
2471
|
+
};
|
|
2472
|
+
const nextMeta = {
|
|
2473
|
+
...prior,
|
|
2474
|
+
schema: SEED_SCHEMA,
|
|
2475
|
+
seedVersion: manifest.version,
|
|
2476
|
+
builtAt: manifest.builtAt,
|
|
2477
|
+
importedAt: Date.now(),
|
|
2478
|
+
chunks: {}
|
|
2479
|
+
};
|
|
2480
|
+
const ordered = [
|
|
2481
|
+
...manifest.chunks.filter((c) => (c.kind ?? "notes") === "notes"),
|
|
2482
|
+
...manifest.chunks.filter((c) => c.kind === "block-index"),
|
|
2483
|
+
...manifest.chunks.filter((c) => c.kind === "block-efforts")
|
|
2484
|
+
];
|
|
2485
|
+
const bySourcePath = /* @__PURE__ */ new Map();
|
|
2486
|
+
const noteIdsByChunk = {};
|
|
2487
|
+
const blockIdsByChunk = {};
|
|
2488
|
+
for (const chunk of ordered) {
|
|
2489
|
+
const payload = JSON.parse(fs.readFileSync(nodePath().resolve(dir, chunk.path), "utf-8"));
|
|
2490
|
+
const ops = [];
|
|
2491
|
+
if ((chunk.kind ?? "notes") === "notes") {
|
|
2492
|
+
const rows2 = payload;
|
|
2493
|
+
const built = rows2.map((row) => buildNoteRecord(row, chunk.id, manifest.version));
|
|
2494
|
+
const noteIds = [];
|
|
2495
|
+
for (const record of built) {
|
|
2496
|
+
bySourcePath.set(record.sourcePath, record.note.id);
|
|
2497
|
+
const existing = notesById.get(record.note.id);
|
|
2498
|
+
if (existing && existing.seedOrigin !== "seed") {
|
|
2499
|
+
summary.skippedUserOwned += 1;
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
noteIds.push(record.note.id);
|
|
2503
|
+
ops.push({ op: "put", store: "notes", value: record.note });
|
|
2504
|
+
ops.push({ op: "put", store: "segments", value: record.segment });
|
|
2505
|
+
if (record.page) ops.push({ op: "put", store: "page", value: record.page });
|
|
2506
|
+
if (record.pageNote) ops.push({ op: "put", store: "page_notes", value: record.pageNote });
|
|
2507
|
+
for (const item of record.tags) {
|
|
2508
|
+
const label = item.label;
|
|
2509
|
+
let tag = tagRowByLabel.get(label);
|
|
2510
|
+
if (!tag) {
|
|
2511
|
+
tag = { id: randomId(), label, type: item.type, createdAt: Date.now() };
|
|
2512
|
+
tagRowByLabel.set(label, tag);
|
|
2513
|
+
ops.push({ op: "put", store: "tags", value: tag });
|
|
2514
|
+
} else if (item.type && !tag.type) {
|
|
2515
|
+
tag = { ...tag, type: item.type };
|
|
2516
|
+
tagRowByLabel.set(label, tag);
|
|
2517
|
+
ops.push({ op: "put", store: "tags", value: tag });
|
|
2518
|
+
}
|
|
2519
|
+
const tagId = tag.id;
|
|
2520
|
+
ops.push({
|
|
2521
|
+
op: "put",
|
|
2522
|
+
store: "note_tags",
|
|
2523
|
+
// Deterministic junction id — re-seeds must upsert the same row
|
|
2524
|
+
// (note_tags has a unique [noteId, tagId] index).
|
|
2525
|
+
value: { id: uuid5(CLI_UUID_NAMESPACE, `note-tag:${record.note.id}:${tagId}`), noteId: record.note.id, tagId }
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
if (chunk.id === EFFORTS_CHUNK_ID) {
|
|
2529
|
+
const effort = markdownToEffort(record.rawContent);
|
|
2530
|
+
if (effort) {
|
|
2531
|
+
ops.push({ op: "put", store: "efforts", value: { ...effort, registrySource: "bundled" } });
|
|
2532
|
+
summary.efforts += 1;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
const priorIds = prior.chunks[chunk.id]?.noteIds ?? [];
|
|
2537
|
+
const goneIds = priorIds.filter((id) => !noteIds.includes(id) && notesById.get(id)?.seedOrigin === "seed");
|
|
2538
|
+
for (const id of goneIds) {
|
|
2539
|
+
ops.push(...await noteCascadeOps(sql, db, id));
|
|
2540
|
+
summary.deleted += 1;
|
|
2541
|
+
}
|
|
2542
|
+
noteIdsByChunk[chunk.id] = rows2.map((row) => seedNoteId(row.path));
|
|
2543
|
+
nextMeta.chunks[chunk.id] = { sha256: chunk.sha256, noteIds: noteIdsByChunk[chunk.id] };
|
|
2544
|
+
summary.notes += noteIds.length;
|
|
2545
|
+
await applyOps(db, ops);
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
2548
|
+
const rows = payload.filter((row) => row.isStatic === true);
|
|
2549
|
+
const rekeyed = rows.map((row) => {
|
|
2550
|
+
const noteId = row.sourcePath ? bySourcePath.get(row.sourcePath) : void 0;
|
|
2551
|
+
if (!noteId) {
|
|
2552
|
+
throw new Error(
|
|
2553
|
+
`[wod seed] static row ${row.id}: sourcePath "${row.sourcePath ?? ""}" has no imported note \u2014 chunk aborted`
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
return chunk.kind === "block-efforts" ? { ...row, noteId } : { ...row, noteId, routeId: row.noteId };
|
|
2557
|
+
});
|
|
2558
|
+
const store = chunk.kind === "block-index" ? "block_index" : "block_efforts";
|
|
2559
|
+
for (const row of rekeyed) ops.push({ op: "put", store, value: row });
|
|
2560
|
+
const priorBlockIds = prior.chunks[chunk.id]?.blockIds ?? [];
|
|
2561
|
+
const writtenIds = new Set(rekeyed.map((row) => row.id));
|
|
2562
|
+
const goneBlocks = priorBlockIds.filter((id) => !writtenIds.has(id));
|
|
2563
|
+
for (const id of goneBlocks) ops.push({ op: "delete", store, key: id });
|
|
2564
|
+
summary.deleted += goneBlocks.length;
|
|
2565
|
+
blockIdsByChunk[chunk.id] = payload.map((row) => row.id);
|
|
2566
|
+
nextMeta.chunks[chunk.id] = { sha256: chunk.sha256, blockIds: blockIdsByChunk[chunk.id] };
|
|
2567
|
+
if (chunk.kind === "block-index") summary.blocks += rekeyed.length;
|
|
2568
|
+
else summary.blockEfforts += rekeyed.length;
|
|
2569
|
+
await applyOps(db, ops);
|
|
2570
|
+
}
|
|
2571
|
+
const manifestIds = new Set(manifest.chunks.map((c) => c.id));
|
|
2572
|
+
for (const [chunkId, checkpoint] of Object.entries(prior.chunks)) {
|
|
2573
|
+
if (manifestIds.has(chunkId)) continue;
|
|
2574
|
+
const sweep = [];
|
|
2575
|
+
for (const id of checkpoint.noteIds ?? []) {
|
|
2576
|
+
if (notesById.get(id)?.seedOrigin === "seed") {
|
|
2577
|
+
sweep.push(...await noteCascadeOps(sql, db, id));
|
|
2578
|
+
summary.deleted += 1;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
for (const id of checkpoint.blockIds ?? []) {
|
|
2582
|
+
sweep.push({ op: "delete", store: "block_index", key: id });
|
|
2583
|
+
summary.deleted += 1;
|
|
2584
|
+
}
|
|
2585
|
+
await applyOps(db, sweep);
|
|
2586
|
+
}
|
|
2587
|
+
await applyOps(db, [{ op: "put", store: "meta", value: nextMeta }]);
|
|
2588
|
+
return summary;
|
|
2589
|
+
}
|
|
2590
|
+
async function noteCascadeOps(sql, db, noteId) {
|
|
2591
|
+
const ops = [{ op: "delete", store: "notes", key: noteId }];
|
|
2592
|
+
for (const store of ["segments", "block_index", "block_efforts", "note_tags"]) {
|
|
2593
|
+
const rows = await sql.listRows(db.db, sql.storeDefinition(store), { only: noteId }, void 0, "by-note");
|
|
2594
|
+
for (const row of rows) {
|
|
2595
|
+
ops.push({ op: "delete", store, key: store === "segments" ? [row.id, row.version ?? 1] : row.id });
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
return ops;
|
|
2599
|
+
}
|
|
2600
|
+
function defaultSeedDirs(cwd) {
|
|
2601
|
+
const base = nodePath().resolve(cwd, "apps", "playground", "public", "seed");
|
|
2602
|
+
return ["core", "community"].map((id) => nodePath().resolve(base, id)).filter((dir) => nodeFs().existsSync(nodePath().resolve(dir, "manifest.json")));
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// src/cli/formatters.ts
|
|
2606
|
+
function escapeCsvField2(field) {
|
|
530
2607
|
if (field === void 0 || field === null) return "";
|
|
531
2608
|
const str = String(field);
|
|
532
2609
|
if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
|
|
@@ -572,9 +2649,9 @@ function formatParseOutput(ir, format, pretty = true) {
|
|
|
572
2649
|
n.type,
|
|
573
2650
|
n.from,
|
|
574
2651
|
n.to,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
2652
|
+
escapeCsvField2(n.raw),
|
|
2653
|
+
escapeCsvField2(n.metrics.map(formatMetric).join("; ")),
|
|
2654
|
+
escapeCsvField2(n.hints?.join("; ") ?? "")
|
|
578
2655
|
].join(",")
|
|
579
2656
|
);
|
|
580
2657
|
return [header, ...rows].join("\n");
|
|
@@ -615,11 +2692,11 @@ function formatExecutionOutput(ir, format, pretty = true) {
|
|
|
615
2692
|
s.outputType,
|
|
616
2693
|
s.timeSpan?.started ?? s.timestamp ?? 0,
|
|
617
2694
|
s.timeSpan?.ended ?? "",
|
|
618
|
-
|
|
2695
|
+
escapeCsvField2(s.sourceBlockKey),
|
|
619
2696
|
s.stackLevel,
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
2697
|
+
escapeCsvField2(s.completionReason ?? ""),
|
|
2698
|
+
escapeCsvField2(s.metrics.map(formatMetric).join("; ")),
|
|
2699
|
+
escapeCsvField2(s.hints?.join("; ") ?? "")
|
|
623
2700
|
].join(",")
|
|
624
2701
|
);
|
|
625
2702
|
return [header, ...rows].join("\n");
|
|
@@ -668,7 +2745,7 @@ function formatQueryOutput(ir, format, pretty = true) {
|
|
|
668
2745
|
run.timestamp,
|
|
669
2746
|
event.outputType,
|
|
670
2747
|
event.id,
|
|
671
|
-
|
|
2748
|
+
escapeCsvField2(event.metrics.map(formatMetric).join("; "))
|
|
672
2749
|
].join(",")
|
|
673
2750
|
);
|
|
674
2751
|
}
|
|
@@ -700,13 +2777,13 @@ function formatQueryOutput(ir, format, pretty = true) {
|
|
|
700
2777
|
const header = "target,id,title_or_label,sourceId";
|
|
701
2778
|
const rows = [];
|
|
702
2779
|
for (const n of data2.notes) {
|
|
703
|
-
rows.push(["note", n.id,
|
|
2780
|
+
rows.push(["note", n.id, escapeCsvField2(n.title), escapeCsvField2(n.sourceId ?? "")].join(","));
|
|
704
2781
|
}
|
|
705
2782
|
for (const b of data2.blocks) {
|
|
706
|
-
rows.push(["block", b.id,
|
|
2783
|
+
rows.push(["block", b.id, escapeCsvField2(b.noteTitle), escapeCsvField2(b.sourceId ?? "")].join(","));
|
|
707
2784
|
}
|
|
708
2785
|
for (const e of data2.efforts ?? []) {
|
|
709
|
-
rows.push(["effort", e.id,
|
|
2786
|
+
rows.push(["effort", e.id, escapeCsvField2(e.label), escapeCsvField2(e.slug)].join(","));
|
|
710
2787
|
}
|
|
711
2788
|
return [header, ...rows].join("\n");
|
|
712
2789
|
}
|
|
@@ -728,11 +2805,48 @@ function formatQueryOutput(ir, format, pretty = true) {
|
|
|
728
2805
|
}
|
|
729
2806
|
return lines2.join("\n");
|
|
730
2807
|
}
|
|
2808
|
+
if (kind === "pipeline-result") {
|
|
2809
|
+
const data2 = ir.data;
|
|
2810
|
+
const sink = data2.chart ? `:${data2.chart.head}` : "data";
|
|
2811
|
+
if (format === "csv") {
|
|
2812
|
+
const header = "series,timestamp,date,value,unit";
|
|
2813
|
+
const rows = [];
|
|
2814
|
+
for (const s of data2.series ?? []) {
|
|
2815
|
+
for (const pt of s.points) {
|
|
2816
|
+
rows.push(
|
|
2817
|
+
[
|
|
2818
|
+
escapeCsvField2(s.label || s.key),
|
|
2819
|
+
pt.ts,
|
|
2820
|
+
formatDate(pt.ts),
|
|
2821
|
+
pt.value,
|
|
2822
|
+
escapeCsvField2(s.unit ?? data2.unit ?? "")
|
|
2823
|
+
].join(",")
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
return [header, ...rows].join("\n");
|
|
2828
|
+
}
|
|
2829
|
+
const lines2 = [];
|
|
2830
|
+
if (data2.error) lines2.push(`Pipeline Error: ${data2.error}`);
|
|
2831
|
+
lines2.push(`Pipeline Result (\u2192 ${sink}):`);
|
|
2832
|
+
lines2.push("\u2500".repeat(80));
|
|
2833
|
+
if (data2.table) {
|
|
2834
|
+
lines2.push(`Table: ${data2.table.rows.length} of ${data2.table.totalCount} rows`);
|
|
2835
|
+
}
|
|
2836
|
+
for (const s of data2.series ?? []) {
|
|
2837
|
+
lines2.push(`${s.label || s.key}: ${s.points.length} points${s.unit ? ` (${s.unit})` : ""}`);
|
|
2838
|
+
}
|
|
2839
|
+
if (data2.notes?.length) lines2.push(`notes: ${data2.notes.length}`);
|
|
2840
|
+
if (data2.blocks?.length) lines2.push(`blocks: ${data2.blocks.length}`);
|
|
2841
|
+
if (data2.efforts?.length) lines2.push(`efforts: ${data2.efforts.length}`);
|
|
2842
|
+
if (data2.runs?.length) lines2.push(`runs: ${data2.runs.length}`);
|
|
2843
|
+
return lines2.join("\n");
|
|
2844
|
+
}
|
|
731
2845
|
const data = ir.data;
|
|
732
2846
|
if (data.scalar !== void 0 && data.series.length === 0) {
|
|
733
2847
|
if (format === "csv") {
|
|
734
2848
|
return `metric,scalar,unit
|
|
735
|
-
${
|
|
2849
|
+
${escapeCsvField2(data.parsed.metric)},${data.scalar},${escapeCsvField2(data.unit ?? "")}`;
|
|
736
2850
|
}
|
|
737
2851
|
return `Scalar Result: ${data.scalar}${data.unit ? " " + data.unit : ""}`;
|
|
738
2852
|
}
|
|
@@ -743,11 +2857,11 @@ ${escapeCsvField(data.parsed.metric)},${data.scalar},${escapeCsvField(data.unit
|
|
|
743
2857
|
for (const pt of s.points) {
|
|
744
2858
|
rows.push(
|
|
745
2859
|
[
|
|
746
|
-
|
|
2860
|
+
escapeCsvField2(s.label || s.key),
|
|
747
2861
|
pt.ts,
|
|
748
2862
|
formatDate(pt.ts),
|
|
749
2863
|
pt.value,
|
|
750
|
-
|
|
2864
|
+
escapeCsvField2(s.unit ?? data.unit ?? "")
|
|
751
2865
|
].join(",")
|
|
752
2866
|
);
|
|
753
2867
|
}
|
|
@@ -788,7 +2902,8 @@ var VERSION = "0.6.0";
|
|
|
788
2902
|
function parseCliArgs(args) {
|
|
789
2903
|
const result = {
|
|
790
2904
|
format: "json",
|
|
791
|
-
packs: []
|
|
2905
|
+
packs: [],
|
|
2906
|
+
sources: []
|
|
792
2907
|
};
|
|
793
2908
|
let i = 0;
|
|
794
2909
|
while (i < args.length) {
|
|
@@ -839,8 +2954,54 @@ function parseCliArgs(args) {
|
|
|
839
2954
|
} else if (arg === "--unit") {
|
|
840
2955
|
result.unit = args[++i];
|
|
841
2956
|
i++;
|
|
2957
|
+
} else if (arg === "--db") {
|
|
2958
|
+
result.db = args[++i];
|
|
2959
|
+
i++;
|
|
2960
|
+
} else if (arg.startsWith("--db=")) {
|
|
2961
|
+
result.db = arg.slice("--db=".length);
|
|
2962
|
+
i++;
|
|
2963
|
+
} else if (arg === "--debug") {
|
|
2964
|
+
result.debug = "";
|
|
2965
|
+
i++;
|
|
2966
|
+
} else if (arg.startsWith("--debug=")) {
|
|
2967
|
+
result.debug = arg.slice("--debug=".length);
|
|
2968
|
+
i++;
|
|
2969
|
+
} else if (arg === "--at") {
|
|
2970
|
+
result.at = args[++i];
|
|
2971
|
+
i++;
|
|
2972
|
+
} else if (arg.startsWith("--at=")) {
|
|
2973
|
+
result.at = arg.slice("--at=".length);
|
|
2974
|
+
i++;
|
|
2975
|
+
} else if (arg === "--source") {
|
|
2976
|
+
const source = args[++i];
|
|
2977
|
+
if (source) result.sources.push(source);
|
|
2978
|
+
i++;
|
|
2979
|
+
} else if (arg.startsWith("--source=")) {
|
|
2980
|
+
result.sources.push(arg.slice("--source=".length));
|
|
2981
|
+
i++;
|
|
2982
|
+
} else if (arg === "--scaffold") {
|
|
2983
|
+
result.scaffold = true;
|
|
2984
|
+
i++;
|
|
2985
|
+
} else if (arg === "--template") {
|
|
2986
|
+
result.template = args[++i];
|
|
2987
|
+
i++;
|
|
2988
|
+
} else if (arg.startsWith("--template=")) {
|
|
2989
|
+
result.template = arg.slice("--template=".length);
|
|
2990
|
+
i++;
|
|
2991
|
+
} else if (arg === "--out") {
|
|
2992
|
+
result.output = args[++i];
|
|
2993
|
+
i++;
|
|
2994
|
+
} else if (arg.startsWith("--out=")) {
|
|
2995
|
+
result.output = arg.slice("--out=".length);
|
|
2996
|
+
i++;
|
|
2997
|
+
} else if (arg === "--dir") {
|
|
2998
|
+
result.dir = args[++i];
|
|
2999
|
+
i++;
|
|
3000
|
+
} else if (arg.startsWith("--dir=")) {
|
|
3001
|
+
result.dir = arg.slice("--dir=".length);
|
|
3002
|
+
i++;
|
|
842
3003
|
} else if (!result.subcommand) {
|
|
843
|
-
if (arg === "parse" || arg === "run" || arg === "query" || arg === "help" || arg === "version") {
|
|
3004
|
+
if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "import" || arg === "build" || arg === "seed" || arg === "reset" || arg === "help" || arg === "version") {
|
|
844
3005
|
result.subcommand = arg;
|
|
845
3006
|
} else {
|
|
846
3007
|
result.target = arg;
|
|
@@ -849,16 +3010,32 @@ function parseCliArgs(args) {
|
|
|
849
3010
|
} else if (!result.target) {
|
|
850
3011
|
result.target = arg;
|
|
851
3012
|
i++;
|
|
3013
|
+
} else if (!result.target2) {
|
|
3014
|
+
result.target2 = arg;
|
|
3015
|
+
i++;
|
|
852
3016
|
} else {
|
|
853
3017
|
i++;
|
|
854
3018
|
}
|
|
855
3019
|
}
|
|
856
3020
|
return result;
|
|
857
3021
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
3022
|
+
function readBunStdinText() {
|
|
3023
|
+
const g = globalThis;
|
|
3024
|
+
if (g && typeof g === "object" && "Bun" in g) {
|
|
3025
|
+
const bun = g.Bun;
|
|
3026
|
+
if (bun && typeof bun === "object" && "stdin" in bun) {
|
|
3027
|
+
const stdin = bun.stdin;
|
|
3028
|
+
if (stdin && typeof stdin === "object" && "text" in stdin) {
|
|
3029
|
+
const text = stdin.text;
|
|
3030
|
+
if (typeof text === "function") return text;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
861
3033
|
}
|
|
3034
|
+
return void 0;
|
|
3035
|
+
}
|
|
3036
|
+
async function readStdin() {
|
|
3037
|
+
const bunText = readBunStdinText();
|
|
3038
|
+
if (bunText) return bunText();
|
|
862
3039
|
return new Promise((resolve, reject) => {
|
|
863
3040
|
const chunks = [];
|
|
864
3041
|
process.stdin.on("data", (chunk) => {
|
|
@@ -880,20 +3057,84 @@ USAGE:
|
|
|
880
3057
|
wod parse <file | -> [options]
|
|
881
3058
|
wod run <file | -> [options]
|
|
882
3059
|
wod query '<wql-string>' [options]
|
|
3060
|
+
wod download <note|session|effort|equipment> <id> [options]
|
|
3061
|
+
wod import <note|session|effort|equipment> <file> [options]
|
|
3062
|
+
wod build [dir] [options]
|
|
3063
|
+
wod seed [options]
|
|
3064
|
+
wod reset [options]
|
|
883
3065
|
|
|
884
3066
|
SUBCOMMANDS:
|
|
885
|
-
parse <file | -> Parse Whiteboard Script
|
|
886
|
-
|
|
887
|
-
|
|
3067
|
+
parse <file | -> Parse Whiteboard Script, create a note in the database
|
|
3068
|
+
(segments + block index), optionally emit parse-tree IR.
|
|
3069
|
+
Markdown carrying a 'wod-note-id' frontmatter stamp
|
|
3070
|
+
(see download) imports back into that same note.
|
|
3071
|
+
run <file | -> Execute the script state machine, record a session +
|
|
3072
|
+
event/summary rows in the database, optionally emit
|
|
3073
|
+
execution-log IR
|
|
3074
|
+
query '<wql>' Evaluate WQL against the database (or an explicit
|
|
3075
|
+
dataset) and emit QueryResult / RowsQueryResult IR
|
|
3076
|
+
download note <id> Compose the note's markdown from its segment rows and
|
|
3077
|
+
write it to <id>.md. Use --at <version> to compose
|
|
3078
|
+
as-of a segment version (default: latest). The stamp
|
|
3079
|
+
in the frontmatter makes 'wod parse' re-import it.
|
|
3080
|
+
Frontmatter tags (general + typed equipment/quality/\u2026)
|
|
3081
|
+
are materialized into the tag graph on parse/import.
|
|
3082
|
+
download session <id>
|
|
3083
|
+
Write the session's generated metrics (event + summary
|
|
3084
|
+
rows, one CSV row per metric) to <id>.csv. Import back
|
|
3085
|
+
with 'wod import session'.
|
|
3086
|
+
download effort <slug>
|
|
3087
|
+
Write the effort registry row as corpus-style markdown
|
|
3088
|
+
(frontmatter id/slug/label/aliases/baseAttributes + body)
|
|
3089
|
+
to <slug>.md. Import back with 'wod import effort'.
|
|
3090
|
+
download equipment
|
|
3091
|
+
Write every equipment tag as a one-column CSV to
|
|
3092
|
+
equipment.csv. Import back with 'wod import equipment'.
|
|
3093
|
+
import note <file> Re-import exported note markdown (same as parse with
|
|
3094
|
+
the stamped identity).
|
|
3095
|
+
import session <file>
|
|
3096
|
+
Recreate a session + its events from a downloaded CSV
|
|
3097
|
+
into the same or a clean database (id-keyed upsert).
|
|
3098
|
+
import effort <file>
|
|
3099
|
+
Upsert an effort registry row from corpus-style markdown.
|
|
3100
|
+
import equipment <file>
|
|
3101
|
+
Create/upgrade equipment tags (CSV, one label per row).
|
|
3102
|
+
build [dir] Compile a seed corpus: validate every .md under
|
|
3103
|
+
<dir>/markdown (parity gate + dashboard format gate) and
|
|
3104
|
+
write manifest.json + content-hashed chunks/ +
|
|
3105
|
+
block-parent-map.json + report.html to <dir>/dist
|
|
3106
|
+
(override with -o/--output). Version stamps derive from
|
|
3107
|
+
<dir>'s git history (full checkout required).
|
|
3108
|
+
|
|
3109
|
+
BUILD OPTIONS (for 'build'):
|
|
3110
|
+
--scaffold Also scaffold markdown/efforts/discovered/*.md stubs for
|
|
3111
|
+
workout effort labels missing from the registry.
|
|
3112
|
+
--template <file> Compile an extra markdown file as the 'template' chunk
|
|
3113
|
+
(the host's playground intake template).
|
|
888
3114
|
|
|
889
|
-
INPUT SOURCING (for 'query'):
|
|
3115
|
+
INPUT SOURCING (for 'query'; without these, the database is queried):
|
|
890
3116
|
-c, --corpus <path> Load historical corpus dataset from a versioned JSON file
|
|
891
3117
|
--stdin-log Read kind: 'execution-log' JSON payload from stdin
|
|
892
3118
|
--stdin-facts Read raw kind: 'fact-set' JSON payload from stdin
|
|
893
3119
|
|
|
3120
|
+
DATABASE (parse / run / query / download / import / seed / reset):
|
|
3121
|
+
--db <path> SQLite database file (default: $WOD_WIKI_DB or
|
|
3122
|
+
./wodwiki.db). Created on demand by parse/run/seed.
|
|
3123
|
+
|
|
3124
|
+
SEED SOURCING (for 'seed'):
|
|
3125
|
+
--source <dir> Extra compiled seed directory (manifest.json + chunks);
|
|
3126
|
+
repeatable. Defaults to the bundled core + community
|
|
3127
|
+
artifacts under apps/playground/public/seed.
|
|
3128
|
+
|
|
894
3129
|
OPTIONS:
|
|
895
3130
|
--format <fmt> Output format: json (default), table, csv
|
|
896
|
-
-o, --output <path> Write
|
|
3131
|
+
-o, --output <path> Write the IR payload (or download) to a path instead of
|
|
3132
|
+
the default; '-' writes to stdout
|
|
3133
|
+
--at <version> With 'download note': compose the markdown as-of the
|
|
3134
|
+
given segment version (default: latest)
|
|
3135
|
+
--debug[=name] Also write the IR payload to <name> (default
|
|
3136
|
+
wod-<subcommand>-debug.json); without it, parse/run
|
|
3137
|
+
print a one-line summary of what was persisted
|
|
897
3138
|
--pack <module> Dynamically load and register a Language Pack (repeatable)
|
|
898
3139
|
--sport <sport> Target sport fence dialect (e.g. climbing, crossfit)
|
|
899
3140
|
--unit <unit> Preferred display unit for queries (e.g. lb, kg)
|
|
@@ -912,7 +3153,7 @@ function printUsage(writer) {
|
|
|
912
3153
|
if (writer) {
|
|
913
3154
|
writer(text);
|
|
914
3155
|
} else {
|
|
915
|
-
|
|
3156
|
+
process.stdout.write(text.endsWith("\n") ? text : text + "\n");
|
|
916
3157
|
}
|
|
917
3158
|
}
|
|
918
3159
|
async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
@@ -951,6 +3192,13 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
951
3192
|
writeOut(formattedText);
|
|
952
3193
|
}
|
|
953
3194
|
};
|
|
3195
|
+
const debugTarget = (subcommand) => parsed.debug === void 0 ? void 0 : parsed.debug || `wod-${subcommand}-debug.json`;
|
|
3196
|
+
const writeDebugFile = (subcommand, ir) => {
|
|
3197
|
+
const target = debugTarget(subcommand);
|
|
3198
|
+
if (target !== void 0) {
|
|
3199
|
+
getFs2().writeFileSync(target, JSON.stringify(ir, null, 2) + "\n", "utf-8");
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
954
3202
|
try {
|
|
955
3203
|
if (parsed.subcommand === "parse") {
|
|
956
3204
|
if (!parsed.target) {
|
|
@@ -968,10 +3216,29 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
968
3216
|
}
|
|
969
3217
|
source = fs.readFileSync(parsed.target, "utf-8");
|
|
970
3218
|
}
|
|
971
|
-
const ir = runParse(source, { sport: parsed.sport, sourceLabel: `cli:wod parse ${parsed.target}` });
|
|
972
|
-
const
|
|
973
|
-
const
|
|
974
|
-
|
|
3219
|
+
const ir = runParse(source, { sport: parsed.sport, sourceLabel: `cli:wod parse ${parsed.target}` });
|
|
3220
|
+
const identity = extractNoteIdentity(source);
|
|
3221
|
+
const tags = extractTypedTags(source);
|
|
3222
|
+
const db = await openCliDb(parsed.db);
|
|
3223
|
+
try {
|
|
3224
|
+
const persisted = await persistNote(db, {
|
|
3225
|
+
source,
|
|
3226
|
+
sourceLabel: parsed.target,
|
|
3227
|
+
noteId: identity?.noteId,
|
|
3228
|
+
title: identity?.title,
|
|
3229
|
+
tags
|
|
3230
|
+
});
|
|
3231
|
+
writeOut(
|
|
3232
|
+
`[wod parse] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.segments} segments, ${persisted.blocks} blocks, ${persisted.efforts} efforts${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db.path}`
|
|
3233
|
+
);
|
|
3234
|
+
} finally {
|
|
3235
|
+
await db.close();
|
|
3236
|
+
}
|
|
3237
|
+
if (parsed.output) {
|
|
3238
|
+
const pretty = Boolean(process.stdout.isTTY);
|
|
3239
|
+
deliverOutput(formatParseOutput(ir, parsed.format, pretty));
|
|
3240
|
+
}
|
|
3241
|
+
writeDebugFile("parse", ir);
|
|
975
3242
|
return 0;
|
|
976
3243
|
}
|
|
977
3244
|
if (parsed.subcommand === "run") {
|
|
@@ -991,9 +3258,24 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
991
3258
|
source = fs.readFileSync(parsed.target, "utf-8");
|
|
992
3259
|
}
|
|
993
3260
|
const ir = await runExecution(source, { sport: parsed.sport, sourceLabel: `cli:wod run ${parsed.target}` });
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
3261
|
+
const db = await openCliDb(parsed.db);
|
|
3262
|
+
try {
|
|
3263
|
+
const persisted = await persistRun(db, {
|
|
3264
|
+
source,
|
|
3265
|
+
sourceLabel: parsed.target,
|
|
3266
|
+
executionLog: ir.data
|
|
3267
|
+
});
|
|
3268
|
+
writeOut(
|
|
3269
|
+
`[wod run] session ${persisted.resultId} \u2014 ${persisted.events} events, ${persisted.summaries} summaries on note ${persisted.noteId} \u2192 ${db.path}`
|
|
3270
|
+
);
|
|
3271
|
+
} finally {
|
|
3272
|
+
await db.close();
|
|
3273
|
+
}
|
|
3274
|
+
if (parsed.output) {
|
|
3275
|
+
const pretty = Boolean(process.stdout.isTTY);
|
|
3276
|
+
deliverOutput(formatExecutionOutput(ir, parsed.format, pretty));
|
|
3277
|
+
}
|
|
3278
|
+
writeDebugFile("run", ir);
|
|
997
3279
|
return 0;
|
|
998
3280
|
}
|
|
999
3281
|
if (parsed.subcommand === "query") {
|
|
@@ -1001,26 +3283,269 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
1001
3283
|
writeErr("[wod query] Error: Missing WQL query argument");
|
|
1002
3284
|
return 1;
|
|
1003
3285
|
}
|
|
3286
|
+
const useDb = !parsed.corpus && !parsed.stdinLog && !parsed.stdinFacts;
|
|
1004
3287
|
let stdinLogContent;
|
|
1005
3288
|
let stdinFactsContent;
|
|
1006
3289
|
if (parsed.stdinLog) {
|
|
1007
3290
|
stdinLogContent = await getStdin();
|
|
1008
3291
|
} else if (parsed.stdinFacts) {
|
|
1009
3292
|
stdinFactsContent = await getStdin();
|
|
1010
|
-
} else if (!parsed.corpus) {
|
|
1011
|
-
writeErr("[wod query] Error: Query requires input data. Specify --corpus <file>, --stdin-log, or --stdin-facts.");
|
|
1012
|
-
return 1;
|
|
1013
3293
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
3294
|
+
let ir;
|
|
3295
|
+
if (useDb) {
|
|
3296
|
+
const dbPath = resolveDbPath(parsed.db);
|
|
3297
|
+
if (!nodeFs().existsSync(dbPath)) {
|
|
3298
|
+
writeErr(
|
|
3299
|
+
`[wod query] No input dataset and no database at ${dbPath}. Specify --corpus <file>, --stdin-log, --stdin-facts, or run \`wod seed\` first.`
|
|
3300
|
+
);
|
|
3301
|
+
return 1;
|
|
3302
|
+
}
|
|
3303
|
+
const db = await openCliDb(parsed.db);
|
|
3304
|
+
try {
|
|
3305
|
+
ir = await runQueryCli(parsed.target, {
|
|
3306
|
+
preferredUnit: parsed.unit,
|
|
3307
|
+
sourceLabel: `cli:wod query "${parsed.target}"`,
|
|
3308
|
+
stores: {
|
|
3309
|
+
eventStore: sqliteEventStore(db.db),
|
|
3310
|
+
noteStore: sqliteNoteStore(db.db),
|
|
3311
|
+
blockStore: sqliteBlockStore(db.db),
|
|
3312
|
+
effortStore: sqliteEffortStore(db.db),
|
|
3313
|
+
blockEffortsStore: sqliteIndexStore(db.db, "block_efforts"),
|
|
3314
|
+
tagsStore: sqliteIndexStore(db.db, "tags"),
|
|
3315
|
+
noteTagsStore: sqliteIndexStore(db.db, "note_tags")
|
|
3316
|
+
}
|
|
3317
|
+
});
|
|
3318
|
+
} finally {
|
|
3319
|
+
await db.close();
|
|
3320
|
+
}
|
|
3321
|
+
} else {
|
|
3322
|
+
if (!parsed.corpus && !stdinLogContent && !stdinFactsContent) {
|
|
3323
|
+
writeErr("[wod query] Error: Query requires input data. Specify --corpus <file>, --stdin-log, or --stdin-facts.");
|
|
3324
|
+
return 1;
|
|
3325
|
+
}
|
|
3326
|
+
ir = await runQueryCli(parsed.target, {
|
|
3327
|
+
corpusPath: parsed.corpus,
|
|
3328
|
+
stdinLog: stdinLogContent,
|
|
3329
|
+
stdinFacts: stdinFactsContent,
|
|
3330
|
+
preferredUnit: parsed.unit,
|
|
3331
|
+
sourceLabel: `cli:wod query "${parsed.target}"`
|
|
3332
|
+
});
|
|
3333
|
+
}
|
|
1021
3334
|
const pretty = !parsed.output && Boolean(process.stdout.isTTY);
|
|
1022
3335
|
const formatted = formatQueryOutput(ir, parsed.format, pretty);
|
|
1023
3336
|
deliverOutput(formatted);
|
|
3337
|
+
writeDebugFile("query", ir);
|
|
3338
|
+
return 0;
|
|
3339
|
+
}
|
|
3340
|
+
if (parsed.subcommand === "download") {
|
|
3341
|
+
const kind = parsed.target;
|
|
3342
|
+
const id = parsed.target2;
|
|
3343
|
+
const DOWNLOAD_KINDS = ["note", "session", "effort", "equipment"];
|
|
3344
|
+
if (!id && kind !== "equipment" || !DOWNLOAD_KINDS.includes(kind ?? "")) {
|
|
3345
|
+
writeErr("[wod download] Usage: wod download <note|session|effort|equipment> <id> [--at <version>] [-o <path>]");
|
|
3346
|
+
return 1;
|
|
3347
|
+
}
|
|
3348
|
+
const targetId = id;
|
|
3349
|
+
const db = await openCliDb(parsed.db);
|
|
3350
|
+
try {
|
|
3351
|
+
if (kind === "note") {
|
|
3352
|
+
const bundle = await getNoteBundle(db.db, targetId);
|
|
3353
|
+
if (!bundle.note) {
|
|
3354
|
+
writeErr(`[wod download] Note not found in ${db.path}: ${id}`);
|
|
3355
|
+
return 1;
|
|
3356
|
+
}
|
|
3357
|
+
const at = parsed.at === void 0 ? void 0 : Number(parsed.at);
|
|
3358
|
+
if (parsed.at !== void 0 && (!Number.isInteger(at) || at < 1)) {
|
|
3359
|
+
writeErr(`[wod download] Invalid --at version: ${parsed.at}`);
|
|
3360
|
+
return 1;
|
|
3361
|
+
}
|
|
3362
|
+
const markdown = composeNoteMarkdown(bundle.note, bundle.segments, at);
|
|
3363
|
+
const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}${at ? `.v${at}` : ""}.md`;
|
|
3364
|
+
if (parsed.output === "-") {
|
|
3365
|
+
writeOut(markdown);
|
|
3366
|
+
} else {
|
|
3367
|
+
nodeFs().writeFileSync(target, markdown, "utf-8");
|
|
3368
|
+
}
|
|
3369
|
+
writeOut(
|
|
3370
|
+
`[wod download] note ${id}${at ? ` v${at}` : ""} \u2192 ${target} (${bundle.segments.length} segment rows)`
|
|
3371
|
+
);
|
|
3372
|
+
} else if (kind === "session") {
|
|
3373
|
+
const bundle = await getSessionBundle(db.db, targetId);
|
|
3374
|
+
if (!bundle) {
|
|
3375
|
+
writeErr(`[wod download] Session not found in ${db.path}: ${id}`);
|
|
3376
|
+
return 1;
|
|
3377
|
+
}
|
|
3378
|
+
const csv = sessionToCsv(bundle.session, bundle.events);
|
|
3379
|
+
const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.csv`;
|
|
3380
|
+
if (parsed.output === "-") {
|
|
3381
|
+
writeOut(csv);
|
|
3382
|
+
} else {
|
|
3383
|
+
nodeFs().writeFileSync(target, csv, "utf-8");
|
|
3384
|
+
}
|
|
3385
|
+
writeOut(`[wod download] session ${id} \u2192 ${target} (${bundle.events.length} events)`);
|
|
3386
|
+
} else if (kind === "effort") {
|
|
3387
|
+
const effort = await getEffortBySlug(db.db, targetId);
|
|
3388
|
+
if (!effort) {
|
|
3389
|
+
writeErr(`[wod download] Effort not found in ${db.path}: ${id}`);
|
|
3390
|
+
return 1;
|
|
3391
|
+
}
|
|
3392
|
+
const markdown = effortToMarkdown(effort);
|
|
3393
|
+
const target = parsed.output && parsed.output !== "-" ? parsed.output : `${id}.md`;
|
|
3394
|
+
if (parsed.output === "-") {
|
|
3395
|
+
writeOut(markdown);
|
|
3396
|
+
} else {
|
|
3397
|
+
nodeFs().writeFileSync(target, markdown, "utf-8");
|
|
3398
|
+
}
|
|
3399
|
+
writeOut(`[wod download] effort ${id} \u2192 ${target}`);
|
|
3400
|
+
} else {
|
|
3401
|
+
const labels = (await listTagsByType(db.db, "equipment")).map((t) => t.label).sort();
|
|
3402
|
+
const csv = equipmentToCsv(labels);
|
|
3403
|
+
const target = parsed.output && parsed.output !== "-" ? parsed.output : "equipment.csv";
|
|
3404
|
+
if (parsed.output === "-") {
|
|
3405
|
+
writeOut(csv);
|
|
3406
|
+
} else {
|
|
3407
|
+
nodeFs().writeFileSync(target, csv, "utf-8");
|
|
3408
|
+
}
|
|
3409
|
+
writeOut(`[wod download] equipment \u2192 ${target} (${labels.length} labels)`);
|
|
3410
|
+
}
|
|
3411
|
+
} finally {
|
|
3412
|
+
await db.close();
|
|
3413
|
+
}
|
|
3414
|
+
return 0;
|
|
3415
|
+
}
|
|
3416
|
+
if (parsed.subcommand === "import") {
|
|
3417
|
+
const kind = parsed.target;
|
|
3418
|
+
const file = parsed.target2;
|
|
3419
|
+
const IMPORT_KINDS = ["note", "session", "effort", "equipment"];
|
|
3420
|
+
if (!file || !IMPORT_KINDS.includes(kind ?? "")) {
|
|
3421
|
+
writeErr("[wod import] Usage: wod import <note|session|effort|equipment> <file>");
|
|
3422
|
+
return 1;
|
|
3423
|
+
}
|
|
3424
|
+
const fs = nodeFs();
|
|
3425
|
+
if (!fs.existsSync(file)) {
|
|
3426
|
+
writeErr(`[wod import] File not found: ${file}`);
|
|
3427
|
+
return 1;
|
|
3428
|
+
}
|
|
3429
|
+
if (kind === "note") {
|
|
3430
|
+
const source = fs.readFileSync(file, "utf-8");
|
|
3431
|
+
const identity = extractNoteIdentity(source);
|
|
3432
|
+
const tags = extractTypedTags(source);
|
|
3433
|
+
const db2 = await openCliDb(parsed.db);
|
|
3434
|
+
try {
|
|
3435
|
+
const persisted = await persistNote(db2, {
|
|
3436
|
+
source,
|
|
3437
|
+
sourceLabel: file,
|
|
3438
|
+
noteId: identity?.noteId,
|
|
3439
|
+
title: identity?.title,
|
|
3440
|
+
tags
|
|
3441
|
+
});
|
|
3442
|
+
writeOut(
|
|
3443
|
+
`[wod import] note ${persisted.noteId}${persisted.created ? "" : " (updated)"} \u2014 ${persisted.segments} segments${persisted.tags > 0 ? `, ${persisted.tags} tags` : ""} \u2192 ${db2.path}`
|
|
3444
|
+
);
|
|
3445
|
+
} finally {
|
|
3446
|
+
await db2.close();
|
|
3447
|
+
}
|
|
3448
|
+
return 0;
|
|
3449
|
+
}
|
|
3450
|
+
if (kind === "effort") {
|
|
3451
|
+
const effort = markdownToEffort(fs.readFileSync(file, "utf-8"));
|
|
3452
|
+
if (!effort) {
|
|
3453
|
+
writeErr(`[wod import] Unparseable effort file (need frontmatter id/slug/label + numeric baseAttributes.met): ${file}`);
|
|
3454
|
+
return 1;
|
|
3455
|
+
}
|
|
3456
|
+
const db2 = await openCliDb(parsed.db);
|
|
3457
|
+
try {
|
|
3458
|
+
await persistEffort(db2, { ...effort, registrySource: "user" });
|
|
3459
|
+
writeOut(`[wod import] effort ${effort.slug} \u2014 ${effort.label} \u2192 ${db2.path}`);
|
|
3460
|
+
} finally {
|
|
3461
|
+
await db2.close();
|
|
3462
|
+
}
|
|
3463
|
+
return 0;
|
|
3464
|
+
}
|
|
3465
|
+
if (kind === "equipment") {
|
|
3466
|
+
const labels = csvToEquipmentLabels(fs.readFileSync(file, "utf-8"));
|
|
3467
|
+
if (labels.length === 0) {
|
|
3468
|
+
writeErr(`[wod import] No equipment labels found in ${file}`);
|
|
3469
|
+
return 1;
|
|
3470
|
+
}
|
|
3471
|
+
const db2 = await openCliDb(parsed.db);
|
|
3472
|
+
try {
|
|
3473
|
+
const added = await persistTypedTags(db2, labels, "equipment");
|
|
3474
|
+
writeOut(`[wod import] equipment \u2014 ${labels.length} labels (${added} new) \u2192 ${db2.path}`);
|
|
3475
|
+
} finally {
|
|
3476
|
+
await db2.close();
|
|
3477
|
+
}
|
|
3478
|
+
return 0;
|
|
3479
|
+
}
|
|
3480
|
+
const { session, events } = csvToSessionRows(fs.readFileSync(file, "utf-8"));
|
|
3481
|
+
const db = await openCliDb(parsed.db);
|
|
3482
|
+
try {
|
|
3483
|
+
const result = await persistSessionBundle(db, session, events);
|
|
3484
|
+
writeOut(
|
|
3485
|
+
`[wod import] session ${session.id} \u2014 ${result.events} events${result.noteCreated ? " (stub note created)" : ""} \u2192 ${db.path}`
|
|
3486
|
+
);
|
|
3487
|
+
} finally {
|
|
3488
|
+
await db.close();
|
|
3489
|
+
}
|
|
3490
|
+
return 0;
|
|
3491
|
+
}
|
|
3492
|
+
if (parsed.subcommand === "build") {
|
|
3493
|
+
const cwd = process.cwd();
|
|
3494
|
+
const root = nodePath().resolve(cwd, parsed.dir ?? parsed.target ?? ".");
|
|
3495
|
+
if (!nodeFs().existsSync(nodePath().resolve(root, "markdown"))) {
|
|
3496
|
+
writeErr(`[wod build] No markdown/ corpus under ${root} \u2014 run from the content repo root (or pass its directory).`);
|
|
3497
|
+
return 1;
|
|
3498
|
+
}
|
|
3499
|
+
const { generateSeed: generateSeed2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
3500
|
+
try {
|
|
3501
|
+
const result = generateSeed2({
|
|
3502
|
+
corpusRoot: root,
|
|
3503
|
+
...parsed.output ? { outDir: nodePath().resolve(cwd, parsed.output) } : {},
|
|
3504
|
+
...parsed.template ? { templateFile: nodePath().resolve(cwd, parsed.template) } : {},
|
|
3505
|
+
scaffoldDiscoveredEfforts: parsed.scaffold === true
|
|
3506
|
+
});
|
|
3507
|
+
const kb = (n) => `${(n / 1024).toFixed(0)} KB`;
|
|
3508
|
+
const totalBytes = result.manifest.chunks.reduce((s, c) => s + c.bytes, 0);
|
|
3509
|
+
writeOut(`[wod build] schema v${result.manifest.schema} version ${result.manifest.version} (${result.manifest.versionLabel}) \u2192 ${result.outDir}`);
|
|
3510
|
+
for (const c of result.manifest.chunks) {
|
|
3511
|
+
writeOut(` ${c.id.padEnd(48)} ${String(c.count).padStart(4)} ${c.kind === "block-index" ? "rows " : "files"} ${kb(c.bytes).padStart(8)}`);
|
|
3512
|
+
}
|
|
3513
|
+
writeOut(`[wod build] ${result.manifest.chunks.length} chunks, ${result.totalFiles} files, ${kb(totalBytes)} total \u2014 scan ${result.passMs.scan}ms, block-index ${result.passMs.blockIndex}ms, block-efforts ${result.passMs.blockEfforts}ms, write ${result.passMs.write}ms`);
|
|
3514
|
+
return 0;
|
|
3515
|
+
} catch (error) {
|
|
3516
|
+
writeErr(`[wod build] ${error.message}`);
|
|
3517
|
+
return 1;
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
if (parsed.subcommand === "seed") {
|
|
3521
|
+
const dirs = parsed.sources.length > 0 ? parsed.sources.map((s) => nodePath().resolve(process.cwd(), s)) : defaultSeedDirs(process.cwd());
|
|
3522
|
+
if (dirs.length === 0) {
|
|
3523
|
+
writeErr(
|
|
3524
|
+
"[wod seed] No seed sources found. Run `bun scripts/sync-seed.ts` to build the bundled artifacts, or pass --source <dir> pointing at a compiled seed directory (manifest.json + chunks)."
|
|
3525
|
+
);
|
|
3526
|
+
return 1;
|
|
3527
|
+
}
|
|
3528
|
+
const db = await openCliDb(parsed.db);
|
|
3529
|
+
try {
|
|
3530
|
+
for (const dir of dirs) {
|
|
3531
|
+
const summary = await importSeedDir(db, dir, "");
|
|
3532
|
+
writeOut(
|
|
3533
|
+
`[wod seed] ${summary.sourceId}: v${summary.version} \u2014 ${summary.notes} notes, ${summary.efforts} efforts, ${summary.blocks} blocks, ${summary.blockEfforts} block-efforts (${summary.deleted} deleted, ${summary.skippedUserOwned} user-owned skipped) \u2192 ${db.path}`
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
} finally {
|
|
3537
|
+
await db.close();
|
|
3538
|
+
}
|
|
3539
|
+
return 0;
|
|
3540
|
+
}
|
|
3541
|
+
if (parsed.subcommand === "reset") {
|
|
3542
|
+
const target = resolveDbPath(parsed.db);
|
|
3543
|
+
if (nodeFs().existsSync(target)) {
|
|
3544
|
+
nodeFs().unlinkSync(target);
|
|
3545
|
+
writeOut(`[wod reset] removed ${target}`);
|
|
3546
|
+
} else {
|
|
3547
|
+
writeOut(`[wod reset] nothing to reset \u2014 ${target} does not exist`);
|
|
3548
|
+
}
|
|
1024
3549
|
return 0;
|
|
1025
3550
|
}
|
|
1026
3551
|
writeErr(`[wod] Unknown subcommand "${parsed.subcommand}"`);
|
|
@@ -1040,13 +3565,13 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
1040
3565
|
}
|
|
1041
3566
|
|
|
1042
3567
|
// src/version.ts
|
|
1043
|
-
var VERSION2 = "0.
|
|
1044
|
-
var GIT_SHA = "
|
|
1045
|
-
var BUILD_TIME = "2026-
|
|
3568
|
+
var VERSION2 = "0.35.2116";
|
|
3569
|
+
var GIT_SHA = "a4d411f";
|
|
3570
|
+
var BUILD_TIME = "2026-10-12T00:14:24.795Z";
|
|
1046
3571
|
var SEMVER = {
|
|
1047
|
-
version: "0.
|
|
1048
|
-
gitSha: "
|
|
1049
|
-
buildTime: "2026-
|
|
3572
|
+
version: "0.35.2116",
|
|
3573
|
+
gitSha: "a4d411f",
|
|
3574
|
+
buildTime: "2026-10-12T00:14:24.795Z"
|
|
1050
3575
|
};
|
|
1051
3576
|
|
|
1052
3577
|
Object.defineProperty(exports, "ABSENT", {
|
|
@@ -1209,6 +3734,10 @@ Object.defineProperty(exports, "ExitBehavior", {
|
|
|
1209
3734
|
enumerable: true,
|
|
1210
3735
|
get: function () { return wodWikiLang.ExitBehavior; }
|
|
1211
3736
|
});
|
|
3737
|
+
Object.defineProperty(exports, "FixtureSyntaxError", {
|
|
3738
|
+
enumerable: true,
|
|
3739
|
+
get: function () { return wodWikiLang.FixtureSyntaxError; }
|
|
3740
|
+
});
|
|
1212
3741
|
Object.defineProperty(exports, "GenericGroupStrategy", {
|
|
1213
3742
|
enumerable: true,
|
|
1214
3743
|
get: function () { return wodWikiLang.GenericGroupStrategy; }
|
|
@@ -1429,10 +3958,18 @@ Object.defineProperty(exports, "calculateElapsed", {
|
|
|
1429
3958
|
enumerable: true,
|
|
1430
3959
|
get: function () { return wodWikiLang.calculateElapsed; }
|
|
1431
3960
|
});
|
|
3961
|
+
Object.defineProperty(exports, "canonicalizeType", {
|
|
3962
|
+
enumerable: true,
|
|
3963
|
+
get: function () { return wodWikiLang.canonicalizeType; }
|
|
3964
|
+
});
|
|
1432
3965
|
Object.defineProperty(exports, "collapseUnresolvedChoices", {
|
|
1433
3966
|
enumerable: true,
|
|
1434
3967
|
get: function () { return wodWikiLang.collapseUnresolvedChoices; }
|
|
1435
3968
|
});
|
|
3969
|
+
Object.defineProperty(exports, "compareStatement", {
|
|
3970
|
+
enumerable: true,
|
|
3971
|
+
get: function () { return wodWikiLang.compareStatement; }
|
|
3972
|
+
});
|
|
1436
3973
|
Object.defineProperty(exports, "compileLineForm", {
|
|
1437
3974
|
enumerable: true,
|
|
1438
3975
|
get: function () { return wodWikiLang.compileLineForm; }
|
|
@@ -1485,6 +4022,10 @@ Object.defineProperty(exports, "dialectStack", {
|
|
|
1485
4022
|
enumerable: true,
|
|
1486
4023
|
get: function () { return wodWikiLang.dialectStack; }
|
|
1487
4024
|
});
|
|
4025
|
+
Object.defineProperty(exports, "diffStatement", {
|
|
4026
|
+
enumerable: true,
|
|
4027
|
+
get: function () { return wodWikiLang.diffStatement; }
|
|
4028
|
+
});
|
|
1488
4029
|
Object.defineProperty(exports, "evaluate", {
|
|
1489
4030
|
enumerable: true,
|
|
1490
4031
|
get: function () { return wodWikiLang.evaluate; }
|
|
@@ -1589,6 +4130,10 @@ Object.defineProperty(exports, "parseExpression", {
|
|
|
1589
4130
|
enumerable: true,
|
|
1590
4131
|
get: function () { return wodWikiLang.parseExpression; }
|
|
1591
4132
|
});
|
|
4133
|
+
Object.defineProperty(exports, "parseMetricLine", {
|
|
4134
|
+
enumerable: true,
|
|
4135
|
+
get: function () { return wodWikiLang.parseMetricLine; }
|
|
4136
|
+
});
|
|
1592
4137
|
Object.defineProperty(exports, "parseScript", {
|
|
1593
4138
|
enumerable: true,
|
|
1594
4139
|
get: function () { return wodWikiLang.parseScript; }
|
|
@@ -1597,6 +4142,10 @@ Object.defineProperty(exports, "realtimeProcessorRegistry", {
|
|
|
1597
4142
|
enumerable: true,
|
|
1598
4143
|
get: function () { return wodWikiLang.realtimeProcessorRegistry; }
|
|
1599
4144
|
});
|
|
4145
|
+
Object.defineProperty(exports, "renderMetric", {
|
|
4146
|
+
enumerable: true,
|
|
4147
|
+
get: function () { return wodWikiLang.renderMetric; }
|
|
4148
|
+
});
|
|
1600
4149
|
Object.defineProperty(exports, "strategyRegistry", {
|
|
1601
4150
|
enumerable: true,
|
|
1602
4151
|
get: function () { return wodWikiLang.strategyRegistry; }
|
|
@@ -1633,86 +4182,6 @@ Object.defineProperty(exports, "writeChoiceSelection", {
|
|
|
1633
4182
|
enumerable: true,
|
|
1634
4183
|
get: function () { return wodWikiLang.writeChoiceSelection; }
|
|
1635
4184
|
});
|
|
1636
|
-
Object.defineProperty(exports, "BlockTimerDisplay", {
|
|
1637
|
-
enumerable: true,
|
|
1638
|
-
get: function () { return react.BlockTimerDisplay; }
|
|
1639
|
-
});
|
|
1640
|
-
Object.defineProperty(exports, "ScriptRuntimeProvider", {
|
|
1641
|
-
enumerable: true,
|
|
1642
|
-
get: function () { return react.ScriptRuntimeProvider; }
|
|
1643
|
-
});
|
|
1644
|
-
Object.defineProperty(exports, "useActiveControls", {
|
|
1645
|
-
enumerable: true,
|
|
1646
|
-
get: function () { return react.useActiveControls; }
|
|
1647
|
-
});
|
|
1648
|
-
Object.defineProperty(exports, "useBlockMemory", {
|
|
1649
|
-
enumerable: true,
|
|
1650
|
-
get: function () { return react.useBlockMemory; }
|
|
1651
|
-
});
|
|
1652
|
-
Object.defineProperty(exports, "useLiveAnalytics", {
|
|
1653
|
-
enumerable: true,
|
|
1654
|
-
get: function () { return react.useLiveAnalytics; }
|
|
1655
|
-
});
|
|
1656
|
-
Object.defineProperty(exports, "useMemorySubscription", {
|
|
1657
|
-
enumerable: true,
|
|
1658
|
-
get: function () { return react.useMemorySubscription; }
|
|
1659
|
-
});
|
|
1660
|
-
Object.defineProperty(exports, "useNextPreview", {
|
|
1661
|
-
enumerable: true,
|
|
1662
|
-
get: function () { return react.useNextPreview; }
|
|
1663
|
-
});
|
|
1664
|
-
Object.defineProperty(exports, "useOutputStatements", {
|
|
1665
|
-
enumerable: true,
|
|
1666
|
-
get: function () { return react.useOutputStatements; }
|
|
1667
|
-
});
|
|
1668
|
-
Object.defineProperty(exports, "usePrimaryTimer", {
|
|
1669
|
-
enumerable: true,
|
|
1670
|
-
get: function () { return react.usePrimaryTimer; }
|
|
1671
|
-
});
|
|
1672
|
-
Object.defineProperty(exports, "useRoundDisplay", {
|
|
1673
|
-
enumerable: true,
|
|
1674
|
-
get: function () { return react.useRoundDisplay; }
|
|
1675
|
-
});
|
|
1676
|
-
Object.defineProperty(exports, "useRuntimeExecution", {
|
|
1677
|
-
enumerable: true,
|
|
1678
|
-
get: function () { return react.useRuntimeExecution; }
|
|
1679
|
-
});
|
|
1680
|
-
Object.defineProperty(exports, "useScriptRuntime", {
|
|
1681
|
-
enumerable: true,
|
|
1682
|
-
get: function () { return react.useScriptRuntime; }
|
|
1683
|
-
});
|
|
1684
|
-
Object.defineProperty(exports, "useSecondaryTimers", {
|
|
1685
|
-
enumerable: true,
|
|
1686
|
-
get: function () { return react.useSecondaryTimers; }
|
|
1687
|
-
});
|
|
1688
|
-
Object.defineProperty(exports, "useSnapshotBlocks", {
|
|
1689
|
-
enumerable: true,
|
|
1690
|
-
get: function () { return react.useSnapshotBlocks; }
|
|
1691
|
-
});
|
|
1692
|
-
Object.defineProperty(exports, "useStackDisplayRows", {
|
|
1693
|
-
enumerable: true,
|
|
1694
|
-
get: function () { return react.useStackDisplayRows; }
|
|
1695
|
-
});
|
|
1696
|
-
Object.defineProperty(exports, "useStackFragmentSources", {
|
|
1697
|
-
enumerable: true,
|
|
1698
|
-
get: function () { return react.useStackFragmentSources; }
|
|
1699
|
-
});
|
|
1700
|
-
Object.defineProperty(exports, "useStackSnapshot", {
|
|
1701
|
-
enumerable: true,
|
|
1702
|
-
get: function () { return react.useStackSnapshot; }
|
|
1703
|
-
});
|
|
1704
|
-
Object.defineProperty(exports, "useStackTimers", {
|
|
1705
|
-
enumerable: true,
|
|
1706
|
-
get: function () { return react.useStackTimers; }
|
|
1707
|
-
});
|
|
1708
|
-
Object.defineProperty(exports, "useTimerDisplay", {
|
|
1709
|
-
enumerable: true,
|
|
1710
|
-
get: function () { return react.useTimerDisplay; }
|
|
1711
|
-
});
|
|
1712
|
-
Object.defineProperty(exports, "useTimerElapsed", {
|
|
1713
|
-
enumerable: true,
|
|
1714
|
-
get: function () { return react.useTimerElapsed; }
|
|
1715
|
-
});
|
|
1716
4185
|
Object.defineProperty(exports, "DASHBOARD_GRID_MAX_COLS", {
|
|
1717
4186
|
enumerable: true,
|
|
1718
4187
|
get: function () { return wodWikiWql.DASHBOARD_GRID_MAX_COLS; }
|
|
@@ -1741,6 +4210,10 @@ Object.defineProperty(exports, "WQL_CALC_TARGETS", {
|
|
|
1741
4210
|
enumerable: true,
|
|
1742
4211
|
get: function () { return wodWikiWql.WQL_CALC_TARGETS; }
|
|
1743
4212
|
});
|
|
4213
|
+
Object.defineProperty(exports, "WQL_CHART_HEADS", {
|
|
4214
|
+
enumerable: true,
|
|
4215
|
+
get: function () { return wodWikiWql.WQL_CHART_HEADS; }
|
|
4216
|
+
});
|
|
1744
4217
|
Object.defineProperty(exports, "WQL_COMPARISON_OPS", {
|
|
1745
4218
|
enumerable: true,
|
|
1746
4219
|
get: function () { return wodWikiWql.WQL_COMPARISON_OPS; }
|
|
@@ -1757,6 +4230,10 @@ Object.defineProperty(exports, "WQL_FIND_TARGETS", {
|
|
|
1757
4230
|
enumerable: true,
|
|
1758
4231
|
get: function () { return wodWikiWql.WQL_FIND_TARGETS; }
|
|
1759
4232
|
});
|
|
4233
|
+
Object.defineProperty(exports, "WQL_FUNCTION_HEADS", {
|
|
4234
|
+
enumerable: true,
|
|
4235
|
+
get: function () { return wodWikiWql.WQL_FUNCTION_HEADS; }
|
|
4236
|
+
});
|
|
1760
4237
|
Object.defineProperty(exports, "WQL_GRAINS", {
|
|
1761
4238
|
enumerable: true,
|
|
1762
4239
|
get: function () { return wodWikiWql.WQL_GRAINS; }
|
|
@@ -1785,22 +4262,22 @@ Object.defineProperty(exports, "WQL_ROLLUP_PERIODS", {
|
|
|
1785
4262
|
enumerable: true,
|
|
1786
4263
|
get: function () { return wodWikiWql.WQL_ROLLUP_PERIODS; }
|
|
1787
4264
|
});
|
|
1788
|
-
Object.defineProperty(exports, "WQL_ROWS_SCOPE_KEYS", {
|
|
1789
|
-
enumerable: true,
|
|
1790
|
-
get: function () { return wodWikiWql.WQL_ROWS_SCOPE_KEYS; }
|
|
1791
|
-
});
|
|
1792
|
-
Object.defineProperty(exports, "WQL_ROWS_TARGETS", {
|
|
1793
|
-
enumerable: true,
|
|
1794
|
-
get: function () { return wodWikiWql.WQL_ROWS_TARGETS; }
|
|
1795
|
-
});
|
|
1796
4265
|
Object.defineProperty(exports, "WQL_SOURCES", {
|
|
1797
4266
|
enumerable: true,
|
|
1798
4267
|
get: function () { return wodWikiWql.WQL_SOURCES; }
|
|
1799
4268
|
});
|
|
4269
|
+
Object.defineProperty(exports, "WQL_SOURCE_HEADS", {
|
|
4270
|
+
enumerable: true,
|
|
4271
|
+
get: function () { return wodWikiWql.WQL_SOURCE_HEADS; }
|
|
4272
|
+
});
|
|
1800
4273
|
Object.defineProperty(exports, "WQL_SOURCE_VALUES", {
|
|
1801
4274
|
enumerable: true,
|
|
1802
4275
|
get: function () { return wodWikiWql.WQL_SOURCE_VALUES; }
|
|
1803
4276
|
});
|
|
4277
|
+
Object.defineProperty(exports, "WQL_STANDARD_DATASETS", {
|
|
4278
|
+
enumerable: true,
|
|
4279
|
+
get: function () { return wodWikiWql.WQL_STANDARD_DATASETS; }
|
|
4280
|
+
});
|
|
1804
4281
|
Object.defineProperty(exports, "WQL_TAG_KEYS", {
|
|
1805
4282
|
enumerable: true,
|
|
1806
4283
|
get: function () { return wodWikiWql.WQL_TAG_KEYS; }
|
|
@@ -1845,9 +4322,9 @@ Object.defineProperty(exports, "isFindQuery", {
|
|
|
1845
4322
|
enumerable: true,
|
|
1846
4323
|
get: function () { return wodWikiWql.isFindQuery; }
|
|
1847
4324
|
});
|
|
1848
|
-
Object.defineProperty(exports, "
|
|
4325
|
+
Object.defineProperty(exports, "isPipelineQuery", {
|
|
1849
4326
|
enumerable: true,
|
|
1850
|
-
get: function () { return wodWikiWql.
|
|
4327
|
+
get: function () { return wodWikiWql.isPipelineQuery; }
|
|
1851
4328
|
});
|
|
1852
4329
|
Object.defineProperty(exports, "normalizeWql", {
|
|
1853
4330
|
enumerable: true,
|
|
@@ -1877,10 +4354,6 @@ Object.defineProperty(exports, "splitAtWhere", {
|
|
|
1877
4354
|
enumerable: true,
|
|
1878
4355
|
get: function () { return wodWikiWql.splitAtWhere; }
|
|
1879
4356
|
});
|
|
1880
|
-
Object.defineProperty(exports, "splitWidgetBody", {
|
|
1881
|
-
enumerable: true,
|
|
1882
|
-
get: function () { return wodWikiWql.splitWidgetBody; }
|
|
1883
|
-
});
|
|
1884
4357
|
Object.defineProperty(exports, "substituteTokens", {
|
|
1885
4358
|
enumerable: true,
|
|
1886
4359
|
get: function () { return wodWikiWql.substituteTokens; }
|
|
@@ -1909,36 +4382,128 @@ Object.defineProperty(exports, "wqlTerms", {
|
|
|
1909
4382
|
enumerable: true,
|
|
1910
4383
|
get: function () { return wodWikiWql.wqlTerms; }
|
|
1911
4384
|
});
|
|
4385
|
+
Object.defineProperty(exports, "BlockTimerDisplay", {
|
|
4386
|
+
enumerable: true,
|
|
4387
|
+
get: function () { return react.BlockTimerDisplay; }
|
|
4388
|
+
});
|
|
4389
|
+
Object.defineProperty(exports, "ScriptRuntimeProvider", {
|
|
4390
|
+
enumerable: true,
|
|
4391
|
+
get: function () { return react.ScriptRuntimeProvider; }
|
|
4392
|
+
});
|
|
4393
|
+
Object.defineProperty(exports, "useActiveControls", {
|
|
4394
|
+
enumerable: true,
|
|
4395
|
+
get: function () { return react.useActiveControls; }
|
|
4396
|
+
});
|
|
4397
|
+
Object.defineProperty(exports, "useBlockMemory", {
|
|
4398
|
+
enumerable: true,
|
|
4399
|
+
get: function () { return react.useBlockMemory; }
|
|
4400
|
+
});
|
|
4401
|
+
Object.defineProperty(exports, "useLiveAnalytics", {
|
|
4402
|
+
enumerable: true,
|
|
4403
|
+
get: function () { return react.useLiveAnalytics; }
|
|
4404
|
+
});
|
|
4405
|
+
Object.defineProperty(exports, "useMemorySubscription", {
|
|
4406
|
+
enumerable: true,
|
|
4407
|
+
get: function () { return react.useMemorySubscription; }
|
|
4408
|
+
});
|
|
4409
|
+
Object.defineProperty(exports, "useNextPreview", {
|
|
4410
|
+
enumerable: true,
|
|
4411
|
+
get: function () { return react.useNextPreview; }
|
|
4412
|
+
});
|
|
4413
|
+
Object.defineProperty(exports, "useOutputStatements", {
|
|
4414
|
+
enumerable: true,
|
|
4415
|
+
get: function () { return react.useOutputStatements; }
|
|
4416
|
+
});
|
|
4417
|
+
Object.defineProperty(exports, "usePrimaryTimer", {
|
|
4418
|
+
enumerable: true,
|
|
4419
|
+
get: function () { return react.usePrimaryTimer; }
|
|
4420
|
+
});
|
|
4421
|
+
Object.defineProperty(exports, "useRoundDisplay", {
|
|
4422
|
+
enumerable: true,
|
|
4423
|
+
get: function () { return react.useRoundDisplay; }
|
|
4424
|
+
});
|
|
4425
|
+
Object.defineProperty(exports, "useRuntimeExecution", {
|
|
4426
|
+
enumerable: true,
|
|
4427
|
+
get: function () { return react.useRuntimeExecution; }
|
|
4428
|
+
});
|
|
4429
|
+
Object.defineProperty(exports, "useScriptRuntime", {
|
|
4430
|
+
enumerable: true,
|
|
4431
|
+
get: function () { return react.useScriptRuntime; }
|
|
4432
|
+
});
|
|
4433
|
+
Object.defineProperty(exports, "useSecondaryTimers", {
|
|
4434
|
+
enumerable: true,
|
|
4435
|
+
get: function () { return react.useSecondaryTimers; }
|
|
4436
|
+
});
|
|
4437
|
+
Object.defineProperty(exports, "useSnapshotBlocks", {
|
|
4438
|
+
enumerable: true,
|
|
4439
|
+
get: function () { return react.useSnapshotBlocks; }
|
|
4440
|
+
});
|
|
4441
|
+
Object.defineProperty(exports, "useStackDisplayRows", {
|
|
4442
|
+
enumerable: true,
|
|
4443
|
+
get: function () { return react.useStackDisplayRows; }
|
|
4444
|
+
});
|
|
4445
|
+
Object.defineProperty(exports, "useStackFragmentSources", {
|
|
4446
|
+
enumerable: true,
|
|
4447
|
+
get: function () { return react.useStackFragmentSources; }
|
|
4448
|
+
});
|
|
4449
|
+
Object.defineProperty(exports, "useStackSnapshot", {
|
|
4450
|
+
enumerable: true,
|
|
4451
|
+
get: function () { return react.useStackSnapshot; }
|
|
4452
|
+
});
|
|
4453
|
+
Object.defineProperty(exports, "useStackTimers", {
|
|
4454
|
+
enumerable: true,
|
|
4455
|
+
get: function () { return react.useStackTimers; }
|
|
4456
|
+
});
|
|
4457
|
+
Object.defineProperty(exports, "useTimerDisplay", {
|
|
4458
|
+
enumerable: true,
|
|
4459
|
+
get: function () { return react.useTimerDisplay; }
|
|
4460
|
+
});
|
|
4461
|
+
Object.defineProperty(exports, "useTimerElapsed", {
|
|
4462
|
+
enumerable: true,
|
|
4463
|
+
get: function () { return react.useTimerElapsed; }
|
|
4464
|
+
});
|
|
1912
4465
|
exports.BUILD_TIME = BUILD_TIME;
|
|
1913
4466
|
exports.GIT_SHA = GIT_SHA;
|
|
4467
|
+
exports.NOTE_ID_FRONTMATTER_KEY = NOTE_ID_FRONTMATTER_KEY;
|
|
1914
4468
|
exports.PackLoadError = PackLoadError;
|
|
1915
4469
|
exports.ParseSyntaxError = ParseSyntaxError;
|
|
1916
4470
|
exports.SEMVER = SEMVER;
|
|
4471
|
+
exports.SESSION_CSV_HEADER = SESSION_CSV_HEADER;
|
|
1917
4472
|
exports.VERSION = VERSION2;
|
|
1918
4473
|
exports.WqlSyntaxError = WqlSyntaxError;
|
|
1919
4474
|
exports.buildStatementTree = buildStatementTree;
|
|
1920
4475
|
exports.buildWorkoutResults = buildWorkoutResults;
|
|
1921
4476
|
exports.cliMain = cliMain;
|
|
4477
|
+
exports.composeNoteMarkdown = composeNoteMarkdown;
|
|
1922
4478
|
exports.createIRFile = createIRFile;
|
|
4479
|
+
exports.csvToEquipmentLabels = csvToEquipmentLabels;
|
|
4480
|
+
exports.csvToSessionRows = csvToSessionRows;
|
|
1923
4481
|
exports.defineLanguagePack = defineLanguagePack;
|
|
1924
|
-
exports.
|
|
4482
|
+
exports.effortToMarkdown = effortToMarkdown;
|
|
4483
|
+
exports.equipmentToCsv = equipmentToCsv;
|
|
4484
|
+
exports.extractNoteIdentity = extractNoteIdentity;
|
|
4485
|
+
exports.extractTypedTags = extractTypedTags;
|
|
1925
4486
|
exports.formatExecutionOutput = formatExecutionOutput;
|
|
1926
4487
|
exports.formatParseOutput = formatParseOutput;
|
|
1927
4488
|
exports.formatQueryOutput = formatQueryOutput;
|
|
1928
4489
|
exports.getRegisteredLanguagePacks = getRegisteredLanguagePacks;
|
|
1929
4490
|
exports.inMemoryEventStore = inMemoryEventStore;
|
|
1930
|
-
exports.
|
|
1931
|
-
exports.inMemoryFactStore = inMemoryFactStore;
|
|
4491
|
+
exports.injectIdentityFrontmatter = injectIdentityFrontmatter;
|
|
1932
4492
|
exports.isIRFile = isIRFile;
|
|
1933
4493
|
exports.listLanguagePacks = listLanguagePacks;
|
|
1934
4494
|
exports.loadLanguagePack = loadLanguagePack;
|
|
1935
4495
|
exports.loadQueryData = loadQueryData;
|
|
4496
|
+
exports.markdownToEffort = markdownToEffort;
|
|
1936
4497
|
exports.parseCliArgs = parseCliArgs;
|
|
4498
|
+
exports.parseCsvLine = parseCsvLine;
|
|
4499
|
+
exports.readFrontmatterMeta = readFrontmatterMeta;
|
|
1937
4500
|
exports.readStdin = readStdin;
|
|
1938
4501
|
exports.registerLanguagePack = registerLanguagePack;
|
|
1939
4502
|
exports.runExecution = runExecution;
|
|
1940
4503
|
exports.runParse = runParse;
|
|
1941
4504
|
exports.runQueryCli = runQueryCli;
|
|
4505
|
+
exports.sessionToCsv = sessionToCsv;
|
|
4506
|
+
exports.splitCsvRecords = splitCsvRecords;
|
|
1942
4507
|
exports.statementToNode = statementToNode;
|
|
1943
4508
|
exports.unregisterLanguagePack = unregisterLanguagePack;
|
|
1944
4509
|
Object.keys(wodWikiCore).forEach(function (k) {
|