yaml-flow 5.4.0 → 6.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/board-live-cards-cli.js +2 -2
- package/board-livecards-server-runtime.js +488 -551
- package/browser/asset-integrity.json +10 -0
- package/browser/board-livecards-runtime-client.js +0 -6
- package/browser/board-livegraph-engine.js +2 -1676
- package/browser/board-livegraph-engine.js.map +1 -1
- package/browser/live-cards.js +347 -26
- package/browser/live-cards.schema.json +418 -132
- package/card-store.js +37 -0
- package/dist/batch/index.cjs +1 -108
- package/dist/batch/index.cjs.map +1 -1
- package/dist/batch/index.js +1 -106
- package/dist/batch/index.js.map +1 -1
- package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
- package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
- package/dist/board-live-cards-public-CltXYgaY.d.cts +314 -0
- package/dist/board-live-cards-public-f-E-FAyp.d.ts +314 -0
- package/dist/board-livegraph-runtime/index.cjs +2 -1671
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +1 -2
- package/dist/board-livegraph-runtime/index.d.ts +1 -2
- package/dist/board-livegraph-runtime/index.js +2 -1662
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/board-livegraph-runtime/jsonata-sync.cjs +7587 -0
- package/dist/card-compute/index.cjs +9 -7159
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +22 -0
- package/dist/card-compute/index.d.ts +22 -0
- package/dist/card-compute/index.js +9 -7145
- package/dist/card-compute/index.js.map +1 -1
- package/dist/card-compute/jsonata-sync.cjs +7587 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +24 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +24 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.js +2 -0
- package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
- package/dist/cli/browser-api/jsonata-sync.cjs +7587 -0
- package/dist/cli/node/artifacts-store-cli.cjs +11 -0
- package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
- package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
- package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
- package/dist/cli/node/artifacts-store-cli.js +11 -0
- package/dist/cli/node/artifacts-store-cli.js.map +1 -0
- package/dist/cli/node/board-live-cards-cli.cjs +15 -0
- package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
- package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
- package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
- package/dist/cli/node/board-live-cards-cli.js +15 -0
- package/dist/cli/node/board-live-cards-cli.js.map +1 -0
- package/dist/cli/node/card-store-cli.cjs +8 -0
- package/dist/cli/node/card-store-cli.cjs.map +1 -0
- package/dist/cli/node/card-store-cli.d.cts +15 -0
- package/dist/cli/node/card-store-cli.d.ts +15 -0
- package/dist/cli/node/card-store-cli.js +8 -0
- package/dist/cli/node/card-store-cli.js.map +1 -0
- package/dist/cli/node/fs-board-adapter.cjs +14 -0
- package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
- package/dist/cli/node/fs-board-adapter.d.cts +204 -0
- package/dist/cli/node/fs-board-adapter.d.ts +204 -0
- package/dist/cli/node/fs-board-adapter.js +14 -0
- package/dist/cli/node/fs-board-adapter.js.map +1 -0
- package/dist/cli/node/jsonata-sync.cjs +7587 -0
- package/dist/cli/node/source-cli-task-executor.cjs +11 -0
- package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
- package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
- package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
- package/dist/cli/node/source-cli-task-executor.js +11 -0
- package/dist/cli/node/source-cli-task-executor.js.map +1 -0
- package/dist/config/index.cjs +1 -79
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.js +1 -76
- package/dist/config/index.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +2 -2129
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +81 -5
- package/dist/continuous-event-graph/index.d.ts +81 -5
- package/dist/continuous-event-graph/index.js +2 -2088
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/continuous-event-graph/jsonata-sync.cjs +7587 -0
- package/dist/event-graph/index.cjs +22 -8292
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.js +22 -8237
- package/dist/event-graph/index.js.map +1 -1
- package/dist/execution-refs.cjs +2 -0
- package/dist/execution-refs.cjs.map +1 -0
- package/dist/execution-refs.d.cts +222 -0
- package/dist/execution-refs.d.ts +222 -0
- package/dist/execution-refs.js +2 -0
- package/dist/execution-refs.js.map +1 -0
- package/dist/index.cjs +29 -13221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +29 -13112
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +5 -617
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.js +5 -610
- package/dist/inference/index.js.map +1 -1
- package/dist/jsonata-sync.cjs +7587 -0
- package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
- package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
- package/dist/pycli/quickjs-board-runtime.global.js +9 -0
- package/dist/pycli/quickjs-board-runtime.global.js.map +1 -0
- package/dist/pycli/quickjs-step-machine-runtime.global.js +5 -0
- package/dist/pycli/quickjs-step-machine-runtime.global.js.map +1 -0
- package/dist/step-machine/index.cjs +11 -7129
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.js +11 -7113
- package/dist/step-machine/index.js.map +1 -1
- package/dist/storage-refs.cjs +10 -0
- package/dist/storage-refs.cjs.map +1 -0
- package/dist/storage-refs.d.cts +92 -0
- package/dist/storage-refs.d.ts +92 -0
- package/dist/storage-refs.js +10 -0
- package/dist/storage-refs.js.map +1 -0
- package/dist/stores/file.cjs +1 -114
- package/dist/stores/file.cjs.map +1 -1
- package/dist/stores/file.js +1 -112
- package/dist/stores/file.js.map +1 -1
- package/dist/stores/index.cjs +1 -231
- package/dist/stores/index.cjs.map +1 -1
- package/dist/stores/index.js +1 -227
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/localStorage.cjs +1 -76
- package/dist/stores/localStorage.cjs.map +1 -1
- package/dist/stores/localStorage.js +1 -74
- package/dist/stores/localStorage.js.map +1 -1
- package/dist/stores/memory.cjs +1 -47
- package/dist/stores/memory.cjs.map +1 -1
- package/dist/stores/memory.js +1 -45
- package/dist/stores/memory.js.map +1 -1
- package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +292 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +553 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +365 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +53 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +15 -6
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +6 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +57 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +7 -2
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +6 -2
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +97 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +50 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +44 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +70 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +25 -172
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
- package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +46 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- package/examples/example-board/agent-instructions.md +11 -5
- package/examples/example-board/demo-chat-handler.js +14 -4
- package/examples/example-board/demo-server-config.json +1 -0
- package/examples/example-board/demo-server.js +19 -34
- package/examples/example-board/demo-shell-browser.html +5 -4
- package/examples/example-board/demo-shell-with-server.html +10 -6
- package/examples/example-board/demo-task-executor.js +81 -35
- package/examples/index.html +0 -14
- package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
- package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- package/package.json +39 -3
- package/schema/live-cards.schema.json +418 -132
- package/dist/cli/board-live-cards-cli.cjs +0 -10644
- package/dist/cli/board-live-cards-cli.cjs.map +0 -1
- package/dist/cli/board-live-cards-cli.d.cts +0 -179
- package/dist/cli/board-live-cards-cli.d.ts +0 -179
- package/dist/cli/board-live-cards-cli.js +0 -10592
- package/dist/cli/board-live-cards-cli.js.map +0 -1
- package/dist/journal-9HEgs7dU.d.ts +0 -28
- package/dist/journal-B-JCfQnh.d.cts +0 -28
- package/dist/schedule-Cszq9LYY.d.ts +0 -21
- package/dist/schedule-qWNL0RQh.d.cts +0 -21
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
- package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* portfolio-t4.js — T4 rapid-fire test only.
|
|
4
|
+
*
|
|
5
|
+
* Runs T0 (init, card setup) then fires 5 portfolio-form upserts
|
|
6
|
+
* back-to-back (no delay) and waits for the board to converge.
|
|
7
|
+
* Asserts final prices contain the iter-5 tickers only (AAPL/MSFT/GOOG/TSLA)
|
|
8
|
+
* and AMZN is absent.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const _REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
18
|
+
|
|
19
|
+
// ── Library imports ────────────────────────────────────────────────────────────
|
|
20
|
+
const _adapterPath = path.join(_REPO_ROOT, 'dist', 'cli', 'node', 'fs-board-adapter.js');
|
|
21
|
+
const {
|
|
22
|
+
createBoardLiveCardsPublic,
|
|
23
|
+
createBoardLiveCardsNonCorePublic,
|
|
24
|
+
createFsBoardPlatformAdapter,
|
|
25
|
+
createFsBoardNonCorePlatformAdapter,
|
|
26
|
+
createCardStorePublic,
|
|
27
|
+
createCardStore,
|
|
28
|
+
parseRef,
|
|
29
|
+
} = await import(pathToFileURL(_adapterPath).href);
|
|
30
|
+
|
|
31
|
+
const FETCH_PRICES_JS = path.join(__dirname, 'portfolio-tracker-fetch-prices.js');
|
|
32
|
+
|
|
33
|
+
// ── Runtime directories ────────────────────────────────────────────────────────
|
|
34
|
+
const _TMP_BASE = path.join(os.tmpdir(), 'experiment-js-t4');
|
|
35
|
+
const CARDSTORE_DIR = path.join(_TMP_BASE, 'cardstore');
|
|
36
|
+
const BOARDRUNTIME_DIR = path.join(_TMP_BASE, 'boardruntime');
|
|
37
|
+
const OUTPUTS_DIR = path.join(_TMP_BASE, 'outputs');
|
|
38
|
+
|
|
39
|
+
const CARDSTORE_REF = `::fs-path::${CARDSTORE_DIR}`;
|
|
40
|
+
const BOARDRUNTIME_REF = `::fs-path::${BOARDRUNTIME_DIR}`;
|
|
41
|
+
const OUTPUTS_REF = `::fs-path::${OUTPUTS_DIR}`;
|
|
42
|
+
|
|
43
|
+
// ── Card definitions ───────────────────────────────────────────────────────────
|
|
44
|
+
const CARD_PORTFOLIO_FORM = {
|
|
45
|
+
id: 'portfolio-form',
|
|
46
|
+
meta: { title: 'Portfolio Holdings Form' },
|
|
47
|
+
provides: [{ bindTo: 'holdings', ref: 'card_data.holdings' }],
|
|
48
|
+
card_data: { holdings: [] },
|
|
49
|
+
view: { elements: [{ kind: 'table', label: 'Holdings', data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } }] }
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const CARD_PRICE_FETCH = {
|
|
53
|
+
id: 'price-fetch',
|
|
54
|
+
meta: { title: 'Fetch Market Prices' },
|
|
55
|
+
requires: ['holdings'],
|
|
56
|
+
provides: [{ bindTo: 'prices', ref: 'fetched_sources.prices' }],
|
|
57
|
+
card_data: {},
|
|
58
|
+
source_defs: [{
|
|
59
|
+
kind: 'mock-quotes',
|
|
60
|
+
bindTo: 'prices',
|
|
61
|
+
outputFile: 'prices.json',
|
|
62
|
+
projections: { tickers: '$append([], requires.holdings.symbol)' }
|
|
63
|
+
}],
|
|
64
|
+
view: { elements: [{ kind: 'table', label: 'Market Prices', data: { bind: 'fetched_sources.prices' } }] }
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const CARD_HOLDINGS_TABLE = {
|
|
68
|
+
id: 'holdings-table',
|
|
69
|
+
meta: { title: 'Holdings Table' },
|
|
70
|
+
requires: ['holdings', 'prices'],
|
|
71
|
+
provides: [{ bindTo: 'table', ref: 'computed_values.table' }],
|
|
72
|
+
card_data: {},
|
|
73
|
+
compute: [{
|
|
74
|
+
bindTo: 'table',
|
|
75
|
+
expr: '{ "rows": $map(requires.holdings, function($h) { { "symbol": $h.symbol, "qty": $h.qty, "price": $lookup(requires.prices, $h.symbol), "value": $h.qty * $lookup(requires.prices, $h.symbol) } }) }'
|
|
76
|
+
}],
|
|
77
|
+
view: { elements: [{ kind: 'table', label: 'Portfolio Positions', data: { bind: 'computed_values.table.rows', columns: ['symbol', 'qty', 'price', 'value'] } }] }
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const CARD_PORTFOLIO_VALUE = {
|
|
81
|
+
id: 'portfolio-value',
|
|
82
|
+
meta: { title: 'Portfolio Total Value' },
|
|
83
|
+
requires: ['table'],
|
|
84
|
+
provides: [{ bindTo: 'totalValue', ref: 'computed_values.totalValue' }],
|
|
85
|
+
card_data: {},
|
|
86
|
+
compute: [{ bindTo: 'totalValue', expr: '$sum(requires.table.rows.value)' }],
|
|
87
|
+
view: { elements: [{ kind: 'metric', label: 'Total Portfolio Value', data: { bind: 'computed_values.totalValue' } }] }
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
91
|
+
function setHoldings(card, holdings) {
|
|
92
|
+
return { ...card, card_data: { ...card.card_data, holdings: Object.entries(holdings).map(([symbol, qty]) => ({ symbol, qty })) } };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function assert(condition, message) {
|
|
96
|
+
if (!condition) { console.error(`[ASSERT FAILED] ${message}`); process.exit(1); }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readJson(filePath) {
|
|
100
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function makeBoard() {
|
|
104
|
+
const br = parseRef(BOARDRUNTIME_REF);
|
|
105
|
+
return createBoardLiveCardsPublic(br, createFsBoardPlatformAdapter(br, _REPO_ROOT, { onWarn: console.warn }));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function makeNonCoreBoard() {
|
|
109
|
+
const br = parseRef(BOARDRUNTIME_REF);
|
|
110
|
+
return createBoardLiveCardsNonCorePublic(br, createFsBoardNonCorePlatformAdapter(br, _REPO_ROOT, { onWarn: console.warn }));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function makeCardStore() {
|
|
114
|
+
const ref = parseRef(CARDSTORE_REF);
|
|
115
|
+
const adapter = createFsBoardPlatformAdapter(ref, _REPO_ROOT, { onWarn: console.warn });
|
|
116
|
+
const kv = adapter.kvStorageForRef(CARDSTORE_REF);
|
|
117
|
+
const cardAdapterObj = {
|
|
118
|
+
readIndex: () => kv.read('_index'),
|
|
119
|
+
writeIndex: (idx) => kv.write('_index', idx),
|
|
120
|
+
readCard: (id) => kv.read(id),
|
|
121
|
+
writeCard: (id, card) => { kv.write(id, card); return id; },
|
|
122
|
+
cardExists: (id) => kv.read(id) !== null,
|
|
123
|
+
defaultCardKey: (id) => id,
|
|
124
|
+
};
|
|
125
|
+
return createCardStorePublic(createCardStore(cardAdapterObj, console.warn));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function checkResult(result, label) {
|
|
129
|
+
if (result.status !== 'success') { console.error(`[ERROR] ${label}: ${result.status} — ${result.error}`); process.exit(1); }
|
|
130
|
+
return result.data;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function waitForCompleted(label, timeoutMs = 90_000, pollMs = 500) {
|
|
134
|
+
const deadline = Date.now() + timeoutMs;
|
|
135
|
+
let pollCount = 0;
|
|
136
|
+
while (Date.now() < deadline) {
|
|
137
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
138
|
+
const result = makeBoard().status({});
|
|
139
|
+
pollCount++;
|
|
140
|
+
if (result.status === 'success') {
|
|
141
|
+
const { card_count, completed, in_progress, pending, failed } = result.data.summary;
|
|
142
|
+
if (card_count > 0 && completed === card_count) {
|
|
143
|
+
console.log(`[${label}] all ${card_count} card(s) completed.`);
|
|
144
|
+
return result.data;
|
|
145
|
+
}
|
|
146
|
+
if (pollCount % 4 === 0) {
|
|
147
|
+
const notDone = result.data.cards.filter(c => c.status !== 'completed').map(c => `${c.name}:${c.status}`);
|
|
148
|
+
console.log(`[${label}] poll#${pollCount}: completed=${completed}/${card_count}, in_progress=${in_progress}, pending=${pending}, failed=${failed} | ${notDone.join(', ')}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
console.error(`[ERROR] ${label}: timed out waiting for all cards to complete.`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const T = () => Date.now();
|
|
157
|
+
|
|
158
|
+
// ── T0 — Init ─────────────────────────────────────────────────────────────────
|
|
159
|
+
console.log('\n=== T0: Init ===');
|
|
160
|
+
if (fs.existsSync(_TMP_BASE)) fs.rmSync(_TMP_BASE, { recursive: true, force: true });
|
|
161
|
+
for (const d of [CARDSTORE_DIR, BOARDRUNTIME_DIR, OUTPUTS_DIR]) fs.mkdirSync(d, { recursive: true });
|
|
162
|
+
console.log(` runtime base: ${_TMP_BASE}`);
|
|
163
|
+
|
|
164
|
+
checkResult(
|
|
165
|
+
makeBoard().init({
|
|
166
|
+
params: { cardStoreRef: CARDSTORE_REF, outputsStoreRef: OUTPUTS_REF },
|
|
167
|
+
body: { 'task-executor-ref': { meta: 'task-executor', howToRun: 'local-node', whatToRun: `::fs-path::${FETCH_PRICES_JS}` } },
|
|
168
|
+
}),
|
|
169
|
+
'init'
|
|
170
|
+
);
|
|
171
|
+
console.log(` [${T()}] init done`);
|
|
172
|
+
|
|
173
|
+
const cardStore = makeCardStore();
|
|
174
|
+
for (const card of [
|
|
175
|
+
setHoldings(CARD_PORTFOLIO_FORM, { AAPL: 10 }),
|
|
176
|
+
CARD_PRICE_FETCH,
|
|
177
|
+
CARD_HOLDINGS_TABLE,
|
|
178
|
+
CARD_PORTFOLIO_VALUE,
|
|
179
|
+
]) {
|
|
180
|
+
const vr = makeNonCoreBoard().validateTmpCard({ body: card });
|
|
181
|
+
console.log(` [${T()}] validateTmpCard ${card.id} done`);
|
|
182
|
+
if (!vr.data?.isValid) { console.error(`[VALIDATE FAILED] ${card.id}:`, JSON.stringify(vr.data?.issues ?? vr.error)); process.exit(1); }
|
|
183
|
+
checkResult(cardStore.set({ body: card }), `card-store set ${card.id}`);
|
|
184
|
+
console.log(` [${T()}] cardStore.set ${card.id} done`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const cardId of ['portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value']) {
|
|
188
|
+
checkResult(makeBoard().upsertCard({ params: { cardId } }), `upsertCard ${cardId}`);
|
|
189
|
+
console.log(` [${T()}] upsertCard ${cardId} done`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await waitForCompleted('T0-settle');
|
|
193
|
+
console.log(`[${T()}] [T0] board settled with initial holdings.`);
|
|
194
|
+
|
|
195
|
+
// ── T4 — Rapid 5× portfolio-form updates (no delay) ───────────────────────────
|
|
196
|
+
console.log('\n=== T4: Rapid 5x portfolio-form updates (no delay) ===');
|
|
197
|
+
|
|
198
|
+
const T4_ITERS = [
|
|
199
|
+
{ AAPL: 50 },
|
|
200
|
+
{ AAPL: 45, MSFT: 30 },
|
|
201
|
+
{ AAPL: 45, MSFT: 30, GOOG: 110 },
|
|
202
|
+
{ AAPL: 40, MSFT: 35, GOOG: 120, TSLA: 70 },
|
|
203
|
+
{ AAPL: 45, MSFT: 30, GOOG: 110, AMZN: 140, TSLA: 60 },
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
// Expected final state: iter 5 holdings (AMZN present in iter 5 but test verifies what actually wins)
|
|
207
|
+
const T4_EXPECTED_FINAL = { AAPL: 45, MSFT: 30, GOOG: 110, AMZN: 140, TSLA: 60 };
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < T4_ITERS.length; i++) {
|
|
210
|
+
const holdings = T4_ITERS[i];
|
|
211
|
+
console.log(` iter ${i + 1}: ${JSON.stringify(holdings)}`);
|
|
212
|
+
checkResult(makeCardStore().set({ body: setHoldings(CARD_PORTFOLIO_FORM, holdings) }), `iter${i + 1} card-store set`);
|
|
213
|
+
console.log(` [${T()}] iter ${i + 1} cardStore.set done`);
|
|
214
|
+
checkResult(makeBoard().upsertCard({ params: { cardId: 'portfolio-form', restart: 'true' } }), `iter${i + 1} upsert`);
|
|
215
|
+
console.log(` [${T()}] iter ${i + 1} upsertCard done`);
|
|
216
|
+
// await waitForCompleted(`T4-iter${i + 1}`); // commented out: rapid-fire, no waits
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
console.log(`\n[${T()}] [T4] all 5 upserts fired — waiting for board to converge...`);
|
|
220
|
+
const t4Final = await waitForCompleted('T4');
|
|
221
|
+
console.log(`[${T()}] [T4] waitForCompleted done`);
|
|
222
|
+
|
|
223
|
+
// ── Assertions ────────────────────────────────────────────────────────────────
|
|
224
|
+
const holdingsPath = path.join(OUTPUTS_DIR, 'data-objects', 'holdings.json');
|
|
225
|
+
const holdings = readJson(holdingsPath);
|
|
226
|
+
console.log('\n[T4] holdings.json (data-object output):', JSON.stringify(holdings, null, 2));
|
|
227
|
+
|
|
228
|
+
const finalCard = readJson(path.join(CARDSTORE_DIR, 'portfolio-form.json'));
|
|
229
|
+
console.log('[T4] cardstore portfolio-form holdings:', JSON.stringify(finalCard.card_data?.holdings, null, 2));
|
|
230
|
+
|
|
231
|
+
// Assert final holdings match iter-5
|
|
232
|
+
const holdingsBySymbol = Object.fromEntries(holdings.map(h => [h.symbol, h.qty]));
|
|
233
|
+
const expectedSymbols = Object.keys(T4_EXPECTED_FINAL).sort();
|
|
234
|
+
const actualSymbols = Object.keys(holdingsBySymbol).sort();
|
|
235
|
+
assert(JSON.stringify(actualSymbols) === JSON.stringify(expectedSymbols),
|
|
236
|
+
`T4: expected symbols ${JSON.stringify(expectedSymbols)}, got ${JSON.stringify(actualSymbols)}`);
|
|
237
|
+
for (const [sym, qty] of Object.entries(T4_EXPECTED_FINAL)) {
|
|
238
|
+
assert(holdingsBySymbol[sym] === qty,
|
|
239
|
+
`T4: expected ${sym} qty=${qty}, got ${holdingsBySymbol[sym]}`);
|
|
240
|
+
}
|
|
241
|
+
console.log('[T4] holdings assertions passed: iter-5 symbols and quantities match.');
|
|
242
|
+
|
|
243
|
+
// Assert prices contain exactly the iter-5 tickers
|
|
244
|
+
const pricesPath = path.join(OUTPUTS_DIR, 'data-objects', 'prices.json');
|
|
245
|
+
const prices = readJson(pricesPath);
|
|
246
|
+
const priceKeys = Object.keys(prices).sort();
|
|
247
|
+
assert(JSON.stringify(priceKeys) === JSON.stringify(expectedSymbols),
|
|
248
|
+
`T4: expected price keys ${JSON.stringify(expectedSymbols)}, got ${JSON.stringify(priceKeys)}`);
|
|
249
|
+
assert(Object.values(prices).every(v => typeof v === 'number'),
|
|
250
|
+
'T4: all price values must be numbers');
|
|
251
|
+
console.log('[T4] prices assertions passed:', JSON.stringify(prices));
|
|
252
|
+
|
|
253
|
+
// Assert holdings-table rows match
|
|
254
|
+
const htCvPath = path.join(OUTPUTS_DIR, 'cards', 'holdings-table', 'computed_values.json');
|
|
255
|
+
const htCv = readJson(htCvPath);
|
|
256
|
+
const rowsBySymbol = Object.fromEntries([].concat(htCv.table.rows).map(r => [r.symbol, r]));
|
|
257
|
+
for (const [sym, qty] of Object.entries(T4_EXPECTED_FINAL)) {
|
|
258
|
+
assert(rowsBySymbol[sym]?.qty === qty,
|
|
259
|
+
`T4: holdings-table expected ${sym} qty=${qty}, got ${rowsBySymbol[sym]?.qty}`);
|
|
260
|
+
const expectedValue = Math.round(qty * prices[sym] * 100) / 100;
|
|
261
|
+
assert(Math.round(rowsBySymbol[sym]?.value * 100) === Math.round(expectedValue * 100),
|
|
262
|
+
`T4: holdings-table expected ${sym} value=${expectedValue}, got ${rowsBySymbol[sym]?.value}`);
|
|
263
|
+
}
|
|
264
|
+
console.log('[T4] holdings-table assertions passed: rows match holdings × prices.');
|
|
265
|
+
|
|
266
|
+
// Assert portfolio-value totalValue = sum(holdings × prices)
|
|
267
|
+
const pvCv = readJson(path.join(OUTPUTS_DIR, 'cards', 'portfolio-value', 'computed_values.json'));
|
|
268
|
+
const expectedTotal = Object.entries(T4_EXPECTED_FINAL).reduce(
|
|
269
|
+
(sum, [sym, qty]) => sum + qty * prices[sym], 0
|
|
270
|
+
);
|
|
271
|
+
assert(Math.round(pvCv.totalValue * 100) === Math.round(expectedTotal * 100),
|
|
272
|
+
`T4: expected totalValue=${Math.round(expectedTotal * 100) / 100}, got ${pvCv.totalValue}`);
|
|
273
|
+
console.log(`[T4] portfolio-value assertion passed: totalValue=${pvCv.totalValue} matches sum(holdings × prices).`);
|
|
274
|
+
|
|
275
|
+
// Assert cardstore portfolio-form holdings match iter-5
|
|
276
|
+
const cardstoreHoldings = Object.fromEntries(
|
|
277
|
+
(finalCard.card_data?.holdings ?? []).map(h => [h.symbol, h.qty])
|
|
278
|
+
);
|
|
279
|
+
for (const [sym, qty] of Object.entries(T4_EXPECTED_FINAL)) {
|
|
280
|
+
assert(cardstoreHoldings[sym] === qty,
|
|
281
|
+
`T4: cardstore expected ${sym} qty=${qty}, got ${cardstoreHoldings[sym]}`);
|
|
282
|
+
}
|
|
283
|
+
assert(Object.keys(cardstoreHoldings).length === Object.keys(T4_EXPECTED_FINAL).length,
|
|
284
|
+
`T4: cardstore has ${Object.keys(cardstoreHoldings).length} symbols, expected ${Object.keys(T4_EXPECTED_FINAL).length}`);
|
|
285
|
+
console.log('[T4] cardstore holdings assertion passed: portfolio-form matches iter-5.');
|
|
286
|
+
|
|
287
|
+
console.log('\nFinal board status summary:');
|
|
288
|
+
const { summary } = t4Final;
|
|
289
|
+
console.log(` completed=${summary.completed}/${summary.card_count}, failed=${summary.failed}`);
|
|
290
|
+
|
|
291
|
+
console.log('\n=== portfolio-t4 completed ===');
|
|
292
|
+
console.log(' runtime base:', _TMP_BASE);
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* portfolio-tracker-fetch-prices.js
|
|
4
|
+
*
|
|
5
|
+
* Task executor for the portfolio board demo.
|
|
6
|
+
* Handles run-source-fetch requests for source_defs with kind: "mock-quotes".
|
|
7
|
+
* Generates random prices (2dp, 10.00–999.99) for each projected ticker.
|
|
8
|
+
*
|
|
9
|
+
* Subcommands:
|
|
10
|
+
* run-source-fetch — fetch mock prices for tickers from _projections
|
|
11
|
+
* validate-source-def — validate source def structure; prints { ok, errors } JSON
|
|
12
|
+
* describe-capabilities — print executor capabilities JSON
|
|
13
|
+
*
|
|
14
|
+
* run-source-fetch protocol:
|
|
15
|
+
* node portfolio-tracker-fetch-prices.js run-source-fetch \
|
|
16
|
+
* --in-ref <::kind::value> \
|
|
17
|
+
* --out-ref <::kind::value> \
|
|
18
|
+
* --err-ref <::kind::value>
|
|
19
|
+
*
|
|
20
|
+
* validate-source-def protocol:
|
|
21
|
+
* node portfolio-tracker-fetch-prices.js validate-source-def --in <source.json>
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import { parseRef, blobStorageForRef, reportComplete, reportFailed } from 'yaml-flow/storage-refs';
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// validate-source-def — structural validation of a source definition
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
function validateSourceDefSubcommand(argv) {
|
|
31
|
+
const inIdx = argv.indexOf('--in');
|
|
32
|
+
const inFile = inIdx !== -1 ? argv[inIdx + 1] : undefined;
|
|
33
|
+
|
|
34
|
+
if (!inFile) {
|
|
35
|
+
console.error('[portfolio-tracker-fetch-prices] Usage: validate-source-def --in <source.json>');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!fs.existsSync(inFile)) {
|
|
40
|
+
console.log(JSON.stringify({ ok: false, errors: [`Input file not found: ${inFile}`] }));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let sourceDef;
|
|
45
|
+
try {
|
|
46
|
+
sourceDef = JSON.parse(fs.readFileSync(inFile, 'utf-8'));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.log(JSON.stringify({ ok: false, errors: [`Cannot parse source file: ${err && err.message || err}`] }));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const errors = [];
|
|
53
|
+
|
|
54
|
+
if (sourceDef.kind !== 'mock-quotes') {
|
|
55
|
+
errors.push(`kind must be "mock-quotes"; got "${sourceDef.kind}".`);
|
|
56
|
+
}
|
|
57
|
+
if (!sourceDef.bindTo || typeof sourceDef.bindTo !== 'string') {
|
|
58
|
+
errors.push('bindTo is required and must be a string.');
|
|
59
|
+
}
|
|
60
|
+
if (!sourceDef.outputFile || typeof sourceDef.outputFile !== 'string') {
|
|
61
|
+
errors.push('outputFile is required and must be a string.');
|
|
62
|
+
}
|
|
63
|
+
if (!sourceDef.projections || typeof sourceDef.projections.tickers !== 'string') {
|
|
64
|
+
errors.push('projections.tickers is required and must be a JSONata expression string.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const result = { ok: errors.length === 0, errors };
|
|
68
|
+
console.log(JSON.stringify(result));
|
|
69
|
+
process.exit(errors.length === 0 ? 0 : 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// describe-capabilities — introspection metadata for this executor
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
const CAPABILITIES = {
|
|
76
|
+
version: '1.0',
|
|
77
|
+
executor: 'portfolio-tracker-fetch-prices',
|
|
78
|
+
subcommands: ['run-source-fetch', 'validate-source-def', 'describe-capabilities'],
|
|
79
|
+
sourceKinds: {
|
|
80
|
+
'mock-quotes': {
|
|
81
|
+
description: 'Generates random mock market prices (10.00–999.99) for each ticker in _projections.tickers.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
kind: { type: 'string', required: true, description: 'Must be "mock-quotes".' },
|
|
84
|
+
bindTo: { type: 'string', required: true, description: 'Token name for the output binding.' },
|
|
85
|
+
outputFile: { type: 'string', required: true, description: 'Relative path to write prices JSON.' },
|
|
86
|
+
projections: {
|
|
87
|
+
type: 'object', required: true,
|
|
88
|
+
properties: {
|
|
89
|
+
tickers: { type: 'string', required: true, description: 'JSONata expression resolving to a string[] of ticker symbols (e.g. "requires.holdings.symbol").' },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
outputShape: '{ [ticker: string]: number } — map of ticker symbol to random price (2dp).',
|
|
94
|
+
example: {
|
|
95
|
+
input: { kind: 'mock-quotes', bindTo: 'prices', outputFile: 'prices.json', projections: { tickers: 'requires.holdings.symbol' } },
|
|
96
|
+
output: { AAPL: 152.34, MSFT: 310.45 },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function describeCapabilities() {
|
|
103
|
+
console.log(JSON.stringify(CAPABILITIES, null, 2));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// run-source-fetch — generate random prices and report back
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
async function runSourceFetchSubcommand(argv) {
|
|
110
|
+
let inRefStr = '';
|
|
111
|
+
let outRefStr = '';
|
|
112
|
+
let errRefStr = '';
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < argv.length; i++) {
|
|
115
|
+
if (argv[i] === '--in-ref' && i + 1 < argv.length) inRefStr = argv[++i];
|
|
116
|
+
else if (argv[i] === '--out-ref' && i + 1 < argv.length) outRefStr = argv[++i];
|
|
117
|
+
else if (argv[i] === '--err-ref' && i + 1 < argv.length) errRefStr = argv[++i];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!inRefStr || !outRefStr || !errRefStr) {
|
|
121
|
+
console.error(
|
|
122
|
+
'Usage: portfolio-tracker-fetch-prices.js run-source-fetch' +
|
|
123
|
+
' --in-ref <ref> --out-ref <ref> --err-ref <ref>',
|
|
124
|
+
);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const inRef = parseRef(inRefStr);
|
|
129
|
+
const outRef = parseRef(outRefStr);
|
|
130
|
+
const errRef = parseRef(errRefStr);
|
|
131
|
+
|
|
132
|
+
const inStorage = blobStorageForRef(inRef);
|
|
133
|
+
const outStorage = blobStorageForRef(outRef);
|
|
134
|
+
const errStorage = blobStorageForRef(errRef);
|
|
135
|
+
|
|
136
|
+
// Read and parse the envelope up front so `callback` is always available for
|
|
137
|
+
// reportFailed — even if a later validation step throws.
|
|
138
|
+
const rawIn = inStorage.read(inRef.value);
|
|
139
|
+
if (!rawIn) {
|
|
140
|
+
console.error(`[portfolio-tracker-fetch-prices] input envelope not found at: ${inRefStr}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
const envelope = JSON.parse(rawIn);
|
|
144
|
+
const callback = envelope.source_def ? envelope.callback : undefined;
|
|
145
|
+
|
|
146
|
+
let didReport = false;
|
|
147
|
+
const safeReportFailed = (msg) => {
|
|
148
|
+
if (didReport) return;
|
|
149
|
+
didReport = true;
|
|
150
|
+
try { errStorage.write(errRef.value, msg); } catch { /* best-effort */ }
|
|
151
|
+
if (callback) { reportFailed(callback, msg); } else { process.exit(1); }
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const sourceDef = envelope.source_def ?? envelope;
|
|
156
|
+
|
|
157
|
+
if (sourceDef.kind !== 'mock-quotes') {
|
|
158
|
+
throw new Error(`Unsupported source kind: expected "mock-quotes", got "${sourceDef.kind}"`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const tickers = sourceDef._projections?.tickers;
|
|
162
|
+
if (!Array.isArray(tickers)) {
|
|
163
|
+
throw new Error('sourceDef._projections.tickers is missing or not an array');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Random 200–300 ms delay (simulates a real market data fetch)
|
|
167
|
+
await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 100));
|
|
168
|
+
|
|
169
|
+
// Generate random prices — 2dp, range 10.00–999.99
|
|
170
|
+
const prices = {};
|
|
171
|
+
for (const ticker of tickers) {
|
|
172
|
+
prices[ticker] = Math.round((10 + Math.random() * 989.99) * 100) / 100;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
outStorage.write(outRef.value, JSON.stringify(prices));
|
|
176
|
+
console.log(`[portfolio-tracker-fetch-prices] wrote prices for: ${tickers.join(', ')}`);
|
|
177
|
+
|
|
178
|
+
didReport = true;
|
|
179
|
+
if (callback) {
|
|
180
|
+
reportComplete(callback, outRef);
|
|
181
|
+
} else {
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
186
|
+
console.error(`[portfolio-tracker-fetch-prices] error: ${msg}`);
|
|
187
|
+
safeReportFailed(msg);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// main — subcommand routing
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
async function main() {
|
|
195
|
+
const sub = process.argv[2];
|
|
196
|
+
if (sub === 'run-source-fetch') {
|
|
197
|
+
await runSourceFetchSubcommand(process.argv.slice(3));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (sub === 'validate-source-def') {
|
|
201
|
+
validateSourceDefSubcommand(process.argv.slice(3));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (sub === 'describe-capabilities') {
|
|
205
|
+
describeCapabilities();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
console.error(
|
|
209
|
+
'Usage: portfolio-tracker-fetch-prices.js <subcommand> [...args]\n' +
|
|
210
|
+
'Subcommands: run-source-fetch, validate-source-def, describe-capabilities',
|
|
211
|
+
);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
main().catch(err => {
|
|
216
|
+
console.error(`[portfolio-tracker-fetch-prices] fatal: ${err && err.message || err}`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|