yaml-flow 4.0.0 → 5.0.0
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/browser/board-livegraph-runtime.js +1453 -0
- package/browser/board-livegraph-runtime.js.map +1 -0
- package/browser/card-compute.js +36 -17
- package/browser/live-cards.js +848 -109
- package/browser/live-cards.schema.json +46 -21
- package/dist/board-livegraph-runtime/index.cjs +1448 -0
- package/dist/board-livegraph-runtime/index.cjs.map +1 -0
- package/dist/board-livegraph-runtime/index.d.cts +101 -0
- package/dist/board-livegraph-runtime/index.d.ts +101 -0
- package/dist/board-livegraph-runtime/index.js +1441 -0
- package/dist/board-livegraph-runtime/index.js.map +1 -0
- package/dist/card-compute/index.cjs +159 -44
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +36 -11
- package/dist/card-compute/index.d.ts +36 -11
- package/dist/card-compute/index.js +156 -44
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +476 -105
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +8 -16
- package/dist/cli/board-live-cards-cli.d.ts +8 -16
- package/dist/cli/board-live-cards-cli.js +476 -106
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +74 -33
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +7 -23
- package/dist/continuous-event-graph/index.d.ts +7 -23
- package/dist/continuous-event-graph/index.js +73 -32
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/index.cjs +1440 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +1434 -56
- package/dist/index.js.map +1 -1
- package/dist/journal-DRfJiheM.d.cts +28 -0
- package/dist/journal-NLYuqege.d.ts +28 -0
- package/dist/{journal-B_2JnBMF.d.ts → live-cards-bridge-Or7fdEJV.d.ts} +5 -32
- package/dist/{journal-BJDjWb5Q.d.cts → live-cards-bridge-vGJ6tMzN.d.cts} +5 -32
- package/dist/schedule-CMcZe5Ny.d.ts +21 -0
- package/dist/schedule-CiucyCan.d.cts +21 -0
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +33 -5
- package/examples/browser/livecards-browser/index.html +37 -684
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +2 -2
- package/examples/example-board/board.yaml +23 -0
- package/examples/example-board/bootstrap_payload.json +1 -0
- package/examples/example-board/cards/card-chain-region-alert.json +39 -0
- package/examples/example-board/cards/card-chain-region-totals.json +26 -0
- package/examples/example-board/cards/card-chain-top-region.json +24 -0
- package/examples/example-board/cards/card-ex-actions.json +32 -0
- package/examples/example-board/cards/card-ex-chart.json +30 -0
- package/examples/example-board/cards/card-ex-filter.json +36 -0
- package/examples/example-board/cards/card-ex-filtered-by-preference.json +59 -0
- package/examples/example-board/cards/card-ex-form.json +91 -0
- package/examples/example-board/cards/card-ex-list.json +22 -0
- package/examples/example-board/cards/card-ex-markdown.json +17 -0
- package/examples/example-board/cards/card-ex-metric.json +19 -0
- package/examples/example-board/cards/card-ex-narrative.json +36 -0
- package/examples/example-board/cards/card-ex-source-http.json +28 -0
- package/examples/example-board/cards/card-ex-source.json +21 -0
- package/examples/example-board/cards/card-ex-status.json +35 -0
- package/examples/example-board/cards/card-ex-table.json +30 -0
- package/examples/example-board/cards/card-ex-todo.json +29 -0
- package/examples/example-board/demo-chat-handler.js +69 -0
- package/examples/example-board/demo-server.js +87 -0
- package/examples/example-board/demo-shell-browser.html +806 -0
- package/examples/example-board/demo-shell-with-server.html +280 -0
- package/examples/example-board/demo-shell.html +62 -0
- package/examples/example-board/demo-task-executor.js +255 -0
- package/examples/example-board/mock.db +15 -0
- package/examples/example-board/reusable-board-runtime-client.js +265 -0
- package/examples/example-board/reusable-runtime-artifacts-adapter.js +233 -0
- package/examples/example-board/reusable-server-runtime.js +1284 -0
- package/examples/index.html +16 -9
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +17 -17
- package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +23 -23
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/package.json +16 -2
- package/schema/card-runtime.schema.json +25 -0
- package/schema/live-cards.schema.json +46 -21
- package/browser/ingest-board.js +0 -296
- package/examples/ingest.js +0 -733
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
import { randomUUID
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
5
|
import { execFileSync, spawn, execFile } from 'child_process';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import fg from 'fast-glob';
|
|
8
8
|
import { lockSync } from 'proper-lockfile';
|
|
9
|
-
import
|
|
9
|
+
import jsonata2 from 'jsonata';
|
|
10
10
|
import 'ajv-formats';
|
|
11
11
|
|
|
12
12
|
// src/cli/board-live-cards-cli.ts
|
|
@@ -638,9 +638,11 @@ var MemoryJournal = class {
|
|
|
638
638
|
return this.buffer.length;
|
|
639
639
|
}
|
|
640
640
|
};
|
|
641
|
+
|
|
642
|
+
// src/continuous-event-graph/reactive.ts
|
|
641
643
|
function computeDataHash(data) {
|
|
642
644
|
const json = stableStringify(data);
|
|
643
|
-
return
|
|
645
|
+
return fnv1a64Hex(json);
|
|
644
646
|
}
|
|
645
647
|
function stableStringify(value) {
|
|
646
648
|
if (value === null || value === void 0 || typeof value !== "object") {
|
|
@@ -653,13 +655,49 @@ function stableStringify(value) {
|
|
|
653
655
|
const keys = Object.keys(obj).sort();
|
|
654
656
|
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
655
657
|
}
|
|
658
|
+
function fnv1a64Hex(input) {
|
|
659
|
+
let hash = 0xcbf29ce484222325n;
|
|
660
|
+
const prime = 0x100000001b3n;
|
|
661
|
+
const mod = 0xffffffffffffffffn;
|
|
662
|
+
for (let i = 0; i < input.length; i++) {
|
|
663
|
+
hash ^= BigInt(input.charCodeAt(i));
|
|
664
|
+
hash = hash * prime & mod;
|
|
665
|
+
}
|
|
666
|
+
return hash.toString(16).padStart(16, "0");
|
|
667
|
+
}
|
|
668
|
+
function base64UrlEncode(input) {
|
|
669
|
+
if (typeof Buffer !== "undefined") {
|
|
670
|
+
return Buffer.from(input, "utf8").toString("base64url");
|
|
671
|
+
}
|
|
672
|
+
if (typeof btoa === "function") {
|
|
673
|
+
const bytes = new TextEncoder().encode(input);
|
|
674
|
+
let binary = "";
|
|
675
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
676
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
677
|
+
}
|
|
678
|
+
throw new Error("No base64 encoder available in this runtime");
|
|
679
|
+
}
|
|
680
|
+
function base64UrlDecode(input) {
|
|
681
|
+
if (typeof Buffer !== "undefined") {
|
|
682
|
+
return Buffer.from(input, "base64url").toString("utf8");
|
|
683
|
+
}
|
|
684
|
+
if (typeof atob === "function") {
|
|
685
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
686
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
687
|
+
const binary = atob(padded);
|
|
688
|
+
const bytes = new Uint8Array(binary.length);
|
|
689
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
690
|
+
return new TextDecoder().decode(bytes);
|
|
691
|
+
}
|
|
692
|
+
throw new Error("No base64 decoder available in this runtime");
|
|
693
|
+
}
|
|
656
694
|
function encodeCallbackToken(taskName) {
|
|
657
695
|
const payload = JSON.stringify({ t: taskName, n: Date.now().toString(36) + Math.random().toString(36).slice(2, 6) });
|
|
658
|
-
return
|
|
696
|
+
return base64UrlEncode(payload);
|
|
659
697
|
}
|
|
660
698
|
function decodeCallbackToken(token) {
|
|
661
699
|
try {
|
|
662
|
-
const payload = JSON.parse(
|
|
700
|
+
const payload = JSON.parse(base64UrlDecode(token));
|
|
663
701
|
if (typeof payload?.t === "string") return { taskName: payload.t };
|
|
664
702
|
return null;
|
|
665
703
|
} catch {
|
|
@@ -936,18 +974,18 @@ function deepSet(obj, path2, value) {
|
|
|
936
974
|
}
|
|
937
975
|
async function run(node, options) {
|
|
938
976
|
if (!node?.compute?.length) return node;
|
|
939
|
-
if (!node.
|
|
977
|
+
if (!node.card_data) node.card_data = {};
|
|
940
978
|
node.computed_values = {};
|
|
941
979
|
node._sourcesData = options?.sourcesData ?? {};
|
|
942
980
|
const ctx = {
|
|
943
|
-
|
|
981
|
+
card_data: node.card_data,
|
|
944
982
|
requires: node.requires ?? {},
|
|
945
|
-
|
|
983
|
+
fetched_sources: node._sourcesData,
|
|
946
984
|
computed_values: node.computed_values
|
|
947
985
|
};
|
|
948
986
|
for (const step of node.compute) {
|
|
949
987
|
try {
|
|
950
|
-
const val = await
|
|
988
|
+
const val = await jsonata2(step.expr).evaluate(ctx);
|
|
951
989
|
deepSet(node.computed_values, step.bindTo, val);
|
|
952
990
|
ctx.computed_values = node.computed_values;
|
|
953
991
|
} catch (err) {
|
|
@@ -958,16 +996,16 @@ async function run(node, options) {
|
|
|
958
996
|
}
|
|
959
997
|
async function evalExpr(expr, node) {
|
|
960
998
|
const ctx = {
|
|
961
|
-
|
|
999
|
+
card_data: node.card_data ?? {},
|
|
962
1000
|
requires: node.requires ?? {},
|
|
963
|
-
|
|
1001
|
+
fetched_sources: node._sourcesData ?? {},
|
|
964
1002
|
computed_values: node.computed_values ?? {}
|
|
965
1003
|
};
|
|
966
|
-
return
|
|
1004
|
+
return jsonata2(expr).evaluate(ctx);
|
|
967
1005
|
}
|
|
968
1006
|
function resolve(node, path2) {
|
|
969
|
-
if (path2.startsWith("
|
|
970
|
-
return deepGet(node._sourcesData ?? {}, path2.slice("
|
|
1007
|
+
if (path2.startsWith("fetched_sources.")) {
|
|
1008
|
+
return deepGet(node._sourcesData ?? {}, path2.slice("fetched_sources.".length));
|
|
971
1009
|
}
|
|
972
1010
|
return deepGet(node, path2);
|
|
973
1011
|
}
|
|
@@ -987,8 +1025,7 @@ var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
|
|
|
987
1025
|
"markdown",
|
|
988
1026
|
"custom"
|
|
989
1027
|
]);
|
|
990
|
-
var
|
|
991
|
-
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "state", "compute", "sources"]);
|
|
1028
|
+
var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "sources"]);
|
|
992
1029
|
function validateNode(node) {
|
|
993
1030
|
const errors = [];
|
|
994
1031
|
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
@@ -999,13 +1036,8 @@ function validateNode(node) {
|
|
|
999
1036
|
for (const key of Object.keys(n)) {
|
|
1000
1037
|
if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: "${key}"`);
|
|
1001
1038
|
}
|
|
1002
|
-
if (n.
|
|
1003
|
-
errors.push("
|
|
1004
|
-
} else {
|
|
1005
|
-
const state = n.state;
|
|
1006
|
-
if (state.status != null && !VALID_STATUSES.has(state.status)) {
|
|
1007
|
-
errors.push(`state.status: must be one of: ${[...VALID_STATUSES].join(", ")}`);
|
|
1008
|
-
}
|
|
1039
|
+
if (n.card_data == null || typeof n.card_data !== "object" || Array.isArray(n.card_data)) {
|
|
1040
|
+
errors.push("card_data: required, must be an object");
|
|
1009
1041
|
}
|
|
1010
1042
|
if (n.meta != null) {
|
|
1011
1043
|
if (typeof n.meta !== "object" || Array.isArray(n.meta)) {
|
|
@@ -1094,17 +1126,33 @@ function validateNode(node) {
|
|
|
1094
1126
|
}
|
|
1095
1127
|
return { ok: errors.length === 0, errors };
|
|
1096
1128
|
}
|
|
1129
|
+
function enrichSources(sources, context) {
|
|
1130
|
+
if (!sources || sources.length === 0) return [];
|
|
1131
|
+
return sources.map((src) => ({
|
|
1132
|
+
...src,
|
|
1133
|
+
_requires: context.requires ?? {},
|
|
1134
|
+
_sourcesData: context.sourcesData ?? {},
|
|
1135
|
+
_computed_values: context.computed_values ?? {}
|
|
1136
|
+
}));
|
|
1137
|
+
}
|
|
1097
1138
|
var CardCompute = {
|
|
1098
1139
|
run,
|
|
1099
1140
|
eval: evalExpr,
|
|
1100
1141
|
resolve,
|
|
1101
|
-
validate: validateNode
|
|
1142
|
+
validate: validateNode,
|
|
1143
|
+
enrichSources
|
|
1102
1144
|
};
|
|
1103
1145
|
|
|
1104
1146
|
// src/cli/board-live-cards-cli.ts
|
|
1105
1147
|
var BOARD_FILE = "board-graph.json";
|
|
1106
1148
|
var JOURNAL_FILE = "board-journal.jsonl";
|
|
1149
|
+
var TASK_EXECUTOR_LOG_FILE = "task-executor.jsonl";
|
|
1107
1150
|
var INVENTORY_FILE = "cards-inventory.jsonl";
|
|
1151
|
+
var RUNTIME_OUT_FILE = ".runtime-out";
|
|
1152
|
+
var DEFAULT_RUNTIME_OUT_DIR = "runtime-out";
|
|
1153
|
+
var RUNTIME_STATUS_FILE = "board-livegraph-status.json";
|
|
1154
|
+
var RUNTIME_CARDS_DIR = "cards";
|
|
1155
|
+
var RUNTIME_DATA_OBJECTS_DIR = "data-objects";
|
|
1108
1156
|
var EMPTY_CONFIG = { settings: { completion: "manual", refreshStrategy: "data-changed" }, tasks: {} };
|
|
1109
1157
|
var BoardJournal = class {
|
|
1110
1158
|
journalPath;
|
|
@@ -1159,7 +1207,34 @@ function lookupCardPath(boardDir, cardId) {
|
|
|
1159
1207
|
}
|
|
1160
1208
|
function appendCardInventory(boardDir, entry) {
|
|
1161
1209
|
const inventoryPath = path.join(boardDir, INVENTORY_FILE);
|
|
1162
|
-
|
|
1210
|
+
const normalized = { ...entry, cardFilePath: path.resolve(entry.cardFilePath) };
|
|
1211
|
+
fs.appendFileSync(inventoryPath, JSON.stringify(normalized) + "\n");
|
|
1212
|
+
}
|
|
1213
|
+
function buildCardInventoryIndex(boardDir) {
|
|
1214
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
1215
|
+
const byCardPath = /* @__PURE__ */ new Map();
|
|
1216
|
+
for (const entry of readCardInventory(boardDir)) {
|
|
1217
|
+
const normalizedPath = path.resolve(entry.cardFilePath);
|
|
1218
|
+
const normalizedEntry = {
|
|
1219
|
+
...entry,
|
|
1220
|
+
cardFilePath: normalizedPath
|
|
1221
|
+
};
|
|
1222
|
+
const existingById = byCardId.get(entry.cardId);
|
|
1223
|
+
if (existingById && existingById.cardFilePath !== normalizedPath) {
|
|
1224
|
+
throw new Error(
|
|
1225
|
+
`Inventory invariant violation: card id "${entry.cardId}" maps to multiple files: "${existingById.cardFilePath}" and "${normalizedPath}"`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const existingByPath = byCardPath.get(normalizedPath);
|
|
1229
|
+
if (existingByPath && existingByPath.cardId !== entry.cardId) {
|
|
1230
|
+
throw new Error(
|
|
1231
|
+
`Inventory invariant violation: file "${normalizedPath}" maps to multiple ids: "${existingByPath.cardId}" and "${entry.cardId}"`
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
byCardId.set(entry.cardId, normalizedEntry);
|
|
1235
|
+
byCardPath.set(normalizedPath, normalizedEntry);
|
|
1236
|
+
}
|
|
1237
|
+
return { byCardId, byCardPath };
|
|
1163
1238
|
}
|
|
1164
1239
|
function initBoard(dir) {
|
|
1165
1240
|
const boardPath = path.join(dir, BOARD_FILE);
|
|
@@ -1195,7 +1270,63 @@ function saveBoard(dir, rg, journal) {
|
|
|
1195
1270
|
lastDrainedJournalId: journal.lastDrainedJournalId,
|
|
1196
1271
|
graph: snap
|
|
1197
1272
|
};
|
|
1198
|
-
|
|
1273
|
+
writeJsonAtomic(path.join(dir, BOARD_FILE), envelope);
|
|
1274
|
+
const live = restore(snap);
|
|
1275
|
+
const statusObject = buildBoardStatusObject(dir, live);
|
|
1276
|
+
writeJsonAtomic(resolveStatusSnapshotPath(dir), statusObject);
|
|
1277
|
+
}
|
|
1278
|
+
function runtimeOutConfigPath(boardDir) {
|
|
1279
|
+
return path.join(boardDir, RUNTIME_OUT_FILE);
|
|
1280
|
+
}
|
|
1281
|
+
function resolveConfiguredRuntimeOutDir(boardDir) {
|
|
1282
|
+
const cfgPath = runtimeOutConfigPath(boardDir);
|
|
1283
|
+
if (fs.existsSync(cfgPath)) {
|
|
1284
|
+
const configured = fs.readFileSync(cfgPath, "utf-8").trim();
|
|
1285
|
+
if (configured) {
|
|
1286
|
+
return path.isAbsolute(configured) ? configured : path.resolve(boardDir, configured);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
const defaultDir = path.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
|
|
1290
|
+
fs.writeFileSync(cfgPath, defaultDir, "utf-8");
|
|
1291
|
+
return defaultDir;
|
|
1292
|
+
}
|
|
1293
|
+
function configureRuntimeOutDir(boardDir, runtimeOut) {
|
|
1294
|
+
let resolved;
|
|
1295
|
+
if (runtimeOut) {
|
|
1296
|
+
resolved = path.isAbsolute(runtimeOut) ? runtimeOut : path.resolve(boardDir, runtimeOut);
|
|
1297
|
+
} else {
|
|
1298
|
+
resolved = path.join(boardDir, DEFAULT_RUNTIME_OUT_DIR);
|
|
1299
|
+
}
|
|
1300
|
+
fs.mkdirSync(resolved, { recursive: true });
|
|
1301
|
+
fs.writeFileSync(runtimeOutConfigPath(boardDir), resolved, "utf-8");
|
|
1302
|
+
return resolved;
|
|
1303
|
+
}
|
|
1304
|
+
function resolveStatusSnapshotPath(boardDir) {
|
|
1305
|
+
return path.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_STATUS_FILE);
|
|
1306
|
+
}
|
|
1307
|
+
function resolveComputedValuesPath(boardDir, cardId) {
|
|
1308
|
+
return path.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_CARDS_DIR, `${cardId}.computed.json`);
|
|
1309
|
+
}
|
|
1310
|
+
function resolveDataObjectsDirPath(boardDir) {
|
|
1311
|
+
return path.join(resolveConfiguredRuntimeOutDir(boardDir), RUNTIME_DATA_OBJECTS_DIR);
|
|
1312
|
+
}
|
|
1313
|
+
function toDataObjectFileName(token) {
|
|
1314
|
+
return token.replace(/[\\/]/g, "__");
|
|
1315
|
+
}
|
|
1316
|
+
function writeRuntimeDataObjects(boardDir, data) {
|
|
1317
|
+
for (const [token, payload] of Object.entries(data)) {
|
|
1318
|
+
if (!token) continue;
|
|
1319
|
+
const fileName = toDataObjectFileName(token);
|
|
1320
|
+
if (!fileName) continue;
|
|
1321
|
+
const filePath = path.join(resolveDataObjectsDirPath(boardDir), fileName);
|
|
1322
|
+
writeJsonAtomic(filePath, payload);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
function writeJsonAtomic(filePath, payload) {
|
|
1326
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1327
|
+
const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1328
|
+
fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
1329
|
+
fs.renameSync(tmpPath, filePath);
|
|
1199
1330
|
}
|
|
1200
1331
|
function withBoardLock(boardDir, fn) {
|
|
1201
1332
|
const boardPath = path.join(boardDir, BOARD_FILE);
|
|
@@ -1273,15 +1404,63 @@ function shouldUseShellForCommand(cmd, forceShell) {
|
|
|
1273
1404
|
if (typeof forceShell === "boolean") return forceShell;
|
|
1274
1405
|
return process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
|
|
1275
1406
|
}
|
|
1407
|
+
var _gitBashPath;
|
|
1408
|
+
var GIT_BASH_CACHE_FILE = path.join(os.tmpdir(), ".board-live-cards-git-bash-cache.json");
|
|
1409
|
+
function findGitBash() {
|
|
1410
|
+
if (_gitBashPath !== void 0) return _gitBashPath;
|
|
1411
|
+
if (process.platform !== "win32") return _gitBashPath = false;
|
|
1412
|
+
try {
|
|
1413
|
+
const cached = JSON.parse(fs.readFileSync(GIT_BASH_CACHE_FILE, "utf8"));
|
|
1414
|
+
if (cached.path === false || typeof cached.path === "string" && fs.existsSync(cached.path)) {
|
|
1415
|
+
return _gitBashPath = cached.path;
|
|
1416
|
+
}
|
|
1417
|
+
} catch {
|
|
1418
|
+
}
|
|
1419
|
+
const candidates = [
|
|
1420
|
+
process.env.SHELL,
|
|
1421
|
+
process.env.PROGRAMFILES && path.join(process.env.PROGRAMFILES, "Git", "usr", "bin", "bash.exe"),
|
|
1422
|
+
process.env.PROGRAMFILES && path.join(process.env.PROGRAMFILES, "Git", "bin", "bash.exe"),
|
|
1423
|
+
process.env["PROGRAMFILES(X86)"] && path.join(process.env["PROGRAMFILES(X86)"], "Git", "bin", "bash.exe"),
|
|
1424
|
+
process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")
|
|
1425
|
+
];
|
|
1426
|
+
for (const c of candidates) {
|
|
1427
|
+
if (c && /bash(\.exe)?$/i.test(c) && fs.existsSync(c)) {
|
|
1428
|
+
_gitBashPath = c;
|
|
1429
|
+
try {
|
|
1430
|
+
fs.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: c }));
|
|
1431
|
+
} catch {
|
|
1432
|
+
}
|
|
1433
|
+
return _gitBashPath;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
_gitBashPath = false;
|
|
1437
|
+
try {
|
|
1438
|
+
fs.writeFileSync(GIT_BASH_CACHE_FILE, JSON.stringify({ path: false }));
|
|
1439
|
+
} catch {
|
|
1440
|
+
}
|
|
1441
|
+
return _gitBashPath;
|
|
1442
|
+
}
|
|
1443
|
+
function shellQuote(s) {
|
|
1444
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
1445
|
+
}
|
|
1276
1446
|
function spawnDetachedCommand(cmd, args) {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1447
|
+
if (process.platform === "win32") {
|
|
1448
|
+
const bash = findGitBash();
|
|
1449
|
+
if (bash) {
|
|
1450
|
+
const shellCmd = [cmd, ...args].map((a) => shellQuote(a.replace(/\\/g, "/"))).join(" ");
|
|
1451
|
+
const child3 = spawn(bash, ["-c", shellCmd], { detached: true, stdio: "ignore", windowsHide: true });
|
|
1452
|
+
child3.unref();
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const child2 = spawn("cmd", ["/c", "start", "/b", "", cmd, ...args], {
|
|
1456
|
+
detached: true,
|
|
1457
|
+
stdio: "ignore",
|
|
1458
|
+
windowsHide: true
|
|
1459
|
+
});
|
|
1460
|
+
child2.unref();
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
1285
1464
|
child.unref();
|
|
1286
1465
|
}
|
|
1287
1466
|
function execCommandSync(cmd, args, options) {
|
|
@@ -1386,7 +1565,7 @@ async function processAccumulatedEventsForced(boardDir, options) {
|
|
|
1386
1565
|
}
|
|
1387
1566
|
function liveCardToTaskConfig(card) {
|
|
1388
1567
|
const requires = card.requires;
|
|
1389
|
-
const provides = card.provides
|
|
1568
|
+
const provides = card.provides?.map((p) => p.bindTo) ?? [];
|
|
1390
1569
|
return {
|
|
1391
1570
|
requires: requires && requires.length > 0 ? requires : void 0,
|
|
1392
1571
|
provides,
|
|
@@ -1411,23 +1590,23 @@ function getCliInvocation(command, args) {
|
|
|
1411
1590
|
}
|
|
1412
1591
|
function invokeRunSources(boardDir, cardPath, callbackToken, callback) {
|
|
1413
1592
|
const { cmd, args } = getCliInvocation("run-sources-internal", ["--card", cardPath, "--token", callbackToken, "--rg", boardDir]);
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1593
|
+
try {
|
|
1594
|
+
spawnDetachedCommand(cmd, args);
|
|
1595
|
+
callback(null);
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
function appendTaskExecutorLog(boardDir, hydratedSource) {
|
|
1601
|
+
try {
|
|
1602
|
+
const entry = {
|
|
1603
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1604
|
+
hydratedSource
|
|
1605
|
+
};
|
|
1606
|
+
fs.appendFileSync(path.join(boardDir, TASK_EXECUTOR_LOG_FILE), JSON.stringify(entry) + "\n", "utf-8");
|
|
1607
|
+
} catch (logErr) {
|
|
1608
|
+
console.error(`[task-executor-log] append failed: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
|
|
1609
|
+
}
|
|
1431
1610
|
}
|
|
1432
1611
|
function invokeSourceDataFetched(sourceToken, tmpFile, callback) {
|
|
1433
1612
|
const { cmd, args } = getCliInvocation("source-data-fetched", ["--tmp", tmpFile, "--token", sourceToken]);
|
|
@@ -1452,7 +1631,7 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1452
1631
|
if (!cardPath) return "task-initiate-failure";
|
|
1453
1632
|
const card = JSON.parse(fs.readFileSync(cardPath, "utf-8"));
|
|
1454
1633
|
const cardId = card.id;
|
|
1455
|
-
const cardState = card.
|
|
1634
|
+
const cardState = card.card_data ?? {};
|
|
1456
1635
|
const allSources = card.sources ?? [];
|
|
1457
1636
|
const requiredSources = allSources.filter((s) => s.optionalForCompletionGating !== true);
|
|
1458
1637
|
const runtime = readRuntimeState(boardDir, cardId);
|
|
@@ -1488,18 +1667,32 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1488
1667
|
}
|
|
1489
1668
|
}
|
|
1490
1669
|
}
|
|
1670
|
+
const requires = {};
|
|
1671
|
+
for (const [token, taskData] of Object.entries(input.state ?? {})) {
|
|
1672
|
+
if (taskData !== null && typeof taskData === "object" && !Array.isArray(taskData)) {
|
|
1673
|
+
const unwrapped = taskData[token];
|
|
1674
|
+
requires[token] = unwrapped !== void 0 ? unwrapped : taskData;
|
|
1675
|
+
} else {
|
|
1676
|
+
requires[token] = taskData;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1491
1679
|
const computeNode = {
|
|
1492
1680
|
id: cardId,
|
|
1493
|
-
|
|
1494
|
-
requires
|
|
1681
|
+
card_data: { ...cardState },
|
|
1682
|
+
requires,
|
|
1495
1683
|
sources: allSources,
|
|
1496
1684
|
compute: card.compute
|
|
1497
1685
|
};
|
|
1686
|
+
computeNode._sourcesData = sourcesData;
|
|
1498
1687
|
if (card.compute) {
|
|
1499
1688
|
await CardCompute.run(computeNode, { sourcesData });
|
|
1500
|
-
const cvPath = path.join(boardDir, `${cardId}.computed_values.json`);
|
|
1501
|
-
fs.writeFileSync(cvPath, JSON.stringify(computeNode.computed_values ?? {}, null, 2));
|
|
1502
1689
|
}
|
|
1690
|
+
const cvPath = resolveComputedValuesPath(boardDir, cardId);
|
|
1691
|
+
writeJsonAtomic(cvPath, {
|
|
1692
|
+
schema_version: "v1",
|
|
1693
|
+
card_id: cardId,
|
|
1694
|
+
computed_values: computeNode.computed_values ?? {}
|
|
1695
|
+
});
|
|
1503
1696
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1504
1697
|
const undeliveredRequired = requiredSources.filter((s) => {
|
|
1505
1698
|
if (!s.outputFile) return false;
|
|
@@ -1519,16 +1712,40 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1519
1712
|
}
|
|
1520
1713
|
}
|
|
1521
1714
|
if (stampedAny) writeRuntimeState(boardDir, cardId, runtime);
|
|
1522
|
-
|
|
1523
|
-
|
|
1715
|
+
const enrichedCard = { ...card };
|
|
1716
|
+
const enrichedSources = CardCompute.enrichSources(
|
|
1717
|
+
Array.isArray(card.sources) ? card.sources : void 0,
|
|
1718
|
+
{
|
|
1719
|
+
requires,
|
|
1720
|
+
sourcesData,
|
|
1721
|
+
computed_values: computeNode.computed_values
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
const sourceCwd = path.dirname(cardPath);
|
|
1725
|
+
enrichedCard.sources = Array.isArray(enrichedSources) ? enrichedSources.map((src) => ({
|
|
1726
|
+
...src,
|
|
1727
|
+
cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : sourceCwd,
|
|
1728
|
+
boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
|
|
1729
|
+
})) : enrichedSources;
|
|
1730
|
+
const enrichedCardPath = path.join(os.tmpdir(), `card-enriched-${cardId}-${Date.now()}.json`);
|
|
1731
|
+
fs.writeFileSync(enrichedCardPath, JSON.stringify(enrichedCard, null, 2), "utf-8");
|
|
1732
|
+
invokeRunSources(boardDir, enrichedCardPath, input.callbackToken, (err) => {
|
|
1733
|
+
if (err) {
|
|
1734
|
+
console.error(`[card-handler] ${input.nodeId}:`, err.message);
|
|
1735
|
+
try {
|
|
1736
|
+
fs.unlinkSync(enrichedCardPath);
|
|
1737
|
+
} catch {
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1524
1740
|
});
|
|
1525
1741
|
return "task-initiated";
|
|
1526
1742
|
}
|
|
1527
|
-
const providesBindings = card.provides ?? [
|
|
1743
|
+
const providesBindings = card.provides ?? [];
|
|
1528
1744
|
const data = {};
|
|
1529
1745
|
for (const { bindTo, src } of providesBindings) {
|
|
1530
1746
|
data[bindTo] = CardCompute.resolve(computeNode, src);
|
|
1531
1747
|
}
|
|
1748
|
+
writeRuntimeDataObjects(boardDir, data);
|
|
1532
1749
|
const undeliveredOptional = allSources.filter((s) => {
|
|
1533
1750
|
if (s.optionalForCompletionGating !== true || !s.outputFile) return false;
|
|
1534
1751
|
const entry = runtime._sources[s.bindTo];
|
|
@@ -1556,18 +1773,20 @@ function createBoardReactiveGraph(boardDir) {
|
|
|
1556
1773
|
function addSingleCardFromFile(dir, cardFile) {
|
|
1557
1774
|
const absCardPath = path.resolve(cardFile);
|
|
1558
1775
|
if (!fs.existsSync(absCardPath)) {
|
|
1559
|
-
|
|
1560
|
-
|
|
1776
|
+
throw new Error(`Card file not found: ${absCardPath}`);
|
|
1777
|
+
}
|
|
1778
|
+
let card;
|
|
1779
|
+
try {
|
|
1780
|
+
card = JSON.parse(fs.readFileSync(absCardPath, "utf-8"));
|
|
1781
|
+
} catch (err) {
|
|
1782
|
+
throw new Error(`Failed to parse card file: ${absCardPath} - ${err instanceof Error ? err.message : String(err)}`);
|
|
1561
1783
|
}
|
|
1562
|
-
const card = JSON.parse(fs.readFileSync(absCardPath, "utf-8"));
|
|
1563
1784
|
if (!card.id) {
|
|
1564
|
-
|
|
1565
|
-
process.exit(1);
|
|
1785
|
+
throw new Error('Card JSON must have an "id" field');
|
|
1566
1786
|
}
|
|
1567
1787
|
const existing = readCardInventory(dir);
|
|
1568
1788
|
if (existing.some((e) => e.cardId === card.id)) {
|
|
1569
|
-
|
|
1570
|
-
process.exit(1);
|
|
1789
|
+
throw new Error(`Card "${card.id}" already exists in inventory`);
|
|
1571
1790
|
}
|
|
1572
1791
|
appendCardInventory(dir, {
|
|
1573
1792
|
cardId: card.id,
|
|
@@ -1594,7 +1813,7 @@ function resolveCardGlobMatches(cardGlob) {
|
|
|
1594
1813
|
unique: true,
|
|
1595
1814
|
dot: false
|
|
1596
1815
|
});
|
|
1597
|
-
return [...matches].sort((a, b) => a.localeCompare(b));
|
|
1816
|
+
return [...matches].map((m) => path.resolve(m)).sort((a, b) => a.localeCompare(b));
|
|
1598
1817
|
}
|
|
1599
1818
|
function cmdAddCards(args) {
|
|
1600
1819
|
const rgIdx = args.indexOf("--rg");
|
|
@@ -1604,16 +1823,14 @@ function cmdAddCards(args) {
|
|
|
1604
1823
|
const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
|
|
1605
1824
|
const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
|
|
1606
1825
|
if (!dir || !cardFile && !cardGlob || cardFile && cardGlob) {
|
|
1607
|
-
|
|
1608
|
-
process.exit(1);
|
|
1826
|
+
throw new Error("Usage: board-live-cards add-cards --rg <dir> (--card <card.json> | --card-glob <glob>)");
|
|
1609
1827
|
}
|
|
1610
1828
|
if (cardFile) {
|
|
1611
1829
|
addSingleCardFromFile(dir, cardFile);
|
|
1612
1830
|
} else {
|
|
1613
1831
|
const matches = resolveCardGlobMatches(cardGlob);
|
|
1614
1832
|
if (matches.length === 0) {
|
|
1615
|
-
|
|
1616
|
-
process.exit(1);
|
|
1833
|
+
throw new Error(`No card files matched glob: ${cardGlob}`);
|
|
1617
1834
|
}
|
|
1618
1835
|
for (const match of matches) {
|
|
1619
1836
|
addSingleCardFromFile(dir, match);
|
|
@@ -1625,30 +1842,34 @@ function cmdAddCards(args) {
|
|
|
1625
1842
|
function cmdInit(args) {
|
|
1626
1843
|
const dir = args[0];
|
|
1627
1844
|
if (!dir) {
|
|
1628
|
-
|
|
1629
|
-
process.exit(1);
|
|
1845
|
+
throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
|
|
1630
1846
|
}
|
|
1631
1847
|
const teIdx = args.indexOf("--task-executor");
|
|
1632
1848
|
const taskExecutor = teIdx !== -1 ? args[teIdx + 1] : void 0;
|
|
1849
|
+
const chIdx = args.indexOf("--chat-handler");
|
|
1850
|
+
const chatHandler = chIdx !== -1 ? args[chIdx + 1] : void 0;
|
|
1851
|
+
const roIdx = args.indexOf("--runtime-out");
|
|
1852
|
+
const runtimeOut = roIdx !== -1 ? args[roIdx + 1] : void 0;
|
|
1853
|
+
if (roIdx !== -1 && !runtimeOut) {
|
|
1854
|
+
throw new Error("Usage: board-live-cards init <dir> [--task-executor <script>] [--chat-handler <script>] [--runtime-out <dir>]");
|
|
1855
|
+
}
|
|
1633
1856
|
const result = initBoard(dir);
|
|
1634
1857
|
if (taskExecutor) {
|
|
1635
1858
|
fs.writeFileSync(path.join(dir, ".task-executor"), taskExecutor, "utf-8");
|
|
1636
1859
|
}
|
|
1860
|
+
if (chatHandler) {
|
|
1861
|
+
fs.writeFileSync(path.join(dir, ".chat-handler"), chatHandler, "utf-8");
|
|
1862
|
+
}
|
|
1863
|
+
const runtimeOutDir = configureRuntimeOutDir(dir, runtimeOut);
|
|
1864
|
+
const live = loadBoard(dir);
|
|
1865
|
+
writeJsonAtomic(resolveStatusSnapshotPath(dir), buildBoardStatusObject(dir, live));
|
|
1637
1866
|
if (result === "exists") {
|
|
1638
|
-
console.log(`Board already initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor updated: ${taskExecutor})` : ""}`);
|
|
1867
|
+
console.log(`Board already initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor updated: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
|
|
1639
1868
|
} else {
|
|
1640
|
-
console.log(`Board initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor: ${taskExecutor})` : ""}`);
|
|
1869
|
+
console.log(`Board initialized at ${path.resolve(dir)}${taskExecutor ? ` (task-executor: ${taskExecutor})` : ""} (runtime-out: ${runtimeOutDir})`);
|
|
1641
1870
|
}
|
|
1642
1871
|
}
|
|
1643
|
-
function
|
|
1644
|
-
const rgIdx = args.indexOf("--rg");
|
|
1645
|
-
const asJson = args.includes("--json");
|
|
1646
|
-
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
1647
|
-
if (!dir) {
|
|
1648
|
-
console.error("Usage: board-live-cards status --rg <dir>");
|
|
1649
|
-
process.exit(1);
|
|
1650
|
-
}
|
|
1651
|
-
const live = loadBoard(dir);
|
|
1872
|
+
function buildBoardStatusObject(dir, live) {
|
|
1652
1873
|
const taskState = live.state.tasks;
|
|
1653
1874
|
const taskConfig = live.config.tasks;
|
|
1654
1875
|
const cardNames = Object.keys(taskState);
|
|
@@ -1665,14 +1886,8 @@ function cmdStatus(args) {
|
|
|
1665
1886
|
for (const p of sched.pending) waitingByCard.set(p.taskName, p.waitingOn);
|
|
1666
1887
|
for (const u of sched.unresolved) waitingByCard.set(u.taskName, u.missingTokens);
|
|
1667
1888
|
for (const b of sched.blocked) waitingByCard.set(b.taskName, b.failedTokens);
|
|
1668
|
-
const providersByToken = /* @__PURE__ */ new Map();
|
|
1669
1889
|
const dependentsByToken = /* @__PURE__ */ new Map();
|
|
1670
1890
|
for (const [name, cfg] of Object.entries(taskConfig)) {
|
|
1671
|
-
for (const token of cfg.provides ?? []) {
|
|
1672
|
-
const providers = providersByToken.get(token) ?? [];
|
|
1673
|
-
providers.push(name);
|
|
1674
|
-
providersByToken.set(token, providers);
|
|
1675
|
-
}
|
|
1676
1891
|
for (const token of cfg.requires ?? []) {
|
|
1677
1892
|
const dependents = dependentsByToken.get(token) ?? [];
|
|
1678
1893
|
dependents.push(name);
|
|
@@ -1742,7 +1957,7 @@ function cmdStatus(args) {
|
|
|
1742
1957
|
const feedsAny = provides.some((p) => (dependentsByToken.get(p) ?? []).some((d) => d !== name));
|
|
1743
1958
|
if (requiresNone && !feedsAny) orphanCards += 1;
|
|
1744
1959
|
}
|
|
1745
|
-
|
|
1960
|
+
return {
|
|
1746
1961
|
schema_version: "v1",
|
|
1747
1962
|
meta: {
|
|
1748
1963
|
board: {
|
|
@@ -1767,6 +1982,23 @@ function cmdStatus(args) {
|
|
|
1767
1982
|
},
|
|
1768
1983
|
cards
|
|
1769
1984
|
};
|
|
1985
|
+
}
|
|
1986
|
+
function cmdStatus(args) {
|
|
1987
|
+
const rgIdx = args.indexOf("--rg");
|
|
1988
|
+
const asJson = args.includes("--json");
|
|
1989
|
+
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
1990
|
+
if (!dir) {
|
|
1991
|
+
console.error("Usage: board-live-cards status --rg <dir>");
|
|
1992
|
+
process.exit(1);
|
|
1993
|
+
}
|
|
1994
|
+
const statusOutPath = resolveStatusSnapshotPath(dir);
|
|
1995
|
+
let statusObject;
|
|
1996
|
+
if (fs.existsSync(statusOutPath)) {
|
|
1997
|
+
statusObject = JSON.parse(fs.readFileSync(statusOutPath, "utf-8"));
|
|
1998
|
+
} else {
|
|
1999
|
+
statusObject = buildBoardStatusObject(dir, loadBoard(dir));
|
|
2000
|
+
writeJsonAtomic(statusOutPath, statusObject);
|
|
2001
|
+
}
|
|
1770
2002
|
if (asJson) {
|
|
1771
2003
|
console.log(JSON.stringify(statusObject, null, 2));
|
|
1772
2004
|
return;
|
|
@@ -1797,6 +2029,7 @@ function cmdTaskCompleted(args) {
|
|
|
1797
2029
|
process.exit(1);
|
|
1798
2030
|
}
|
|
1799
2031
|
const data = dataIdx !== -1 ? JSON.parse(args[dataIdx + 1]) : {};
|
|
2032
|
+
writeRuntimeDataObjects(dir, data);
|
|
1800
2033
|
appendEventToJournal(dir, {
|
|
1801
2034
|
type: "task-completed",
|
|
1802
2035
|
taskName: decoded.taskName,
|
|
@@ -1923,6 +2156,12 @@ function cmdRunSources(args) {
|
|
|
1923
2156
|
process.exit(1);
|
|
1924
2157
|
}
|
|
1925
2158
|
const card = JSON.parse(fs.readFileSync(cardFilePath, "utf-8"));
|
|
2159
|
+
if (path.basename(cardFilePath).startsWith("card-enriched-")) {
|
|
2160
|
+
try {
|
|
2161
|
+
fs.unlinkSync(cardFilePath);
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
1926
2165
|
console.log(`[run-sources-internal] Processing card "${card.id}"`);
|
|
1927
2166
|
const executorFile = path.join(boardDir, ".task-executor");
|
|
1928
2167
|
const taskExecutor = fs.existsSync(executorFile) ? fs.readFileSync(executorFile, "utf-8").trim() : void 0;
|
|
@@ -1951,7 +2190,12 @@ function cmdRunSources(args) {
|
|
|
1951
2190
|
const inFile = path.join(os.tmpdir(), `card-source-in-${src.bindTo}-${Date.now()}.json`);
|
|
1952
2191
|
const outFile2 = path.join(os.tmpdir(), `card-source-out-${src.bindTo}-${Date.now()}.json`);
|
|
1953
2192
|
const errFile = path.join(os.tmpdir(), `card-source-err-${src.bindTo}-${Date.now()}.txt`);
|
|
1954
|
-
const sourceForExecutor = {
|
|
2193
|
+
const sourceForExecutor = {
|
|
2194
|
+
...src,
|
|
2195
|
+
cwd: typeof src.cwd === "string" && src.cwd ? src.cwd : path.dirname(cardFilePath || ""),
|
|
2196
|
+
boardDir: typeof src.boardDir === "string" && src.boardDir ? src.boardDir : boardDir
|
|
2197
|
+
};
|
|
2198
|
+
appendTaskExecutorLog(boardDir, sourceForExecutor);
|
|
1955
2199
|
fs.writeFileSync(inFile, JSON.stringify(sourceForExecutor, null, 2), "utf-8");
|
|
1956
2200
|
console.log(`[run-sources-internal] task-executor: ${taskExecutor} run-source-fetch --in ${inFile} --out ${outFile2} --err ${errFile}`);
|
|
1957
2201
|
try {
|
|
@@ -2108,17 +2352,14 @@ function cmdUpdateCard(args) {
|
|
|
2108
2352
|
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
2109
2353
|
const cardId = idIdx !== -1 ? args[idIdx + 1] : void 0;
|
|
2110
2354
|
if (!dir || !cardId) {
|
|
2111
|
-
|
|
2112
|
-
process.exit(1);
|
|
2355
|
+
throw new Error("Usage: board-live-cards update-card --rg <dir> --card-id <card-id> [--restart]");
|
|
2113
2356
|
}
|
|
2114
2357
|
const cardPath = lookupCardPath(dir, cardId);
|
|
2115
2358
|
if (!cardPath) {
|
|
2116
|
-
|
|
2117
|
-
process.exit(1);
|
|
2359
|
+
throw new Error(`Card "${cardId}" not found in inventory`);
|
|
2118
2360
|
}
|
|
2119
2361
|
if (!fs.existsSync(cardPath)) {
|
|
2120
|
-
|
|
2121
|
-
process.exit(1);
|
|
2362
|
+
throw new Error(`Card file not found: ${cardPath}`);
|
|
2122
2363
|
}
|
|
2123
2364
|
const card = JSON.parse(fs.readFileSync(cardPath, "utf-8"));
|
|
2124
2365
|
const taskConfig = liveCardToTaskConfig(card);
|
|
@@ -2138,6 +2379,121 @@ function cmdUpdateCard(args) {
|
|
|
2138
2379
|
void processAccumulatedEventsInfinitePass(dir);
|
|
2139
2380
|
console.log(`Card "${cardId}" updated${restart ? " (restarted)" : ""}.`);
|
|
2140
2381
|
}
|
|
2382
|
+
function cmdUpsertCard(args) {
|
|
2383
|
+
const rgIdx = args.indexOf("--rg");
|
|
2384
|
+
const cardIdx = args.indexOf("--card");
|
|
2385
|
+
const globIdx = args.indexOf("--card-glob");
|
|
2386
|
+
const cardIdIdx = args.indexOf("--card-id");
|
|
2387
|
+
const restart = args.includes("--restart");
|
|
2388
|
+
const dir = rgIdx !== -1 ? args[rgIdx + 1] : void 0;
|
|
2389
|
+
const cardFile = cardIdx !== -1 ? args[cardIdx + 1] : void 0;
|
|
2390
|
+
const cardGlob = globIdx !== -1 ? args[globIdx + 1] : void 0;
|
|
2391
|
+
const requestedCardId = cardIdIdx !== -1 ? args[cardIdIdx + 1] : void 0;
|
|
2392
|
+
if (!dir || !cardFile && !cardGlob || cardFile && cardGlob) {
|
|
2393
|
+
console.error("Usage: board-live-cards upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]");
|
|
2394
|
+
process.exit(1);
|
|
2395
|
+
}
|
|
2396
|
+
if (cardGlob && requestedCardId) {
|
|
2397
|
+
console.error("Usage: --card-id may be used only with --card (single file), not with --card-glob");
|
|
2398
|
+
process.exit(1);
|
|
2399
|
+
}
|
|
2400
|
+
const cardFiles = cardFile ? [path.resolve(cardFile)] : resolveCardGlobMatches(cardGlob);
|
|
2401
|
+
if (!cardFile && cardFiles.length === 0) {
|
|
2402
|
+
console.error(`No card files matched glob: ${cardGlob}`);
|
|
2403
|
+
process.exit(1);
|
|
2404
|
+
}
|
|
2405
|
+
const idx = buildCardInventoryIndex(dir);
|
|
2406
|
+
const batchByCardId = /* @__PURE__ */ new Map();
|
|
2407
|
+
const batchByCardPath = /* @__PURE__ */ new Map();
|
|
2408
|
+
const plans = [];
|
|
2409
|
+
const logs = [];
|
|
2410
|
+
for (const absCardPath of cardFiles) {
|
|
2411
|
+
if (!fs.existsSync(absCardPath)) {
|
|
2412
|
+
console.error(`Card file not found: ${absCardPath}`);
|
|
2413
|
+
process.exit(1);
|
|
2414
|
+
}
|
|
2415
|
+
const card = JSON.parse(fs.readFileSync(absCardPath, "utf-8"));
|
|
2416
|
+
if (!card.id) {
|
|
2417
|
+
console.error(`Card JSON must have an "id" field (${absCardPath})`);
|
|
2418
|
+
process.exit(1);
|
|
2419
|
+
}
|
|
2420
|
+
if (requestedCardId && requestedCardId !== card.id) {
|
|
2421
|
+
console.error(
|
|
2422
|
+
`Card id mismatch: --card-id "${requestedCardId}" does not match file id "${card.id}" (${absCardPath})`
|
|
2423
|
+
);
|
|
2424
|
+
process.exit(1);
|
|
2425
|
+
}
|
|
2426
|
+
const seenPathCardId = batchByCardPath.get(absCardPath);
|
|
2427
|
+
if (seenPathCardId && seenPathCardId !== card.id) {
|
|
2428
|
+
console.error(
|
|
2429
|
+
`Upsert rejected: file "${absCardPath}" appears multiple times in batch with conflicting ids ("${seenPathCardId}" vs "${card.id}")`
|
|
2430
|
+
);
|
|
2431
|
+
process.exit(1);
|
|
2432
|
+
}
|
|
2433
|
+
const seenCardPath = batchByCardId.get(card.id);
|
|
2434
|
+
if (seenCardPath && seenCardPath !== absCardPath) {
|
|
2435
|
+
console.error(
|
|
2436
|
+
`Upsert rejected: card id "${card.id}" appears multiple times in batch with conflicting files ("${seenCardPath}" vs "${absCardPath}")`
|
|
2437
|
+
);
|
|
2438
|
+
process.exit(1);
|
|
2439
|
+
}
|
|
2440
|
+
const existingById = idx.byCardId.get(card.id);
|
|
2441
|
+
const existingByPath = idx.byCardPath.get(absCardPath);
|
|
2442
|
+
if (existingByPath && existingByPath.cardId !== card.id) {
|
|
2443
|
+
console.error(
|
|
2444
|
+
`Upsert rejected: file "${absCardPath}" is already mapped to card id "${existingByPath.cardId}", cannot remap to "${card.id}"`
|
|
2445
|
+
);
|
|
2446
|
+
process.exit(1);
|
|
2447
|
+
}
|
|
2448
|
+
if (existingById && existingById.cardFilePath !== absCardPath) {
|
|
2449
|
+
console.error(
|
|
2450
|
+
`Upsert rejected: card id "${card.id}" is already mapped to file "${existingById.cardFilePath}", cannot remap to "${absCardPath}"`
|
|
2451
|
+
);
|
|
2452
|
+
process.exit(1);
|
|
2453
|
+
}
|
|
2454
|
+
batchByCardPath.set(absCardPath, card.id);
|
|
2455
|
+
batchByCardId.set(card.id, absCardPath);
|
|
2456
|
+
plans.push({
|
|
2457
|
+
card,
|
|
2458
|
+
absCardPath,
|
|
2459
|
+
isInsert: !existingById
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
for (const plan of plans) {
|
|
2463
|
+
const { card, absCardPath, isInsert } = plan;
|
|
2464
|
+
if (isInsert) {
|
|
2465
|
+
const newEntry = {
|
|
2466
|
+
cardId: card.id,
|
|
2467
|
+
cardFilePath: absCardPath,
|
|
2468
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2469
|
+
};
|
|
2470
|
+
appendCardInventory(dir, newEntry);
|
|
2471
|
+
idx.byCardId.set(card.id, newEntry);
|
|
2472
|
+
idx.byCardPath.set(absCardPath, newEntry);
|
|
2473
|
+
}
|
|
2474
|
+
const taskConfig = liveCardToTaskConfig(card);
|
|
2475
|
+
appendEventToJournal(dir, {
|
|
2476
|
+
type: "task-upsert",
|
|
2477
|
+
taskName: card.id,
|
|
2478
|
+
taskConfig,
|
|
2479
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2480
|
+
});
|
|
2481
|
+
if (restart) {
|
|
2482
|
+
appendEventToJournal(dir, {
|
|
2483
|
+
type: "task-restart",
|
|
2484
|
+
taskName: card.id,
|
|
2485
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
logs.push(`Card "${card.id}" ${isInsert ? "upserted (inserted)" : "upserted (updated)"}${restart ? " (restarted)" : ""}.`);
|
|
2489
|
+
}
|
|
2490
|
+
void processAccumulatedEventsInfinitePass(dir);
|
|
2491
|
+
if (cardGlob) {
|
|
2492
|
+
console.log(`Upserted ${cardFiles.length} cards from glob: ${cardGlob}${restart ? " (restarted)" : ""}`);
|
|
2493
|
+
} else {
|
|
2494
|
+
console.log(logs[0]);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2141
2497
|
async function cmdTryDrain(args) {
|
|
2142
2498
|
const rgIdx = args.indexOf("--rg");
|
|
2143
2499
|
const inlineLoop = args.includes("--inline-loop");
|
|
@@ -2181,6 +2537,8 @@ async function cli(argv) {
|
|
|
2181
2537
|
return cmdAddCards(rest);
|
|
2182
2538
|
case "update-card":
|
|
2183
2539
|
return cmdUpdateCard(rest);
|
|
2540
|
+
case "upsert-card":
|
|
2541
|
+
return cmdUpsertCard(rest);
|
|
2184
2542
|
case "remove-card":
|
|
2185
2543
|
return cmdRemoveCard(rest);
|
|
2186
2544
|
case "retrigger":
|
|
@@ -2200,9 +2558,7 @@ async function cli(argv) {
|
|
|
2200
2558
|
case "process-accumulated-events":
|
|
2201
2559
|
return await cmdTryDrain(rest);
|
|
2202
2560
|
default:
|
|
2203
|
-
|
|
2204
|
-
console.error("Run: board-live-cards help");
|
|
2205
|
-
process.exit(1);
|
|
2561
|
+
throw new Error(`Unknown command: ${cmd ?? "(none)"}`);
|
|
2206
2562
|
}
|
|
2207
2563
|
}
|
|
2208
2564
|
function cmdHelp() {
|
|
@@ -2213,14 +2569,18 @@ USAGE
|
|
|
2213
2569
|
board-live-cards-cli <command> [options]
|
|
2214
2570
|
|
|
2215
2571
|
BOARD MANAGEMENT
|
|
2216
|
-
init <dir> [--task-executor <script>]
|
|
2572
|
+
init <dir> [--task-executor <script>] [--runtime-out <dir>]
|
|
2217
2573
|
Create a new board in <dir>.
|
|
2218
2574
|
If --task-executor is given, writes <dir>/.task-executor with the script path.
|
|
2575
|
+
Writes <dir>/.runtime-out (default: <dir>/runtime-out).
|
|
2576
|
+
Published runtime files:
|
|
2577
|
+
<runtime-out>/board-livegraph-status.json
|
|
2578
|
+
<runtime-out>/cards/<card-id>.computed.json
|
|
2219
2579
|
Re-running init on an existing board is safe; --task-executor updates the registration.
|
|
2220
2580
|
|
|
2221
2581
|
status --rg <dir> [--json]
|
|
2222
|
-
|
|
2223
|
-
--json emits
|
|
2582
|
+
Read and print the published status snapshot from <runtime-out>/board-livegraph-status.json.
|
|
2583
|
+
--json emits the stable machine-readable status object.
|
|
2224
2584
|
|
|
2225
2585
|
CARD MANAGEMENT
|
|
2226
2586
|
add-cards --rg <dir> (--card <card.json> | --card-glob <glob>)
|
|
@@ -2233,6 +2593,16 @@ CARD MANAGEMENT
|
|
|
2233
2593
|
Re-read the card JSON from disk and patch the board.
|
|
2234
2594
|
--restart clears the task so it re-triggers from scratch.
|
|
2235
2595
|
|
|
2596
|
+
upsert-card --rg <dir> (--card <card.json> | --card-glob <glob>) [--card-id <card-id>] [--restart]
|
|
2597
|
+
Insert or update one or many cards.
|
|
2598
|
+
Enforces strict one-to-one mapping between card id and file path:
|
|
2599
|
+
- same id + same file path: update
|
|
2600
|
+
- new id + new file path: insert
|
|
2601
|
+
- id remap or file remap: rejected
|
|
2602
|
+
If --card-id is provided, it must match the id inside the file.
|
|
2603
|
+
--card-id is valid only with --card (single file), not with --card-glob.
|
|
2604
|
+
--restart clears the task so it re-triggers from scratch.
|
|
2605
|
+
|
|
2236
2606
|
remove-card --rg <dir> --id <card-id>
|
|
2237
2607
|
Remove a card and its task from the board.
|
|
2238
2608
|
|
|
@@ -2327,6 +2697,6 @@ if (isMain) {
|
|
|
2327
2697
|
});
|
|
2328
2698
|
}
|
|
2329
2699
|
|
|
2330
|
-
export { BoardJournal, appendCardInventory, appendEventToJournal, cli, createBoardReactiveGraph, decodeSourceToken, encodeSourceToken, getUndrainedEntries, initBoard, liveCardToTaskConfig, loadBoard, loadBoardEnvelope, lookupCardPath, processAccumulatedEvents, processAccumulatedEventsForced, processAccumulatedEventsInfinitePass, readCardInventory, saveBoard, withBoardLock };
|
|
2700
|
+
export { BoardJournal, appendCardInventory, appendEventToJournal, buildCardInventoryIndex, cli, createBoardReactiveGraph, decodeSourceToken, encodeSourceToken, getUndrainedEntries, initBoard, liveCardToTaskConfig, loadBoard, loadBoardEnvelope, lookupCardPath, processAccumulatedEvents, processAccumulatedEventsForced, processAccumulatedEventsInfinitePass, readCardInventory, saveBoard, withBoardLock };
|
|
2331
2701
|
//# sourceMappingURL=board-live-cards-cli.js.map
|
|
2332
2702
|
//# sourceMappingURL=board-live-cards-cli.js.map
|