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,553 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* portfolio-tracker-public.js
|
|
4
|
+
*
|
|
5
|
+
* Identical E2E logic to portfolio-tracker.py, implemented directly against
|
|
6
|
+
* the yaml-flow public Node.js libraries — no CLI subprocess spawning.
|
|
7
|
+
*
|
|
8
|
+
* Imports:
|
|
9
|
+
* yaml-flow/board-live-cards-node — createBoardLiveCardsPublic,
|
|
10
|
+
* createFsBoardPlatformAdapter,
|
|
11
|
+
* createCardStorePublic,
|
|
12
|
+
* createCardStore, parseRef, serializeRef
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import net from 'node:net';
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const _REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
23
|
+
|
|
24
|
+
// ── Library imports ────────────────────────────────────────────────────────────
|
|
25
|
+
const _adapterPath = path.join(_REPO_ROOT, 'dist', 'cli', 'node', 'fs-board-adapter.js');
|
|
26
|
+
const {
|
|
27
|
+
createBoardLiveCardsPublic,
|
|
28
|
+
createBoardLiveCardsNonCorePublic,
|
|
29
|
+
createFsBoardPlatformAdapter,
|
|
30
|
+
createFsBoardNonCorePlatformAdapter,
|
|
31
|
+
createCardStorePublic,
|
|
32
|
+
createCardStore,
|
|
33
|
+
parseRef,
|
|
34
|
+
} = await import(pathToFileURL(_adapterPath).href);
|
|
35
|
+
|
|
36
|
+
const FETCH_PRICES_JS = path.join(__dirname, 'portfolio-tracker-fetch-prices.js');
|
|
37
|
+
|
|
38
|
+
// ── Runtime directories ────────────────────────────────────────────────────────
|
|
39
|
+
const _TMP_BASE = path.join(os.tmpdir(), 'experiment-js');
|
|
40
|
+
const CARDSTORE_DIR = path.join(_TMP_BASE, 'cardstore');
|
|
41
|
+
const BOARDRUNTIME_DIR = path.join(_TMP_BASE, 'boardruntime');
|
|
42
|
+
const OUTPUTS_DIR = path.join(_TMP_BASE, 'outputs');
|
|
43
|
+
|
|
44
|
+
const CARDSTORE_REF = `::fs-path::${CARDSTORE_DIR}`;
|
|
45
|
+
const BOARDRUNTIME_REF = `::fs-path::${BOARDRUNTIME_DIR}`;
|
|
46
|
+
const OUTPUTS_REF = `::fs-path::${OUTPUTS_DIR}`;
|
|
47
|
+
const NOTIFY_CHANNEL = 'yaml-flow-board-notify-portfolio-tracker-public';
|
|
48
|
+
|
|
49
|
+
// ── Card definitions ───────────────────────────────────────────────────────────
|
|
50
|
+
const CARD_PORTFOLIO_FORM = {
|
|
51
|
+
id: 'portfolio-form',
|
|
52
|
+
meta: { title: 'Portfolio Holdings Form' },
|
|
53
|
+
provides: [{ bindTo: 'holdings', ref: 'card_data.holdings' }],
|
|
54
|
+
card_data: { holdings: [] },
|
|
55
|
+
view: {
|
|
56
|
+
elements: [
|
|
57
|
+
{ kind: 'table', label: 'Holdings',
|
|
58
|
+
data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const CARD_PRICE_FETCH = {
|
|
64
|
+
id: 'price-fetch',
|
|
65
|
+
meta: { title: 'Fetch Market Prices' },
|
|
66
|
+
requires: ['holdings'],
|
|
67
|
+
provides: [{ bindTo: 'prices', ref: 'fetched_sources.prices' }],
|
|
68
|
+
card_data: {},
|
|
69
|
+
source_defs: [{
|
|
70
|
+
kind: 'mock-quotes',
|
|
71
|
+
bindTo: 'prices',
|
|
72
|
+
outputFile: 'prices.json',
|
|
73
|
+
projections: { tickers: '$append([], requires.holdings.symbol)' }
|
|
74
|
+
}],
|
|
75
|
+
view: {
|
|
76
|
+
elements: [
|
|
77
|
+
{ kind: 'table', label: 'Market Prices',
|
|
78
|
+
data: { bind: 'fetched_sources.prices' } }
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const CARD_HOLDINGS_TABLE = {
|
|
84
|
+
id: 'holdings-table',
|
|
85
|
+
meta: { title: 'Holdings Table' },
|
|
86
|
+
requires: ['holdings', 'prices'],
|
|
87
|
+
provides: [{ bindTo: 'table', ref: 'computed_values.table' }],
|
|
88
|
+
card_data: {},
|
|
89
|
+
compute: [{
|
|
90
|
+
bindTo: 'table',
|
|
91
|
+
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) } }) }'
|
|
92
|
+
}],
|
|
93
|
+
view: {
|
|
94
|
+
elements: [
|
|
95
|
+
{ kind: 'table', label: 'Portfolio Positions',
|
|
96
|
+
data: { bind: 'computed_values.table.rows', columns: ['symbol', 'qty', 'price', 'value'] } }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const CARD_PORTFOLIO_VALUE = {
|
|
102
|
+
id: 'portfolio-value',
|
|
103
|
+
meta: { title: 'Portfolio Total Value' },
|
|
104
|
+
requires: ['table'],
|
|
105
|
+
provides: [{ bindTo: 'totalValue', ref: 'computed_values.totalValue' }],
|
|
106
|
+
card_data: {},
|
|
107
|
+
compute: [
|
|
108
|
+
{ bindTo: 'totalValue', expr: '$sum(requires.table.rows.value)' }
|
|
109
|
+
],
|
|
110
|
+
view: {
|
|
111
|
+
elements: [
|
|
112
|
+
{ kind: 'metric', label: 'Total Portfolio Value',
|
|
113
|
+
data: { bind: 'computed_values.totalValue' } }
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
119
|
+
function setHoldings(card, holdings) {
|
|
120
|
+
return {
|
|
121
|
+
...card,
|
|
122
|
+
card_data: {
|
|
123
|
+
...card.card_data,
|
|
124
|
+
holdings: Object.entries(holdings).map(([symbol, qty]) => ({ symbol, qty })),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assert(condition, message) {
|
|
130
|
+
if (!condition) {
|
|
131
|
+
console.error(`[ASSERT FAILED] ${message}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function makeBoard() {
|
|
137
|
+
const br = parseRef(BOARDRUNTIME_REF);
|
|
138
|
+
return createBoardLiveCardsPublic(br, createFsBoardPlatformAdapter(br, _REPO_ROOT, {
|
|
139
|
+
onWarn: console.warn,
|
|
140
|
+
notifyChannel: NOTIFY_CHANNEL,
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function makeNonCoreBoard() {
|
|
145
|
+
const br = parseRef(BOARDRUNTIME_REF);
|
|
146
|
+
return createBoardLiveCardsNonCorePublic(br, createFsBoardNonCorePlatformAdapter(br, _REPO_ROOT, { onWarn: console.warn }));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function makeCardStore() {
|
|
150
|
+
const ref = parseRef(CARDSTORE_REF);
|
|
151
|
+
const adapter = createFsBoardPlatformAdapter(ref, _REPO_ROOT, { onWarn: console.warn });
|
|
152
|
+
const kv = adapter.kvStorageForRef(CARDSTORE_REF);
|
|
153
|
+
const cardAdapterObj = {
|
|
154
|
+
readIndex: () => kv.read('_index'),
|
|
155
|
+
writeIndex: (idx) => kv.write('_index', idx),
|
|
156
|
+
readCard: (id) => kv.read(id),
|
|
157
|
+
writeCard: (id, card) => { kv.write(id, card); return id; },
|
|
158
|
+
cardExists: (id) => kv.read(id) !== null,
|
|
159
|
+
defaultCardKey: (id) => id,
|
|
160
|
+
};
|
|
161
|
+
return createCardStorePublic(createCardStore(cardAdapterObj, console.warn));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── NS — notification state class (compact log + full payload map) ───────────
|
|
165
|
+
class NotificationState {
|
|
166
|
+
constructor() {
|
|
167
|
+
this.log = [];
|
|
168
|
+
this.statusGen = 0;
|
|
169
|
+
this.values = {
|
|
170
|
+
status: null,
|
|
171
|
+
computedValues: {},
|
|
172
|
+
dataObjects: {},
|
|
173
|
+
cards: {},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
append(event) {
|
|
178
|
+
const at = new Date().toISOString();
|
|
179
|
+
if (event.kind === 'status') {
|
|
180
|
+
this.log.push({ at, type: event.kind, key: 'status' });
|
|
181
|
+
this.values.status = event.status;
|
|
182
|
+
this.statusGen++;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (event.kind === 'computed_values') {
|
|
186
|
+
this.log.push({ at, type: event.kind, key: event.cardId });
|
|
187
|
+
this.values.computedValues[event.cardId] = event.values;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (event.kind === 'data_object') {
|
|
191
|
+
this.log.push({ at, type: event.kind, key: event.key });
|
|
192
|
+
this.values.dataObjects[event.key] = event.payload;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (event.kind === 'card_refreshed') {
|
|
196
|
+
this.log.push({ at, type: event.kind, key: event.cardId });
|
|
197
|
+
this.values.cards[event.cardId] = event.card;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
latestStatus() {
|
|
202
|
+
return this.values.status;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
countByType(type) {
|
|
206
|
+
let count = 0;
|
|
207
|
+
for (const n of this.log) {
|
|
208
|
+
if (n.type === type) count++;
|
|
209
|
+
}
|
|
210
|
+
return count;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
summary() {
|
|
214
|
+
const byType = {};
|
|
215
|
+
const keysByType = {
|
|
216
|
+
computed_values: new Set(),
|
|
217
|
+
data_object: new Set(),
|
|
218
|
+
card_refreshed: new Set(),
|
|
219
|
+
status: new Set(),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
for (const n of this.log) {
|
|
223
|
+
byType[n.type] = (byType[n.type] ?? 0) + 1;
|
|
224
|
+
if (n.type in keysByType) keysByType[n.type].add(n.key);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
totalNotifications: this.log.length,
|
|
229
|
+
byType,
|
|
230
|
+
keysByType: {
|
|
231
|
+
computed_values: [...keysByType.computed_values].sort(),
|
|
232
|
+
data_object: [...keysByType.data_object].sort(),
|
|
233
|
+
card_refreshed: [...keysByType.card_refreshed].sort(),
|
|
234
|
+
status: [...keysByType.status].sort(),
|
|
235
|
+
},
|
|
236
|
+
latestStatusSummary: this.values.status?.summary ?? null,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const NS = new NotificationState();
|
|
242
|
+
|
|
243
|
+
function appendNS(event) {
|
|
244
|
+
NS.append(event);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function namedPipePath(pipeName) {
|
|
248
|
+
if (process.platform === 'win32') return `\\\\.\\pipe\\${pipeName}`;
|
|
249
|
+
return path.join(os.tmpdir(), `${pipeName}.sock`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function startPipeConsumer(pipeName) {
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
const pipePath = namedPipePath(pipeName);
|
|
255
|
+
const sockets = new Set();
|
|
256
|
+
if (process.platform !== 'win32' && fs.existsSync(pipePath)) {
|
|
257
|
+
try { fs.rmSync(pipePath, { force: true }); } catch { /* best-effort */ }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const server = net.createServer((socket) => {
|
|
261
|
+
sockets.add(socket);
|
|
262
|
+
socket.on('close', () => sockets.delete(socket));
|
|
263
|
+
let buf = '';
|
|
264
|
+
socket.on('data', (chunk) => {
|
|
265
|
+
buf += chunk.toString('utf-8');
|
|
266
|
+
while (true) {
|
|
267
|
+
const i = buf.indexOf('\n');
|
|
268
|
+
if (i < 0) break;
|
|
269
|
+
const line = buf.slice(0, i).trim();
|
|
270
|
+
buf = buf.slice(i + 1);
|
|
271
|
+
if (!line) continue;
|
|
272
|
+
try {
|
|
273
|
+
const msg = JSON.parse(line);
|
|
274
|
+
const n = msg?.notification ?? msg;
|
|
275
|
+
if (n && typeof n.kind === 'string') appendNS(n);
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.warn(`[pipe-consumer] invalid notification line: ${e instanceof Error ? e.message : String(e)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
server.once('error', (e) => reject(e));
|
|
284
|
+
server.listen(pipePath, () => resolve({ server, pipePath, sockets }));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getNS() { return NS; }
|
|
289
|
+
|
|
290
|
+
function checkResult(result, label) {
|
|
291
|
+
if (result.status !== 'success') {
|
|
292
|
+
console.error(`[ERROR] ${label}: ${result.status} — ${result.error}`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
return result.data;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function waitForCompleted(label, expectedCardCount, timeoutMs = 90_000, pollMs = 500) {
|
|
299
|
+
const deadline = Date.now() + timeoutMs;
|
|
300
|
+
let pollCount = 0;
|
|
301
|
+
const startGen = getNS().statusGen;
|
|
302
|
+
while (Date.now() < deadline) {
|
|
303
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
304
|
+
pollCount++;
|
|
305
|
+
|
|
306
|
+
// Only consider status updates that arrived after this wait began
|
|
307
|
+
if (getNS().statusGen <= startGen) {
|
|
308
|
+
if (pollCount % 4 === 0) {
|
|
309
|
+
console.log(`[${label}] poll#${pollCount} waiting for new status notification via named pipe...`);
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const nsStatus = getNS().latestStatus();
|
|
315
|
+
if (!nsStatus) continue;
|
|
316
|
+
|
|
317
|
+
const { card_count, completed, in_progress, pending, failed } = nsStatus.summary;
|
|
318
|
+
|
|
319
|
+
if (card_count >= expectedCardCount && completed === card_count) {
|
|
320
|
+
console.log(`[${label}] all ${card_count} card(s) completed (via named-pipe notification).`);
|
|
321
|
+
return nsStatus;
|
|
322
|
+
}
|
|
323
|
+
if (pollCount % 4 === 0) {
|
|
324
|
+
const notDone = nsStatus.cards.filter(c => c.status !== 'completed').map(c => `${c.name}:${c.status}`);
|
|
325
|
+
console.log(`[${label}] poll#${pollCount} summary: ${card_count} cards, completed=${completed}, in_progress=${in_progress}, pending=${pending}, failed=${failed} | stuck: ${notDone.join(', ')}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
console.error(`[ERROR] ${label}: timed out waiting for all cards to complete.`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function sortedKeys(obj) {
|
|
333
|
+
if (!obj || typeof obj !== 'object') return [];
|
|
334
|
+
return Object.keys(obj).sort();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function readOutputsDataObject(key) {
|
|
338
|
+
const result = makeBoard().getOutputsDataObject({ params: { key } });
|
|
339
|
+
return result.status === 'success' ? result.data : undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function readOutputsComputedValues(key) {
|
|
343
|
+
const result = makeBoard().getOutputsComputedValues({ params: { key } });
|
|
344
|
+
return result.status === 'success' ? result.data : undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function waitForPortfolioOutputs(label, expectedHoldingsBySymbol, timeoutMs = 30_000, pollMs = 300) {
|
|
348
|
+
const deadline = Date.now() + timeoutMs;
|
|
349
|
+
const expectedSymbols = Object.keys(expectedHoldingsBySymbol).sort();
|
|
350
|
+
let pollCount = 0;
|
|
351
|
+
|
|
352
|
+
while (Date.now() < deadline) {
|
|
353
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
354
|
+
pollCount++;
|
|
355
|
+
|
|
356
|
+
const prices = readOutputsDataObject('prices');
|
|
357
|
+
const holdingsTable = readOutputsComputedValues('holdings-table');
|
|
358
|
+
const rowsRaw = holdingsTable?.table?.rows;
|
|
359
|
+
if (!prices || rowsRaw === undefined || rowsRaw === null) continue;
|
|
360
|
+
const rows = [].concat(rowsRaw);
|
|
361
|
+
|
|
362
|
+
const priceSymbols = sortedKeys(prices);
|
|
363
|
+
const rowsBySymbol = Object.fromEntries(rows.map(r => [r.symbol, r.qty]));
|
|
364
|
+
const rowSymbols = sortedKeys(rowsBySymbol);
|
|
365
|
+
const hasSymbols = JSON.stringify(priceSymbols) === JSON.stringify(expectedSymbols)
|
|
366
|
+
&& JSON.stringify(rowSymbols) === JSON.stringify(expectedSymbols);
|
|
367
|
+
|
|
368
|
+
let qtyMatches = true;
|
|
369
|
+
for (const sym of expectedSymbols) {
|
|
370
|
+
if (rowsBySymbol[sym] !== expectedHoldingsBySymbol[sym]) {
|
|
371
|
+
qtyMatches = false;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (hasSymbols && qtyMatches) {
|
|
377
|
+
return { prices, holdingsTable, rowsBySymbol };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (pollCount % 5 === 0) {
|
|
381
|
+
console.log(`[${label}] waiting for output convergence: symbols=${JSON.stringify(priceSymbols)} rows=${JSON.stringify(rowSymbols)}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.error(`[ERROR] ${label}: timed out waiting for outputs to match expected holdings.`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── T0a — Create runtime directories ──────────────────────────────────────────
|
|
390
|
+
console.log('\n=== T0a: Create runtime directories ===');
|
|
391
|
+
if (fs.existsSync(_TMP_BASE)) {
|
|
392
|
+
fs.rmSync(_TMP_BASE, { recursive: true, force: true });
|
|
393
|
+
console.log(` cleaned: ${_TMP_BASE} (including .tmp, .card-runtime, journal)`);
|
|
394
|
+
}
|
|
395
|
+
for (const d of [CARDSTORE_DIR, BOARDRUNTIME_DIR, OUTPUTS_DIR]) {
|
|
396
|
+
fs.mkdirSync(d, { recursive: true });
|
|
397
|
+
console.log(` created: ${d}`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const pipeConsumer = await startPipeConsumer(NOTIFY_CHANNEL);
|
|
401
|
+
|
|
402
|
+
// ── T0b — Init board ───────────────────────────────────────────────────────────
|
|
403
|
+
console.log('\n=== T0b: Init board ===');
|
|
404
|
+
checkResult(
|
|
405
|
+
makeBoard().init({
|
|
406
|
+
params: { cardStoreRef: CARDSTORE_REF, outputsStoreRef: OUTPUTS_REF },
|
|
407
|
+
body: {
|
|
408
|
+
'task-executor-ref': {
|
|
409
|
+
meta: 'task-executor',
|
|
410
|
+
howToRun: 'local-node',
|
|
411
|
+
whatToRun: `::fs-path::${FETCH_PRICES_JS}`,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
}),
|
|
415
|
+
'init'
|
|
416
|
+
);
|
|
417
|
+
console.log(JSON.stringify({ status: 'success' }, null, 2));
|
|
418
|
+
|
|
419
|
+
// ── T0c — Validate and set all cards into card store ─────────────────────────
|
|
420
|
+
console.log('\n=== T0c: Validate and set all cards into card store ===');
|
|
421
|
+
const cardStore = makeCardStore();
|
|
422
|
+
for (const card of [
|
|
423
|
+
setHoldings(CARD_PORTFOLIO_FORM, { NVDA: 100 }),
|
|
424
|
+
CARD_PRICE_FETCH,
|
|
425
|
+
CARD_HOLDINGS_TABLE,
|
|
426
|
+
CARD_PORTFOLIO_VALUE,
|
|
427
|
+
]) {
|
|
428
|
+
const vr = makeNonCoreBoard().validateTmpCard({ body: card });
|
|
429
|
+
if (!vr.data?.isValid) {
|
|
430
|
+
console.error(`[VALIDATE FAILED] card ${card.id}:`, JSON.stringify(vr.data?.issues ?? vr.error));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
console.log(` [validate] ${card.id}: ok`);
|
|
434
|
+
const r = checkResult(cardStore.set({ body: card }), `card-store set ${card.id}`);
|
|
435
|
+
console.error(`card-store set: wrote ${r.count} card(s)`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ── T0d — Upsert cards to board ────────────────────────────────────────────────
|
|
439
|
+
console.log('\n=== T0d: Upsert cards to board ===');
|
|
440
|
+
for (const cardId of ['portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value']) {
|
|
441
|
+
checkResult(makeBoard().upsertCard({ params: { cardId } }), `upsertCard ${cardId}`);
|
|
442
|
+
console.log(JSON.stringify({ status: 'success' }, null, 2));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ── T1 — Wait for all cards completed ──────────────────────────────────────────
|
|
446
|
+
console.log('\n=== T1: Wait for all cards completed ===');
|
|
447
|
+
await waitForCompleted('T1', 4);
|
|
448
|
+
|
|
449
|
+
const { prices: pricesT1, holdingsTable: htCvT1, rowsBySymbol: rowsBySymbolT1 } = await waitForPortfolioOutputs('T1', { NVDA: 100 });
|
|
450
|
+
assert(typeof pricesT1 === 'object' && pricesT1 !== null && Object.keys(pricesT1).length > 0,
|
|
451
|
+
'T1: prices data object is empty or not an object');
|
|
452
|
+
assert(JSON.stringify(Object.keys(pricesT1).sort()) === JSON.stringify(['NVDA']),
|
|
453
|
+
`T1: expected keys {NVDA}, got ${JSON.stringify(Object.keys(pricesT1))}`);
|
|
454
|
+
assert(Object.values(pricesT1).every(v => typeof v === 'number'),
|
|
455
|
+
'T1: all price values must be numbers');
|
|
456
|
+
assert(rowsBySymbolT1['NVDA'] === 100,
|
|
457
|
+
`T1: expected NVDA qty=100, got ${rowsBySymbolT1['NVDA']}`);
|
|
458
|
+
console.log('[T1] assertion passed: prices has NVDA with numeric values, NVDA qty=100.');
|
|
459
|
+
|
|
460
|
+
// ── T2a — Update holdings (GOOG added) ────────────────────────────────────────
|
|
461
|
+
console.log('\n=== T2a: Update holdings (GOOG added) ===');
|
|
462
|
+
checkResult(
|
|
463
|
+
makeCardStore().set({ body: setHoldings(CARD_PORTFOLIO_FORM, { NVDA: 50, GOOG: 100 }) }),
|
|
464
|
+
'card-store set portfolio-form'
|
|
465
|
+
);
|
|
466
|
+
console.error('card-store set: wrote 1 card(s)');
|
|
467
|
+
|
|
468
|
+
// ── T2b — Upsert portfolio-form with restart ───────────────────────────────────
|
|
469
|
+
console.log('\n=== T2b: Upsert portfolio-form --restart ===');
|
|
470
|
+
checkResult(
|
|
471
|
+
makeBoard().upsertCard({ params: { cardId: 'portfolio-form', restart: 'true' } }),
|
|
472
|
+
'upsertCard portfolio-form restart'
|
|
473
|
+
);
|
|
474
|
+
console.log(JSON.stringify({ status: 'success' }, null, 2));
|
|
475
|
+
|
|
476
|
+
// ── T2c — Wait and assert ──────────────────────────────────────────────────────
|
|
477
|
+
console.log('\n=== T2c: Wait for all cards completed ===');
|
|
478
|
+
await waitForCompleted('T2c', 4);
|
|
479
|
+
|
|
480
|
+
const { prices: pricesT2c, holdingsTable: htCvT2c, rowsBySymbol: rowsBySymbolT2c } = await waitForPortfolioOutputs('T2c', { NVDA: 50, GOOG: 100 });
|
|
481
|
+
assert(JSON.stringify(Object.keys(pricesT2c).sort()) === JSON.stringify(['GOOG', 'NVDA']),
|
|
482
|
+
`T2c: expected keys {GOOG, NVDA}, got ${JSON.stringify(Object.keys(pricesT2c))}`);
|
|
483
|
+
|
|
484
|
+
assert(htCvT2c.table.rows.length === 2,
|
|
485
|
+
`T2c: expected 2 rows in holdings-table, got ${htCvT2c.table.rows.length}`);
|
|
486
|
+
assert(rowsBySymbolT2c['NVDA'] === 50,
|
|
487
|
+
`T2c: expected NVDA qty=50, got ${rowsBySymbolT2c['NVDA']}`);
|
|
488
|
+
assert(rowsBySymbolT2c['GOOG'] === 100,
|
|
489
|
+
`T2c: expected GOOG qty=100, got ${rowsBySymbolT2c['GOOG']}`);
|
|
490
|
+
console.log('[T2c] assertions passed: 2 tickers in prices, 2 rows in holdings-table, NVDA qty=50, GOOG qty=100.');
|
|
491
|
+
|
|
492
|
+
// ── T3 — Retrigger price-fetch ─────────────────────────────────────────────────
|
|
493
|
+
console.log('\n=== T3: Retrigger price-fetch ===');
|
|
494
|
+
checkResult(makeBoard().retrigger({ params: { id: 'price-fetch' } }), 'retrigger price-fetch');
|
|
495
|
+
console.log(JSON.stringify({ status: 'success' }, null, 2));
|
|
496
|
+
await waitForCompleted('T3', 4);
|
|
497
|
+
|
|
498
|
+
const { prices: pricesT3, rowsBySymbol: rowsBySymbolT3 } = await waitForPortfolioOutputs('T3', { NVDA: 50, GOOG: 100 });
|
|
499
|
+
assert(JSON.stringify(Object.keys(pricesT3).sort()) === JSON.stringify(['GOOG', 'NVDA']),
|
|
500
|
+
`T3: expected keys {GOOG, NVDA}, got ${JSON.stringify(Object.keys(pricesT3))}`);
|
|
501
|
+
assert(rowsBySymbolT3['NVDA'] === 50,
|
|
502
|
+
`T3: expected NVDA qty=50, got ${rowsBySymbolT3['NVDA']}`);
|
|
503
|
+
assert(rowsBySymbolT3['GOOG'] === 100,
|
|
504
|
+
`T3: expected GOOG qty=100, got ${rowsBySymbolT3['GOOG']}`);
|
|
505
|
+
const pvCvT3 = checkResult(makeBoard().getOutputsComputedValues({ params: { key: 'portfolio-value' } }), 'T3 getOutputsComputedValues portfolio-value');
|
|
506
|
+
const expectedTotalT3 = Math.round(
|
|
507
|
+
(rowsBySymbolT3['NVDA'] * pricesT3['NVDA'] + rowsBySymbolT3['GOOG'] * pricesT3['GOOG']) * 100
|
|
508
|
+
) / 100;
|
|
509
|
+
assert(Math.round(pvCvT3.totalValue * 100) === Math.round(expectedTotalT3 * 100),
|
|
510
|
+
`T3: expected totalValue=${expectedTotalT3}, got ${pvCvT3.totalValue}`);
|
|
511
|
+
console.log(`[T3] assertions passed: 2 tickers, NVDA qty=50, GOOG qty=100, totalValue=${pvCvT3.totalValue}.`);
|
|
512
|
+
|
|
513
|
+
// ── T4 — Rapid 5× portfolio-form updates ──────────────────────────────────────
|
|
514
|
+
// console.log('\n=== T4: Rapid 5x portfolio-form updates ===');
|
|
515
|
+
// for (const holdings of [
|
|
516
|
+
// { AAPL: 50 },
|
|
517
|
+
// { AAPL: 45, MSFT: 30, },
|
|
518
|
+
// { AAPL: 45, MSFT: 30, GOOG: 110, },
|
|
519
|
+
// { AAPL: 40, MSFT: 35, GOOG: 120, TSLA: 70 },
|
|
520
|
+
// { AAPL: 45, MSFT: 30, GOOG: 110, AMZN: 140, TSLA: 60 },
|
|
521
|
+
// ]) {
|
|
522
|
+
// checkResult(makeCardStore().set({ body: setHoldings(CARD_PORTFOLIO_FORM, holdings) }),
|
|
523
|
+
// 'card-store set portfolio-form');
|
|
524
|
+
// console.error('card-store set: wrote 1 card(s)');
|
|
525
|
+
// checkResult(makeBoard().upsertCard({ params: { cardId: 'portfolio-form', restart: 'true' } }),
|
|
526
|
+
// 'upsertCard portfolio-form restart');
|
|
527
|
+
// console.log(JSON.stringify({ status: 'success' }, null, 2));
|
|
528
|
+
// await new Promise(r => setTimeout(r, 2000));
|
|
529
|
+
// }
|
|
530
|
+
|
|
531
|
+
// await waitForCompleted('T4');
|
|
532
|
+
|
|
533
|
+
console.log('\nFinal board status (from NS):');
|
|
534
|
+
const finalStatusData = getNS().latestStatus();
|
|
535
|
+
console.log(JSON.stringify(finalStatusData, null, 2));
|
|
536
|
+
|
|
537
|
+
console.log('\nNotification summary (NS):');
|
|
538
|
+
console.log(JSON.stringify(getNS().summary(), null, 2));
|
|
539
|
+
|
|
540
|
+
console.log('\n=== portfolio-tracker-public completed successfully ===');
|
|
541
|
+
console.log('\n--- Runtime directories ---');
|
|
542
|
+
console.log(' cardstore: ', CARDSTORE_DIR);
|
|
543
|
+
console.log(' boardruntime: ', BOARDRUNTIME_DIR);
|
|
544
|
+
console.log(' outputs: ', OUTPUTS_DIR);
|
|
545
|
+
|
|
546
|
+
for (const socket of pipeConsumer.sockets) {
|
|
547
|
+
try { socket.destroy(); } catch { /* best-effort */ }
|
|
548
|
+
}
|
|
549
|
+
await new Promise((resolve) => pipeConsumer.server.close(resolve));
|
|
550
|
+
if (process.platform !== 'win32' && fs.existsSync(pipeConsumer.pipePath)) {
|
|
551
|
+
try { fs.rmSync(pipeConsumer.pipePath, { force: true }); } catch { /* best-effort */ }
|
|
552
|
+
}
|
|
553
|
+
process.exit(0);
|