@beignet/cli 0.0.48 → 0.0.49
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 +34 -0
- package/README.md +76 -21
- package/dist/analysis/contract-factories.d.ts +17 -0
- package/dist/analysis/contract-factories.d.ts.map +1 -0
- package/dist/analysis/contract-factories.js +176 -0
- package/dist/analysis/contract-factories.js.map +1 -0
- package/dist/analysis/layers.d.ts +5 -0
- package/dist/analysis/layers.d.ts.map +1 -0
- package/dist/analysis/layers.js +172 -0
- package/dist/analysis/layers.js.map +1 -0
- package/dist/analysis/port-wiring.d.ts +23 -0
- package/dist/analysis/port-wiring.d.ts.map +1 -0
- package/dist/analysis/port-wiring.js +379 -0
- package/dist/analysis/port-wiring.js.map +1 -0
- package/dist/analysis/source-index.d.ts +5 -0
- package/dist/analysis/source-index.d.ts.map +1 -1
- package/dist/analysis/source-index.js +171 -42
- package/dist/analysis/source-index.js.map +1 -1
- package/dist/analysis/workspace.d.ts +10 -4
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +66 -6
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +311 -92
- package/dist/app-map.js.map +1 -1
- package/dist/check.js +37 -3
- package/dist/check.js.map +1 -1
- package/dist/explain.js +4 -4
- package/dist/explain.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +415 -168
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +309 -487
- package/dist/lint.js.map +1 -1
- package/dist/make/inbox.d.ts.map +1 -1
- package/dist/make/inbox.js +11 -4
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.js +2 -2
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +144 -8
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +19 -19
- package/dist/mcp.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +142 -7
- package/dist/provider-add.js.map +1 -1
- package/dist/registry-edits.d.ts +7 -0
- package/dist/registry-edits.d.ts.map +1 -1
- package/dist/registry-edits.js +237 -53
- package/dist/registry-edits.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +16 -0
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +9 -1
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +2 -0
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +1 -0
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +3 -2
- package/dist/templates/shared.js.map +1 -1
- package/package.json +7 -6
- package/skills/app-structure/SKILL.md +46 -5
- package/src/analysis/contract-factories.ts +256 -0
- package/src/analysis/layers.ts +266 -0
- package/src/analysis/port-wiring.ts +506 -0
- package/src/analysis/source-index.ts +225 -57
- package/src/analysis/workspace.ts +115 -6
- package/src/app-map.ts +418 -109
- package/src/check.ts +51 -3
- package/src/explain.ts +4 -4
- package/src/inspect.ts +583 -215
- package/src/lint.ts +393 -643
- package/src/make/inbox.ts +11 -4
- package/src/make/payments.ts +2 -2
- package/src/make.ts +225 -8
- package/src/mcp.ts +19 -22
- package/src/provider-add.ts +186 -7
- package/src/registry-edits.ts +313 -63
- package/src/templates/agents.ts +16 -0
- package/src/templates/base.ts +9 -1
- package/src/templates/server.ts +2 -0
- package/src/templates/shared.ts +3 -2
package/src/app-map.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import ts from "typescript";
|
|
3
|
+
import {
|
|
4
|
+
contractBindingNames,
|
|
5
|
+
contractFactoryBindings,
|
|
6
|
+
isImportedContractDeclaration,
|
|
7
|
+
} from "./analysis/contract-factories.js";
|
|
8
|
+
import { analyzePortWiring } from "./analysis/port-wiring.js";
|
|
3
9
|
import {
|
|
4
10
|
createSourceIndex,
|
|
5
11
|
exportedVariableDeclarations,
|
|
@@ -175,6 +181,12 @@ type RegistryRecord = {
|
|
|
175
181
|
file: string;
|
|
176
182
|
declaration: ts.VariableDeclaration;
|
|
177
183
|
values: ts.Expression[];
|
|
184
|
+
outbox?: {
|
|
185
|
+
events: ts.Expression[];
|
|
186
|
+
jobs: ts.Expression[];
|
|
187
|
+
eventsIndeterminate: boolean;
|
|
188
|
+
jobsIndeterminate: boolean;
|
|
189
|
+
};
|
|
178
190
|
};
|
|
179
191
|
|
|
180
192
|
/** Build a deterministic, side-effect-free map of one Beignet app. */
|
|
@@ -474,6 +486,20 @@ export async function mapApp(
|
|
|
474
486
|
if (registry) {
|
|
475
487
|
addNode(registry.node);
|
|
476
488
|
registries.push(registry);
|
|
489
|
+
if (registry.outbox?.eventsIndeterminate) {
|
|
490
|
+
unresolved.push({
|
|
491
|
+
code: "outbox_events_unresolved",
|
|
492
|
+
message: `Could not resolve the events entries for outbox registry ${exportName}.`,
|
|
493
|
+
source: registry.node.source,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
if (registry.outbox?.jobsIndeterminate) {
|
|
497
|
+
unresolved.push({
|
|
498
|
+
code: "outbox_jobs_unresolved",
|
|
499
|
+
message: `Could not resolve the jobs entries for outbox registry ${exportName}.`,
|
|
500
|
+
source: registry.node.source,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
477
503
|
}
|
|
478
504
|
}
|
|
479
505
|
}
|
|
@@ -651,6 +677,18 @@ export async function mapApp(
|
|
|
651
677
|
sourceIndex,
|
|
652
678
|
addNode,
|
|
653
679
|
addEdge,
|
|
680
|
+
unresolved,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
await addOutboxDeliveryGraph({
|
|
684
|
+
workspace,
|
|
685
|
+
sourceIndex,
|
|
686
|
+
registries,
|
|
687
|
+
nodes,
|
|
688
|
+
edges,
|
|
689
|
+
addNode,
|
|
690
|
+
addEdge,
|
|
691
|
+
unresolved,
|
|
654
692
|
});
|
|
655
693
|
|
|
656
694
|
await addTables({ workspace, sourceIndex, addNode });
|
|
@@ -1021,54 +1059,62 @@ async function addPorts(args: {
|
|
|
1021
1059
|
sourceIndex: ReturnType<typeof createSourceIndex>;
|
|
1022
1060
|
addNode(node: AppMapNode): void;
|
|
1023
1061
|
addEdge(edge: AppMapEdge): void;
|
|
1062
|
+
unresolved: AppMapUnresolvedReference[];
|
|
1024
1063
|
}): Promise<void> {
|
|
1025
1064
|
const portsFile = args.workspace.config.paths.ports;
|
|
1026
1065
|
if (!args.workspace.fileSet.has(portsFile)) return;
|
|
1027
1066
|
const sourceFile = await args.workspace.readSourceFile(portsFile);
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1067
|
+
const analysis = await analyzePortWiring(args.workspace);
|
|
1068
|
+
if (analysis.declarationIndeterminate) {
|
|
1069
|
+
args.unresolved.push({
|
|
1070
|
+
code: "app_ports_declaration_unresolved",
|
|
1071
|
+
message: `Could not fully resolve AppPorts in ${portsFile}.`,
|
|
1072
|
+
source: { file: portsFile },
|
|
1073
|
+
});
|
|
1033
1074
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1075
|
+
if (analysis.indeterminate) {
|
|
1076
|
+
args.unresolved.push({
|
|
1077
|
+
code: "port_wiring_unresolved",
|
|
1078
|
+
message: `Could not fully resolve port bindings in ${args.workspace.config.paths.portWiring}.`,
|
|
1079
|
+
source: { file: args.workspace.config.paths.portWiring },
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
for (const name of analysis.declared) {
|
|
1083
|
+
const property = analysis.properties.get(name);
|
|
1084
|
+
const feature = property
|
|
1085
|
+
? await portFeature(
|
|
1086
|
+
property.type,
|
|
1087
|
+
portsFile,
|
|
1088
|
+
args.workspace.config.paths.features,
|
|
1089
|
+
args.sourceIndex,
|
|
1090
|
+
)
|
|
1091
|
+
: undefined;
|
|
1048
1092
|
const id = portId(name);
|
|
1049
1093
|
args.addNode({
|
|
1050
1094
|
id,
|
|
1051
1095
|
kind: "port",
|
|
1052
1096
|
name,
|
|
1053
1097
|
...(feature ? { feature } : {}),
|
|
1054
|
-
source:
|
|
1098
|
+
source: property
|
|
1099
|
+
? await args.sourceIndex.sourceLocation(portsFile, property)
|
|
1100
|
+
: { file: args.workspace.config.paths.portWiring },
|
|
1055
1101
|
status:
|
|
1056
|
-
|
|
1102
|
+
analysis.bound.has(name) || analysis.deferred.has(name)
|
|
1057
1103
|
? "registered"
|
|
1058
1104
|
: "missing",
|
|
1059
1105
|
details: {
|
|
1060
|
-
binding:
|
|
1106
|
+
binding: analysis.bound.has(name)
|
|
1061
1107
|
? "bound"
|
|
1062
|
-
:
|
|
1108
|
+
: analysis.deferred.has(name)
|
|
1063
1109
|
? "deferred"
|
|
1064
1110
|
: "unresolved",
|
|
1065
|
-
type: property.type
|
|
1111
|
+
...(property?.type ? { type: property.type.getText(sourceFile) } : {}),
|
|
1066
1112
|
},
|
|
1067
1113
|
});
|
|
1068
1114
|
if (feature) {
|
|
1069
1115
|
args.addEdge(exactEdge("owns", featureId(feature), id, portsFile));
|
|
1070
1116
|
}
|
|
1071
|
-
if (
|
|
1117
|
+
if (analysis.bound.has(name)) {
|
|
1072
1118
|
const bindingId = `provider:app:${name}`;
|
|
1073
1119
|
args.addNode({
|
|
1074
1120
|
id: bindingId,
|
|
@@ -1090,6 +1136,182 @@ async function addPorts(args: {
|
|
|
1090
1136
|
}
|
|
1091
1137
|
}
|
|
1092
1138
|
|
|
1139
|
+
async function addOutboxDeliveryGraph(args: {
|
|
1140
|
+
workspace: AnalysisWorkspace;
|
|
1141
|
+
sourceIndex: ReturnType<typeof createSourceIndex>;
|
|
1142
|
+
registries: RegistryRecord[];
|
|
1143
|
+
nodes: Map<string, AppMapNode>;
|
|
1144
|
+
edges: AppMapEdge[];
|
|
1145
|
+
addNode(node: AppMapNode): void;
|
|
1146
|
+
addEdge(edge: AppMapEdge): void;
|
|
1147
|
+
unresolved: AppMapUnresolvedReference[];
|
|
1148
|
+
}): Promise<void> {
|
|
1149
|
+
const outboxRegistries = args.registries.filter(
|
|
1150
|
+
(registry) => registry.outbox !== undefined,
|
|
1151
|
+
);
|
|
1152
|
+
if (outboxRegistries.length === 0) return;
|
|
1153
|
+
const jobRegistries = outboxRegistries.filter(
|
|
1154
|
+
(registry) => registry.outbox && registry.outbox.jobs.length > 0,
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
const jobsPortId = portId("jobs");
|
|
1158
|
+
if (jobRegistries.length > 0 && !args.nodes.has(jobsPortId)) {
|
|
1159
|
+
args.addNode({
|
|
1160
|
+
id: jobsPortId,
|
|
1161
|
+
kind: "port",
|
|
1162
|
+
name: "jobs",
|
|
1163
|
+
source: { file: args.workspace.config.paths.ports },
|
|
1164
|
+
status: "missing",
|
|
1165
|
+
details: {
|
|
1166
|
+
binding: "unresolved",
|
|
1167
|
+
requiredBy: "outbox-jobs",
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
for (const registry of jobRegistries) {
|
|
1173
|
+
args.addEdge(
|
|
1174
|
+
exactEdge("depends-on", registry.node.id, jobsPortId, registry.file),
|
|
1175
|
+
);
|
|
1176
|
+
for (const jobId of registeredJobIds(
|
|
1177
|
+
registry.node.id,
|
|
1178
|
+
args.nodes,
|
|
1179
|
+
args.edges,
|
|
1180
|
+
)) {
|
|
1181
|
+
args.addEdge(exactEdge("depends-on", jobId, jobsPortId, registry.file));
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const runtimeDirectories = new Set([
|
|
1186
|
+
directoryPath(args.workspace.config.paths.routes),
|
|
1187
|
+
directoryPath(args.workspace.config.paths.server),
|
|
1188
|
+
directoryPath(args.workspace.config.paths.outbox),
|
|
1189
|
+
directoryPath(args.workspace.config.paths.portWiring),
|
|
1190
|
+
"server",
|
|
1191
|
+
"infra",
|
|
1192
|
+
]);
|
|
1193
|
+
for (const file of args.workspace.files) {
|
|
1194
|
+
if (
|
|
1195
|
+
!isTypeScriptSource(file) ||
|
|
1196
|
+
isTestFile(file) ||
|
|
1197
|
+
![...runtimeDirectories].some(
|
|
1198
|
+
(directory) =>
|
|
1199
|
+
directory === "." ||
|
|
1200
|
+
file === directory ||
|
|
1201
|
+
file.startsWith(`${directory}/`),
|
|
1202
|
+
)
|
|
1203
|
+
) {
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
const sourceFile = await args.workspace.readSourceFile(file);
|
|
1207
|
+
const providerLifecycle = isProviderLifecycleSource(file, sourceFile);
|
|
1208
|
+
const calls: ts.CallExpression[] = [];
|
|
1209
|
+
visitCalls(sourceFile, (call) => {
|
|
1210
|
+
const name = callMemberName(call);
|
|
1211
|
+
if (
|
|
1212
|
+
name === "createOutboxDrainRoute" ||
|
|
1213
|
+
(name === "drainOutbox" && !providerLifecycle)
|
|
1214
|
+
) {
|
|
1215
|
+
calls.push(call);
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
for (const [index, call] of calls.entries()) {
|
|
1219
|
+
const id = `entrypoint:${file}#outbox-drain${index === 0 ? "" : `-${index + 1}`}`;
|
|
1220
|
+
args.addNode({
|
|
1221
|
+
id,
|
|
1222
|
+
kind: "entrypoint",
|
|
1223
|
+
name: `${file} outbox drain`,
|
|
1224
|
+
source: await args.sourceIndex.sourceLocation(file, call),
|
|
1225
|
+
status: "registered",
|
|
1226
|
+
details: { type: "outbox-drain" },
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
const options = call.arguments[0];
|
|
1230
|
+
const registryExpression =
|
|
1231
|
+
options && ts.isObjectLiteralExpression(unwrapExpression(options))
|
|
1232
|
+
? objectPropertyExpressionAnalysis(
|
|
1233
|
+
unwrapExpression(options) as ts.ObjectLiteralExpression,
|
|
1234
|
+
"registry",
|
|
1235
|
+
).value
|
|
1236
|
+
: undefined;
|
|
1237
|
+
const reference = registryExpression
|
|
1238
|
+
? await args.sourceIndex.resolveExpression(file, registryExpression)
|
|
1239
|
+
: undefined;
|
|
1240
|
+
const resolvedRegistry = reference
|
|
1241
|
+
? outboxRegistries.find(
|
|
1242
|
+
(registry) =>
|
|
1243
|
+
registry.file === reference.file &&
|
|
1244
|
+
registry.node.source.exportName === reference.exportName,
|
|
1245
|
+
)
|
|
1246
|
+
: outboxRegistries.length === 1
|
|
1247
|
+
? outboxRegistries[0]
|
|
1248
|
+
: undefined;
|
|
1249
|
+
if (resolvedRegistry) {
|
|
1250
|
+
args.addEdge(
|
|
1251
|
+
sourceNodeEdge(
|
|
1252
|
+
"depends-on",
|
|
1253
|
+
id,
|
|
1254
|
+
resolvedRegistry.node.id,
|
|
1255
|
+
file,
|
|
1256
|
+
registryExpression ?? call,
|
|
1257
|
+
),
|
|
1258
|
+
);
|
|
1259
|
+
if (resolvedRegistry.outbox?.jobs.length) {
|
|
1260
|
+
args.addEdge(
|
|
1261
|
+
sourceNodeEdge("depends-on", id, jobsPortId, file, call),
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
} else {
|
|
1265
|
+
args.unresolved.push({
|
|
1266
|
+
code: "outbox_drain_registry_unresolved",
|
|
1267
|
+
message: `Could not resolve the outbox registry used by a drain in ${file}.`,
|
|
1268
|
+
source: await args.sourceIndex.sourceLocation(file, call),
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function isProviderLifecycleSource(
|
|
1276
|
+
file: string,
|
|
1277
|
+
sourceFile: ts.SourceFile,
|
|
1278
|
+
): boolean {
|
|
1279
|
+
if (
|
|
1280
|
+
/(^|\/)provider(s)?\.ts$/.test(file) ||
|
|
1281
|
+
/(^|\/)[^/]*provider[^/]*\.ts$/.test(file) ||
|
|
1282
|
+
/(^|\/)providers?\//.test(file)
|
|
1283
|
+
) {
|
|
1284
|
+
return true;
|
|
1285
|
+
}
|
|
1286
|
+
let providerCall = false;
|
|
1287
|
+
visitCalls(sourceFile, (call) => {
|
|
1288
|
+
if (callMemberName(call) === "createProvider") providerCall = true;
|
|
1289
|
+
});
|
|
1290
|
+
return providerCall;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
function registeredJobIds(
|
|
1294
|
+
registryId: string,
|
|
1295
|
+
nodes: Map<string, AppMapNode>,
|
|
1296
|
+
edges: AppMapEdge[],
|
|
1297
|
+
): string[] {
|
|
1298
|
+
const visited = new Set<string>([registryId]);
|
|
1299
|
+
let frontier = [registryId];
|
|
1300
|
+
while (frontier.length > 0) {
|
|
1301
|
+
const next: string[] = [];
|
|
1302
|
+
for (const from of frontier) {
|
|
1303
|
+
for (const edge of edges) {
|
|
1304
|
+
if (edge.kind !== "registers" || edge.from !== from) continue;
|
|
1305
|
+
if (visited.has(edge.to)) continue;
|
|
1306
|
+
visited.add(edge.to);
|
|
1307
|
+
next.push(edge.to);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
frontier = next;
|
|
1311
|
+
}
|
|
1312
|
+
return [...visited].filter((id) => nodes.get(id)?.kind === "job").sort();
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1093
1315
|
async function addOpenApiSurface(args: {
|
|
1094
1316
|
workspace: AnalysisWorkspace;
|
|
1095
1317
|
inspection: InspectAppResult;
|
|
@@ -1298,14 +1520,22 @@ async function addUnresolvedContractDeclarations(args: {
|
|
|
1298
1520
|
}): Promise<void> {
|
|
1299
1521
|
for (const file of appMapContractSourceFiles(args.workspace)) {
|
|
1300
1522
|
const sourceFile = await args.workspace.readSourceFile(file);
|
|
1523
|
+
const factoryBindings = contractFactoryBindings(sourceFile);
|
|
1524
|
+
const bindingNames = contractBindingNames(sourceFile, factoryBindings);
|
|
1301
1525
|
for (const { exportName, declaration } of exportedVariableDeclarations(
|
|
1302
1526
|
sourceFile,
|
|
1303
1527
|
)) {
|
|
1304
1528
|
if (args.contractIds.has(symbolKey(file, exportName))) continue;
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1529
|
+
if (
|
|
1530
|
+
!declaration.initializer ||
|
|
1531
|
+
!isImportedContractDeclaration(
|
|
1532
|
+
declaration.initializer,
|
|
1533
|
+
factoryBindings,
|
|
1534
|
+
bindingNames,
|
|
1535
|
+
)
|
|
1536
|
+
) {
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1309
1539
|
|
|
1310
1540
|
args.unresolved.push({
|
|
1311
1541
|
code: "contract_declaration_unresolved",
|
|
@@ -1348,8 +1578,8 @@ async function registryRecord(args: {
|
|
|
1348
1578
|
}): Promise<RegistryRecord | undefined> {
|
|
1349
1579
|
const initializer = args.declaration.initializer;
|
|
1350
1580
|
if (!initializer) return undefined;
|
|
1351
|
-
const
|
|
1352
|
-
if (!
|
|
1581
|
+
const registry = registryValues(initializer, args.exportName);
|
|
1582
|
+
if (!registry) return undefined;
|
|
1353
1583
|
const feature = featureForFile(
|
|
1354
1584
|
args.file,
|
|
1355
1585
|
directoryPath(args.workspace.config.paths.features),
|
|
@@ -1357,7 +1587,8 @@ async function registryRecord(args: {
|
|
|
1357
1587
|
return {
|
|
1358
1588
|
file: args.file,
|
|
1359
1589
|
declaration: args.declaration,
|
|
1360
|
-
values,
|
|
1590
|
+
values: registry.values,
|
|
1591
|
+
...(registry.outbox ? { outbox: registry.outbox } : {}),
|
|
1361
1592
|
node: {
|
|
1362
1593
|
id: registryId(args.file, args.exportName),
|
|
1363
1594
|
kind: "registry",
|
|
@@ -1369,6 +1600,13 @@ async function registryRecord(args: {
|
|
|
1369
1600
|
args.exportName,
|
|
1370
1601
|
),
|
|
1371
1602
|
status: "registered",
|
|
1603
|
+
...(registry.outbox
|
|
1604
|
+
? {
|
|
1605
|
+
details: {
|
|
1606
|
+
registryType: "outbox",
|
|
1607
|
+
},
|
|
1608
|
+
}
|
|
1609
|
+
: {}),
|
|
1372
1610
|
},
|
|
1373
1611
|
};
|
|
1374
1612
|
}
|
|
@@ -1376,10 +1614,20 @@ async function registryRecord(args: {
|
|
|
1376
1614
|
function registryValues(
|
|
1377
1615
|
initializer: ts.Expression,
|
|
1378
1616
|
exportName: string,
|
|
1379
|
-
):
|
|
1617
|
+
):
|
|
1618
|
+
| {
|
|
1619
|
+
values: ts.Expression[];
|
|
1620
|
+
outbox?: {
|
|
1621
|
+
events: ts.Expression[];
|
|
1622
|
+
jobs: ts.Expression[];
|
|
1623
|
+
eventsIndeterminate: boolean;
|
|
1624
|
+
jobsIndeterminate: boolean;
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
| undefined {
|
|
1380
1628
|
const expression = unwrapExpression(initializer);
|
|
1381
1629
|
if (ts.isArrayLiteralExpression(expression) && isRegistryName(exportName)) {
|
|
1382
|
-
return arrayExpressions(expression);
|
|
1630
|
+
return { values: arrayExpressions(expression) };
|
|
1383
1631
|
}
|
|
1384
1632
|
if (!ts.isCallExpression(expression)) return undefined;
|
|
1385
1633
|
|
|
@@ -1390,100 +1638,161 @@ function registryValues(
|
|
|
1390
1638
|
name === "defineAgentCapabilityRegistry"
|
|
1391
1639
|
) {
|
|
1392
1640
|
const first = expression.arguments[0];
|
|
1393
|
-
return
|
|
1394
|
-
|
|
1395
|
-
|
|
1641
|
+
return {
|
|
1642
|
+
values:
|
|
1643
|
+
first && ts.isArrayLiteralExpression(unwrapExpression(first))
|
|
1644
|
+
? arrayExpressions(
|
|
1645
|
+
unwrapExpression(first) as ts.ArrayLiteralExpression,
|
|
1646
|
+
)
|
|
1647
|
+
: [],
|
|
1648
|
+
};
|
|
1396
1649
|
}
|
|
1397
|
-
if (
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
return
|
|
1405
|
-
|
|
1406
|
-
:
|
|
1407
|
-
|
|
1650
|
+
if (name === "defineOutboxRegistry") {
|
|
1651
|
+
const initializers = localVariableInitializers(expression.getSourceFile());
|
|
1652
|
+
const argument = expression.arguments[0];
|
|
1653
|
+
const object = argument
|
|
1654
|
+
? resolveRegistryExpression(argument, initializers)
|
|
1655
|
+
: undefined;
|
|
1656
|
+
if (!object || !ts.isObjectLiteralExpression(object)) {
|
|
1657
|
+
return {
|
|
1658
|
+
values: [],
|
|
1659
|
+
outbox: {
|
|
1660
|
+
events: [],
|
|
1661
|
+
jobs: [],
|
|
1662
|
+
eventsIndeterminate: true,
|
|
1663
|
+
jobsIndeterminate: true,
|
|
1664
|
+
},
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
const valuesFor = (property: "events" | "jobs") => {
|
|
1668
|
+
const match = objectPropertyExpressionAnalysis(object, property);
|
|
1669
|
+
return {
|
|
1670
|
+
values: match.value
|
|
1671
|
+
? registryValueExpressions(match.value, initializers)
|
|
1672
|
+
: [],
|
|
1673
|
+
indeterminate: match.indeterminate,
|
|
1674
|
+
};
|
|
1675
|
+
};
|
|
1676
|
+
const events = valuesFor("events");
|
|
1677
|
+
const jobs = valuesFor("jobs");
|
|
1678
|
+
const outbox = {
|
|
1679
|
+
events: events.values,
|
|
1680
|
+
jobs: jobs.values,
|
|
1681
|
+
eventsIndeterminate: events.indeterminate,
|
|
1682
|
+
jobsIndeterminate: jobs.indeterminate,
|
|
1683
|
+
};
|
|
1684
|
+
return { values: [...outbox.events, ...outbox.jobs], outbox };
|
|
1408
1685
|
}
|
|
1409
1686
|
if (
|
|
1410
1687
|
name === "defineUploads" &&
|
|
1411
1688
|
ts.isObjectLiteralExpression(expression.arguments[0])
|
|
1412
1689
|
) {
|
|
1413
|
-
return
|
|
1414
|
-
|
|
1415
|
-
|
|
1690
|
+
return {
|
|
1691
|
+
values: expression.arguments[0].properties.flatMap((property) =>
|
|
1692
|
+
ts.isPropertyAssignment(property) ? [property.initializer] : [],
|
|
1693
|
+
),
|
|
1694
|
+
};
|
|
1416
1695
|
}
|
|
1417
1696
|
return undefined;
|
|
1418
1697
|
}
|
|
1419
1698
|
|
|
1420
|
-
function
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1699
|
+
function objectPropertyExpressionAnalysis(
|
|
1700
|
+
object: ts.ObjectLiteralExpression,
|
|
1701
|
+
name: string,
|
|
1702
|
+
): { value?: ts.Expression; indeterminate: boolean } {
|
|
1703
|
+
let value: ts.Expression | undefined;
|
|
1704
|
+
let indeterminate = false;
|
|
1705
|
+
for (const property of object.properties) {
|
|
1706
|
+
if (ts.isSpreadAssignment(property) || !property.name) {
|
|
1707
|
+
value = undefined;
|
|
1708
|
+
indeterminate = true;
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
const propertyKey = propertyName(property.name);
|
|
1712
|
+
if (!propertyKey) {
|
|
1713
|
+
value = undefined;
|
|
1714
|
+
indeterminate = true;
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
if (propertyKey !== name) continue;
|
|
1718
|
+
if (ts.isPropertyAssignment(property)) {
|
|
1719
|
+
value = property.initializer;
|
|
1720
|
+
indeterminate = false;
|
|
1721
|
+
} else if (ts.isShorthandPropertyAssignment(property)) {
|
|
1722
|
+
value = property.name;
|
|
1723
|
+
indeterminate = false;
|
|
1724
|
+
} else {
|
|
1725
|
+
value = undefined;
|
|
1726
|
+
indeterminate = true;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return { ...(value ? { value } : {}), indeterminate };
|
|
1425
1730
|
}
|
|
1426
1731
|
|
|
1427
|
-
function
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1732
|
+
function localVariableInitializers(
|
|
1733
|
+
sourceFile: ts.SourceFile,
|
|
1734
|
+
): Map<string, ts.Expression> {
|
|
1735
|
+
const initializers = new Map<string, ts.Expression>();
|
|
1736
|
+
for (const statement of sourceFile.statements) {
|
|
1737
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
1738
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1739
|
+
if (ts.isIdentifier(declaration.name) && declaration.initializer) {
|
|
1740
|
+
initializers.set(declaration.name.text, declaration.initializer);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1434
1743
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1744
|
+
return initializers;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function resolveRegistryExpression(
|
|
1748
|
+
expression: ts.Expression,
|
|
1749
|
+
initializers: Map<string, ts.Expression>,
|
|
1750
|
+
visited = new Set<string>(),
|
|
1751
|
+
): ts.Expression {
|
|
1752
|
+
const current = unwrapExpression(expression);
|
|
1753
|
+
if (!ts.isIdentifier(current) || visited.has(current.text)) return current;
|
|
1754
|
+
const initializer = initializers.get(current.text);
|
|
1755
|
+
if (!initializer) return current;
|
|
1756
|
+
const nextVisited = new Set(visited);
|
|
1757
|
+
nextVisited.add(current.text);
|
|
1758
|
+
return resolveRegistryExpression(initializer, initializers, nextVisited);
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function registryValueExpressions(
|
|
1762
|
+
expression: ts.Expression,
|
|
1763
|
+
initializers: Map<string, ts.Expression>,
|
|
1764
|
+
visited = new Set<string>(),
|
|
1765
|
+
): ts.Expression[] {
|
|
1766
|
+
const current = unwrapExpression(expression);
|
|
1767
|
+
if (ts.isArrayLiteralExpression(current)) {
|
|
1768
|
+
return current.elements.flatMap((element) =>
|
|
1769
|
+
ts.isSpreadElement(element)
|
|
1770
|
+
? registryValueExpressions(
|
|
1771
|
+
element.expression,
|
|
1772
|
+
initializers,
|
|
1773
|
+
new Set(visited),
|
|
1774
|
+
)
|
|
1775
|
+
: ts.isExpression(element)
|
|
1776
|
+
? [element]
|
|
1777
|
+
: [],
|
|
1438
1778
|
);
|
|
1439
1779
|
}
|
|
1440
|
-
if (ts.
|
|
1441
|
-
const
|
|
1442
|
-
if (
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1780
|
+
if (ts.isIdentifier(current) && !visited.has(current.text)) {
|
|
1781
|
+
const initializer = initializers.get(current.text);
|
|
1782
|
+
if (initializer) {
|
|
1783
|
+
const nextVisited = new Set(visited);
|
|
1784
|
+
nextVisited.add(current.text);
|
|
1785
|
+
return registryValueExpressions(initializer, initializers, nextVisited);
|
|
1786
|
+
}
|
|
1447
1787
|
}
|
|
1448
|
-
return [];
|
|
1788
|
+
return [current];
|
|
1449
1789
|
}
|
|
1450
1790
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
const result = { bound: new Set<string>(), deferred: new Set<string>() };
|
|
1456
|
-
const file = workspace.config.paths.portWiring;
|
|
1457
|
-
if (!workspace.fileSet.has(file)) return result;
|
|
1458
|
-
const sourceFile = await workspace.readSourceFile(file);
|
|
1459
|
-
|
|
1460
|
-
visitCalls(sourceFile, (call) => {
|
|
1461
|
-
const object = call.arguments.find(ts.isObjectLiteralExpression);
|
|
1462
|
-
if (
|
|
1463
|
-
!object?.properties.some(
|
|
1464
|
-
(property) =>
|
|
1465
|
-
property.name !== undefined &&
|
|
1466
|
-
propertyName(property.name) === "bound",
|
|
1467
|
-
)
|
|
1468
|
-
) {
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
const bound = propertyAssignment(object, "bound")?.initializer;
|
|
1472
|
-
if (bound && ts.isObjectLiteralExpression(bound)) {
|
|
1473
|
-
for (const property of bound.properties) {
|
|
1474
|
-
const name = property.name ? propertyName(property.name) : undefined;
|
|
1475
|
-
if (name) result.bound.add(name);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
const deferred = propertyAssignment(object, "deferred")?.initializer;
|
|
1479
|
-
if (deferred && ts.isArrayLiteralExpression(deferred)) {
|
|
1480
|
-
for (const element of deferred.elements) {
|
|
1481
|
-
const name = literalText(element);
|
|
1482
|
-
if (name) result.deferred.add(name);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1791
|
+
function arrayExpressions(array: ts.ArrayLiteralExpression): ts.Expression[] {
|
|
1792
|
+
return array.elements.flatMap((element) => {
|
|
1793
|
+
if (ts.isSpreadElement(element)) return [element.expression];
|
|
1794
|
+
return ts.isExpression(element) ? [element] : [];
|
|
1485
1795
|
});
|
|
1486
|
-
return result;
|
|
1487
1796
|
}
|
|
1488
1797
|
|
|
1489
1798
|
function discoverFeatureNames(
|