@reicek/neataptic-ts 0.1.25 → 0.1.26
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/.github/copilot-instructions.md +11 -0
- package/.github/skills/trace-analyzer-extension/SKILL.md +3 -3
- package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +1 -1
- package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +1 -1
- package/.github/skills/trace-audit-reporting/SKILL.md +3 -3
- package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +1 -1
- package/package.json +19 -13
- package/plans/Flappy_Bird_Folder_Documentation_Pass.md +4 -4
- package/plans/README.md +24 -0
- package/plans/Roadmap.md +62 -40
- package/plans/analyze-trace-solid-split.plans.md +66 -0
- package/plans/architecture-solid-split.plans.md +9 -15
- package/plans/asciiMaze-typescript-repair.plans.md +1 -1
- package/plans/generate-docs-solid-split.plans.md +87 -0
- package/plans/methods-docs.plans.md +25 -1
- package/plans/methods-solid-split.plans.md +14 -14
- package/plans/neat-docs.plans.md +9 -1
- package/plans/neat-test-surface-repair.plans.md +1 -1
- package/plans/render-docs-html-solid-split.plans.md +68 -0
- package/plans/src-no-explicit-any-cleanup.plans.md +1 -1
- package/plans/utils-docs.plans.md +6 -1
- package/scripts/analyze-trace/analyze-trace.analysis.ts +479 -0
- package/scripts/analyze-trace/analyze-trace.constants.ts +35 -0
- package/scripts/analyze-trace/analyze-trace.io.ts +69 -0
- package/scripts/analyze-trace/analyze-trace.report.ts +100 -0
- package/scripts/analyze-trace/analyze-trace.shared.ts +116 -0
- package/scripts/analyze-trace/analyze-trace.ts +45 -0
- package/scripts/analyze-trace/analyze-trace.types.ts +72 -0
- package/scripts/assets/theme.css +80 -23
- package/scripts/copy-examples.ts +239 -0
- package/scripts/export-onnx.ts +223 -0
- package/scripts/generate-bench-tables.ts +378 -37
- package/scripts/generate-docs/generate-docs.constants.ts +107 -0
- package/scripts/generate-docs/generate-docs.order.ts +355 -0
- package/scripts/generate-docs/generate-docs.state.ts +31 -0
- package/scripts/generate-docs/generate-docs.targets.ts +165 -0
- package/scripts/generate-docs/generate-docs.ts +63 -0
- package/scripts/generate-docs/generate-docs.types.ts +112 -0
- package/scripts/generate-docs/output/generate-docs.output.folder-index.utils.ts +167 -0
- package/scripts/generate-docs/output/generate-docs.output.ordering.utils.ts +353 -0
- package/scripts/generate-docs/output/generate-docs.output.readme.utils.ts +420 -0
- package/scripts/generate-docs/output/generate-docs.output.ts +123 -0
- package/scripts/generate-docs/output/generate-docs.output.warnings.utils.ts +219 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.collection.utils.ts +365 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.jsdoc.utils.ts +373 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.normalize.utils.ts +155 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.render.utils.ts +149 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.signature.utils.ts +289 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.ts +11 -0
- package/scripts/mermaid-cli.mjs +102 -22
- package/scripts/mermaid-cli.ts +736 -0
- package/scripts/render-docs-html/render-docs-html.assets.ts +54 -0
- package/scripts/render-docs-html/render-docs-html.mermaid.ts +245 -0
- package/scripts/{render-docs-html.sidebar.ts → render-docs-html/render-docs-html.navigation.ts} +141 -144
- package/scripts/render-docs-html/render-docs-html.pages.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.shared.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.types.ts +42 -0
- package/scripts/render-docs-html.ts +23 -587
- package/scripts/run-docs.ts +238 -0
- package/scripts/write-dist-docs-pkg.ts +40 -0
- package/src/README.md +75 -75
- package/src/architecture/connection/README.md +5 -5
- package/src/architecture/layer/README.md +508 -508
- package/src/architecture/network/README.md +1458 -1458
- package/src/architecture/network/activate/README.md +694 -694
- package/src/architecture/network/bootstrap/README.md +77 -77
- package/src/architecture/network/connect/README.md +74 -74
- package/src/architecture/network/deterministic/README.md +135 -135
- package/src/architecture/network/evolve/README.md +364 -364
- package/src/architecture/network/gating/README.md +130 -130
- package/src/architecture/network/genetic/README.md +399 -399
- package/src/architecture/network/mutate/README.md +897 -897
- package/src/architecture/network/onnx/README.md +720 -720
- package/src/architecture/network/onnx/export/README.md +728 -728
- package/src/architecture/network/onnx/export/layers/README.md +450 -450
- package/src/architecture/network/onnx/import/README.md +618 -618
- package/src/architecture/network/onnx/schema/README.md +32 -32
- package/src/architecture/network/prune/README.md +245 -245
- package/src/architecture/network/remove/README.md +135 -135
- package/src/architecture/network/runtime/README.md +106 -106
- package/src/architecture/network/serialize/README.md +542 -542
- package/src/architecture/network/slab/README.md +608 -608
- package/src/architecture/network/standalone/README.md +212 -212
- package/src/architecture/network/stats/README.md +84 -84
- package/src/architecture/network/topology/README.md +465 -465
- package/src/architecture/network/training/README.md +200 -200
- package/src/architecture/node/README.md +5 -5
- package/src/architecture/nodePool/README.md +14 -14
- package/src/methods/README.md +99 -99
- package/src/methods/activation/README.md +189 -189
- package/src/methods/cost/README.md +131 -131
- package/src/methods/rate/README.md +86 -86
- package/src/multithreading/README.md +77 -77
- package/src/multithreading/workers/browser/README.md +8 -8
- package/src/multithreading/workers/node/README.md +8 -8
- package/src/neat/README.md +148 -148
- package/src/neat/adaptive/README.md +120 -120
- package/src/neat/adaptive/acceptance/README.md +40 -40
- package/src/neat/adaptive/complexity/README.md +137 -137
- package/src/neat/adaptive/core/README.md +197 -197
- package/src/neat/adaptive/lineage/README.md +90 -90
- package/src/neat/adaptive/mutation/README.md +284 -284
- package/src/neat/compat/README.md +43 -43
- package/src/neat/compat/core/README.md +90 -90
- package/src/neat/diversity/README.md +35 -35
- package/src/neat/diversity/core/README.md +88 -88
- package/src/neat/evaluate/README.md +85 -85
- package/src/neat/evaluate/auto-distance/README.md +75 -75
- package/src/neat/evaluate/entropy-compat/README.md +37 -37
- package/src/neat/evaluate/entropy-sharing/README.md +43 -43
- package/src/neat/evaluate/fitness/README.md +23 -23
- package/src/neat/evaluate/novelty/README.md +120 -120
- package/src/neat/evaluate/objectives/README.md +17 -17
- package/src/neat/evaluate/shared/README.md +94 -94
- package/src/neat/evolve/README.md +96 -96
- package/src/neat/evolve/adaptive/README.md +60 -60
- package/src/neat/evolve/objectives/README.md +63 -63
- package/src/neat/evolve/offspring/README.md +56 -56
- package/src/neat/evolve/population/README.md +171 -171
- package/src/neat/evolve/runtime/README.md +79 -79
- package/src/neat/evolve/speciation/README.md +74 -74
- package/src/neat/evolve/warnings/README.md +10 -10
- package/src/neat/export/README.md +114 -114
- package/src/neat/helpers/README.md +50 -50
- package/src/neat/init/README.md +9 -9
- package/src/neat/lineage/core/README.md +101 -101
- package/src/neat/multiobjective/category/README.md +74 -74
- package/src/neat/multiobjective/crowding/README.md +272 -272
- package/src/neat/multiobjective/dominance/README.md +171 -171
- package/src/neat/multiobjective/fronts/README.md +68 -68
- package/src/neat/multiobjective/metrics/README.md +43 -43
- package/src/neat/multiobjective/objectives/README.md +31 -31
- package/src/neat/multiobjective/shared/README.md +27 -27
- package/src/neat/mutation/README.md +97 -97
- package/src/neat/mutation/add-conn/README.md +115 -115
- package/src/neat/mutation/add-node/README.md +126 -126
- package/src/neat/mutation/flow/README.md +149 -149
- package/src/neat/mutation/repair/README.md +185 -185
- package/src/neat/mutation/select/README.md +117 -117
- package/src/neat/mutation/shared/README.md +32 -32
- package/src/neat/objectives/README.md +25 -25
- package/src/neat/objectives/core/README.md +67 -67
- package/src/neat/pruning/README.md +40 -40
- package/src/neat/pruning/core/README.md +171 -171
- package/src/neat/pruning/facade/README.md +32 -32
- package/src/neat/rng/README.md +104 -104
- package/src/neat/rng/core/README.md +137 -137
- package/src/neat/rng/facade/README.md +50 -50
- package/src/neat/selection/README.md +111 -111
- package/src/neat/selection/core/README.md +227 -227
- package/src/neat/selection/facade/README.md +61 -61
- package/src/neat/shared/README.md +163 -163
- package/src/neat/speciation/README.md +31 -31
- package/src/neat/speciation/threshold/README.md +35 -35
- package/src/neat/species/README.md +25 -25
- package/src/neat/species/core/README.md +20 -20
- package/src/neat/species/core/shared/README.md +18 -18
- package/src/neat/species/history/context/README.md +22 -22
- package/src/neat/telemetry/accessors/README.md +58 -58
- package/src/neat/telemetry/exports/README.md +233 -233
- package/src/neat/telemetry/facade/README.md +252 -252
- package/src/neat/telemetry/facade/archive/README.md +57 -57
- package/src/neat/telemetry/facade/buffer/README.md +43 -43
- package/src/neat/telemetry/facade/lineage/README.md +12 -12
- package/src/neat/telemetry/facade/objectives/README.md +44 -44
- package/src/neat/telemetry/facade/runtime/README.md +26 -26
- package/src/neat/telemetry/facade/species/README.md +27 -27
- package/src/neat/telemetry/metrics/README.md +696 -696
- package/src/neat/telemetry/recorder/README.md +57 -57
- package/src/neat/telemetry/types/README.md +32 -32
- package/src/neat/topology-intent/README.md +75 -75
- package/src/utils/README.md +193 -193
- package/test/examples/asciiMaze/browser-entry/README.md +92 -92
- package/test/examples/asciiMaze/dashboardManager/README.md +109 -109
- package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +28 -28
- package/test/examples/asciiMaze/evolutionEngine/README.md +1527 -1527
- package/test/examples/asciiMaze/mazeMovement/README.md +105 -105
- package/test/examples/asciiMaze/mazeMovement/finalization/README.md +16 -16
- package/test/examples/asciiMaze/mazeMovement/policy/README.md +57 -57
- package/test/examples/asciiMaze/mazeMovement/runtime/README.md +52 -52
- package/test/examples/asciiMaze/mazeMovement/shaping/README.md +46 -46
- package/test/examples/flappy_bird/browser-entry/README.md +508 -508
- package/test/examples/flappy_bird/browser-entry/host/README.md +101 -101
- package/test/examples/flappy_bird/browser-entry/host/resize/README.md +144 -144
- package/test/examples/flappy_bird/browser-entry/network-view/README.md +194 -194
- package/test/examples/flappy_bird/browser-entry/playback/README.md +278 -278
- package/test/examples/flappy_bird/browser-entry/playback/background/README.md +129 -129
- package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +502 -502
- package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +139 -139
- package/test/examples/flappy_bird/browser-entry/playback/snapshot/README.md +10 -10
- package/test/examples/flappy_bird/browser-entry/playback/trail/README.md +43 -43
- package/test/examples/flappy_bird/browser-entry/playback/worker-channel/README.md +30 -30
- package/test/examples/flappy_bird/browser-entry/runtime/README.md +59 -59
- package/test/examples/flappy_bird/browser-entry/visualization/README.md +276 -276
- package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +16 -16
- package/test/examples/flappy_bird/constants/README.md +1070 -1070
- package/test/examples/flappy_bird/environment/README.md +22 -22
- package/test/examples/flappy_bird/evaluation/README.md +32 -32
- package/test/examples/flappy_bird/evaluation/rollout/README.md +141 -141
- package/test/examples/flappy_bird/flappy-evolution-worker/README.md +425 -425
- package/test/examples/flappy_bird/simulation-shared/README.md +170 -170
- package/test/examples/flappy_bird/simulation-shared/observation/README.md +109 -109
- package/test/examples/flappy_bird/trainer/README.md +325 -325
- package/test/examples/flappy_bird/trainer/evaluation/README.md +74 -74
- package/scripts/analyze-trace.ts +0 -590
- package/scripts/copy-examples.mjs +0 -114
- package/scripts/export-onnx.mjs +0 -86
- package/scripts/generate-bench-tables.mjs +0 -182
- package/scripts/generate-docs.ts +0 -2900
- package/scripts/write-dist-docs-pkg.mjs +0 -16
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CLI to export a JSON ONNX model from a serialized network state.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npm run onnx:export -- --in network.json --out model.onnx.json [--metadata] [--batch] [--legacy] [--partial] [--mixed]
|
|
6
|
+
*
|
|
7
|
+
* This stays intentionally lightweight. For larger automation flows, prefer
|
|
8
|
+
* calling the ONNX export API directly from application code.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { pathToFileURL } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const PRIMARY_ONNX_MODULE_PATH = path.resolve(
|
|
16
|
+
'dist',
|
|
17
|
+
'architecture',
|
|
18
|
+
'onnx.js',
|
|
19
|
+
);
|
|
20
|
+
const FALLBACK_ONNX_MODULE_PATH = path.resolve(
|
|
21
|
+
'dist',
|
|
22
|
+
'architecture',
|
|
23
|
+
'network',
|
|
24
|
+
'network.onnx.js',
|
|
25
|
+
);
|
|
26
|
+
const NETWORK_MODULE_PATH = path.resolve('dist', 'architecture', 'network.js');
|
|
27
|
+
|
|
28
|
+
interface ExportOnnxOptions {
|
|
29
|
+
includeMetadata: boolean;
|
|
30
|
+
batchDimension: boolean;
|
|
31
|
+
legacyNodeOrdering: boolean;
|
|
32
|
+
allowPartialConnectivity: boolean;
|
|
33
|
+
allowMixedActivations: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface NetworkLike {
|
|
37
|
+
toJSON?: () => unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface NetworkFactory<TNetwork extends NetworkLike> {
|
|
41
|
+
fromJSON(raw: unknown): TNetwork;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface OnnxExportModule<TNetwork extends NetworkLike> {
|
|
45
|
+
exportToONNX: (network: TNetwork, options: ExportOnnxOptions) => unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface DistDependencies<TNetwork extends NetworkLike> {
|
|
49
|
+
Network: NetworkFactory<TNetwork>;
|
|
50
|
+
exportToONNX: OnnxExportModule<TNetwork>['exportToONNX'];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ParsedCliOptions extends ExportOnnxOptions {
|
|
54
|
+
inputFile: string;
|
|
55
|
+
outputFile: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Runs the ONNX export CLI.
|
|
60
|
+
*
|
|
61
|
+
* @returns Promise resolved when export succeeds.
|
|
62
|
+
*/
|
|
63
|
+
async function main(): Promise<void> {
|
|
64
|
+
const rawArguments = process.argv.slice(2);
|
|
65
|
+
if (hasFlag(rawArguments, '--help') || hasFlag(rawArguments, '-h')) {
|
|
66
|
+
printUsage();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const cliOptions = parseCliOptions(rawArguments);
|
|
71
|
+
const { Network, exportToONNX } = await loadDistDependencies<NetworkLike>();
|
|
72
|
+
const networkJson = readJsonFile(cliOptions.inputFile);
|
|
73
|
+
const network = Network.fromJSON(networkJson);
|
|
74
|
+
const onnxJson = exportToONNX(network, cliOptions);
|
|
75
|
+
|
|
76
|
+
fs.writeFileSync(
|
|
77
|
+
path.resolve(cliOptions.outputFile),
|
|
78
|
+
`${JSON.stringify(onnxJson, null, 2)}\n`,
|
|
79
|
+
'utf8',
|
|
80
|
+
);
|
|
81
|
+
console.log(`ONNX JSON written to ${cliOptions.outputFile}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Parses CLI options from raw process arguments.
|
|
86
|
+
*
|
|
87
|
+
* @param rawArguments - CLI arguments after the script path.
|
|
88
|
+
* @returns Parsed and validated CLI options.
|
|
89
|
+
*/
|
|
90
|
+
function parseCliOptions(rawArguments: readonly string[]): ParsedCliOptions {
|
|
91
|
+
const inputFile = resolveOptionValue(rawArguments, '--in');
|
|
92
|
+
const outputFile = resolveOptionValue(rawArguments, '--out');
|
|
93
|
+
|
|
94
|
+
if (!inputFile || !outputFile) {
|
|
95
|
+
printUsageAndExit('Error: --in and --out are required.');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
inputFile,
|
|
100
|
+
outputFile,
|
|
101
|
+
includeMetadata: hasFlag(rawArguments, '--metadata'),
|
|
102
|
+
batchDimension: hasFlag(rawArguments, '--batch'),
|
|
103
|
+
legacyNodeOrdering: hasFlag(rawArguments, '--legacy'),
|
|
104
|
+
allowPartialConnectivity: hasFlag(rawArguments, '--partial'),
|
|
105
|
+
allowMixedActivations: hasFlag(rawArguments, '--mixed'),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolves one option value from `--name value` or `--name=value` syntax.
|
|
111
|
+
*
|
|
112
|
+
* @param rawArguments - CLI arguments after the script path.
|
|
113
|
+
* @param optionName - Long option name including leading dashes.
|
|
114
|
+
* @returns Option value when present.
|
|
115
|
+
*/
|
|
116
|
+
function resolveOptionValue(
|
|
117
|
+
rawArguments: readonly string[],
|
|
118
|
+
optionName: string,
|
|
119
|
+
): string | undefined {
|
|
120
|
+
const exactIndex = rawArguments.indexOf(optionName);
|
|
121
|
+
if (exactIndex >= 0) {
|
|
122
|
+
return rawArguments[exactIndex + 1];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const inlineArgument = rawArguments.find((argument) =>
|
|
126
|
+
argument.startsWith(`${optionName}=`),
|
|
127
|
+
);
|
|
128
|
+
return inlineArgument?.slice(optionName.length + 1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Checks whether a boolean flag is present.
|
|
133
|
+
*
|
|
134
|
+
* @param rawArguments - CLI arguments after the script path.
|
|
135
|
+
* @param optionName - Flag name including leading dashes.
|
|
136
|
+
* @returns `true` when the flag is present.
|
|
137
|
+
*/
|
|
138
|
+
function hasFlag(rawArguments: readonly string[], optionName: string): boolean {
|
|
139
|
+
return rawArguments.includes(optionName);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Loads the built network and ONNX export modules from `dist/`.
|
|
144
|
+
*
|
|
145
|
+
* @returns The built network constructor and ONNX export function.
|
|
146
|
+
*/
|
|
147
|
+
async function loadDistDependencies<TNetwork extends NetworkLike>(): Promise<
|
|
148
|
+
DistDependencies<TNetwork>
|
|
149
|
+
> {
|
|
150
|
+
const onnxModule = (await importModuleWithFallback<
|
|
151
|
+
Partial<OnnxExportModule<TNetwork>>
|
|
152
|
+
>(PRIMARY_ONNX_MODULE_PATH, FALLBACK_ONNX_MODULE_PATH)) as Partial<
|
|
153
|
+
OnnxExportModule<TNetwork>
|
|
154
|
+
>;
|
|
155
|
+
const networkModule = (await import(
|
|
156
|
+
pathToFileURL(NETWORK_MODULE_PATH).href
|
|
157
|
+
)) as { default?: NetworkFactory<TNetwork> };
|
|
158
|
+
|
|
159
|
+
if (!networkModule.default || typeof onnxModule.exportToONNX !== 'function') {
|
|
160
|
+
throw new Error(
|
|
161
|
+
'Failed to load built ONNX export dependencies from dist/.',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
Network: networkModule.default,
|
|
167
|
+
exportToONNX: onnxModule.exportToONNX,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Imports the primary built module, falling back to a secondary path when the
|
|
173
|
+
* public layout has shifted.
|
|
174
|
+
*
|
|
175
|
+
* @param primaryModulePath - Preferred built module path.
|
|
176
|
+
* @param fallbackModulePath - Fallback built module path.
|
|
177
|
+
* @returns Imported module namespace.
|
|
178
|
+
*/
|
|
179
|
+
async function importModuleWithFallback<TModule>(
|
|
180
|
+
primaryModulePath: string,
|
|
181
|
+
fallbackModulePath: string,
|
|
182
|
+
): Promise<TModule> {
|
|
183
|
+
try {
|
|
184
|
+
return (await import(pathToFileURL(primaryModulePath).href)) as TModule;
|
|
185
|
+
} catch {
|
|
186
|
+
return (await import(pathToFileURL(fallbackModulePath).href)) as TModule;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Reads and parses one JSON file.
|
|
192
|
+
*
|
|
193
|
+
* @param filePath - Source JSON file path.
|
|
194
|
+
* @returns Parsed JSON value.
|
|
195
|
+
*/
|
|
196
|
+
function readJsonFile(filePath: string): unknown {
|
|
197
|
+
return JSON.parse(fs.readFileSync(path.resolve(filePath), 'utf8')) as unknown;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Prints CLI usage text. */
|
|
201
|
+
function printUsage(): void {
|
|
202
|
+
console.log(
|
|
203
|
+
'Usage: npm run onnx:export -- --in network.json --out model.onnx.json [--metadata] [--batch] [--legacy] [--partial] [--mixed]',
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Prints CLI usage text and exits the process with failure.
|
|
209
|
+
*
|
|
210
|
+
* @param message - Error message shown before usage text.
|
|
211
|
+
* @returns Never returns.
|
|
212
|
+
*/
|
|
213
|
+
function printUsageAndExit(message: string): never {
|
|
214
|
+
console.error(message);
|
|
215
|
+
printUsage();
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
main().catch((error: unknown) => {
|
|
220
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
221
|
+
console.error('Export failed:', errorMessage);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
|
@@ -14,46 +14,336 @@
|
|
|
14
14
|
* - Gracefully degrades when fields missing (older artifact schema).
|
|
15
15
|
* - Extend later for variance/regression annotation summaries.
|
|
16
16
|
*/
|
|
17
|
-
import fs from 'fs';
|
|
18
|
-
import path from 'path';
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
19
|
|
|
20
|
+
/** Canonical benchmark artifact consumed by the table generator. */
|
|
21
|
+
const BENCHMARK_ARTIFACT_PATH = path.resolve(
|
|
22
|
+
'test/benchmarks/benchmark.results.json',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/** Metrics shown in the variant delta table. */
|
|
26
|
+
const DELTA_METRICS = ['buildMs', 'fwdAvgMs', 'bytesPerConn'] as const;
|
|
27
|
+
|
|
28
|
+
/** Metrics shown in the heap and resident-set table. */
|
|
29
|
+
const HEAP_METRICS = ['heapUsed', 'rss'] as const;
|
|
30
|
+
|
|
31
|
+
/** Full metric set recognized across both current and legacy artifact schemas. */
|
|
32
|
+
const AGGREGATED_METRICS = [...DELTA_METRICS, ...HEAP_METRICS] as const;
|
|
33
|
+
|
|
34
|
+
/** Benchmark variant identifiers emitted by the build-variant suite. */
|
|
35
|
+
type BenchmarkMode = 'src' | 'dist';
|
|
36
|
+
|
|
37
|
+
/** Metric names that participate in src-vs-dist regression comparison. */
|
|
38
|
+
type DeltaMetricName = (typeof DELTA_METRICS)[number];
|
|
39
|
+
|
|
40
|
+
/** Metric names that participate in the heap summary table. */
|
|
41
|
+
type HeapMetricName = (typeof HEAP_METRICS)[number];
|
|
42
|
+
|
|
43
|
+
/** All metric names recognized by the script. */
|
|
44
|
+
type BenchmarkMetricName = (typeof AGGREGATED_METRICS)[number];
|
|
45
|
+
|
|
46
|
+
/** Legacy flat-artifact key names such as `buildMsMean`. */
|
|
47
|
+
type LegacyMeanMetricKey = `${BenchmarkMetricName}Mean`;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Aggregate statistics for one benchmark metric.
|
|
51
|
+
*
|
|
52
|
+
* The current table generator only consumes `mean`, but the wider schema can
|
|
53
|
+
* also carry percentile and spread values that may be used by later reports.
|
|
54
|
+
*/
|
|
20
55
|
interface AggregatedMetric {
|
|
21
|
-
mean
|
|
22
|
-
p50
|
|
23
|
-
p95
|
|
24
|
-
std
|
|
56
|
+
mean?: number;
|
|
57
|
+
p50?: number;
|
|
58
|
+
p95?: number;
|
|
59
|
+
std?: number;
|
|
25
60
|
}
|
|
26
61
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Scenario-level metrics under one size bucket.
|
|
64
|
+
*
|
|
65
|
+
* The script currently reads the `all` scenario, but the normalized shape keeps
|
|
66
|
+
* the scenario layer so future reporting can distinguish specialized runs.
|
|
67
|
+
*/
|
|
68
|
+
type AggregatedScenarioBucket = Partial<
|
|
69
|
+
Record<BenchmarkMetricName, AggregatedMetric>
|
|
70
|
+
>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Size bucket keyed by scenario name.
|
|
74
|
+
*
|
|
75
|
+
* Example: `1024 -> all -> buildMs -> { mean: ... }`.
|
|
76
|
+
*/
|
|
77
|
+
type AggregatedSizeBucket = Record<
|
|
78
|
+
string,
|
|
79
|
+
AggregatedScenarioBucket | undefined
|
|
80
|
+
> & {
|
|
81
|
+
all?: AggregatedScenarioBucket;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** Aggregated metrics keyed by benchmark size. */
|
|
85
|
+
type AggregatedModeBucket = Record<string, AggregatedSizeBucket>;
|
|
86
|
+
|
|
87
|
+
/** Fully normalized aggregated results keyed by build variant. */
|
|
88
|
+
interface NormalizedAggregatedResults {
|
|
89
|
+
src: AggregatedModeBucket;
|
|
90
|
+
dist: AggregatedModeBucket;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Minimal artifact contract needed by this script. */
|
|
94
|
+
interface BenchmarkArtifact {
|
|
95
|
+
aggregated?: unknown;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Legacy array-based aggregated entry shape.
|
|
100
|
+
*
|
|
101
|
+
* Older benchmark artifacts stored one flat record per `(mode, size)` pair
|
|
102
|
+
* using `fooMean` properties instead of the nested scenario bucket structure.
|
|
103
|
+
*/
|
|
104
|
+
interface LegacyAggregatedEntry extends Partial<
|
|
105
|
+
Record<LegacyMeanMetricKey, number>
|
|
106
|
+
> {
|
|
107
|
+
mode: BenchmarkMode;
|
|
108
|
+
size: number | string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Pads or truncates a value to fixed width for the fenced monospace tables.
|
|
113
|
+
*
|
|
114
|
+
* @param v - Cell value to render.
|
|
115
|
+
* @param w - Target cell width.
|
|
116
|
+
* @returns Fixed-width cell content.
|
|
117
|
+
*/
|
|
118
|
+
function cell(v: string | number | undefined | null, w: number): string {
|
|
29
119
|
const s = String(v ?? '');
|
|
30
120
|
return s.length >= w ? s.slice(0, w) : s + ' '.repeat(w - s.length);
|
|
31
121
|
}
|
|
32
122
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Formats a numeric value with trimmed trailing zeros.
|
|
125
|
+
*
|
|
126
|
+
* This keeps markdown tables compact without losing the ability to request
|
|
127
|
+
* higher precision for smaller benchmark values.
|
|
128
|
+
*
|
|
129
|
+
* @param n - Candidate numeric value.
|
|
130
|
+
* @param digits - Maximum fixed decimal precision.
|
|
131
|
+
* @returns Formatted number or an empty string when the input is not finite.
|
|
132
|
+
*/
|
|
133
|
+
function fmtNum(n: number | undefined, digits = 4): string {
|
|
134
|
+
if (typeof n !== 'number' || !Number.isFinite(n)) return '';
|
|
36
135
|
return n.toFixed(digits).replace(/0+$/, '').replace(/\.$/, '');
|
|
37
136
|
}
|
|
38
137
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Loads the benchmark artifact from disk.
|
|
140
|
+
*
|
|
141
|
+
* @returns Parsed artifact when present and readable, otherwise `null`.
|
|
142
|
+
*/
|
|
143
|
+
function loadArtifact(): BenchmarkArtifact | null {
|
|
144
|
+
if (!fs.existsSync(BENCHMARK_ARTIFACT_PATH)) {
|
|
145
|
+
console.error(
|
|
146
|
+
'[bench:tables] Artifact not found:',
|
|
147
|
+
BENCHMARK_ARTIFACT_PATH,
|
|
148
|
+
);
|
|
43
149
|
return null;
|
|
44
150
|
}
|
|
151
|
+
|
|
45
152
|
try {
|
|
46
|
-
return JSON.parse(
|
|
47
|
-
|
|
48
|
-
|
|
153
|
+
return JSON.parse(
|
|
154
|
+
fs.readFileSync(BENCHMARK_ARTIFACT_PATH, 'utf8'),
|
|
155
|
+
) as BenchmarkArtifact;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error('[bench:tables] Parse error', error);
|
|
49
158
|
return null;
|
|
50
159
|
}
|
|
51
160
|
}
|
|
52
161
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Normalizes the artifact's aggregated payload into one stable nested shape.
|
|
164
|
+
*
|
|
165
|
+
* The benchmark pipeline has emitted both nested and legacy flat array schemas.
|
|
166
|
+
* Folding both into the same mode/size/scenario/metric structure keeps the
|
|
167
|
+
* table builders simple and resilient.
|
|
168
|
+
*
|
|
169
|
+
* @param artifact - Parsed benchmark artifact.
|
|
170
|
+
* @returns Normalized aggregated result buckets.
|
|
171
|
+
*/
|
|
172
|
+
function normalizeAggregated(
|
|
173
|
+
artifact: BenchmarkArtifact,
|
|
174
|
+
): NormalizedAggregatedResults {
|
|
175
|
+
if (Array.isArray(artifact.aggregated)) {
|
|
176
|
+
return normalizeLegacyAggregated(artifact.aggregated);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!isRecord(artifact.aggregated)) {
|
|
180
|
+
return createEmptyAggregatedResults();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
src: normalizeModeBucket(artifact.aggregated.src),
|
|
185
|
+
dist: normalizeModeBucket(artifact.aggregated.dist),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Converts the older flat array schema into the normalized nested shape.
|
|
191
|
+
*
|
|
192
|
+
* @param rawEntries - Legacy aggregated entries.
|
|
193
|
+
* @returns Normalized aggregated result buckets.
|
|
194
|
+
*/
|
|
195
|
+
function normalizeLegacyAggregated(
|
|
196
|
+
rawEntries: readonly unknown[],
|
|
197
|
+
): NormalizedAggregatedResults {
|
|
198
|
+
const aggregatedResults = createEmptyAggregatedResults();
|
|
199
|
+
|
|
200
|
+
for (const rawEntry of rawEntries) {
|
|
201
|
+
if (!isLegacyAggregatedEntry(rawEntry)) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const sizeKey = String(rawEntry.size);
|
|
206
|
+
const scenarioBucket = ensureAllScenarioBucket(
|
|
207
|
+
aggregatedResults[rawEntry.mode],
|
|
208
|
+
sizeKey,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
for (const metricName of AGGREGATED_METRICS) {
|
|
212
|
+
const meanKey = `${metricName}Mean` as const;
|
|
213
|
+
const meanValue = rawEntry[meanKey];
|
|
214
|
+
if (typeof meanValue === 'number' && Number.isFinite(meanValue)) {
|
|
215
|
+
scenarioBucket[metricName] = { mean: meanValue };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return aggregatedResults;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Normalizes one top-level mode bucket such as `src` or `dist`.
|
|
225
|
+
*
|
|
226
|
+
* @param rawModeBucket - Unknown raw mode payload.
|
|
227
|
+
* @returns Normalized size buckets.
|
|
228
|
+
*/
|
|
229
|
+
function normalizeModeBucket(rawModeBucket: unknown): AggregatedModeBucket {
|
|
230
|
+
if (!isRecord(rawModeBucket)) {
|
|
231
|
+
return {};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return Object.fromEntries(
|
|
235
|
+
Object.entries(rawModeBucket).map(([sizeKey, rawSizeBucket]) => [
|
|
236
|
+
sizeKey,
|
|
237
|
+
normalizeSizeBucket(rawSizeBucket),
|
|
238
|
+
]),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Normalizes one size bucket keyed by scenario names.
|
|
244
|
+
*
|
|
245
|
+
* @param rawSizeBucket - Unknown raw size payload.
|
|
246
|
+
* @returns Normalized scenario buckets.
|
|
247
|
+
*/
|
|
248
|
+
function normalizeSizeBucket(rawSizeBucket: unknown): AggregatedSizeBucket {
|
|
249
|
+
if (!isRecord(rawSizeBucket)) {
|
|
250
|
+
return {};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return Object.fromEntries(
|
|
254
|
+
Object.entries(rawSizeBucket).map(([scenarioName, rawScenarioBucket]) => [
|
|
255
|
+
scenarioName,
|
|
256
|
+
normalizeScenarioBucket(rawScenarioBucket),
|
|
257
|
+
]),
|
|
258
|
+
) as AggregatedSizeBucket;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Normalizes one scenario bucket keyed by metric name.
|
|
263
|
+
*
|
|
264
|
+
* Invalid or incomplete metric objects are dropped rather than guessed.
|
|
265
|
+
*
|
|
266
|
+
* @param rawScenarioBucket - Unknown raw scenario payload.
|
|
267
|
+
* @returns Normalized metric bucket.
|
|
268
|
+
*/
|
|
269
|
+
function normalizeScenarioBucket(
|
|
270
|
+
rawScenarioBucket: unknown,
|
|
271
|
+
): AggregatedScenarioBucket {
|
|
272
|
+
if (!isRecord(rawScenarioBucket)) {
|
|
273
|
+
return {};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const normalizedEntries = AGGREGATED_METRICS.flatMap((metricName) => {
|
|
277
|
+
const rawMetric = rawScenarioBucket[metricName];
|
|
278
|
+
if (!isRecord(rawMetric)) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const meanValue = rawMetric.mean;
|
|
283
|
+
if (typeof meanValue !== 'number' || !Number.isFinite(meanValue)) {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return [[metricName, { mean: meanValue }] as const];
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return Object.fromEntries(normalizedEntries) as AggregatedScenarioBucket;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Ensures the `all` scenario exists for one `(mode, size)` pair.
|
|
295
|
+
*
|
|
296
|
+
* @param modeBucket - Normalized mode bucket.
|
|
297
|
+
* @param sizeKey - Size identifier.
|
|
298
|
+
* @returns Mutable `all` scenario bucket.
|
|
299
|
+
*/
|
|
300
|
+
function ensureAllScenarioBucket(
|
|
301
|
+
modeBucket: AggregatedModeBucket,
|
|
302
|
+
sizeKey: string,
|
|
303
|
+
): AggregatedScenarioBucket {
|
|
304
|
+
modeBucket[sizeKey] ??= {};
|
|
305
|
+
modeBucket[sizeKey].all ??= {};
|
|
306
|
+
return modeBucket[sizeKey].all!;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Creates an empty normalized results shell.
|
|
311
|
+
*
|
|
312
|
+
* @returns Empty `src` and `dist` mode buckets.
|
|
313
|
+
*/
|
|
314
|
+
function createEmptyAggregatedResults(): NormalizedAggregatedResults {
|
|
315
|
+
return {
|
|
316
|
+
src: {},
|
|
317
|
+
dist: {},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Resolves size keys in ascending numeric order.
|
|
323
|
+
*
|
|
324
|
+
* @param aggregatedResults - Normalized aggregated result buckets.
|
|
325
|
+
* @returns Ordered size keys.
|
|
326
|
+
*/
|
|
327
|
+
function resolveOrderedSizes(
|
|
328
|
+
aggregatedResults: NormalizedAggregatedResults,
|
|
329
|
+
): string[] {
|
|
330
|
+
return [...new Set(Object.keys(aggregatedResults.src))].toSorted(
|
|
331
|
+
compareNumericSizeKeys,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Builds the variant delta table comparing `src` and `dist` means.
|
|
337
|
+
*
|
|
338
|
+
* The result is tuned for markdown fenced blocks rather than GFM pipe tables,
|
|
339
|
+
* which keeps wide benchmark rows easier to scan in docs and pull requests.
|
|
340
|
+
*
|
|
341
|
+
* @param artifact - Parsed benchmark artifact.
|
|
342
|
+
* @returns Monospace markdown table body.
|
|
343
|
+
*/
|
|
344
|
+
function buildVariantDeltaTable(artifact: BenchmarkArtifact): string {
|
|
345
|
+
const aggregatedResults = normalizeAggregated(artifact);
|
|
346
|
+
const orderedSizes = resolveOrderedSizes(aggregatedResults);
|
|
57
347
|
const rows: string[] = [];
|
|
58
348
|
rows.push(
|
|
59
349
|
'Size Metric Src Mean Dist Mean Δ Abs Δ % Flag Result',
|
|
@@ -61,13 +351,10 @@ function buildVariantDeltaTable(artifact: any): string {
|
|
|
61
351
|
rows.push(
|
|
62
352
|
'------ ----------------- ------------- ------------- ------------- ---------- ------------- ------------------------------------------------------------',
|
|
63
353
|
);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
agg.src?.[size]?.all?.[metric];
|
|
69
|
-
const distAgg: AggregatedMetric | undefined =
|
|
70
|
-
agg.dist?.[size]?.all?.[metric];
|
|
354
|
+
for (const size of orderedSizes) {
|
|
355
|
+
for (const metric of DELTA_METRICS) {
|
|
356
|
+
const srcAgg = aggregatedResults.src[size]?.all?.[metric];
|
|
357
|
+
const distAgg = aggregatedResults.dist[size]?.all?.[metric];
|
|
71
358
|
const sMean = srcAgg?.mean;
|
|
72
359
|
const dMean = distAgg?.mean;
|
|
73
360
|
let dAbs: number | undefined;
|
|
@@ -123,10 +410,15 @@ function buildVariantDeltaTable(artifact: any): string {
|
|
|
123
410
|
return rows.join('\n');
|
|
124
411
|
}
|
|
125
412
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
413
|
+
/**
|
|
414
|
+
* Builds the heap and RSS comparison table.
|
|
415
|
+
*
|
|
416
|
+
* @param artifact - Parsed benchmark artifact.
|
|
417
|
+
* @returns Monospace markdown table body.
|
|
418
|
+
*/
|
|
419
|
+
function buildHeapTable(artifact: BenchmarkArtifact): string {
|
|
420
|
+
const aggregatedResults = normalizeAggregated(artifact);
|
|
421
|
+
const orderedSizes = resolveOrderedSizes(aggregatedResults);
|
|
130
422
|
const rows: string[] = [];
|
|
131
423
|
rows.push(
|
|
132
424
|
'Size Metric Src Bytes Dist Bytes Src MB Dist MB Δ Bytes Δ % Note',
|
|
@@ -134,10 +426,10 @@ function buildHeapTable(artifact: any): string {
|
|
|
134
426
|
rows.push(
|
|
135
427
|
'------ -------------- ------------ ------------ --------- --------- ----------- ------- -------------------------------------------------',
|
|
136
428
|
);
|
|
137
|
-
for (const size of
|
|
138
|
-
for (const metric of
|
|
139
|
-
const s =
|
|
140
|
-
const d =
|
|
429
|
+
for (const size of orderedSizes) {
|
|
430
|
+
for (const metric of HEAP_METRICS) {
|
|
431
|
+
const s = aggregatedResults.src[size]?.all?.[metric]?.mean;
|
|
432
|
+
const d = aggregatedResults.dist[size]?.all?.[metric]?.mean;
|
|
141
433
|
if (typeof s !== 'number' || typeof d !== 'number') continue;
|
|
142
434
|
const dBytes = d - s;
|
|
143
435
|
const dPct = s === 0 ? 0 : (dBytes / s) * 100;
|
|
@@ -165,6 +457,55 @@ function buildHeapTable(artifact: any): string {
|
|
|
165
457
|
return rows.join('\n');
|
|
166
458
|
}
|
|
167
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Compares size keys numerically, falling back to lexical comparison.
|
|
462
|
+
*
|
|
463
|
+
* @param left - Left size key.
|
|
464
|
+
* @param right - Right size key.
|
|
465
|
+
* @returns Standard comparator delta.
|
|
466
|
+
*/
|
|
467
|
+
function compareNumericSizeKeys(left: string, right: string): number {
|
|
468
|
+
const leftValue = Number.parseInt(left, 10);
|
|
469
|
+
const rightValue = Number.parseInt(right, 10);
|
|
470
|
+
|
|
471
|
+
if (Number.isNaN(leftValue) || Number.isNaN(rightValue)) {
|
|
472
|
+
return left.localeCompare(right);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return leftValue - rightValue || left.localeCompare(right);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Narrows an unknown value to the legacy flat aggregated entry shape.
|
|
480
|
+
*
|
|
481
|
+
* @param value - Candidate legacy entry.
|
|
482
|
+
* @returns `true` when the value matches the legacy schema.
|
|
483
|
+
*/
|
|
484
|
+
function isLegacyAggregatedEntry(
|
|
485
|
+
value: unknown,
|
|
486
|
+
): value is LegacyAggregatedEntry {
|
|
487
|
+
return (
|
|
488
|
+
isRecord(value) &&
|
|
489
|
+
(value.mode === 'src' || value.mode === 'dist') &&
|
|
490
|
+
(typeof value.size === 'number' || typeof value.size === 'string')
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Narrows an unknown value to a plain object record.
|
|
496
|
+
*
|
|
497
|
+
* @param value - Candidate value.
|
|
498
|
+
* @returns `true` when the value is a non-array object.
|
|
499
|
+
*/
|
|
500
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
501
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Runs the artifact load and emits both markdown tables to standard output.
|
|
506
|
+
*
|
|
507
|
+
* @returns Nothing.
|
|
508
|
+
*/
|
|
168
509
|
function main() {
|
|
169
510
|
const artifact = loadArtifact();
|
|
170
511
|
if (!artifact) process.exit(1);
|