@bitcobblers/wod-wiki-engine 0.35.2128 → 0.36.2130
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 +758 -194
- package/dist/bin/wod.cjs.map +1 -1
- package/dist/bin/wod.d.cts +1 -1
- package/dist/bin/wod.d.ts +1 -1
- package/dist/bin/wod.mjs +5 -3
- 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-Y6WO6MXS.mjs → chunk-BMHE7DDG.mjs} +3 -9
- package/dist/chunk-BMHE7DDG.mjs.map +1 -0
- package/dist/chunk-EUQWEX6Z.mjs +253 -0
- package/dist/chunk-EUQWEX6Z.mjs.map +1 -0
- package/dist/{chunk-N4KFWY6Z.mjs → chunk-UKBQ36RW.mjs} +377 -193
- package/dist/chunk-UKBQ36RW.mjs.map +1 -0
- package/dist/{compiler-SXUCF7AU.mjs → compiler-NODHKWJJ.mjs} +4 -3
- package/dist/compiler-NODHKWJJ.mjs.map +1 -0
- package/dist/index.cjs +763 -199
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -8
- package/dist/index.d.ts +27 -8
- package/dist/index.mjs +10 -8
- package/dist/index.mjs.map +1 -1
- package/dist/server/app.cjs +299 -0
- package/dist/server/app.cjs.map +1 -0
- package/dist/server/app.d.cts +24 -0
- package/dist/server/app.d.ts +24 -0
- package/dist/server/app.mjs +301 -0
- package/dist/server/app.mjs.map +1 -0
- package/dist/server-4P6AJAGT.mjs +128 -0
- package/dist/server-4P6AJAGT.mjs.map +1 -0
- package/dist/web/playground/ai-skills/parse.md +656 -0
- package/dist/web/playground/assets/main-Bhn9NHgQ.css +2 -0
- package/dist/web/playground/assets/main-CGSJY1vM.js +212 -0
- package/dist/web/playground/assets/rolldown-runtime-C0FnF6B9.js +1 -0
- package/dist/web/playground/assets/statementBuilderFlow-PTgOOzz5.js +2 -0
- package/dist/web/playground/assets/vendor-BlJqgFME.js +201 -0
- package/dist/web/playground/assets/vendor-codemirror-BtJgoBjf.js +40 -0
- package/dist/web/playground/fonts/Inter-Regular.woff2 +0 -0
- package/dist/web/playground/fonts/Inter-SemiBold.woff2 +0 -0
- package/dist/web/playground/images/hero/editing.png +0 -0
- package/dist/web/playground/images/hero/journal.png +0 -0
- package/dist/web/playground/images/hero/results.png +0 -0
- package/dist/web/playground/images/hero/timer.png +0 -0
- package/dist/web/playground/images/wod-wiki-logo-dark.png +0 -0
- package/dist/web/playground/images/wod-wiki-logo-light.png +0 -0
- package/dist/web/playground/index.html +60 -0
- package/dist/web/playground/seed/core/chunks/canvas.e6c77ad0.json +1 -0
- package/dist/web/playground/seed/core/chunks/template.e5e0e82d.json +1 -0
- package/dist/web/playground/seed/core/manifest.json +24 -0
- package/dist/web/playground/seed/core/report.html +43 -0
- package/dist/web/server/ai-skills/parse.md +656 -0
- package/dist/web/server/assets/main-Bhn9NHgQ.css +2 -0
- package/dist/web/server/assets/main-DhN2J5Ax.js +211 -0
- package/dist/web/server/assets/rolldown-runtime-C0FnF6B9.js +1 -0
- package/dist/web/server/assets/statementBuilderFlow-Dj0o2Wpk.js +2 -0
- package/dist/web/server/assets/vendor-BlJqgFME.js +201 -0
- package/dist/web/server/assets/vendor-codemirror-BtJgoBjf.js +40 -0
- package/dist/web/server/fonts/Inter-Regular.woff2 +0 -0
- package/dist/web/server/fonts/Inter-SemiBold.woff2 +0 -0
- package/dist/web/server/images/hero/editing.png +0 -0
- package/dist/web/server/images/hero/journal.png +0 -0
- package/dist/web/server/images/hero/results.png +0 -0
- package/dist/web/server/images/hero/timer.png +0 -0
- package/dist/web/server/images/wod-wiki-logo-dark.png +0 -0
- package/dist/web/server/images/wod-wiki-logo-light.png +0 -0
- package/dist/web/server/index.html +60 -0
- package/dist/web/server/seed/core/chunks/canvas.e6c77ad0.json +1 -0
- package/dist/web/server/seed/core/chunks/template.e5e0e82d.json +1 -0
- package/dist/web/server/seed/core/manifest.json +24 -0
- package/dist/web/server/seed/core/report.html +43 -0
- package/package.json +11 -5
- package/dist/chunk-N4KFWY6Z.mjs.map +0 -1
- package/dist/chunk-Y6WO6MXS.mjs.map +0 -1
- package/dist/compiler-SXUCF7AU.mjs.map +0 -1
package/dist/bin/wod.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S bun --no-env-file
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var wodWikiLang = require('@bitcobblers/wod-wiki-lang');
|
|
@@ -10,6 +10,7 @@ var wodWikiWql = require('@bitcobblers/wod-wiki-wql');
|
|
|
10
10
|
var crypto$1 = require('crypto');
|
|
11
11
|
var wodWikiStorage = require('@bitcobblers/wod-wiki-storage');
|
|
12
12
|
|
|
13
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
14
|
var __defProp = Object.defineProperty;
|
|
14
15
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
15
16
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -25,6 +26,252 @@ var __export = (target, all) => {
|
|
|
25
26
|
for (var name in all)
|
|
26
27
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
27
28
|
};
|
|
29
|
+
function nodeRequire() {
|
|
30
|
+
const g = globalThis;
|
|
31
|
+
if (typeof g.process?.versions?.node === "undefined" && typeof g.Bun === "undefined") {
|
|
32
|
+
throw new Error("SQLite operations are only supported in Node / Bun environments");
|
|
33
|
+
}
|
|
34
|
+
const req = g.require ?? (typeof __require === "function" ? __require : void 0);
|
|
35
|
+
if (!req) throw new Error("SQLite operations require a CJS require shim (missing tsup banner?)");
|
|
36
|
+
return req;
|
|
37
|
+
}
|
|
38
|
+
function loadSql() {
|
|
39
|
+
sqlModulePromise ??= import(
|
|
40
|
+
/* @vite-ignore */
|
|
41
|
+
'@bitcobblers/wod-wiki-storage/sql'
|
|
42
|
+
);
|
|
43
|
+
return sqlModulePromise;
|
|
44
|
+
}
|
|
45
|
+
function resolveDbPath(dbPath) {
|
|
46
|
+
const chosen = dbPath ?? process.env.WOD_WIKI_DB ?? "./wodwiki.db";
|
|
47
|
+
return nodePath().resolve(process.cwd(), chosen);
|
|
48
|
+
}
|
|
49
|
+
async function openCliDb(target, opts) {
|
|
50
|
+
if (target === void 0 || typeof target === "string") {
|
|
51
|
+
return openSqliteDb(resolveDbPath(target), opts);
|
|
52
|
+
}
|
|
53
|
+
switch (target.kind) {
|
|
54
|
+
case "sqlite":
|
|
55
|
+
return openSqliteDb(target.path, opts);
|
|
56
|
+
case "turso":
|
|
57
|
+
return openRemoteDb(
|
|
58
|
+
{ DB_DRIVER: "turso", DATABASE_URL: target.url, LIBSQL_AUTH_TOKEN: target.authToken },
|
|
59
|
+
target.label,
|
|
60
|
+
opts
|
|
61
|
+
);
|
|
62
|
+
case "postgres":
|
|
63
|
+
return openRemoteDb({ DB_DRIVER: "postgres", DATABASE_URL: target.url }, target.label, opts);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function openSqliteDb(resolved, opts) {
|
|
67
|
+
const [{ createClient }, sql] = await Promise.all([
|
|
68
|
+
import(
|
|
69
|
+
/* @vite-ignore */
|
|
70
|
+
'@libsql/client'
|
|
71
|
+
),
|
|
72
|
+
loadSql()
|
|
73
|
+
]);
|
|
74
|
+
const initialize = opts?.initialize ?? true;
|
|
75
|
+
if (!initialize && !nodeFs().existsSync(resolved)) {
|
|
76
|
+
throw new Error(`[wod] no database at ${resolved} \u2014 run \`wod init\` to create and initialize it`);
|
|
77
|
+
}
|
|
78
|
+
if (initialize) nodeFs().mkdirSync(nodePath().dirname(resolved), { recursive: true });
|
|
79
|
+
const db = sql.libsqlDatabase(createClient({ url: `file:${resolved}` }));
|
|
80
|
+
try {
|
|
81
|
+
if (initialize) {
|
|
82
|
+
await sql.initializeStore(db);
|
|
83
|
+
await sql.initializeDomain(db);
|
|
84
|
+
} else {
|
|
85
|
+
await sql.verifyReadiness(db).catch((error) => {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
throw new Error(`${message} \u2014 run \`wod init\` to initialize it`, { cause: error });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return { db, storage: new sql.SqlStorage(db), path: resolved, close: () => db.close() };
|
|
91
|
+
} catch (error) {
|
|
92
|
+
await db.close();
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function openRemoteDb(env, label, opts) {
|
|
97
|
+
const [{ openDatabase }, sql] = await Promise.all([
|
|
98
|
+
import(
|
|
99
|
+
/* @vite-ignore */
|
|
100
|
+
'@bitcobblers/wod-wiki-storage/sql/node'
|
|
101
|
+
),
|
|
102
|
+
loadSql()
|
|
103
|
+
]);
|
|
104
|
+
let db;
|
|
105
|
+
try {
|
|
106
|
+
db = await openDatabase(env, { create: opts?.create });
|
|
107
|
+
if (opts?.initialize === false) {
|
|
108
|
+
await sql.verifyReadiness(db);
|
|
109
|
+
} else {
|
|
110
|
+
await sql.initializeStore(db);
|
|
111
|
+
await sql.initializeDomain(db);
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
await db?.close().catch(() => {
|
|
115
|
+
});
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
throw new Error(
|
|
118
|
+
`[wod] ${label}: ${message}${opts?.initialize === false ? " \u2014 run `wod init` to initialize it" : ""}`,
|
|
119
|
+
{ cause: error }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return { db, storage: new sql.SqlStorage(db), path: label, close: () => db.close() };
|
|
123
|
+
}
|
|
124
|
+
function formatV5Digest(digest) {
|
|
125
|
+
const bytes = digest.slice(0, 16);
|
|
126
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
127
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
128
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0"));
|
|
129
|
+
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("")}`;
|
|
130
|
+
}
|
|
131
|
+
function uuid5(namespace, name) {
|
|
132
|
+
return formatV5Digest(nodeCrypto().createHash("sha1").update(namespace).update(name).digest());
|
|
133
|
+
}
|
|
134
|
+
function stableNoteId(sourceLabel, content) {
|
|
135
|
+
if (sourceLabel && sourceLabel !== "-") {
|
|
136
|
+
return uuid5(CLI_UUID_NAMESPACE, `wod-cli:${nodePath().resolve(process.cwd(), sourceLabel)}`);
|
|
137
|
+
}
|
|
138
|
+
return uuid5(CLI_UUID_NAMESPACE, `wod-cli:stdin:${content.length}:${content.slice(0, 256)}`);
|
|
139
|
+
}
|
|
140
|
+
function randomId() {
|
|
141
|
+
return nodeCrypto().randomUUID();
|
|
142
|
+
}
|
|
143
|
+
async function allRows(sql, db, store) {
|
|
144
|
+
return sql.listRows(db, sql.storeDefinition(store));
|
|
145
|
+
}
|
|
146
|
+
async function rowsByIndex(sql, db, store, index, only) {
|
|
147
|
+
return sql.listRows(db, sql.storeDefinition(store), { only }, void 0, index);
|
|
148
|
+
}
|
|
149
|
+
function sqliteEventStore(db) {
|
|
150
|
+
const all = async () => await allRows(await loadSql(), db, "events");
|
|
151
|
+
return {
|
|
152
|
+
getEventsByTimeRange: async (start, end) => (await all()).filter((r) => r.timestamp >= start && r.timestamp <= end),
|
|
153
|
+
getEventsByResult: async (resultId) => (await all()).filter((r) => r.resultId === resultId),
|
|
154
|
+
getEventsForNote: async (noteId) => (await all()).filter((r) => r.noteId === noteId),
|
|
155
|
+
getEventsByContent: async (blockContentId5) => (await all()).filter((r) => r.blockContentId === blockContentId5),
|
|
156
|
+
scanAll: all,
|
|
157
|
+
appendEvents: async (appended) => {
|
|
158
|
+
const sql = await loadSql();
|
|
159
|
+
await db.transaction(async (tx) => {
|
|
160
|
+
const eventsDef = sql.storeDefinition("events");
|
|
161
|
+
for (const row of appended) await sql.putRow(tx, eventsDef, row);
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
finalizeSummaries: async (resultId, finals) => {
|
|
165
|
+
const sql = await loadSql();
|
|
166
|
+
await db.transaction(async (tx) => {
|
|
167
|
+
const eventsDef = sql.storeDefinition("events");
|
|
168
|
+
const existing = (await sql.listRows(tx, eventsDef)).filter(
|
|
169
|
+
(r) => r.resultId === resultId && r.grain === "summary" && r.origin !== "user"
|
|
170
|
+
);
|
|
171
|
+
for (const row of existing) await sql.deleteRows(tx, eventsDef, row.id);
|
|
172
|
+
for (const row of finals) await sql.putRow(tx, eventsDef, row);
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
deleteEvents: async (ids) => {
|
|
176
|
+
const sql = await loadSql();
|
|
177
|
+
await db.transaction(async (tx) => {
|
|
178
|
+
const eventsDef = sql.storeDefinition("events");
|
|
179
|
+
for (const id of ids) await sql.deleteRows(tx, eventsDef, id);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function sqliteNoteStore(db) {
|
|
185
|
+
return {
|
|
186
|
+
getAllNotes: async () => await allRows(await loadSql(), db, "notes"),
|
|
187
|
+
getNotesByIds: async (noteIds) => {
|
|
188
|
+
const sql = await loadSql();
|
|
189
|
+
const rows = await sql.getRows(db, sql.storeDefinition("notes"), [...noteIds]);
|
|
190
|
+
return rows.filter((row) => row !== void 0);
|
|
191
|
+
},
|
|
192
|
+
getNoteIdsForTag: async (label) => {
|
|
193
|
+
const sql = await loadSql();
|
|
194
|
+
const tags = (await allRows(sql, db, "tags")).filter((t) => t.label === label);
|
|
195
|
+
const ids = {};
|
|
196
|
+
for (const tag of tags) {
|
|
197
|
+
for (const row of await rowsByIndex(sql, db, "note_tags", "by-tag", tag.id)) {
|
|
198
|
+
ids[row.noteId] = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return new Set(Object.keys(ids));
|
|
202
|
+
},
|
|
203
|
+
getNoteTagLabels: async (noteId) => {
|
|
204
|
+
const sql = await loadSql();
|
|
205
|
+
const tagRows = await allRows(sql, db, "tags");
|
|
206
|
+
const labelById = {};
|
|
207
|
+
for (const t of tagRows) labelById[t.id] = t.label;
|
|
208
|
+
const links = await rowsByIndex(sql, db, "note_tags", "by-note", noteId);
|
|
209
|
+
return links.map((l) => labelById[l.tagId]).filter((l) => typeof l === "string");
|
|
210
|
+
},
|
|
211
|
+
queryDomain: async (request) => {
|
|
212
|
+
const result = await (await loadSql()).handleDomainQuery(db, request);
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function sqliteBlockStore(db) {
|
|
218
|
+
return {
|
|
219
|
+
getAllBlocks: async () => await allRows(await loadSql(), db, "block_index"),
|
|
220
|
+
queryDomain: async (request) => {
|
|
221
|
+
const result = await (await loadSql()).handleDomainQuery(db, { ...request, plan: "blocks" });
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function sqliteEffortStore(db) {
|
|
227
|
+
return {
|
|
228
|
+
getAllEfforts: async () => {
|
|
229
|
+
const stored = await allRows(await loadSql(), db, "efforts");
|
|
230
|
+
const bySlug = {};
|
|
231
|
+
for (const effort of stored) bySlug[effort.slug] = effort;
|
|
232
|
+
for (const effort of wodWikiLang.bundledEfforts) {
|
|
233
|
+
bySlug[effort.slug] ??= effort;
|
|
234
|
+
}
|
|
235
|
+
return Object.values(bySlug);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function sqliteIndexStore(db, store) {
|
|
240
|
+
return {
|
|
241
|
+
getAllFromIndex: async (index, key) => rowsByIndex(await loadSql(), db, store, index, key)
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async function getNoteBundle(db, noteId) {
|
|
245
|
+
const sql = await loadSql();
|
|
246
|
+
const note = await sql.getRow(db, sql.storeDefinition("notes"), noteId);
|
|
247
|
+
return { note };
|
|
248
|
+
}
|
|
249
|
+
async function getSessionBundle(db, resultId) {
|
|
250
|
+
const sql = await loadSql();
|
|
251
|
+
const session = await sql.getRow(db, sql.storeDefinition("sessions"), resultId);
|
|
252
|
+
if (!session) return void 0;
|
|
253
|
+
const events = await sql.listRows(db, sql.storeDefinition("events"), { only: resultId }, void 0, "by-result-grain");
|
|
254
|
+
return { session, events };
|
|
255
|
+
}
|
|
256
|
+
async function getEffortBySlug(db, slug) {
|
|
257
|
+
const sql = await loadSql();
|
|
258
|
+
const efforts = await allRows(sql, db, "efforts");
|
|
259
|
+
return efforts.find((e) => e.slug === slug);
|
|
260
|
+
}
|
|
261
|
+
async function listTagsByType(db, type) {
|
|
262
|
+
const sql = await loadSql();
|
|
263
|
+
const tags = await allRows(sql, db, "tags");
|
|
264
|
+
return tags.filter((t) => t.type === type);
|
|
265
|
+
}
|
|
266
|
+
var nodePath, nodeFs, nodeCrypto, sqlModulePromise, CLI_UUID_NAMESPACE;
|
|
267
|
+
var init_db = __esm({
|
|
268
|
+
"src/cli/db.ts"() {
|
|
269
|
+
nodePath = () => nodeRequire()("node:path");
|
|
270
|
+
nodeFs = () => nodeRequire()("node:fs");
|
|
271
|
+
nodeCrypto = () => nodeRequire()("node:crypto");
|
|
272
|
+
CLI_UUID_NAMESPACE = "c0ffee00-8b1a-4e2c-9d3f-5a6b7c8d9e0f";
|
|
273
|
+
}
|
|
274
|
+
});
|
|
28
275
|
|
|
29
276
|
// src/cli/seedbuild/frontmatter.ts
|
|
30
277
|
function unquote(value) {
|
|
@@ -805,6 +1052,132 @@ var init_compiler = __esm({
|
|
|
805
1052
|
};
|
|
806
1053
|
}
|
|
807
1054
|
});
|
|
1055
|
+
|
|
1056
|
+
// src/cli/server.ts
|
|
1057
|
+
var server_exports = {};
|
|
1058
|
+
__export(server_exports, {
|
|
1059
|
+
runCliServer: () => runCliServer
|
|
1060
|
+
});
|
|
1061
|
+
function bun() {
|
|
1062
|
+
const b = globalThis.Bun;
|
|
1063
|
+
if (!b) {
|
|
1064
|
+
throw new Error("wod server/playground requires the Bun runtime \u2014 start with: bun wod server");
|
|
1065
|
+
}
|
|
1066
|
+
return b;
|
|
1067
|
+
}
|
|
1068
|
+
function moduleDir() {
|
|
1069
|
+
const p = nodePath();
|
|
1070
|
+
const meta = ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('wod.cjs', document.baseURI).href)) });
|
|
1071
|
+
if (typeof meta.dir === "string") return p.resolve(meta.dir);
|
|
1072
|
+
if (typeof meta.url === "string" && meta.url.startsWith("file:")) {
|
|
1073
|
+
return p.resolve(p.dirname(decodeURIComponent(new URL(".", meta.url).pathname)));
|
|
1074
|
+
}
|
|
1075
|
+
throw new Error("wod server/playground: cannot determine the installed engine module directory");
|
|
1076
|
+
}
|
|
1077
|
+
function engineRoot() {
|
|
1078
|
+
const p = nodePath();
|
|
1079
|
+
const fs = nodeFs();
|
|
1080
|
+
for (let dir = moduleDir(); ; dir = p.dirname(dir)) {
|
|
1081
|
+
try {
|
|
1082
|
+
if (JSON.parse(fs.readFileSync(p.resolve(dir, "package.json"), "utf8")).name === "@bitcobblers/wod-wiki-engine") return dir;
|
|
1083
|
+
} catch {
|
|
1084
|
+
}
|
|
1085
|
+
const parent = p.dirname(dir);
|
|
1086
|
+
if (parent === dir) break;
|
|
1087
|
+
}
|
|
1088
|
+
throw new Error("wod server/playground: cannot locate the installed @bitcobblers/wod-wiki-engine package");
|
|
1089
|
+
}
|
|
1090
|
+
function resolveDist(mode, options) {
|
|
1091
|
+
const p = nodePath();
|
|
1092
|
+
const explicit = options.dist?.trim() || options.env.PLAYGROUND_DIST?.trim() || void 0;
|
|
1093
|
+
if (explicit) {
|
|
1094
|
+
const dist2 = p.resolve(explicit);
|
|
1095
|
+
if (!nodeFs().existsSync(p.resolve(dist2, "index.html"))) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`wod ${mode}: no built frontend (index.html) at ${dist2} \u2014 --dist/PLAYGROUND_DIST must point at an already-built directory (the CLI never builds frontends at runtime)`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
return dist2;
|
|
1101
|
+
}
|
|
1102
|
+
const dist = p.resolve(engineRoot(), "dist", "web", mode === "playground" ? "playground" : "server");
|
|
1103
|
+
if (!nodeFs().existsSync(p.resolve(dist, "index.html"))) {
|
|
1104
|
+
throw new Error(
|
|
1105
|
+
`wod ${mode}: packaged frontend missing \u2014 expected ${dist}/index.html. Reinstall @bitcobblers/wod-wiki-engine (bun add @bitcobblers/wod-wiki-engine), or rebuild the release assets from a checkout (bun run build:cli) and reinstall the package.`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
return dist;
|
|
1109
|
+
}
|
|
1110
|
+
function resolveBind(mode, options) {
|
|
1111
|
+
const host = options.host?.trim() || options.env.HOST?.trim() || "127.0.0.1";
|
|
1112
|
+
const rawPort = options.port?.trim() || options.env.PORT?.trim() || "3000";
|
|
1113
|
+
const port = Number(rawPort);
|
|
1114
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
1115
|
+
throw new Error(`wod ${mode}: invalid port ${JSON.stringify(rawPort)} (need integer 1..65535)`);
|
|
1116
|
+
}
|
|
1117
|
+
return { host, port };
|
|
1118
|
+
}
|
|
1119
|
+
async function runCliServer(mode, options, writeOut) {
|
|
1120
|
+
const runtime = bun();
|
|
1121
|
+
const { host, port } = resolveBind(mode, options);
|
|
1122
|
+
let db;
|
|
1123
|
+
let dbLabel = "no database";
|
|
1124
|
+
if (mode === "server") {
|
|
1125
|
+
const database = options.database;
|
|
1126
|
+
if (!database) {
|
|
1127
|
+
throw new Error("wod server: no database configuration resolved \u2014 this is a runner bug (expected resolveDatabaseConfig output)");
|
|
1128
|
+
}
|
|
1129
|
+
try {
|
|
1130
|
+
db = await openCliDb(database, { initialize: false });
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1133
|
+
throw new Error(`wod server: database not ready \u2014 run \`wod init\` first. ${message}`, { cause: e });
|
|
1134
|
+
}
|
|
1135
|
+
dbLabel = db.path;
|
|
1136
|
+
}
|
|
1137
|
+
let server;
|
|
1138
|
+
try {
|
|
1139
|
+
const dist = resolveDist(mode, options);
|
|
1140
|
+
const handlerModule = "@bitcobblers/wod-wiki-engine/server";
|
|
1141
|
+
const { createHandler } = await import(
|
|
1142
|
+
/* @vite-ignore */
|
|
1143
|
+
handlerModule
|
|
1144
|
+
);
|
|
1145
|
+
const handler = createHandler(db?.db ?? null, { playgroundDist: dist });
|
|
1146
|
+
server = runtime.serve({ port, hostname: host, fetch: handler });
|
|
1147
|
+
if (mode === "server") {
|
|
1148
|
+
writeOut(`wod server: http://${host}:${port} \u2014 ${dbLabel} \xB7 dist ${dist}`);
|
|
1149
|
+
} else {
|
|
1150
|
+
writeOut(`wod playground: http://${host}:${port} \u2014 in-browser storage (no database) \xB7 dist ${dist}`);
|
|
1151
|
+
}
|
|
1152
|
+
} catch (e) {
|
|
1153
|
+
if (db) await db.close();
|
|
1154
|
+
throw e;
|
|
1155
|
+
}
|
|
1156
|
+
let closing = false;
|
|
1157
|
+
const shutdown = async () => {
|
|
1158
|
+
if (closing) process.exit(0);
|
|
1159
|
+
closing = true;
|
|
1160
|
+
try {
|
|
1161
|
+
await server.stop();
|
|
1162
|
+
} finally {
|
|
1163
|
+
if (db) await db.close();
|
|
1164
|
+
process.exit(0);
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
process.on("SIGINT", () => {
|
|
1168
|
+
void shutdown();
|
|
1169
|
+
});
|
|
1170
|
+
process.on("SIGTERM", () => {
|
|
1171
|
+
void shutdown();
|
|
1172
|
+
});
|
|
1173
|
+
await new Promise(() => {
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
var init_server = __esm({
|
|
1177
|
+
"src/cli/server.ts"() {
|
|
1178
|
+
init_db();
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
808
1181
|
var activePacks = /* @__PURE__ */ new Map();
|
|
809
1182
|
function registerLanguagePack(pack) {
|
|
810
1183
|
const packId = pack.identity?.id ?? pack.identity?.name ?? "anonymous-pack";
|
|
@@ -1175,7 +1548,7 @@ function loadQueryData(options) {
|
|
|
1175
1548
|
} else if (obj.facts || obj.results || obj.notes || obj.blocks || obj.efforts || obj.tags || obj.logs) {
|
|
1176
1549
|
const corpus = payload;
|
|
1177
1550
|
if (corpus.facts) data.facts = corpus.facts;
|
|
1178
|
-
if (corpus.results) data.results = corpus.results;
|
|
1551
|
+
if (Array.isArray(corpus.results)) data.results = corpus.results;
|
|
1179
1552
|
if (corpus.notes) data.notes = corpus.notes;
|
|
1180
1553
|
if (corpus.blocks) data.blocks = corpus.blocks;
|
|
1181
1554
|
if (corpus.efforts) data.efforts = corpus.efforts;
|
|
@@ -1302,193 +1675,256 @@ async function runQueryCli(wqlString, options = {}) {
|
|
|
1302
1675
|
});
|
|
1303
1676
|
return createIRFile("query-result", result, { source: options.sourceLabel ?? "cli:wod query" });
|
|
1304
1677
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1678
|
+
|
|
1679
|
+
// src/cli/runner.ts
|
|
1680
|
+
init_db();
|
|
1681
|
+
|
|
1682
|
+
// src/cli/config.ts
|
|
1683
|
+
function nodeRequire2() {
|
|
1309
1684
|
const g = globalThis;
|
|
1310
1685
|
if (typeof g.process?.versions?.node === "undefined" && typeof g.Bun === "undefined") {
|
|
1311
|
-
throw new Error("
|
|
1686
|
+
throw new Error("database configuration is only supported in Node / Bun environments");
|
|
1312
1687
|
}
|
|
1313
1688
|
const req = g.require ?? (typeof __require === "function" ? __require : void 0);
|
|
1314
|
-
if (!req) throw new Error("
|
|
1689
|
+
if (!req) throw new Error("database configuration requires a CJS require shim (missing tsup banner?)");
|
|
1315
1690
|
return req;
|
|
1316
1691
|
}
|
|
1317
|
-
var
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1692
|
+
var nodePath2 = () => nodeRequire2()("node:path");
|
|
1693
|
+
var nodeFs2 = () => nodeRequire2()("node:fs");
|
|
1694
|
+
function parseEnvFile(content) {
|
|
1695
|
+
return nodeRequire2()("node:util").parseEnv(content);
|
|
1696
|
+
}
|
|
1697
|
+
function loadCliEnvironment(envFile) {
|
|
1698
|
+
const fs = nodeFs2();
|
|
1699
|
+
const path = nodePath2();
|
|
1700
|
+
let file;
|
|
1701
|
+
if (envFile !== void 0) {
|
|
1702
|
+
file = path.resolve(process.cwd(), envFile);
|
|
1703
|
+
if (!fs.existsSync(file)) throw new Error(`[wod] --env-file: file not found: ${envFile}`);
|
|
1704
|
+
} else {
|
|
1705
|
+
let dir = process.cwd();
|
|
1706
|
+
for (; ; ) {
|
|
1707
|
+
const candidate = path.join(dir, ".env");
|
|
1708
|
+
if (fs.existsSync(candidate)) {
|
|
1709
|
+
file = candidate;
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
const parent = path.dirname(dir);
|
|
1713
|
+
if (parent === dir) break;
|
|
1714
|
+
dir = parent;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
return file !== void 0 ? parseEnvFile(fs.readFileSync(file, "utf-8")) : {};
|
|
1718
|
+
}
|
|
1719
|
+
var SQLITE_URL_SCHEMES = /^(libsql|https?|wss?|file):\/\//;
|
|
1720
|
+
var PG_URL_SCHEMES = /^postgres(ql)?:\/\//;
|
|
1721
|
+
var TURSO_URL_SCHEMES = /^(libsql|https?|wss?):\/\//;
|
|
1722
|
+
var PG_FLAG_KEYS = [
|
|
1723
|
+
"postgresHost",
|
|
1724
|
+
"postgresPort",
|
|
1725
|
+
"postgresUser",
|
|
1726
|
+
"postgresPassword",
|
|
1727
|
+
"postgresDatabase",
|
|
1728
|
+
"postgresSsl"
|
|
1729
|
+
];
|
|
1730
|
+
var PG_ENV_KEYS = [
|
|
1731
|
+
"POSTGRES_HOST",
|
|
1732
|
+
"POSTGRES_PORT",
|
|
1733
|
+
"POSTGRES_USER",
|
|
1734
|
+
"POSTGRES_PASSWORD",
|
|
1735
|
+
"POSTGRES_DATABASE",
|
|
1736
|
+
"POSTGRES_SSL"
|
|
1737
|
+
];
|
|
1738
|
+
var isBackend = (v) => v === "sqlite" || v === "postgres" || v === "turso";
|
|
1739
|
+
function setEnv(v) {
|
|
1740
|
+
return v !== void 0 && v.trim() !== "" ? v : void 0;
|
|
1741
|
+
}
|
|
1742
|
+
function fail(message) {
|
|
1743
|
+
throw new Error(`[wod] ${message}`);
|
|
1744
|
+
}
|
|
1745
|
+
function parsePort(v, source) {
|
|
1746
|
+
if (!/^\d+$/.test(v)) fail(`${source} must be an integer 1..65535, got ${JSON.stringify(v)}`);
|
|
1747
|
+
const port = Number(v);
|
|
1748
|
+
if (port < 1 || port > 65535) fail(`${source} must be an integer 1..65535, got ${v}`);
|
|
1749
|
+
return port;
|
|
1750
|
+
}
|
|
1751
|
+
function sslMode(v, source) {
|
|
1752
|
+
if (v === void 0 || v === "") return "prefer";
|
|
1753
|
+
if (typeof v === "boolean") return v ? "require" : "disable";
|
|
1754
|
+
const s = v.trim().toLowerCase();
|
|
1755
|
+
if (["true", "1", "yes", "require"].includes(s)) return "require";
|
|
1756
|
+
if (["false", "0", "no", "disable", "disabled"].includes(s)) return "disable";
|
|
1757
|
+
if (s === "prefer" || s === "verify-ca" || s === "verify-full") return s;
|
|
1758
|
+
fail(`${source} must be disable|prefer|require|verify-ca|verify-full, got ${JSON.stringify(v)}`);
|
|
1759
|
+
}
|
|
1760
|
+
function safeUrlLabel(raw) {
|
|
1340
1761
|
try {
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
await db.close();
|
|
1346
|
-
throw error;
|
|
1762
|
+
const u = new URL(raw);
|
|
1763
|
+
return `${u.protocol}//${u.host}${u.pathname}`;
|
|
1764
|
+
} catch {
|
|
1765
|
+
return "<unparsable-url>";
|
|
1347
1766
|
}
|
|
1348
1767
|
}
|
|
1349
|
-
function
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
1353
|
-
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0"));
|
|
1354
|
-
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("")}`;
|
|
1355
|
-
}
|
|
1356
|
-
function uuid5(namespace, name) {
|
|
1357
|
-
return formatV5Digest(nodeCrypto().createHash("sha1").update(namespace).update(name).digest());
|
|
1358
|
-
}
|
|
1359
|
-
var CLI_UUID_NAMESPACE = "c0ffee00-8b1a-4e2c-9d3f-5a6b7c8d9e0f";
|
|
1360
|
-
function stableNoteId(sourceLabel, content) {
|
|
1361
|
-
if (sourceLabel && sourceLabel !== "-") {
|
|
1362
|
-
return uuid5(CLI_UUID_NAMESPACE, `wod-cli:${nodePath().resolve(process.cwd(), sourceLabel)}`);
|
|
1768
|
+
function postgresFromUrl(url, source) {
|
|
1769
|
+
if (!PG_URL_SCHEMES.test(url)) {
|
|
1770
|
+
fail(`${source} must start with postgres:// or postgresql://`);
|
|
1363
1771
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
}
|
|
1372
|
-
async function rowsByIndex(sql, db, store, index, only) {
|
|
1373
|
-
return sql.listRows(db, sql.storeDefinition(store), { only }, void 0, index);
|
|
1374
|
-
}
|
|
1375
|
-
function sqliteEventStore(db) {
|
|
1376
|
-
const all = async () => await allRows(await loadSql(), db, "events");
|
|
1772
|
+
let u;
|
|
1773
|
+
try {
|
|
1774
|
+
u = new URL(url);
|
|
1775
|
+
} catch {
|
|
1776
|
+
fail(`${source} is not a parseable URL`);
|
|
1777
|
+
}
|
|
1778
|
+
const database = decodeURIComponent(u.pathname.replace(/^\/+|\/+$/g, ""));
|
|
1779
|
+
if (database === "") fail(`${source} has no database name`);
|
|
1377
1780
|
return {
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
for (const row of appended) await sql.putRow(tx, eventsDef, row);
|
|
1388
|
-
});
|
|
1389
|
-
},
|
|
1390
|
-
finalizeSummaries: async (resultId, finals) => {
|
|
1391
|
-
const sql = await loadSql();
|
|
1392
|
-
await db.transaction(async (tx) => {
|
|
1393
|
-
const eventsDef = sql.storeDefinition("events");
|
|
1394
|
-
const existing = (await sql.listRows(tx, eventsDef)).filter(
|
|
1395
|
-
(r) => r.resultId === resultId && r.grain === "summary" && r.origin !== "user"
|
|
1396
|
-
);
|
|
1397
|
-
for (const row of existing) await sql.deleteRows(tx, eventsDef, row.id);
|
|
1398
|
-
for (const row of finals) await sql.putRow(tx, eventsDef, row);
|
|
1399
|
-
});
|
|
1400
|
-
},
|
|
1401
|
-
deleteEvents: async (ids) => {
|
|
1402
|
-
const sql = await loadSql();
|
|
1403
|
-
await db.transaction(async (tx) => {
|
|
1404
|
-
const eventsDef = sql.storeDefinition("events");
|
|
1405
|
-
for (const id of ids) await sql.deleteRows(tx, eventsDef, id);
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1781
|
+
kind: "postgres",
|
|
1782
|
+
label: safeUrlLabel(url),
|
|
1783
|
+
url,
|
|
1784
|
+
host: u.hostname,
|
|
1785
|
+
port: u.port !== "" ? parsePort(u.port, `${source} port`) : 5432,
|
|
1786
|
+
database,
|
|
1787
|
+
user: u.username !== "" ? decodeURIComponent(u.username) : void 0,
|
|
1788
|
+
password: u.password !== "" ? decodeURIComponent(u.password) : void 0,
|
|
1789
|
+
ssl: sslMode(u.searchParams.get("sslmode") ?? u.searchParams.get("ssl") ?? void 0, `${source} ssl parameter`)
|
|
1408
1790
|
};
|
|
1409
1791
|
}
|
|
1410
|
-
function
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
const sql = await loadSql();
|
|
1420
|
-
const tags = (await allRows(sql, db, "tags")).filter((t) => t.label === label);
|
|
1421
|
-
const ids = {};
|
|
1422
|
-
for (const tag of tags) {
|
|
1423
|
-
for (const row of await rowsByIndex(sql, db, "note_tags", "by-tag", tag.id)) {
|
|
1424
|
-
ids[row.noteId] = true;
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
return new Set(Object.keys(ids));
|
|
1428
|
-
},
|
|
1429
|
-
getNoteTagLabels: async (noteId) => {
|
|
1430
|
-
const sql = await loadSql();
|
|
1431
|
-
const tagRows = await allRows(sql, db, "tags");
|
|
1432
|
-
const labelById = {};
|
|
1433
|
-
for (const t of tagRows) labelById[t.id] = t.label;
|
|
1434
|
-
const links = await rowsByIndex(sql, db, "note_tags", "by-note", noteId);
|
|
1435
|
-
return links.map((l) => labelById[l.tagId]).filter((l) => typeof l === "string");
|
|
1436
|
-
},
|
|
1437
|
-
queryDomain: async (request) => {
|
|
1438
|
-
const result = await (await loadSql()).handleDomainQuery(db, request);
|
|
1439
|
-
return result;
|
|
1440
|
-
}
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
function sqliteBlockStore(db) {
|
|
1444
|
-
return {
|
|
1445
|
-
getAllBlocks: async () => await allRows(await loadSql(), db, "block_index"),
|
|
1446
|
-
queryDomain: async (request) => {
|
|
1447
|
-
const result = await (await loadSql()).handleDomainQuery(db, { ...request, plan: "blocks" });
|
|
1448
|
-
return result;
|
|
1792
|
+
function resolvePostgres(options, e, databaseUrl) {
|
|
1793
|
+
const flagProps = PG_FLAG_KEYS.some((k) => options[k] !== void 0);
|
|
1794
|
+
const envProps = PG_ENV_KEYS.some((k) => setEnv(e[k]) !== void 0);
|
|
1795
|
+
if (flagProps || envProps && databaseUrl === void 0) {
|
|
1796
|
+
const host = options.postgresHost ?? setEnv(e.POSTGRES_HOST) ?? "127.0.0.1";
|
|
1797
|
+
const port = parsePort(options.postgresPort ?? setEnv(e.POSTGRES_PORT) ?? "5432", "--postgres-port/POSTGRES_PORT");
|
|
1798
|
+
const database = options.postgresDatabase ?? setEnv(e.POSTGRES_DATABASE);
|
|
1799
|
+
if (database === void 0) {
|
|
1800
|
+
fail("--postgres-database (or POSTGRES_DATABASE) is required for per-property PostgreSQL configuration");
|
|
1449
1801
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1802
|
+
const user = options.postgresUser ?? setEnv(e.POSTGRES_USER);
|
|
1803
|
+
const password = options.postgresPassword ?? setEnv(e.POSTGRES_PASSWORD);
|
|
1804
|
+
const ssl = sslMode(options.postgresSsl ?? setEnv(e.POSTGRES_SSL), "--postgres-ssl/POSTGRES_SSL");
|
|
1805
|
+
try {
|
|
1806
|
+
const u = new URL(`postgresql://${host}`);
|
|
1807
|
+
u.port = String(port);
|
|
1808
|
+
u.pathname = `/${database}`;
|
|
1809
|
+
if (user !== void 0) u.username = user;
|
|
1810
|
+
if (password !== void 0) u.password = password;
|
|
1811
|
+
u.searchParams.set("sslmode", ssl);
|
|
1812
|
+
return {
|
|
1813
|
+
kind: "postgres",
|
|
1814
|
+
label: `postgresql://${u.host}${u.pathname}`,
|
|
1815
|
+
url: u.toString(),
|
|
1816
|
+
host,
|
|
1817
|
+
port,
|
|
1818
|
+
database,
|
|
1819
|
+
user,
|
|
1820
|
+
password,
|
|
1821
|
+
ssl
|
|
1822
|
+
};
|
|
1823
|
+
} catch {
|
|
1824
|
+
fail("invalid PostgreSQL host/database in per-property configuration");
|
|
1462
1825
|
}
|
|
1463
|
-
}
|
|
1826
|
+
}
|
|
1827
|
+
if (databaseUrl !== void 0) return postgresFromUrl(databaseUrl, "DB_DRIVER=postgres DATABASE_URL");
|
|
1828
|
+
fail("DB_DRIVER=postgres requires DATABASE_URL starting with postgres:// or postgresql:// \u2014 or per-property --postgres-host/--postgres-database flags");
|
|
1464
1829
|
}
|
|
1465
|
-
function
|
|
1830
|
+
function resolveTurso(options, e, databaseUrl) {
|
|
1831
|
+
const url = options.tursoUrl ?? setEnv(e.TURSO_DATABASE_URL) ?? databaseUrl;
|
|
1832
|
+
if (url === void 0) {
|
|
1833
|
+
fail("DB_DRIVER=turso requires --turso-url / TURSO_DATABASE_URL or DATABASE_URL (libsql:// or https://)");
|
|
1834
|
+
}
|
|
1835
|
+
if (PG_URL_SCHEMES.test(url)) fail("DB_DRIVER=turso cannot use a postgres:// URL");
|
|
1836
|
+
if (!TURSO_URL_SCHEMES.test(url)) fail("DB_DRIVER=turso requires a libsql://, https://, http:// or wss:// URL");
|
|
1466
1837
|
return {
|
|
1467
|
-
|
|
1838
|
+
kind: "turso",
|
|
1839
|
+
label: safeUrlLabel(url),
|
|
1840
|
+
url,
|
|
1841
|
+
authToken: options.tursoAuthToken ?? setEnv(e.TURSO_AUTH_TOKEN) ?? setEnv(e.LIBSQL_AUTH_TOKEN)
|
|
1468
1842
|
};
|
|
1469
1843
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1844
|
+
function resolveSqlite(options, e, databaseUrl) {
|
|
1845
|
+
if (options.db !== void 0) {
|
|
1846
|
+
const path2 = nodePath2().resolve(process.cwd(), options.db);
|
|
1847
|
+
return { kind: "sqlite", label: path2, path: path2 };
|
|
1848
|
+
}
|
|
1849
|
+
if (databaseUrl !== void 0) {
|
|
1850
|
+
if (PG_URL_SCHEMES.test(databaseUrl)) fail("DB_DRIVER=sqlite cannot use a postgres:// DATABASE_URL");
|
|
1851
|
+
if (databaseUrl.startsWith("file:")) {
|
|
1852
|
+
const path2 = nodePath2().resolve(process.cwd(), decodeURIComponent(databaseUrl.slice("file:".length).split("?")[0]));
|
|
1853
|
+
return { kind: "sqlite", label: path2, path: path2 };
|
|
1854
|
+
}
|
|
1855
|
+
if (!SQLITE_URL_SCHEMES.test(databaseUrl)) {
|
|
1856
|
+
fail("DB_DRIVER=sqlite requires a libsql://, https://, http://, wss:// or file:// DATABASE_URL");
|
|
1857
|
+
}
|
|
1858
|
+
return { kind: "turso", label: safeUrlLabel(databaseUrl), url: databaseUrl, authToken: setEnv(e.LIBSQL_AUTH_TOKEN) };
|
|
1859
|
+
}
|
|
1860
|
+
const path = nodePath2().resolve(process.cwd(), setEnv(e.WOD_WIKI_DB) ?? setEnv(e.SQLITE_PATH) ?? "./wodwiki.db");
|
|
1861
|
+
return { kind: "sqlite", label: path, path };
|
|
1862
|
+
}
|
|
1863
|
+
function envBackend(e) {
|
|
1864
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
1865
|
+
if (setEnv(e.WOD_WIKI_DB) !== void 0 || setEnv(e.SQLITE_PATH) !== void 0) kinds.add("sqlite");
|
|
1866
|
+
const dbUrl = setEnv(e.DATABASE_URL);
|
|
1867
|
+
if (PG_ENV_KEYS.some((k) => setEnv(e[k]) !== void 0) || dbUrl !== void 0 && PG_URL_SCHEMES.test(dbUrl)) {
|
|
1868
|
+
kinds.add("postgres");
|
|
1869
|
+
} else if (dbUrl !== void 0 && SQLITE_URL_SCHEMES.test(dbUrl)) {
|
|
1870
|
+
kinds.add("sqlite");
|
|
1871
|
+
}
|
|
1872
|
+
if (setEnv(e.TURSO_DATABASE_URL) !== void 0) kinds.add("turso");
|
|
1873
|
+
if (kinds.size > 1) {
|
|
1874
|
+
fail(`ambiguous database configuration: ${[...kinds].join(" + ")} env vars are both set \u2014 set DB_DRIVER (or pass explicit flags) to choose`);
|
|
1875
|
+
}
|
|
1876
|
+
return kinds.values().next().value ?? "sqlite";
|
|
1486
1877
|
}
|
|
1487
|
-
|
|
1488
|
-
const
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1878
|
+
function resolveDatabaseConfig(options = {}, env) {
|
|
1879
|
+
const e = env ?? { ...loadCliEnvironment(options.envFile), ...process.env };
|
|
1880
|
+
const driverFlag = options.dbDriver;
|
|
1881
|
+
if (driverFlag !== void 0 && !isBackend(driverFlag)) {
|
|
1882
|
+
fail(`--db-driver must be sqlite|postgres|turso, got ${JSON.stringify(driverFlag)}`);
|
|
1883
|
+
}
|
|
1884
|
+
const pinned = [];
|
|
1885
|
+
if (options.db !== void 0) pinned.push(["--db", "sqlite"]);
|
|
1886
|
+
if (driverFlag !== void 0) pinned.push([`--db-driver ${driverFlag}`, driverFlag]);
|
|
1887
|
+
if (options.tursoUrl !== void 0) pinned.push(["--turso-url", "turso"]);
|
|
1888
|
+
if (options.tursoAuthToken !== void 0) pinned.push(["--turso-auth-token", "turso"]);
|
|
1889
|
+
if (PG_FLAG_KEYS.some((k) => options[k] !== void 0)) pinned.push(["--postgres-*", "postgres"]);
|
|
1890
|
+
const kinds = new Set(pinned.map(([, kind]) => kind));
|
|
1891
|
+
if (kinds.size > 1) {
|
|
1892
|
+
fail(`conflicting database selectors: ${pinned.map(([flag, kind]) => `${flag} (${kind})`).join(", ")}`);
|
|
1893
|
+
}
|
|
1894
|
+
if (options.databaseUrl !== void 0) {
|
|
1895
|
+
if (options.db !== void 0) fail("conflicting database selectors: --db and --database-url are both set");
|
|
1896
|
+
if (options.tursoUrl !== void 0) fail("conflicting database selectors: --turso-url and --database-url are both set");
|
|
1897
|
+
if (PG_FLAG_KEYS.some((k) => options[k] !== void 0)) {
|
|
1898
|
+
fail("conflicting database selectors: --database-url and --postgres-* flags are both set");
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
let backend;
|
|
1902
|
+
if (pinned.length > 0) {
|
|
1903
|
+
backend = pinned[0][1];
|
|
1904
|
+
} else {
|
|
1905
|
+
const envDriver = setEnv(e.DB_DRIVER);
|
|
1906
|
+
if (envDriver !== void 0) {
|
|
1907
|
+
if (!isBackend(envDriver)) {
|
|
1908
|
+
fail(`Unsupported DB_DRIVER: ${envDriver} (expected sqlite|turso|postgres)`);
|
|
1909
|
+
}
|
|
1910
|
+
backend = envDriver;
|
|
1911
|
+
} else {
|
|
1912
|
+
backend = envBackend(e);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
const databaseUrl = options.databaseUrl ?? setEnv(e.DATABASE_URL);
|
|
1916
|
+
switch (backend) {
|
|
1917
|
+
case "postgres":
|
|
1918
|
+
return resolvePostgres(options, e, databaseUrl);
|
|
1919
|
+
case "turso":
|
|
1920
|
+
return resolveTurso(options, e, databaseUrl);
|
|
1921
|
+
case "sqlite":
|
|
1922
|
+
return resolveSqlite(options, e, databaseUrl);
|
|
1923
|
+
}
|
|
1491
1924
|
}
|
|
1925
|
+
|
|
1926
|
+
// src/cli/persist.ts
|
|
1927
|
+
init_db();
|
|
1492
1928
|
var registeredWidgets = wodWikiCore.KNOWN_SECTION_WIDGETS;
|
|
1493
1929
|
var saveStores = ["notes", "block_revisions", "block_index", "block_efforts", "tags", "note_tags", "sessions"];
|
|
1494
1930
|
function extractEffortSlugs(statements) {
|
|
@@ -1758,6 +2194,9 @@ async function persistTypedTags(db, labels, type) {
|
|
|
1758
2194
|
return added;
|
|
1759
2195
|
});
|
|
1760
2196
|
}
|
|
2197
|
+
|
|
2198
|
+
// src/cli/seed.ts
|
|
2199
|
+
init_db();
|
|
1761
2200
|
var NOTE_ID_FRONTMATTER_KEY = "wod-note-id";
|
|
1762
2201
|
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;
|
|
1763
2202
|
function extractNoteIdentity(markdown) {
|
|
@@ -2880,16 +3319,53 @@ ${source}
|
|
|
2880
3319
|
${fence}
|
|
2881
3320
|
`;
|
|
2882
3321
|
}
|
|
3322
|
+
var VALUE_FLAGS = [
|
|
3323
|
+
["--db", "db"],
|
|
3324
|
+
["--db-driver", "dbDriver"],
|
|
3325
|
+
["--database-url", "databaseUrl"],
|
|
3326
|
+
["--postgres-host", "postgresHost"],
|
|
3327
|
+
["--postgres-port", "postgresPort"],
|
|
3328
|
+
["--postgres-user", "postgresUser"],
|
|
3329
|
+
["--postgres-password", "postgresPassword"],
|
|
3330
|
+
["--postgres-database", "postgresDatabase"],
|
|
3331
|
+
["--postgres-ssl", "postgresSsl"],
|
|
3332
|
+
["--turso-url", "tursoUrl"],
|
|
3333
|
+
["--turso-auth-token", "tursoAuthToken"],
|
|
3334
|
+
["--env-file", "envFile"],
|
|
3335
|
+
["--host", "host"],
|
|
3336
|
+
["--port", "port"],
|
|
3337
|
+
["--dist", "dist"]
|
|
3338
|
+
];
|
|
3339
|
+
function readValueFlag(args, i, flag) {
|
|
3340
|
+
const inline = args[i].startsWith(`${flag}=`);
|
|
3341
|
+
if (inline) {
|
|
3342
|
+
const value2 = args[i].slice(flag.length + 1);
|
|
3343
|
+
if (!value2) throw new Error(`Missing value for ${flag}`);
|
|
3344
|
+
return { value: value2, next: i + 1 };
|
|
3345
|
+
}
|
|
3346
|
+
const value = args[i + 1];
|
|
3347
|
+
if (value === void 0 || value === "" || value.startsWith("--")) throw new Error(`Missing value for ${flag}`);
|
|
3348
|
+
return { value, next: i + 2 };
|
|
3349
|
+
}
|
|
2883
3350
|
function parseCliArgs(args) {
|
|
2884
3351
|
const result = {
|
|
2885
3352
|
format: "json",
|
|
2886
3353
|
packs: [],
|
|
2887
3354
|
sources: []
|
|
2888
3355
|
};
|
|
3356
|
+
const valueArgs = result;
|
|
2889
3357
|
let i = 0;
|
|
2890
3358
|
while (i < args.length) {
|
|
2891
3359
|
const arg = args[i];
|
|
2892
3360
|
if (arg === "--at" || arg.startsWith("--at=")) throw new Error("Unknown option --at; whole Note exports have no as-of version");
|
|
3361
|
+
const valueFlag = VALUE_FLAGS.find(([flag]) => arg === flag || arg.startsWith(`${flag}=`));
|
|
3362
|
+
if (valueFlag) {
|
|
3363
|
+
const [flag, key] = valueFlag;
|
|
3364
|
+
const { value, next } = readValueFlag(args, i, flag);
|
|
3365
|
+
valueArgs[key] = value;
|
|
3366
|
+
i = next;
|
|
3367
|
+
continue;
|
|
3368
|
+
}
|
|
2893
3369
|
if (arg === "--help" || arg === "-h") {
|
|
2894
3370
|
result.help = true;
|
|
2895
3371
|
i++;
|
|
@@ -2936,12 +3412,6 @@ function parseCliArgs(args) {
|
|
|
2936
3412
|
} else if (arg === "--unit") {
|
|
2937
3413
|
result.unit = args[++i];
|
|
2938
3414
|
i++;
|
|
2939
|
-
} else if (arg === "--db") {
|
|
2940
|
-
result.db = args[++i];
|
|
2941
|
-
i++;
|
|
2942
|
-
} else if (arg.startsWith("--db=")) {
|
|
2943
|
-
result.db = arg.slice("--db=".length);
|
|
2944
|
-
i++;
|
|
2945
3415
|
} else if (arg === "--debug") {
|
|
2946
3416
|
result.debug = "";
|
|
2947
3417
|
i++;
|
|
@@ -2977,7 +3447,7 @@ function parseCliArgs(args) {
|
|
|
2977
3447
|
result.dir = arg.slice("--dir=".length);
|
|
2978
3448
|
i++;
|
|
2979
3449
|
} else if (!result.subcommand) {
|
|
2980
|
-
if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "import" || arg === "build" || arg === "seed" || arg === "reset" || arg === "help" || arg === "version") {
|
|
3450
|
+
if (arg === "parse" || arg === "run" || arg === "query" || arg === "download" || arg === "import" || arg === "build" || arg === "seed" || arg === "reset" || arg === "init" || arg === "server" || arg === "serve" || arg === "playground" || arg === "help" || arg === "version") {
|
|
2981
3451
|
result.subcommand = arg;
|
|
2982
3452
|
} else {
|
|
2983
3453
|
result.target = arg;
|
|
@@ -2993,14 +3463,19 @@ function parseCliArgs(args) {
|
|
|
2993
3463
|
i++;
|
|
2994
3464
|
}
|
|
2995
3465
|
}
|
|
3466
|
+
for (const [flag, value] of [["--port", result.port], ["--postgres-port", result.postgresPort]]) {
|
|
3467
|
+
if (value !== void 0 && (!/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 65535)) {
|
|
3468
|
+
throw new Error(`Invalid value for ${flag}: "${value}" is not a TCP port (1-65535)`);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
2996
3471
|
return result;
|
|
2997
3472
|
}
|
|
2998
3473
|
function readBunStdinText() {
|
|
2999
3474
|
const g = globalThis;
|
|
3000
3475
|
if (g && typeof g === "object" && "Bun" in g) {
|
|
3001
|
-
const
|
|
3002
|
-
if (
|
|
3003
|
-
const stdin =
|
|
3476
|
+
const bun2 = g.Bun;
|
|
3477
|
+
if (bun2 && typeof bun2 === "object" && "stdin" in bun2) {
|
|
3478
|
+
const stdin = bun2.stdin;
|
|
3004
3479
|
if (stdin && typeof stdin === "object" && "text" in stdin) {
|
|
3005
3480
|
const text = stdin.text;
|
|
3006
3481
|
if (typeof text === "function") return text;
|
|
@@ -3038,6 +3513,11 @@ USAGE:
|
|
|
3038
3513
|
wod build [dir] [options]
|
|
3039
3514
|
wod seed [options]
|
|
3040
3515
|
wod reset [options]
|
|
3516
|
+
wod init Initialize the configured database (idempotent)
|
|
3517
|
+
wod server [options]
|
|
3518
|
+
Serve the app (HTTP API + packaged frontend; alias: serve)
|
|
3519
|
+
wod playground [options]
|
|
3520
|
+
Serve the local playground (browser IndexedDB build)
|
|
3041
3521
|
|
|
3042
3522
|
SUBCOMMANDS:
|
|
3043
3523
|
parse <file | -> Parse Whiteboard Script, create a note in the database
|
|
@@ -3091,9 +3571,47 @@ INPUT SOURCING (for 'query'; without these, the database is queried):
|
|
|
3091
3571
|
--stdin-log Read kind: 'execution-log' JSON payload from stdin
|
|
3092
3572
|
--stdin-facts Read raw kind: 'fact-set' JSON payload from stdin
|
|
3093
3573
|
|
|
3094
|
-
DATABASE (parse / run / query / download / import / seed / reset):
|
|
3095
|
-
|
|
3096
|
-
|
|
3574
|
+
DATABASE (init / parse / run / query / download / import / seed / reset / server):
|
|
3575
|
+
Pick one backend; explicit flags win over the process environment, the
|
|
3576
|
+
process environment wins over .env files. Run 'wod init' once first: it
|
|
3577
|
+
creates the SQLite file + schema, creates the PostgreSQL database when
|
|
3578
|
+
your role is authorized to (otherwise use an existing/provisioned one),
|
|
3579
|
+
or installs the schema in your provisioned Turso database \u2014 idempotently,
|
|
3580
|
+
preserving existing notes. 'wod server' refuses an uninitialized database.
|
|
3581
|
+
--db <path> Local SQLite database file (default: $WOD_WIKI_DB
|
|
3582
|
+
or ./wodwiki.db). Passing --db always selects SQLite,
|
|
3583
|
+
even when the environment names a remote backend.
|
|
3584
|
+
Created on demand by parse/run/seed.
|
|
3585
|
+
--db-driver <name> Backend driver: sqlite (default), postgres, or
|
|
3586
|
+
turso ($DB_DRIVER)
|
|
3587
|
+
--database-url <url> Database connection URL ($DATABASE_URL); accepts
|
|
3588
|
+
PostgreSQL, local SQLite, and remote libSQL URLs.
|
|
3589
|
+
--postgres-host <host> PostgreSQL host ($POSTGRES_HOST)
|
|
3590
|
+
--postgres-port <port> PostgreSQL port ($POSTGRES_PORT)
|
|
3591
|
+
--postgres-user <user> PostgreSQL user ($POSTGRES_USER)
|
|
3592
|
+
--postgres-password <pw> PostgreSQL password ($POSTGRES_PASSWORD)
|
|
3593
|
+
--postgres-database <db> PostgreSQL database name ($POSTGRES_DATABASE)
|
|
3594
|
+
--postgres-ssl <mode> PostgreSQL SSL mode, e.g. disable/prefer/require
|
|
3595
|
+
($POSTGRES_SSL)
|
|
3596
|
+
--turso-url <url> Turso/libSQL database URL (libsql://\u2026). The
|
|
3597
|
+
hosted database must already exist (provisioned on
|
|
3598
|
+
Turso); only its schema is installed.
|
|
3599
|
+
--turso-auth-token <token> Turso authentication token (never printed)
|
|
3600
|
+
--env-file <path> Load variables from this file; a missing file is
|
|
3601
|
+
an error. Without it: ./.env when present, otherwise
|
|
3602
|
+
the workspace-root .env. Credentials are never logged.
|
|
3603
|
+
|
|
3604
|
+
HOSTING (for 'server' / 'playground'; requires the Bun runtime):
|
|
3605
|
+
server HTTP API over the configured database plus the frontend
|
|
3606
|
+
packaged inside the engine module. Requires an
|
|
3607
|
+
initialized database. ('serve' is an accepted alias.)
|
|
3608
|
+
playground Serves the playground app backed by browser IndexedDB;
|
|
3609
|
+
the configured database is not touched.
|
|
3610
|
+
--host <host> Bind address (default 127.0.0.1, $HOST)
|
|
3611
|
+
--port <port> TCP port (default 3000, $PORT)
|
|
3612
|
+
--dist <dir> Serve this prebuilt frontend directory instead of the
|
|
3613
|
+
packaged one ($PLAYGROUND_DIST); must already contain
|
|
3614
|
+
index.html.
|
|
3097
3615
|
|
|
3098
3616
|
SEED SOURCING (for 'seed'):
|
|
3099
3617
|
--source <dir> Extra compiled seed directory (manifest.json + chunks);
|
|
@@ -3113,6 +3631,15 @@ OPTIONS:
|
|
|
3113
3631
|
-h, --help Show help message
|
|
3114
3632
|
-v, --version Show version number
|
|
3115
3633
|
|
|
3634
|
+
ENVIRONMENT (flags > process env > .env):
|
|
3635
|
+
WOD_WIKI_DB, SQLITE_PATH SQLite file path
|
|
3636
|
+
DB_DRIVER sqlite | postgres | turso
|
|
3637
|
+
DATABASE_URL PostgreSQL connection URL
|
|
3638
|
+
POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER,
|
|
3639
|
+
POSTGRES_PASSWORD, POSTGRES_DATABASE, POSTGRES_SSL
|
|
3640
|
+
TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, LIBSQL_AUTH_TOKEN
|
|
3641
|
+
HOST, PORT, PLAYGROUND_DIST server / playground defaults
|
|
3642
|
+
|
|
3116
3643
|
EXIT CODES:
|
|
3117
3644
|
0 - OK / Success
|
|
3118
3645
|
1 - General / Runtime error
|
|
@@ -3132,6 +3659,10 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3132
3659
|
const writeOut = io.stdout ?? ((t) => process.stdout.write(t + "\n"));
|
|
3133
3660
|
const writeErr = io.stderr ?? ((t) => process.stderr.write(t + "\n"));
|
|
3134
3661
|
const getStdin = io.readStdinFn ?? readStdin;
|
|
3662
|
+
let envLoaded;
|
|
3663
|
+
const cliEnv = () => envLoaded ??= { ...loadCliEnvironment(parsed.envFile), ...process.env };
|
|
3664
|
+
let dbResolved;
|
|
3665
|
+
const cliDb = () => dbResolved ??= resolveDatabaseConfig(parsed, cliEnv());
|
|
3135
3666
|
let parsed;
|
|
3136
3667
|
try {
|
|
3137
3668
|
parsed = parseCliArgs(argv);
|
|
@@ -3198,7 +3729,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3198
3729
|
const ir = runParse(source, { sport: parsed.sport, sourceLabel: `cli:wod parse ${parsed.target}` });
|
|
3199
3730
|
const identity = extractNoteIdentity(source);
|
|
3200
3731
|
const tags = extractTypedTags(source);
|
|
3201
|
-
const db = await openCliDb(
|
|
3732
|
+
const db = await openCliDb(cliDb());
|
|
3202
3733
|
try {
|
|
3203
3734
|
const persisted = await persistNote(db, {
|
|
3204
3735
|
source: noteSource,
|
|
@@ -3237,7 +3768,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3237
3768
|
source = fs.readFileSync(parsed.target, "utf-8");
|
|
3238
3769
|
}
|
|
3239
3770
|
const identity = extractNoteIdentity(source);
|
|
3240
|
-
const db = await openCliDb(
|
|
3771
|
+
const db = await openCliDb(cliDb());
|
|
3241
3772
|
let ir;
|
|
3242
3773
|
try {
|
|
3243
3774
|
const started = await startRun(db, {
|
|
@@ -3288,14 +3819,14 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3288
3819
|
}
|
|
3289
3820
|
let ir;
|
|
3290
3821
|
if (useDb) {
|
|
3291
|
-
const
|
|
3292
|
-
if (!nodeFs().existsSync(
|
|
3822
|
+
const config = cliDb();
|
|
3823
|
+
if (config.kind === "sqlite" && !nodeFs().existsSync(config.path)) {
|
|
3293
3824
|
writeErr(
|
|
3294
|
-
`[wod query] No input dataset and no database at ${
|
|
3825
|
+
`[wod query] No input dataset and no database at ${config.path}. Specify --corpus <file>, --stdin-log, --stdin-facts, or run \`wod seed\` first.`
|
|
3295
3826
|
);
|
|
3296
3827
|
return 1;
|
|
3297
3828
|
}
|
|
3298
|
-
const db = await openCliDb(
|
|
3829
|
+
const db = await openCliDb(config, config.kind === "sqlite" ? void 0 : { initialize: false });
|
|
3299
3830
|
try {
|
|
3300
3831
|
ir = await runQueryCli(parsed.target, {
|
|
3301
3832
|
preferredUnit: parsed.unit,
|
|
@@ -3341,7 +3872,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3341
3872
|
return 1;
|
|
3342
3873
|
}
|
|
3343
3874
|
const targetId = id;
|
|
3344
|
-
const db = await openCliDb(
|
|
3875
|
+
const db = await openCliDb(cliDb());
|
|
3345
3876
|
try {
|
|
3346
3877
|
if (kind === "note") {
|
|
3347
3878
|
const bundle = await getNoteBundle(db.db, targetId);
|
|
@@ -3418,7 +3949,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3418
3949
|
const source = fs.readFileSync(file, "utf-8");
|
|
3419
3950
|
const identity = extractNoteIdentity(source);
|
|
3420
3951
|
const tags = extractTypedTags(source);
|
|
3421
|
-
const db2 = await openCliDb(
|
|
3952
|
+
const db2 = await openCliDb(cliDb());
|
|
3422
3953
|
try {
|
|
3423
3954
|
const persisted = await persistNote(db2, {
|
|
3424
3955
|
source,
|
|
@@ -3441,7 +3972,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3441
3972
|
writeErr(`[wod import] Unparseable effort file (need frontmatter id/slug/label + numeric baseAttributes.met): ${file}`);
|
|
3442
3973
|
return 1;
|
|
3443
3974
|
}
|
|
3444
|
-
const db2 = await openCliDb(
|
|
3975
|
+
const db2 = await openCliDb(cliDb());
|
|
3445
3976
|
try {
|
|
3446
3977
|
await persistEffort(db2, { ...effort, registrySource: "user" });
|
|
3447
3978
|
writeOut(`[wod import] effort ${effort.slug} \u2014 ${effort.label} \u2192 ${db2.path}`);
|
|
@@ -3456,7 +3987,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3456
3987
|
writeErr(`[wod import] No equipment labels found in ${file}`);
|
|
3457
3988
|
return 1;
|
|
3458
3989
|
}
|
|
3459
|
-
const db2 = await openCliDb(
|
|
3990
|
+
const db2 = await openCliDb(cliDb());
|
|
3460
3991
|
try {
|
|
3461
3992
|
const added = await persistTypedTags(db2, labels, "equipment");
|
|
3462
3993
|
writeOut(`[wod import] equipment \u2014 ${labels.length} labels (${added} new) \u2192 ${db2.path}`);
|
|
@@ -3466,7 +3997,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3466
3997
|
return 0;
|
|
3467
3998
|
}
|
|
3468
3999
|
const { session, events } = csvToSessionRows(fs.readFileSync(file, "utf-8"));
|
|
3469
|
-
const db = await openCliDb(
|
|
4000
|
+
const db = await openCliDb(cliDb());
|
|
3470
4001
|
try {
|
|
3471
4002
|
const result = await persistSessionBundle(db, session, events);
|
|
3472
4003
|
writeOut(
|
|
@@ -3513,7 +4044,7 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3513
4044
|
);
|
|
3514
4045
|
return 1;
|
|
3515
4046
|
}
|
|
3516
|
-
const db = await openCliDb(
|
|
4047
|
+
const db = await openCliDb(cliDb());
|
|
3517
4048
|
try {
|
|
3518
4049
|
for (const dir of dirs) {
|
|
3519
4050
|
const summary = await importSeedDir(db, dir, "");
|
|
@@ -3527,7 +4058,14 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3527
4058
|
return 0;
|
|
3528
4059
|
}
|
|
3529
4060
|
if (parsed.subcommand === "reset") {
|
|
3530
|
-
const
|
|
4061
|
+
const config = cliDb();
|
|
4062
|
+
if (config.kind !== "sqlite") {
|
|
4063
|
+
writeErr(
|
|
4064
|
+
`[wod reset] refusing to reset the configured ${config.kind} database \u2014 reset only removes a local SQLite file. Clear remote databases through their provider.`
|
|
4065
|
+
);
|
|
4066
|
+
return 1;
|
|
4067
|
+
}
|
|
4068
|
+
const target = config.path;
|
|
3531
4069
|
if (nodeFs().existsSync(target)) {
|
|
3532
4070
|
nodeFs().unlinkSync(target);
|
|
3533
4071
|
writeOut(`[wod reset] removed ${target}`);
|
|
@@ -3536,6 +4074,32 @@ async function cliMain(argv = process.argv.slice(2), io = {}) {
|
|
|
3536
4074
|
}
|
|
3537
4075
|
return 0;
|
|
3538
4076
|
}
|
|
4077
|
+
if (parsed.subcommand === "init") {
|
|
4078
|
+
const db = await openCliDb(cliDb(), { create: true });
|
|
4079
|
+
try {
|
|
4080
|
+
writeOut(`[wod init] database ready at ${db.path}`);
|
|
4081
|
+
} finally {
|
|
4082
|
+
await db.close();
|
|
4083
|
+
}
|
|
4084
|
+
return 0;
|
|
4085
|
+
}
|
|
4086
|
+
if (parsed.subcommand === "server" || parsed.subcommand === "serve" || parsed.subcommand === "playground") {
|
|
4087
|
+
if (!("Bun" in globalThis)) {
|
|
4088
|
+
writeErr(
|
|
4089
|
+
`[wod ${parsed.subcommand}] Error: hosting requires the Bun runtime (https://bun.sh) \u2014 run \`bun wod server\` / \`bun wod playground\`.`
|
|
4090
|
+
);
|
|
4091
|
+
return 1;
|
|
4092
|
+
}
|
|
4093
|
+
const mode = parsed.subcommand === "playground" ? "playground" : "server";
|
|
4094
|
+
const { runCliServer: runCliServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
4095
|
+
const options = { env: cliEnv() };
|
|
4096
|
+
if (mode === "server") options.database = cliDb();
|
|
4097
|
+
if (parsed.host !== void 0) options.host = parsed.host;
|
|
4098
|
+
if (parsed.port !== void 0) options.port = parsed.port;
|
|
4099
|
+
if (parsed.dist !== void 0) options.dist = parsed.dist;
|
|
4100
|
+
await runCliServer2(mode, options, writeOut);
|
|
4101
|
+
return 0;
|
|
4102
|
+
}
|
|
3539
4103
|
writeErr(`[wod] Unknown subcommand "${parsed.subcommand}"`);
|
|
3540
4104
|
return 1;
|
|
3541
4105
|
} catch (error) {
|