@saptools/service-flow 0.1.68 → 0.1.69
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/CHANGELOG.md +10 -0
- package/README.md +11 -5
- package/TECHNICAL-NOTE.md +9 -0
- package/dist/{chunk-AEM4JY22.js → chunk-3N3B5KHV.js} +6983 -5500
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +317 -105
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +45 -19
- package/src/db/003-current-fact-semantics.ts +1 -0
- package/src/db/004-package-target-invalidation.ts +10 -0
- package/src/db/006-relative-symbol-resolution.ts +28 -7
- package/src/db/008-relative-fact-semantics.ts +6 -3
- package/src/db/009-binding-fact-semantics.ts +5 -0
- package/src/db/012-binding-reference-proof.ts +4 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/repositories.ts +22 -5
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +66 -5
- package/src/indexer/workspace-indexer.ts +141 -29
- package/src/linker/004-event-subscription-handler-linker.ts +32 -11
- package/src/linker/006-event-template-link.ts +72 -0
- package/src/linker/007-call-edge-insertion.ts +568 -0
- package/src/linker/cross-repo-linker.ts +2 -165
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/006-binding-identity.ts +6 -1
- package/src/parsers/014-service-binding-helper-flow.ts +70 -4
- package/src/parsers/021-binding-visibility.ts +17 -1
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/outbound-call-parser.ts +146 -509
- package/src/parsers/symbol-parser.ts +37 -5
- package/src/trace/007-implementation-start-diagnostic.ts +1 -0
- package/src/trace/011-event-subscriber-traversal.ts +100 -8
- package/src/trace/013-trace-root-scopes.ts +2 -3
- package/src/trace/014-compact-contract.ts +6 -0
- package/src/trace/015-trace-edge-recorder.ts +61 -4
- package/src/trace/016-compact-projector.ts +2 -3
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +74 -1
- package/src/trace/021-compact-decision-normalization.ts +75 -9
- package/src/trace/024-compact-observation-decision.ts +7 -2
- package/src/trace/026-trace-start-scope.ts +1 -0
- package/src/trace/027-trace-scope-execution.ts +33 -33
- package/src/trace/030-event-runtime-resolution.ts +151 -0
- package/src/trace/031-local-call-expansion.ts +37 -0
- package/src/trace/implementation-hints.ts +9 -6
- package/src/trace/selectors.ts +1 -0
- package/src/types.ts +2 -1
- package/src/version.ts +1 -1
- package/dist/chunk-AEM4JY22.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ANALYZER_VERSION,
|
|
4
|
+
PreparedRepositorySnapshotError,
|
|
4
5
|
VERSION,
|
|
5
6
|
analyzePackagePublicSurface,
|
|
7
|
+
binaryCompare,
|
|
6
8
|
bindingSite,
|
|
7
9
|
classifyODataPathIntent,
|
|
8
10
|
classifyOutboundCallsInSource,
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
implementationHintSuggestions,
|
|
19
21
|
insertCalls,
|
|
20
22
|
insertSymbolCalls,
|
|
23
|
+
isPreparedRepositorySnapshotError,
|
|
21
24
|
lexicalIdentifierDeclaration,
|
|
22
25
|
lexicalIdentifierDeclarations,
|
|
23
26
|
lexicalScopeChain,
|
|
@@ -37,6 +40,7 @@ import {
|
|
|
37
40
|
parseServiceBindings,
|
|
38
41
|
parseVars,
|
|
39
42
|
projectBoundedInOrder,
|
|
43
|
+
recordPreparedSnapshotFailure,
|
|
40
44
|
redactValue,
|
|
41
45
|
selectCallOwner,
|
|
42
46
|
selectVisibleBinding,
|
|
@@ -44,7 +48,7 @@ import {
|
|
|
44
48
|
stableLocalValueReference,
|
|
45
49
|
symbolImportReference,
|
|
46
50
|
trace
|
|
47
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-3N3B5KHV.js";
|
|
48
52
|
|
|
49
53
|
// src/cli.ts
|
|
50
54
|
import { Command, Option } from "commander";
|
|
@@ -493,6 +497,15 @@ function insertBindings(db, repoId, rows) {
|
|
|
493
497
|
r.helperChain ? JSON.stringify(r.helperChain) : null
|
|
494
498
|
);
|
|
495
499
|
}
|
|
500
|
+
function bindingSnapshotError(failureCode, fact) {
|
|
501
|
+
return new PreparedRepositorySnapshotError(failureCode, {
|
|
502
|
+
factKind: "service_binding",
|
|
503
|
+
sourceFile: fact.sourceFile,
|
|
504
|
+
sourceLine: fact.sourceLine,
|
|
505
|
+
callSiteStartOffset: fact.bindingSiteStartOffset,
|
|
506
|
+
callSiteEndOffset: fact.bindingSiteEndOffset
|
|
507
|
+
});
|
|
508
|
+
}
|
|
496
509
|
function persistedOwnerCandidates(db, repoId, fact) {
|
|
497
510
|
const rows = db.prepare(`SELECT id,kind,qualified_name qualifiedName,
|
|
498
511
|
start_offset startOffset,end_offset endOffset FROM symbols
|
|
@@ -519,21 +532,21 @@ function bindingOwnerId(db, repoId, fact) {
|
|
|
519
532
|
);
|
|
520
533
|
if (fact.ownerResolution === "ownerless_file_scope") {
|
|
521
534
|
if (selected.status !== "none")
|
|
522
|
-
throw
|
|
535
|
+
throw bindingSnapshotError("binding_owner_mismatch", fact);
|
|
523
536
|
return null;
|
|
524
537
|
}
|
|
525
538
|
if (fact.ownerResolution !== "owned_exact" || selected.status !== "resolved" || selected.owner?.qualifiedName !== fact.sourceSymbolQualifiedName)
|
|
526
|
-
throw
|
|
539
|
+
throw bindingSnapshotError("binding_owner_mismatch", fact);
|
|
527
540
|
const owner = selected.owner;
|
|
528
541
|
if (!owner)
|
|
529
|
-
throw
|
|
542
|
+
throw bindingSnapshotError("binding_owner_mismatch", fact);
|
|
530
543
|
return owner.id;
|
|
531
544
|
}
|
|
532
545
|
function assertUniquePreparedBindingSites(rows) {
|
|
533
546
|
const seen = /* @__PURE__ */ new Set();
|
|
534
547
|
for (const row of rows) {
|
|
535
548
|
if (row.bindingSiteStartOffset === void 0 || row.bindingSiteEndOffset === void 0)
|
|
536
|
-
throw
|
|
549
|
+
throw bindingSnapshotError("binding_site_missing", row);
|
|
537
550
|
const key = [
|
|
538
551
|
row.sourceFile,
|
|
539
552
|
row.variableName,
|
|
@@ -541,7 +554,7 @@ function assertUniquePreparedBindingSites(rows) {
|
|
|
541
554
|
row.bindingSiteEndOffset
|
|
542
555
|
].join("\0");
|
|
543
556
|
if (seen.has(key))
|
|
544
|
-
throw
|
|
557
|
+
throw bindingSnapshotError("duplicate_service_binding_site", row);
|
|
545
558
|
seen.add(key);
|
|
546
559
|
}
|
|
547
560
|
}
|
|
@@ -1074,13 +1087,16 @@ function rejectedReference(call, chain, reason, status = "unresolved") {
|
|
|
1074
1087
|
};
|
|
1075
1088
|
}
|
|
1076
1089
|
function resolvedReference(call, selected, chain, scopeIndex) {
|
|
1090
|
+
const helperReturn = selected.fact.helperChain?.some(
|
|
1091
|
+
(step) => step.bindingOrigin === "single_hop_helper_return"
|
|
1092
|
+
) ?? false;
|
|
1077
1093
|
return {
|
|
1078
1094
|
status: "resolved_exact",
|
|
1079
1095
|
variableName: call.serviceVariableName,
|
|
1080
1096
|
bindingSourceFile: selected.fact.sourceFile,
|
|
1081
1097
|
bindingSiteStartOffset: selected.site.startOffset,
|
|
1082
1098
|
bindingSiteEndOffset: selected.site.endOffset,
|
|
1083
|
-
resolutionStrategy: selected.site.flow === "assignment" ? "deterministic_reaching_assignment" : selected.site.aliasSource ? "lexical_alias_declaration" : "lexical_declaration",
|
|
1099
|
+
resolutionStrategy: selected.site.flow === "assignment" ? "deterministic_reaching_assignment" : selected.site.aliasSource ? "lexical_alias_declaration" : helperReturn ? "single_hop_helper_return" : "lexical_declaration",
|
|
1084
1100
|
lexicalScopeChain: chain,
|
|
1085
1101
|
bindingScopeIndex: scopeIndex,
|
|
1086
1102
|
scopeChainTotal: chain.length,
|
|
@@ -1829,6 +1845,11 @@ function isFunctionLike(node) {
|
|
|
1829
1845
|
function exported(node) {
|
|
1830
1846
|
return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Export);
|
|
1831
1847
|
}
|
|
1848
|
+
function defaultExported(node) {
|
|
1849
|
+
return Boolean(
|
|
1850
|
+
ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Default
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1832
1853
|
function isPublicClassMethod(node) {
|
|
1833
1854
|
const flags = ts8.getCombinedModifierFlags(node);
|
|
1834
1855
|
return (flags & ts8.ModifierFlags.Private) === 0 && (flags & ts8.ModifierFlags.Protected) === 0;
|
|
@@ -1922,6 +1943,7 @@ function symbolSourceEvidence(collection, node, options) {
|
|
|
1922
1943
|
if (options.classContainerExported && ts8.isMethodDeclaration(node) && isPublicClassMethod(node)) return {
|
|
1923
1944
|
source: "exported_class_instance_member",
|
|
1924
1945
|
exportedClass: options.parentRoot,
|
|
1946
|
+
exportedClassExportKind: options.classContainerDefaultExported ? "default" : "named",
|
|
1925
1947
|
memberKind: "class_method"
|
|
1926
1948
|
};
|
|
1927
1949
|
if (options.declaredExportName) return {
|
|
@@ -1945,6 +1967,16 @@ function exportedClassMember(collection, kind, parentName, parentRoot, node) {
|
|
|
1945
1967
|
if (kind !== "method" || !parentName || !collection.exportedClasses.has(parentRoot) || !ts8.isMethodDeclaration(node)) return false;
|
|
1946
1968
|
return Boolean(ts8.getCombinedModifierFlags(node) & ts8.ModifierFlags.Static) && isPublicClassMethod(node);
|
|
1947
1969
|
}
|
|
1970
|
+
function classExportState(collection, parentName, parentRoot) {
|
|
1971
|
+
return {
|
|
1972
|
+
classContainerExported: Boolean(
|
|
1973
|
+
parentName && collection.exportedClasses.has(parentRoot)
|
|
1974
|
+
),
|
|
1975
|
+
classContainerDefaultExported: Boolean(
|
|
1976
|
+
parentName && collection.defaultExportedClasses.has(parentRoot)
|
|
1977
|
+
)
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1948
1980
|
function symbolNames(collection, kind, localName, node, parentName, exportedName) {
|
|
1949
1981
|
const parentRoot = parentName?.split(".")[0] ?? "";
|
|
1950
1982
|
const declaredExportName = exportedName ?? collection.exportNames.get(
|
|
@@ -1961,15 +1993,13 @@ function symbolNames(collection, kind, localName, node, parentName, exportedName
|
|
|
1961
1993
|
parentRoot,
|
|
1962
1994
|
node
|
|
1963
1995
|
);
|
|
1964
|
-
const
|
|
1965
|
-
parentName && collection.exportedClasses.has(parentRoot)
|
|
1966
|
-
);
|
|
1996
|
+
const classState = classExportState(collection, parentName, parentRoot);
|
|
1967
1997
|
return {
|
|
1968
1998
|
parentRoot,
|
|
1969
1999
|
declaredExportName,
|
|
1970
2000
|
qualifiedName,
|
|
1971
2001
|
objectExported,
|
|
1972
|
-
|
|
2002
|
+
...classState,
|
|
1973
2003
|
classMemberExported,
|
|
1974
2004
|
effectiveName: classMemberExported || objectExported ? qualifiedName : declaredExportName
|
|
1975
2005
|
};
|
|
@@ -1988,6 +2018,7 @@ function addExecutableSymbol(collection, kind, localName, node, parentName, expo
|
|
|
1988
2018
|
qualifiedName: names.qualifiedName,
|
|
1989
2019
|
declaredExportName: names.declaredExportName,
|
|
1990
2020
|
classContainerExported: names.classContainerExported,
|
|
2021
|
+
classContainerDefaultExported: names.classContainerDefaultExported,
|
|
1991
2022
|
classMemberExported: names.classMemberExported,
|
|
1992
2023
|
objectExported: names.objectExported,
|
|
1993
2024
|
evidence
|
|
@@ -2135,6 +2166,8 @@ function collectClassDeclaration(collection, node) {
|
|
|
2135
2166
|
collection.declaredClasses.add(node.name.text);
|
|
2136
2167
|
if (exported(node) || collection.exportNames.has(node.name.text))
|
|
2137
2168
|
collection.exportedClasses.add(node.name.text);
|
|
2169
|
+
if (defaultExported(node))
|
|
2170
|
+
collection.defaultExportedClasses.add(node.name.text);
|
|
2138
2171
|
for (const member of node.members)
|
|
2139
2172
|
visitDeclaredSymbol(collection, member, node.name.text);
|
|
2140
2173
|
return true;
|
|
@@ -2231,6 +2264,7 @@ function createCollection(source, sourceFile2, classifiedCalls) {
|
|
|
2231
2264
|
exportNames: exportDeclarations(source),
|
|
2232
2265
|
objectExports: /* @__PURE__ */ new Set(),
|
|
2233
2266
|
exportedClasses: /* @__PURE__ */ new Set(),
|
|
2267
|
+
defaultExportedClasses: /* @__PURE__ */ new Set(),
|
|
2234
2268
|
declaredClasses: /* @__PURE__ */ new Set(),
|
|
2235
2269
|
proxies: /* @__PURE__ */ new Map(),
|
|
2236
2270
|
instances: /* @__PURE__ */ new Map()
|
|
@@ -2451,6 +2485,12 @@ function createPackageInvalidationBatch(publishingRepoIds) {
|
|
|
2451
2485
|
affectedWorkspaceIds: /* @__PURE__ */ new Set()
|
|
2452
2486
|
};
|
|
2453
2487
|
}
|
|
2488
|
+
function mergePackageInvalidationEffects(target, source) {
|
|
2489
|
+
for (const repoId of source.affectedCallerRepoIds)
|
|
2490
|
+
target.affectedCallerRepoIds.add(repoId);
|
|
2491
|
+
for (const workspaceId of source.affectedWorkspaceIds)
|
|
2492
|
+
target.affectedWorkspaceIds.add(workspaceId);
|
|
2493
|
+
}
|
|
2454
2494
|
function finalizePackageTargetInvalidations(db, batch) {
|
|
2455
2495
|
const increment = db.prepare(`UPDATE repositories
|
|
2456
2496
|
SET fact_generation=fact_generation+1 WHERE id=?`);
|
|
@@ -2540,10 +2580,43 @@ function publishPreparedRepositoryIndex(db, prepared, invalidations) {
|
|
|
2540
2580
|
insertCalls(db, repoId, prepared.parsed.calls);
|
|
2541
2581
|
db.prepare("UPDATE repositories SET last_indexed_at=?, index_status='indexed', error_count=0, fingerprint=?, fact_generation=COALESCE(fact_generation,0)+1, graph_stale_reason='facts_changed', graph_stale_at=?, fact_analyzer_version=? WHERE id=?").run(now, prepared.fingerprint, now, ANALYZER_VERSION, repoId);
|
|
2542
2582
|
}
|
|
2583
|
+
function publishOneRepository(db, prepared, invalidations) {
|
|
2584
|
+
try {
|
|
2585
|
+
db.transaction(() => withPublicationSavepoint(
|
|
2586
|
+
db,
|
|
2587
|
+
prepared.repo.id,
|
|
2588
|
+
() => publishPreparedRepositoryIndex(db, prepared, invalidations)
|
|
2589
|
+
));
|
|
2590
|
+
return { ok: true };
|
|
2591
|
+
} catch (error) {
|
|
2592
|
+
recordIndexFailure(db, prepared.repo.id, error);
|
|
2593
|
+
return { ok: false, error };
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
function withPublicationSavepoint(db, repoId, publish) {
|
|
2597
|
+
const name = `service_flow_repository_${repoId}`;
|
|
2598
|
+
db.exec(`SAVEPOINT ${name}`);
|
|
2599
|
+
try {
|
|
2600
|
+
const result = publish();
|
|
2601
|
+
db.exec(`RELEASE SAVEPOINT ${name}`);
|
|
2602
|
+
return result;
|
|
2603
|
+
} catch (error) {
|
|
2604
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${name}`);
|
|
2605
|
+
db.exec(`RELEASE SAVEPOINT ${name}`);
|
|
2606
|
+
throw error;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2543
2609
|
function recordIndexFailure(db, repoId, error) {
|
|
2610
|
+
if (isPreparedRepositorySnapshotError(error)) {
|
|
2611
|
+
recordPreparedSnapshotFailure(db, repoId, error);
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2544
2614
|
const message = errorMessage(error);
|
|
2545
2615
|
db.prepare("UPDATE repositories SET index_status='failed', error_count=1 WHERE id=?").run(repoId);
|
|
2546
|
-
db.prepare(
|
|
2616
|
+
db.prepare(`DELETE FROM diagnostics WHERE repo_id=? AND (
|
|
2617
|
+
code IN ('index_failed_snapshot_preserved','source_read_failed')
|
|
2618
|
+
OR code GLOB 'invalid_prepared_repository_snapshot:*'
|
|
2619
|
+
)`).run(repoId);
|
|
2547
2620
|
db.prepare("INSERT INTO diagnostics(repo_id,severity,code,message) VALUES(?,?,?,?)").run(repoId, "error", "source_read_failed", `Index failed before publication; previous facts and fingerprint were preserved. ${message}`);
|
|
2548
2621
|
}
|
|
2549
2622
|
async function parseAllSourceFacts(root, sources) {
|
|
@@ -2624,9 +2697,8 @@ function repositoryFingerprint(sources, facts, packageJsonText) {
|
|
|
2624
2697
|
}
|
|
2625
2698
|
|
|
2626
2699
|
// src/indexer/cds-extension-resolver.ts
|
|
2627
|
-
function materializeCdsExtensionOperations(db, workspaceId) {
|
|
2628
|
-
const extensions = db
|
|
2629
|
-
FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND s.is_extend=1`).all(workspaceId);
|
|
2700
|
+
function materializeCdsExtensionOperations(db, workspaceId, excludedRepoIds = /* @__PURE__ */ new Set()) {
|
|
2701
|
+
const extensions = extensionRows(db, workspaceId, excludedRepoIds);
|
|
2630
2702
|
db.transaction(() => {
|
|
2631
2703
|
const changedRepos = /* @__PURE__ */ new Set();
|
|
2632
2704
|
for (const extension of extensions) {
|
|
@@ -2635,6 +2707,21 @@ function materializeCdsExtensionOperations(db, workspaceId) {
|
|
|
2635
2707
|
for (const repoId of changedRepos) markRepositoryDerivedFactsChanged(db, repoId);
|
|
2636
2708
|
});
|
|
2637
2709
|
}
|
|
2710
|
+
function extensionRows(db, workspaceId, excludedRepoIds) {
|
|
2711
|
+
const excluded = [...excludedRepoIds].sort((left, right) => left - right);
|
|
2712
|
+
const exclusion = excluded.length > 0 ? ` AND r.id NOT IN (${excluded.map(() => "?").join(",")})` : "";
|
|
2713
|
+
return db.prepare(`SELECT s.id,r.id repoId,s.service_name serviceName,
|
|
2714
|
+
s.qualified_name qualifiedName,s.source_file sourceFile,
|
|
2715
|
+
s.extension_module_specifier moduleSpecifier,
|
|
2716
|
+
s.extension_imported_symbol importedSymbol,
|
|
2717
|
+
s.extension_import_kind importKind
|
|
2718
|
+
FROM cds_services s JOIN repositories r ON r.id=s.repo_id
|
|
2719
|
+
WHERE r.workspace_id=? AND s.is_extend=1${exclusion}
|
|
2720
|
+
ORDER BY r.id,s.id`).all(
|
|
2721
|
+
workspaceId,
|
|
2722
|
+
...excluded
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2638
2725
|
function reconcileExtension(db, workspaceId, extension) {
|
|
2639
2726
|
const bases = resolveBase(db, workspaceId, extension);
|
|
2640
2727
|
const status = bases.length === 1 ? "resolved" : bases.length > 1 ? "ambiguous" : "unresolved";
|
|
@@ -2788,8 +2875,13 @@ async function indexWorkspace(db, workspaceId, options) {
|
|
|
2788
2875
|
};
|
|
2789
2876
|
try {
|
|
2790
2877
|
await prepareRepositories(repos, options.force, state);
|
|
2791
|
-
|
|
2792
|
-
|
|
2878
|
+
return publishPreparedWorkspaceRows(
|
|
2879
|
+
db,
|
|
2880
|
+
workspaceId,
|
|
2881
|
+
runId,
|
|
2882
|
+
state.rows,
|
|
2883
|
+
options
|
|
2884
|
+
);
|
|
2793
2885
|
} catch (error) {
|
|
2794
2886
|
finishFailedRun(db, runId, state, error);
|
|
2795
2887
|
if (state.activeRepoId && state.rows.length < repos.length)
|
|
@@ -2819,31 +2911,90 @@ async function prepareRepositories(repos, force, state) {
|
|
|
2819
2911
|
state.skippedCount += result.skipped ? 1 : 0;
|
|
2820
2912
|
}
|
|
2821
2913
|
}
|
|
2822
|
-
function
|
|
2914
|
+
function publishPreparedWorkspaceRows(db, workspaceId, runId, rows, options = {}) {
|
|
2915
|
+
const state = publicationState(rows);
|
|
2823
2916
|
db.transaction(() => {
|
|
2824
|
-
const
|
|
2825
|
-
|
|
2826
|
-
);
|
|
2917
|
+
const effects = createPackageInvalidationBatch([]);
|
|
2918
|
+
const publishedRepoIds = [];
|
|
2827
2919
|
for (const row of state.rows) {
|
|
2828
2920
|
state.activeRepoId = row.repo.id;
|
|
2829
|
-
|
|
2921
|
+
if (row.skipped) continue;
|
|
2922
|
+
const result = publishPreparedRow(db, row);
|
|
2923
|
+
if (result.status === "failed") {
|
|
2924
|
+
recordPublicationFailure(db, state, row, result.error);
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
state.indexedCount += 1;
|
|
2928
|
+
publishedRepoIds.push(row.repo.id);
|
|
2929
|
+
mergePackageInvalidationEffects(effects, result.effects);
|
|
2830
2930
|
}
|
|
2831
2931
|
if (options.injectDerivedMaterializationFailure)
|
|
2832
2932
|
throw new Error("Injected derived materialization failure");
|
|
2833
|
-
materializeCdsExtensionOperations(
|
|
2933
|
+
materializeCdsExtensionOperations(
|
|
2934
|
+
db,
|
|
2935
|
+
workspaceId,
|
|
2936
|
+
state.failedRepoIds
|
|
2937
|
+
);
|
|
2938
|
+
const invalidations = createPackageInvalidationBatch(publishedRepoIds);
|
|
2939
|
+
mergePackageInvalidationEffects(invalidations, effects);
|
|
2834
2940
|
finalizePackageTargetInvalidations(db, invalidations);
|
|
2835
|
-
|
|
2941
|
+
finishCompletedRun(db, runId, state);
|
|
2942
|
+
});
|
|
2943
|
+
return indexSummary(rows.length, state);
|
|
2944
|
+
}
|
|
2945
|
+
function publicationState(rows) {
|
|
2946
|
+
return {
|
|
2947
|
+
rows,
|
|
2948
|
+
fileCount: rows.reduce((total, row) => total + row.fileCount, 0),
|
|
2949
|
+
diagnosticCount: rows.reduce(
|
|
2950
|
+
(total, row) => total + row.diagnosticCount,
|
|
2951
|
+
0
|
|
2952
|
+
),
|
|
2953
|
+
skippedCount: rows.filter((row) => row.skipped).length,
|
|
2954
|
+
indexedCount: 0,
|
|
2955
|
+
publicationFailureCount: 0,
|
|
2956
|
+
failedRepoIds: /* @__PURE__ */ new Set(),
|
|
2957
|
+
failedRepos: []
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
function publishPreparedRow(db, row) {
|
|
2961
|
+
const effects = createPackageInvalidationBatch([row.repo.id]);
|
|
2962
|
+
const outcome = publishOneRepository(db, row, effects);
|
|
2963
|
+
if (!outcome.ok && !isPreparedRepositorySnapshotError(outcome.error))
|
|
2964
|
+
throw outcome.error;
|
|
2965
|
+
return outcome.ok ? { status: "published", effects } : { status: "failed", error: outcome.error };
|
|
2966
|
+
}
|
|
2967
|
+
function recordPublicationFailure(db, state, row, error) {
|
|
2968
|
+
state.failedRepoIds.add(row.repo.id);
|
|
2969
|
+
state.failedRepos.push({
|
|
2970
|
+
name: row.repo.name,
|
|
2971
|
+
code: isPreparedRepositorySnapshotError(error) ? error.message : "source_read_failed"
|
|
2836
2972
|
});
|
|
2973
|
+
state.publicationFailureCount += 1;
|
|
2837
2974
|
}
|
|
2838
|
-
function
|
|
2839
|
-
|
|
2840
|
-
|
|
2975
|
+
function finishCompletedRun(db, runId, state) {
|
|
2976
|
+
const status = completedRunStatus(
|
|
2977
|
+
state.rows.length,
|
|
2978
|
+
state.publicationFailureCount
|
|
2979
|
+
);
|
|
2980
|
+
const error = status === "success" ? null : `${state.publicationFailureCount} repositories failed index publication.`;
|
|
2981
|
+
db.prepare(`UPDATE index_runs SET finished_at=?,status=?,
|
|
2982
|
+
file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
|
|
2841
2983
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2984
|
+
status,
|
|
2842
2985
|
state.fileCount,
|
|
2843
|
-
state
|
|
2986
|
+
completedDiagnosticCount(state),
|
|
2987
|
+
error,
|
|
2844
2988
|
runId
|
|
2845
2989
|
);
|
|
2846
2990
|
}
|
|
2991
|
+
function completedRunStatus(repoCount, failedCount) {
|
|
2992
|
+
if (failedCount === 0) return "success";
|
|
2993
|
+
return failedCount === repoCount ? "failed" : "partial_failure";
|
|
2994
|
+
}
|
|
2995
|
+
function completedDiagnosticCount(state) {
|
|
2996
|
+
return state.diagnosticCount + state.publicationFailureCount;
|
|
2997
|
+
}
|
|
2847
2998
|
function finishFailedRun(db, runId, state, error) {
|
|
2848
2999
|
db.prepare(`UPDATE index_runs SET finished_at=?,status='failed',
|
|
2849
3000
|
file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
|
|
@@ -2857,10 +3008,12 @@ function finishFailedRun(db, runId, state, error) {
|
|
|
2857
3008
|
function indexSummary(repoCount, state) {
|
|
2858
3009
|
return {
|
|
2859
3010
|
repoCount,
|
|
2860
|
-
indexedCount:
|
|
3011
|
+
indexedCount: state.indexedCount,
|
|
2861
3012
|
skippedCount: state.skippedCount,
|
|
3013
|
+
failedCount: state.publicationFailureCount,
|
|
3014
|
+
failedRepos: [...state.failedRepos].sort((left, right) => binaryCompare(left.name, right.name) || binaryCompare(left.code, right.code)),
|
|
2862
3015
|
fileCount: state.fileCount,
|
|
2863
|
-
diagnosticCount: state
|
|
3016
|
+
diagnosticCount: completedDiagnosticCount(state)
|
|
2864
3017
|
};
|
|
2865
3018
|
}
|
|
2866
3019
|
|
|
@@ -4008,6 +4161,16 @@ async function markCleanClaimFailed(dbPath, runId, error) {
|
|
|
4008
4161
|
}
|
|
4009
4162
|
}
|
|
4010
4163
|
|
|
4164
|
+
// src/cli/001-index-summary.ts
|
|
4165
|
+
function indexCommandOutcome(summary) {
|
|
4166
|
+
const failed = summary.failedCount > 0 ? `, failed ${summary.failedCount} (${summary.failedRepos.map(({ name, code }) => `${name}: ${code}`).join(", ")})` : "";
|
|
4167
|
+
return {
|
|
4168
|
+
stdout: `Indexed ${summary.indexedCount} repositories, skipped ${summary.skippedCount}${failed}, ${summary.fileCount} files, ${summary.diagnosticCount} diagnostics
|
|
4169
|
+
`,
|
|
4170
|
+
exitCode: summary.failedCount > 0 ? 1 : 0
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4011
4174
|
// src/cli.ts
|
|
4012
4175
|
var stdout = createStdoutWriter(process.stdout, fail);
|
|
4013
4176
|
var TRACE_FORMATS = ["table", "json", "mermaid", "compact-json"];
|
|
@@ -4155,26 +4318,30 @@ function runGraphCommand(opts) {
|
|
|
4155
4318
|
writeTraceOutput(db, start, options, opts.format);
|
|
4156
4319
|
});
|
|
4157
4320
|
}
|
|
4158
|
-
function
|
|
4159
|
-
|
|
4160
|
-
program.name("service-flow").description(
|
|
4321
|
+
function configuredProgram() {
|
|
4322
|
+
return new Command().name("service-flow").description(
|
|
4161
4323
|
"Trace SAP CAP service-to-service flows across multi-repository workspaces"
|
|
4162
4324
|
).version(VERSION);
|
|
4325
|
+
}
|
|
4326
|
+
function registerInitCommand(program) {
|
|
4163
4327
|
program.command("init").argument("<workspace>").option("--db <path>").option("--ignore <pattern...>").action(
|
|
4164
4328
|
(workspace, opts) => void init(workspace, opts).catch(fail)
|
|
4165
4329
|
);
|
|
4330
|
+
}
|
|
4331
|
+
function registerIndexCommand(program) {
|
|
4166
4332
|
program.command("index").option("--workspace <path>").option("--repo <name>").option("--force").action(
|
|
4167
4333
|
(opts) => void withWorkspace(opts.workspace, async (db, workspaceId) => {
|
|
4168
4334
|
const r = await indexWorkspace(db, workspaceId, {
|
|
4169
4335
|
repo: opts.repo,
|
|
4170
4336
|
force: Boolean(opts.force)
|
|
4171
4337
|
});
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
);
|
|
4338
|
+
const outcome = indexCommandOutcome(r);
|
|
4339
|
+
writeStdout(outcome.stdout);
|
|
4340
|
+
if (outcome.exitCode !== 0) process.exitCode = outcome.exitCode;
|
|
4176
4341
|
}).catch(fail)
|
|
4177
4342
|
);
|
|
4343
|
+
}
|
|
4344
|
+
function registerLinkCommand(program) {
|
|
4178
4345
|
program.command("link").option("--workspace <path>").option("--force").action(
|
|
4179
4346
|
(opts) => void withWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4180
4347
|
const r = linkWorkspace(db, workspaceId);
|
|
@@ -4186,90 +4353,121 @@ function createProgram() {
|
|
|
4186
4353
|
);
|
|
4187
4354
|
}).catch(fail)
|
|
4188
4355
|
);
|
|
4356
|
+
}
|
|
4357
|
+
function registerTraceCommand(program) {
|
|
4189
4358
|
program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").addOption(traceFormatOption()).option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).option("--dynamic-mode <mode>", "strict|candidates|infer", "strict").option("--max-dynamic-candidates <n>", "maximum dynamic candidates to show", "5").action((opts) => void runTraceCommand(opts).catch(fail));
|
|
4359
|
+
}
|
|
4360
|
+
function listRepositoriesCommand(opts) {
|
|
4361
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => writeStdout(
|
|
4362
|
+
renderJson(
|
|
4363
|
+
listRepositories(db, workspaceId).map((repo) => ({
|
|
4364
|
+
name: repo.name,
|
|
4365
|
+
kind: repo.kind,
|
|
4366
|
+
packageName: repo.package_name
|
|
4367
|
+
}))
|
|
4368
|
+
)
|
|
4369
|
+
));
|
|
4370
|
+
}
|
|
4371
|
+
function listServicesCommand(opts) {
|
|
4372
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4373
|
+
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4374
|
+
if (selection.diagnostic) {
|
|
4375
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
const repo = selection.repo;
|
|
4379
|
+
const rows = db.prepare(
|
|
4380
|
+
"SELECT r.name repo,s.service_path servicePath,s.qualified_name qualifiedName FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) ORDER BY r.name,s.service_path"
|
|
4381
|
+
).all(workspaceId, repo?.id, repo?.id);
|
|
4382
|
+
writeStdout(renderJson(rows));
|
|
4383
|
+
});
|
|
4384
|
+
}
|
|
4385
|
+
function listOperationsCommand(opts) {
|
|
4386
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4387
|
+
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4388
|
+
if (selection.diagnostic) {
|
|
4389
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
const repo = selection.repo;
|
|
4393
|
+
const rows = db.prepare(
|
|
4394
|
+
"SELECT r.name repo,s.service_path servicePath,o.operation_name operation,o.operation_path path FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) AND (? IS NULL OR s.service_path=?)"
|
|
4395
|
+
).all(
|
|
4396
|
+
workspaceId,
|
|
4397
|
+
repo?.id,
|
|
4398
|
+
repo?.id,
|
|
4399
|
+
opts.service,
|
|
4400
|
+
opts.service
|
|
4401
|
+
);
|
|
4402
|
+
writeStdout(renderJson(rows));
|
|
4403
|
+
});
|
|
4404
|
+
}
|
|
4405
|
+
function listCallsCommand(opts) {
|
|
4406
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4407
|
+
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4408
|
+
if (selection.diagnostic) {
|
|
4409
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
4410
|
+
return;
|
|
4411
|
+
}
|
|
4412
|
+
const repo = selection.repo;
|
|
4413
|
+
const rows = db.prepare(
|
|
4414
|
+
"SELECT r.name repo,c.call_type type,c.operation_path_expr path,c.source_file file,c.source_line line FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?) AND (? IS NULL OR c.operation_path_expr=? OR c.operation_path_expr=? OR c.payload_summary LIKE ?)"
|
|
4415
|
+
).all(
|
|
4416
|
+
workspaceId,
|
|
4417
|
+
repo?.id,
|
|
4418
|
+
repo?.id,
|
|
4419
|
+
opts.operation,
|
|
4420
|
+
opts.operation,
|
|
4421
|
+
opts.operation ? `/${opts.operation}` : void 0,
|
|
4422
|
+
opts.operation ? `%${opts.operation}%` : void 0
|
|
4423
|
+
);
|
|
4424
|
+
writeStdout(renderJson(rows));
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
function registerListCommands(program) {
|
|
4190
4428
|
const list = program.command("list");
|
|
4191
4429
|
list.command("repos").option("--workspace <path>").action(
|
|
4192
|
-
(opts) => void
|
|
4193
|
-
opts.workspace,
|
|
4194
|
-
(db, workspaceId) => writeStdout(
|
|
4195
|
-
renderJson(
|
|
4196
|
-
listRepositories(db, workspaceId).map((r) => ({
|
|
4197
|
-
name: r.name,
|
|
4198
|
-
kind: r.kind,
|
|
4199
|
-
packageName: r.package_name
|
|
4200
|
-
}))
|
|
4201
|
-
)
|
|
4202
|
-
)
|
|
4203
|
-
).catch(fail)
|
|
4430
|
+
(opts) => void listRepositoriesCommand(opts).catch(fail)
|
|
4204
4431
|
);
|
|
4205
4432
|
list.command("services").option("--workspace <path>").option("--repo <name>").action(
|
|
4206
|
-
(opts) => void
|
|
4207
|
-
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4208
|
-
if (selection.diagnostic) {
|
|
4209
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
4210
|
-
return;
|
|
4211
|
-
}
|
|
4212
|
-
const repo = selection.repo;
|
|
4213
|
-
const rows = db.prepare(
|
|
4214
|
-
"SELECT r.name repo,s.service_path servicePath,s.qualified_name qualifiedName FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) ORDER BY r.name,s.service_path"
|
|
4215
|
-
).all(workspaceId, repo?.id, repo?.id);
|
|
4216
|
-
writeStdout(renderJson(rows));
|
|
4217
|
-
}).catch(fail)
|
|
4433
|
+
(opts) => void listServicesCommand(opts).catch(fail)
|
|
4218
4434
|
);
|
|
4219
4435
|
list.command("operations").option("--workspace <path>").option("--repo <name>").option("--service <path>").action(
|
|
4220
|
-
(opts) => void
|
|
4221
|
-
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4222
|
-
if (selection.diagnostic) {
|
|
4223
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
4224
|
-
return;
|
|
4225
|
-
}
|
|
4226
|
-
const repo = selection.repo;
|
|
4227
|
-
const rows = db.prepare(
|
|
4228
|
-
"SELECT r.name repo,s.service_path servicePath,o.operation_name operation,o.operation_path path FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) AND (? IS NULL OR s.service_path=?)"
|
|
4229
|
-
).all(workspaceId, repo?.id, repo?.id, opts.service, opts.service);
|
|
4230
|
-
writeStdout(renderJson(rows));
|
|
4231
|
-
}).catch(fail)
|
|
4436
|
+
(opts) => void listOperationsCommand(opts).catch(fail)
|
|
4232
4437
|
);
|
|
4233
4438
|
list.command("calls").option("--workspace <path>").option("--repo <name>").option("--operation <name>").action(
|
|
4234
|
-
(opts) => void
|
|
4235
|
-
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
4236
|
-
if (selection.diagnostic) {
|
|
4237
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
4238
|
-
return;
|
|
4239
|
-
}
|
|
4240
|
-
const repo = selection.repo;
|
|
4241
|
-
const rows = db.prepare(
|
|
4242
|
-
"SELECT r.name repo,c.call_type type,c.operation_path_expr path,c.source_file file,c.source_line line FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?) AND (? IS NULL OR c.operation_path_expr=? OR c.operation_path_expr=? OR c.payload_summary LIKE ?)"
|
|
4243
|
-
).all(
|
|
4244
|
-
workspaceId,
|
|
4245
|
-
repo?.id,
|
|
4246
|
-
repo?.id,
|
|
4247
|
-
opts.operation,
|
|
4248
|
-
opts.operation,
|
|
4249
|
-
opts.operation ? `/${opts.operation}` : void 0,
|
|
4250
|
-
opts.operation ? `%${opts.operation}%` : void 0
|
|
4251
|
-
);
|
|
4252
|
-
writeStdout(renderJson(rows));
|
|
4253
|
-
}).catch(fail)
|
|
4439
|
+
(opts) => void listCallsCommand(opts).catch(fail)
|
|
4254
4440
|
);
|
|
4441
|
+
}
|
|
4442
|
+
function registerGraphCommand(program) {
|
|
4255
4443
|
program.command("graph").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").addOption(graphFormatOption()).option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).option("--dynamic-mode <mode>", "strict|candidates|infer", "strict").option("--max-dynamic-candidates <n>", "maximum dynamic candidates to show", "5").action((opts) => void runGraphCommand(opts).catch(fail));
|
|
4444
|
+
}
|
|
4445
|
+
function inspectRepositoryCommand(name, opts) {
|
|
4446
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4447
|
+
const selection = selectRepository(db, name, workspaceId);
|
|
4448
|
+
writeStdout(renderJson(
|
|
4449
|
+
selection.repo ?? selection.diagnostic ?? { error: "repo not found" }
|
|
4450
|
+
));
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
function inspectOperationCommand(selector, opts) {
|
|
4454
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4455
|
+
const rows = db.prepare(
|
|
4456
|
+
"SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)"
|
|
4457
|
+
).all(workspaceId, selector, selector);
|
|
4458
|
+
writeStdout(renderJson(rows));
|
|
4459
|
+
});
|
|
4460
|
+
}
|
|
4461
|
+
function registerInspectCommands(program) {
|
|
4256
4462
|
const inspect = program.command("inspect");
|
|
4257
4463
|
inspect.command("repo").argument("<name>").option("--workspace <path>").action(
|
|
4258
|
-
(name, opts) => void
|
|
4259
|
-
const selection = selectRepository(db, name, workspaceId);
|
|
4260
|
-
writeStdout(renderJson(
|
|
4261
|
-
selection.repo ?? selection.diagnostic ?? { error: "repo not found" }
|
|
4262
|
-
));
|
|
4263
|
-
}).catch(fail)
|
|
4464
|
+
(name, opts) => void inspectRepositoryCommand(name, opts).catch(fail)
|
|
4264
4465
|
);
|
|
4265
4466
|
inspect.command("operation").argument("<selector>").option("--workspace <path>").action(
|
|
4266
|
-
(selector, opts) => void
|
|
4267
|
-
const rows = db.prepare(
|
|
4268
|
-
"SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)"
|
|
4269
|
-
).all(workspaceId, selector, selector);
|
|
4270
|
-
writeStdout(renderJson(rows));
|
|
4271
|
-
}).catch(fail)
|
|
4467
|
+
(selector, opts) => void inspectOperationCommand(selector, opts).catch(fail)
|
|
4272
4468
|
);
|
|
4469
|
+
}
|
|
4470
|
+
function registerDoctorCommand(program) {
|
|
4273
4471
|
program.command("doctor").option("--workspace <path>").option("--strict").option("--detail").option("--format <format>", "json|table").action(
|
|
4274
4472
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
4275
4473
|
const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), {
|
|
@@ -4279,6 +4477,8 @@ function createProgram() {
|
|
|
4279
4477
|
writeStdout(renderDoctorDiagnostics(allDiagnostics, opts.format));
|
|
4280
4478
|
}).catch(fail)
|
|
4281
4479
|
);
|
|
4480
|
+
}
|
|
4481
|
+
function registerCleanCommand(program) {
|
|
4282
4482
|
program.command("clean").option("--workspace <path>").option("--db-only").action(
|
|
4283
4483
|
(opts) => void (async () => {
|
|
4284
4484
|
const config = await loadWorkspaceConfig(opts.workspace);
|
|
@@ -4286,6 +4486,18 @@ function createProgram() {
|
|
|
4286
4486
|
writeStdout("Cleaned service-flow state\n");
|
|
4287
4487
|
})().catch(fail)
|
|
4288
4488
|
);
|
|
4489
|
+
}
|
|
4490
|
+
function createProgram() {
|
|
4491
|
+
const program = configuredProgram();
|
|
4492
|
+
registerInitCommand(program);
|
|
4493
|
+
registerIndexCommand(program);
|
|
4494
|
+
registerLinkCommand(program);
|
|
4495
|
+
registerTraceCommand(program);
|
|
4496
|
+
registerListCommands(program);
|
|
4497
|
+
registerGraphCommand(program);
|
|
4498
|
+
registerInspectCommands(program);
|
|
4499
|
+
registerDoctorCommand(program);
|
|
4500
|
+
registerCleanCommand(program);
|
|
4289
4501
|
return program;
|
|
4290
4502
|
}
|
|
4291
4503
|
function collect(value, previous) {
|