@beignet/cli 0.0.48 → 0.0.50
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 +54 -0
- package/README.md +117 -22
- 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 +11 -4
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +78 -8
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map-changes.d.ts +103 -0
- package/dist/app-map-changes.d.ts.map +1 -0
- package/dist/app-map-changes.js +949 -0
- package/dist/app-map-changes.js.map +1 -0
- 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/git-changes.d.ts +30 -0
- package/dist/git-changes.d.ts.map +1 -0
- package/dist/git-changes.js +367 -0
- package/dist/git-changes.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +706 -186
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +3 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -0
- package/dist/lib.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/shared.d.ts.map +1 -1
- package/dist/make/shared.js +31 -27
- package/dist/make/shared.js.map +1 -1
- 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 +43 -21
- 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 +37 -1
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +13 -2
- 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 +71 -7
- 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 +134 -9
- package/src/app-map-changes.ts +1462 -0
- package/src/app-map.ts +418 -109
- package/src/check.ts +51 -3
- package/src/explain.ts +4 -4
- package/src/git-changes.ts +511 -0
- package/src/index.ts +39 -1
- package/src/inspect.ts +1005 -238
- package/src/lib.ts +21 -0
- package/src/lint.ts +393 -643
- package/src/make/inbox.ts +11 -4
- package/src/make/payments.ts +2 -2
- package/src/make/shared.ts +31 -27
- package/src/make.ts +225 -8
- package/src/mcp.ts +51 -24
- package/src/provider-add.ts +186 -7
- package/src/registry-edits.ts +313 -63
- package/src/templates/agents.ts +37 -1
- package/src/templates/base.ts +13 -2
- package/src/templates/server.ts +2 -0
- package/src/templates/shared.ts +3 -2
package/dist/inspect.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseProviderPackageMetadata } from "@beignet/core/providers";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { contractFactoryBindings, isImportedContractFactoryCall, } from "./analysis/contract-factories.js";
|
|
6
|
+
import { canonicalFeatureLayerPath } from "./analysis/layers.js";
|
|
7
|
+
import { analyzePortWiringFiles } from "./analysis/port-wiring.js";
|
|
8
|
+
import { exportedVariableDeclarations } from "./analysis/source-index.js";
|
|
4
9
|
import { createPainter } from "./ansi.js";
|
|
5
10
|
import { clientDirPath, clientFormsPath, clientIndexPath, defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
|
|
6
11
|
import { renderDatabaseSchema, resolveDatabaseSchemaDialect } from "./db.js";
|
|
@@ -8,7 +13,7 @@ import { applyPlannedDoctorFixes, createDoctorFixPlan, planDoctorFixFileChange,
|
|
|
8
13
|
import { formatGithubAnnotation, } from "./github-annotations.js";
|
|
9
14
|
import { createListenersRegistrySource, listenersFilePath, providersFilePath, updateListenersRegistrySource, wireListenersProviderSource, } from "./make/shared.js";
|
|
10
15
|
import { detectedProviderVariants, diagnosticPackageJsonFile, formatProviderEnvAlternatives, formatProviderMetadataIssues, installedPackageNames, providerDoctorRulesForInstalledPackages, providerOperationalConfigFiles, providerRequiredEnvExists, providerRequiredTableExists, providerVariantRegistrationSeverity, readProviderListEntries, readProviderPackageMetadataSource, registrationTokenHint, serverProviderFiles, } from "./provider-audit.js";
|
|
11
|
-
import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, codeCharacterIndexes, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex,
|
|
16
|
+
import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, codeCharacterIndexes, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryEntryState, outboxRegistryIdentifiers, } from "./registry-edits.js";
|
|
12
17
|
import { currentGeneratedPackageScripts, externalVersions, isLegacyGeneratedPackageScript, legacyGeneratedPackageScripts, } from "./templates/shared.js";
|
|
13
18
|
import { testSupportTemplateFiles } from "./templates/testing.js";
|
|
14
19
|
import { getCliVersion } from "./version.js";
|
|
@@ -20,9 +25,10 @@ export async function inspectApp(options = {}) {
|
|
|
20
25
|
? resolveConfig(options.config)
|
|
21
26
|
: await loadBeignetConfig(targetDir, files);
|
|
22
27
|
const convention = inspectConvention(files, config);
|
|
23
|
-
const
|
|
28
|
+
const inspectedContracts = await readContracts(targetDir, files, config);
|
|
29
|
+
const { contracts } = inspectedContracts;
|
|
24
30
|
const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts);
|
|
25
|
-
const diagnostics = await inspectDiagnostics(targetDir, files, config, convention, contracts, matchedRoutes, Boolean(options.strict));
|
|
31
|
+
const diagnostics = await inspectDiagnostics(targetDir, files, config, convention, contracts, matchedRoutes, Boolean(options.strict), inspectedContracts.queryTransportDiagnostics);
|
|
26
32
|
return {
|
|
27
33
|
schemaVersion: 1,
|
|
28
34
|
targetDir,
|
|
@@ -76,7 +82,7 @@ async function buildDoctorFixPlan(options = {}) {
|
|
|
76
82
|
? resolveConfig(options.config)
|
|
77
83
|
: await loadBeignetConfig(targetDir, files);
|
|
78
84
|
const convention = inspectConvention(files, config);
|
|
79
|
-
const contracts = await readContracts(targetDir, files, config);
|
|
85
|
+
const { contracts } = await readContracts(targetDir, files, config);
|
|
80
86
|
const matchedRoutes = await inspectRouteSurface(targetDir, files, config, contracts);
|
|
81
87
|
const operations = [];
|
|
82
88
|
const packageFix = await planPackageScripts(targetDir, files, convention);
|
|
@@ -318,7 +324,6 @@ export const productionHardeningDiagnosticCodes = new Set([
|
|
|
318
324
|
"BEIGNET_BILLING_IDEMPOTENCY_PORT_MISSING",
|
|
319
325
|
"BEIGNET_CORS_CREDENTIALS_WILDCARD",
|
|
320
326
|
"BEIGNET_CRON_SECRET_MISSING",
|
|
321
|
-
"BEIGNET_DEVTOOLS_ROUTE_EXPOSED",
|
|
322
327
|
"BEIGNET_ENTITLEMENTS_PORT_MISSING",
|
|
323
328
|
"BEIGNET_PAYMENTS_STRIPE_MEMORY_PROVIDER",
|
|
324
329
|
"BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
|
|
@@ -409,12 +414,28 @@ async function listFiles(targetDir) {
|
|
|
409
414
|
}
|
|
410
415
|
async function readContracts(targetDir, files, config) {
|
|
411
416
|
const contracts = [];
|
|
417
|
+
const queryTransportDiagnostics = [];
|
|
412
418
|
const contractFiles = contractSourceFiles(files, config);
|
|
413
419
|
for (const file of contractFiles) {
|
|
414
420
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
415
|
-
|
|
421
|
+
for (const parsed of parseContracts(source, file)) {
|
|
422
|
+
contracts.push(parsed.contract);
|
|
423
|
+
if (!parsed.queryTransportMissing)
|
|
424
|
+
continue;
|
|
425
|
+
queryTransportDiagnostics.push({
|
|
426
|
+
severity: "error",
|
|
427
|
+
code: "BEIGNET_QUERY_TRANSPORT_MISSING",
|
|
428
|
+
file,
|
|
429
|
+
contract: parsed.contract.exportName,
|
|
430
|
+
subject: { file, exportName: parsed.contract.exportName },
|
|
431
|
+
message: `${parsed.contract.exportName} declares a query schema without an explicit query transport. Pass defineQueryTransport(...) as the second .query(...) argument so the client, server, and OpenAPI document share one wire format.`,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
416
434
|
}
|
|
417
|
-
return
|
|
435
|
+
return {
|
|
436
|
+
contracts: contracts.sort(compareContracts),
|
|
437
|
+
queryTransportDiagnostics,
|
|
438
|
+
};
|
|
418
439
|
}
|
|
419
440
|
function inspectConvention(files, config) {
|
|
420
441
|
const missingNextLayout = missingRequirements(files, [
|
|
@@ -464,47 +485,77 @@ function hasRequirement(files, requirement) {
|
|
|
464
485
|
}
|
|
465
486
|
function parseContracts(source, file) {
|
|
466
487
|
const contracts = [];
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (methodMatch) {
|
|
474
|
-
const receiver = methodMatch[1];
|
|
475
|
-
const pathPrefix = groupPrefixes.get(receiver) ?? "";
|
|
476
|
-
const routePath = joinContractPaths(pathPrefix, methodMatch[3]);
|
|
477
|
-
contracts.push({
|
|
478
|
-
exportName: exportMatch[1],
|
|
479
|
-
file,
|
|
480
|
-
metadata: parseContractMetadata(block, groupErrorRefs.get(receiver) ?? []),
|
|
481
|
-
method: methodMatch[2].toUpperCase(),
|
|
482
|
-
path: routePath,
|
|
483
|
-
});
|
|
488
|
+
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
489
|
+
const assignments = variableAssignments(sourceFile);
|
|
490
|
+
const assignmentsByName = new Map(assignments.map((assignment) => [assignment.name, assignment.expression]));
|
|
491
|
+
const factoryBindings = contractFactoryBindings(sourceFile);
|
|
492
|
+
for (const { exportName, declaration } of exportedVariableDeclarations(sourceFile)) {
|
|
493
|
+
if (!declaration.initializer)
|
|
484
494
|
continue;
|
|
485
|
-
|
|
486
|
-
|
|
495
|
+
const chain = resolveContractCallChain(declaration.initializer, assignmentsByName);
|
|
496
|
+
if (!chain)
|
|
497
|
+
continue;
|
|
498
|
+
const directContract = parseDirectContract(chain, factoryBindings);
|
|
487
499
|
if (directContract) {
|
|
488
500
|
contracts.push({
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
501
|
+
contract: {
|
|
502
|
+
exportName,
|
|
503
|
+
file,
|
|
504
|
+
metadata: parseContractMetadata(applyContractMetadataCalls(emptyContractMetadataState(), chain.calls)),
|
|
505
|
+
method: directContract.method,
|
|
506
|
+
path: normalizeContractPath(directContract.path),
|
|
507
|
+
},
|
|
508
|
+
queryTransportMissing: contractQueryHasTransport(chain) === false,
|
|
494
509
|
});
|
|
510
|
+
continue;
|
|
495
511
|
}
|
|
512
|
+
const methodIndex = chain.calls.findIndex((call) => isHttpMethod(call.name.toUpperCase()));
|
|
513
|
+
if (methodIndex === -1)
|
|
514
|
+
continue;
|
|
515
|
+
const methodCall = chain.calls[methodIndex];
|
|
516
|
+
const routePath = staticString(methodCall.arguments[0]);
|
|
517
|
+
if (!routePath)
|
|
518
|
+
continue;
|
|
519
|
+
const hasGroupFactory = contractFactoryCall(chain, "defineContractGroup", factoryBindings);
|
|
520
|
+
if (!hasGroupFactory)
|
|
521
|
+
continue;
|
|
522
|
+
const pathPrefix = applyStaticPrefixes("", chain.calls.slice(0, methodIndex));
|
|
523
|
+
if (pathPrefix === undefined)
|
|
524
|
+
continue;
|
|
525
|
+
contracts.push({
|
|
526
|
+
contract: {
|
|
527
|
+
exportName,
|
|
528
|
+
file,
|
|
529
|
+
metadata: parseContractMetadata(applyContractMetadataCalls(emptyContractMetadataState(), chain.calls)),
|
|
530
|
+
method: methodCall.name.toUpperCase(),
|
|
531
|
+
path: joinContractPaths(pathPrefix, routePath),
|
|
532
|
+
},
|
|
533
|
+
queryTransportMissing: contractQueryHasTransport(chain) === false,
|
|
534
|
+
});
|
|
496
535
|
}
|
|
497
536
|
return contracts;
|
|
498
537
|
}
|
|
499
|
-
function
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
538
|
+
function unwrapContractExpression(expression) {
|
|
539
|
+
let current = expression;
|
|
540
|
+
while (ts.isParenthesizedExpression(current) ||
|
|
541
|
+
ts.isAsExpression(current) ||
|
|
542
|
+
ts.isTypeAssertionExpression(current) ||
|
|
543
|
+
ts.isNonNullExpression(current) ||
|
|
544
|
+
ts.isSatisfiesExpression(current)) {
|
|
545
|
+
current = current.expression;
|
|
546
|
+
}
|
|
547
|
+
return current;
|
|
548
|
+
}
|
|
549
|
+
function emptyContractMetadataState() {
|
|
550
|
+
return { catalogErrors: [] };
|
|
551
|
+
}
|
|
552
|
+
function parseContractMetadata(state) {
|
|
553
|
+
const authRequired = state.authRequired === true;
|
|
554
|
+
const authorizationAbility = state.authorizationAbility;
|
|
555
|
+
const auditRequired = state.audit === true ||
|
|
556
|
+
state.auditRequired === true ||
|
|
557
|
+
state.authorizationAuditRequired === true;
|
|
558
|
+
const catalogErrors = state.catalogErrors;
|
|
508
559
|
if (!authRequired &&
|
|
509
560
|
!authorizationAbility &&
|
|
510
561
|
!auditRequired &&
|
|
@@ -518,100 +569,271 @@ function parseContractMetadata(block, inheritedCatalogErrors = []) {
|
|
|
518
569
|
catalogErrors,
|
|
519
570
|
};
|
|
520
571
|
}
|
|
521
|
-
function
|
|
522
|
-
const
|
|
523
|
-
|
|
572
|
+
function applyContractMetadataCalls(initialState, calls) {
|
|
573
|
+
const state = {
|
|
574
|
+
...initialState,
|
|
575
|
+
catalogErrors: [...initialState.catalogErrors],
|
|
576
|
+
};
|
|
577
|
+
for (const call of calls) {
|
|
578
|
+
if (call.name === "meta") {
|
|
579
|
+
applyContractMetaObject(state, objectLiteral(call.arguments[0]));
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if (call.name === "errors") {
|
|
583
|
+
state.catalogErrors = applyCatalogErrorCall(state.catalogErrors, call.arguments[0]);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return state;
|
|
587
|
+
}
|
|
588
|
+
function applyContractMetaObject(state, metadata) {
|
|
589
|
+
if (!metadata) {
|
|
590
|
+
state.authRequired = undefined;
|
|
591
|
+
state.authorizationAbility = undefined;
|
|
592
|
+
state.authorizationAuditRequired = undefined;
|
|
593
|
+
state.audit = undefined;
|
|
594
|
+
state.auditRequired = undefined;
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const auth = objectPropertyResult(metadata, "auth");
|
|
598
|
+
if (auth.kind !== "absent") {
|
|
599
|
+
state.authRequired =
|
|
600
|
+
auth.kind === "value" ? staticRequiredValue(auth.value) : undefined;
|
|
601
|
+
}
|
|
602
|
+
const authorization = objectPropertyResult(metadata, "authorization");
|
|
603
|
+
if (authorization.kind !== "absent") {
|
|
604
|
+
const value = authorization.kind === "value"
|
|
605
|
+
? objectLiteral(authorization.value)
|
|
606
|
+
: undefined;
|
|
607
|
+
if (!value) {
|
|
608
|
+
state.authorizationAbility = undefined;
|
|
609
|
+
state.authorizationAuditRequired = undefined;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
const ability = objectPropertyResult(value, "ability");
|
|
613
|
+
state.authorizationAbility =
|
|
614
|
+
ability.kind === "value" ? staticString(ability.value) : undefined;
|
|
615
|
+
state.authorizationAuditRequired = objectRequiresAudit(value);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const audit = objectPropertyResult(metadata, "audit");
|
|
619
|
+
const auditRequired = objectPropertyResult(metadata, "auditRequired");
|
|
620
|
+
if (audit.kind !== "absent") {
|
|
621
|
+
state.audit =
|
|
622
|
+
audit.kind === "value" ? staticRequiredValue(audit.value) : undefined;
|
|
623
|
+
}
|
|
624
|
+
if (auditRequired.kind !== "absent") {
|
|
625
|
+
state.auditRequired =
|
|
626
|
+
auditRequired.kind === "value"
|
|
627
|
+
? staticBoolean(auditRequired.value)
|
|
628
|
+
: undefined;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function objectRequiresAudit(metadata) {
|
|
632
|
+
const audit = objectPropertyResult(metadata, "audit");
|
|
633
|
+
const auditRequired = objectPropertyResult(metadata, "auditRequired");
|
|
634
|
+
if (audit.kind === "unknown" || auditRequired.kind === "unknown") {
|
|
524
635
|
return undefined;
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
636
|
+
}
|
|
637
|
+
const auditValue = audit.kind === "value" ? staticRequiredValue(audit.value) : false;
|
|
638
|
+
const auditRequiredValue = auditRequired.kind === "value" ? staticBoolean(auditRequired.value) : false;
|
|
639
|
+
if (auditValue === undefined || auditRequiredValue === undefined) {
|
|
529
640
|
return undefined;
|
|
530
|
-
|
|
641
|
+
}
|
|
642
|
+
return auditValue || auditRequiredValue;
|
|
643
|
+
}
|
|
644
|
+
function staticRequiredValue(expression) {
|
|
645
|
+
const value = staticString(expression);
|
|
646
|
+
return value === undefined ? undefined : value === "required";
|
|
647
|
+
}
|
|
648
|
+
function staticBoolean(expression) {
|
|
649
|
+
if (expression.kind === ts.SyntaxKind.TrueKeyword)
|
|
650
|
+
return true;
|
|
651
|
+
if (expression.kind === ts.SyntaxKind.FalseKeyword)
|
|
652
|
+
return false;
|
|
653
|
+
return undefined;
|
|
654
|
+
}
|
|
655
|
+
function parseDirectContract(chain, factoryBindings) {
|
|
656
|
+
const factoryCall = contractFactoryCall(chain, "defineContract", factoryBindings);
|
|
657
|
+
if (!factoryCall)
|
|
658
|
+
return undefined;
|
|
659
|
+
const options = objectLiteral(factoryCall.arguments[0]);
|
|
660
|
+
if (!options)
|
|
661
|
+
return undefined;
|
|
662
|
+
const methodValue = staticString(objectProperty(options, "method"));
|
|
663
|
+
const contractPath = staticString(objectProperty(options, "path"));
|
|
664
|
+
if (!methodValue || !contractPath)
|
|
665
|
+
return undefined;
|
|
666
|
+
const method = methodValue.toUpperCase();
|
|
531
667
|
if (!isHttpMethod(method))
|
|
532
668
|
return undefined;
|
|
533
|
-
return { method, path:
|
|
669
|
+
return { method, path: contractPath };
|
|
534
670
|
}
|
|
535
671
|
function isHttpMethod(method) {
|
|
536
672
|
return ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].includes(method);
|
|
537
673
|
}
|
|
538
|
-
function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
name
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
674
|
+
function variableAssignments(sourceFile) {
|
|
675
|
+
return sourceFile.statements
|
|
676
|
+
.filter(ts.isVariableStatement)
|
|
677
|
+
.flatMap((statement) => [...statement.declarationList.declarations]
|
|
678
|
+
.filter((declaration) => ts.isIdentifier(declaration.name) &&
|
|
679
|
+
declaration.initializer !== undefined)
|
|
680
|
+
.map((declaration) => ({
|
|
681
|
+
name: declaration.name.text,
|
|
682
|
+
expression: declaration.initializer,
|
|
683
|
+
})));
|
|
684
|
+
}
|
|
685
|
+
function resolveContractCallChain(expression, assignments, resolving = new Set()) {
|
|
686
|
+
const chain = contractCallChain(expression);
|
|
687
|
+
if (!chain || chain.rootCall || !chain.baseIdentifier)
|
|
688
|
+
return chain;
|
|
689
|
+
const baseExpression = assignments.get(chain.baseIdentifier);
|
|
690
|
+
if (!baseExpression || resolving.has(chain.baseIdentifier))
|
|
691
|
+
return chain;
|
|
692
|
+
const nextResolving = new Set(resolving);
|
|
693
|
+
nextResolving.add(chain.baseIdentifier);
|
|
694
|
+
const baseChain = resolveContractCallChain(baseExpression, assignments, nextResolving);
|
|
695
|
+
if (!baseChain)
|
|
696
|
+
return chain;
|
|
697
|
+
return {
|
|
698
|
+
...baseChain,
|
|
699
|
+
calls: [...baseChain.calls, ...chain.calls],
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function contractCallChain(rawExpression) {
|
|
703
|
+
const calls = [];
|
|
704
|
+
let expression = rawExpression;
|
|
705
|
+
while (true) {
|
|
706
|
+
expression = unwrapContractExpression(expression);
|
|
707
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
708
|
+
expression = expression.expression;
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (ts.isCallExpression(expression)) {
|
|
712
|
+
const callee = unwrapContractExpression(expression.expression);
|
|
713
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
714
|
+
calls.unshift({
|
|
715
|
+
name: callee.name.text,
|
|
716
|
+
arguments: [...expression.arguments],
|
|
717
|
+
node: expression,
|
|
718
|
+
});
|
|
719
|
+
expression = callee.expression;
|
|
554
720
|
continue;
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
721
|
+
}
|
|
722
|
+
if (ts.isIdentifier(callee)) {
|
|
723
|
+
return {
|
|
724
|
+
baseIdentifier: callee.text,
|
|
725
|
+
rootCall: {
|
|
726
|
+
name: callee.text,
|
|
727
|
+
arguments: [...expression.arguments],
|
|
728
|
+
node: expression,
|
|
729
|
+
},
|
|
730
|
+
calls,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
return undefined;
|
|
734
|
+
}
|
|
735
|
+
if (ts.isIdentifier(expression)) {
|
|
736
|
+
return { baseIdentifier: expression.text, calls };
|
|
558
737
|
}
|
|
738
|
+
return undefined;
|
|
559
739
|
}
|
|
560
|
-
return prefixes;
|
|
561
740
|
}
|
|
562
|
-
function
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
continue;
|
|
576
|
-
const baseErrors = baseGroupErrors(assignment.expression, groupErrors);
|
|
577
|
-
if (baseErrors === undefined)
|
|
578
|
-
continue;
|
|
579
|
-
groupErrors.set(assignment.name, dedupeCatalogErrorRefs([
|
|
580
|
-
...baseErrors,
|
|
581
|
-
...parseCatalogErrorRefs(assignment.expression),
|
|
582
|
-
]));
|
|
583
|
-
changed = true;
|
|
584
|
-
}
|
|
741
|
+
function contractFactoryCall(chain, name, factoryBindings) {
|
|
742
|
+
const calls = chain.rootCall ? [chain.rootCall] : chain.calls;
|
|
743
|
+
return calls.find((call) => isImportedContractFactoryCall(call.node, name, factoryBindings));
|
|
744
|
+
}
|
|
745
|
+
function applyStaticPrefixes(initialPrefix, calls) {
|
|
746
|
+
let prefix = initialPrefix;
|
|
747
|
+
for (const call of calls) {
|
|
748
|
+
if (call.name !== "prefix")
|
|
749
|
+
continue;
|
|
750
|
+
const next = staticString(call.arguments[0]);
|
|
751
|
+
if (next === undefined)
|
|
752
|
+
return undefined;
|
|
753
|
+
prefix = joinContractPaths(prefix, next);
|
|
585
754
|
}
|
|
586
|
-
return
|
|
755
|
+
return prefix;
|
|
756
|
+
}
|
|
757
|
+
function contractQueryHasTransport(chain) {
|
|
758
|
+
const queryCall = chain.calls.find((call) => call.name === "query");
|
|
759
|
+
return queryCall ? queryCall.arguments.length >= 2 : undefined;
|
|
587
760
|
}
|
|
588
|
-
function
|
|
589
|
-
|
|
761
|
+
function applyCatalogErrorCall(current, expression) {
|
|
762
|
+
const errors = objectLiteral(expression);
|
|
763
|
+
if (!errors)
|
|
590
764
|
return [];
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
765
|
+
let merged = [...current];
|
|
766
|
+
for (const property of errors.properties) {
|
|
767
|
+
if (ts.isSpreadAssignment(property)) {
|
|
768
|
+
merged = [];
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
const key = staticPropertyName(property.name);
|
|
772
|
+
if (!key) {
|
|
773
|
+
merged = [];
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
merged = merged.filter((ref) => ref.key !== key);
|
|
777
|
+
if (!ts.isPropertyAssignment(property))
|
|
778
|
+
continue;
|
|
779
|
+
const value = unwrapContractExpression(property.initializer);
|
|
780
|
+
if (!ts.isPropertyAccessExpression(value) ||
|
|
781
|
+
!ts.isIdentifier(value.expression)) {
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
merged.push({
|
|
785
|
+
key,
|
|
786
|
+
catalog: value.expression.text,
|
|
787
|
+
catalogKey: value.name.text,
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
return merged;
|
|
595
791
|
}
|
|
596
|
-
function
|
|
597
|
-
if (expression
|
|
598
|
-
return "";
|
|
599
|
-
const baseMatch = /^\s*([A-Za-z_$][\w$]*)\b/.exec(expression);
|
|
600
|
-
if (!baseMatch)
|
|
792
|
+
function objectLiteral(expression) {
|
|
793
|
+
if (!expression)
|
|
601
794
|
return undefined;
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
795
|
+
const unwrapped = unwrapContractExpression(expression);
|
|
796
|
+
return ts.isObjectLiteralExpression(unwrapped) ? unwrapped : undefined;
|
|
797
|
+
}
|
|
798
|
+
function objectProperty(object, name) {
|
|
799
|
+
const result = objectPropertyResult(object, name);
|
|
800
|
+
return result.kind === "value" ? result.value : undefined;
|
|
801
|
+
}
|
|
802
|
+
function objectPropertyResult(object, name) {
|
|
803
|
+
for (const property of [...object.properties].reverse()) {
|
|
804
|
+
if (ts.isSpreadAssignment(property))
|
|
805
|
+
return { kind: "unknown" };
|
|
806
|
+
const propertyName = staticPropertyName(property.name);
|
|
807
|
+
if (propertyName === undefined)
|
|
808
|
+
return { kind: "unknown" };
|
|
809
|
+
if (propertyName !== name)
|
|
810
|
+
continue;
|
|
811
|
+
if (ts.isPropertyAssignment(property)) {
|
|
812
|
+
return {
|
|
813
|
+
kind: "value",
|
|
814
|
+
value: unwrapContractExpression(property.initializer),
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
return { kind: "unknown" };
|
|
818
|
+
}
|
|
819
|
+
return { kind: "absent" };
|
|
820
|
+
}
|
|
821
|
+
function staticPropertyName(name) {
|
|
822
|
+
if (ts.isIdentifier(name) ||
|
|
823
|
+
ts.isStringLiteral(name) ||
|
|
824
|
+
ts.isNumericLiteral(name)) {
|
|
825
|
+
return name.text;
|
|
826
|
+
}
|
|
827
|
+
return undefined;
|
|
606
828
|
}
|
|
607
|
-
function
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
829
|
+
function staticString(expression) {
|
|
830
|
+
if (!expression)
|
|
831
|
+
return undefined;
|
|
832
|
+
const value = unwrapContractExpression(expression);
|
|
833
|
+
return ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value)
|
|
834
|
+
? value.text
|
|
835
|
+
: undefined;
|
|
613
836
|
}
|
|
614
|
-
const errorCatalogRefRegex = /(?:^|[,{])\s*([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)/g;
|
|
615
837
|
function parseErrorCatalogKeys(source) {
|
|
616
838
|
const keys = new Set();
|
|
617
839
|
const catalogBodies = objectArgumentBodies(source, /\bdefineErrors\s*\(/g);
|
|
@@ -691,18 +913,6 @@ function topLevelObjectKeys(body) {
|
|
|
691
913
|
}
|
|
692
914
|
return keys;
|
|
693
915
|
}
|
|
694
|
-
function dedupeCatalogErrorRefs(refs) {
|
|
695
|
-
const seen = new Set();
|
|
696
|
-
const deduped = [];
|
|
697
|
-
for (const ref of refs) {
|
|
698
|
-
const key = `${ref.key}:${ref.catalog}:${ref.catalogKey}`;
|
|
699
|
-
if (seen.has(key))
|
|
700
|
-
continue;
|
|
701
|
-
seen.add(key);
|
|
702
|
-
deduped.push(ref);
|
|
703
|
-
}
|
|
704
|
-
return deduped;
|
|
705
|
-
}
|
|
706
916
|
function joinContractPaths(prefix, routePath) {
|
|
707
917
|
if (!prefix)
|
|
708
918
|
return normalizeContractPath(routePath);
|
|
@@ -985,8 +1195,8 @@ function contractPathMatchesCatchAll(contractPath, catchAllPrefix) {
|
|
|
985
1195
|
return (contractPath === catchAllPrefix ||
|
|
986
1196
|
contractPath.startsWith(`${catchAllPrefix}/`));
|
|
987
1197
|
}
|
|
988
|
-
async function inspectDiagnostics(targetDir, files, config, convention, contracts, matchedRoutes, strict) {
|
|
989
|
-
const diagnostics = [];
|
|
1198
|
+
async function inspectDiagnostics(targetDir, files, config, convention, contracts, matchedRoutes, strict, queryTransportDiagnostics) {
|
|
1199
|
+
const diagnostics = [...queryTransportDiagnostics];
|
|
990
1200
|
const frameworkLayout = config.framework === "next" ? convention.nextLayout : convention.webLayout;
|
|
991
1201
|
const missingFrameworkLayout = config.framework === "next"
|
|
992
1202
|
? convention.missingNextLayout
|
|
@@ -1025,7 +1235,7 @@ async function inspectDiagnostics(targetDir, files, config, convention, contract
|
|
|
1025
1235
|
diagnostics.push(unmatchedRouteDiagnostic(routeHandler));
|
|
1026
1236
|
}
|
|
1027
1237
|
}
|
|
1028
|
-
diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectInngestProductionPath(targetDir, files, config)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files, config, convention)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectRuntimeManifestDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
|
|
1238
|
+
diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectInngestProductionPath(targetDir, files, config)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files, config, convention)), ...(await inspectOutboxJobDispatcherDrift(targetDir, files, config)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectRuntimeManifestDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
|
|
1029
1239
|
return dedupeDiagnostics(diagnostics);
|
|
1030
1240
|
}
|
|
1031
1241
|
async function inspectFrameworkAdapter(targetDir, files, config) {
|
|
@@ -1485,13 +1695,13 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
|
|
|
1485
1695
|
hasSecurityHeadersHook = true;
|
|
1486
1696
|
}
|
|
1487
1697
|
if (containsCallExpression(source, "createDevtoolsRoute") &&
|
|
1488
|
-
|
|
1489
|
-
!/\
|
|
1698
|
+
!/\bauthorize\s*(?::|\()/.test(source) &&
|
|
1699
|
+
!/\ballowUnauthenticatedDevelopmentAccess\s*:\s*true\b/.test(source)) {
|
|
1490
1700
|
diagnostics.push({
|
|
1491
1701
|
severity: "warning",
|
|
1492
|
-
code: "
|
|
1702
|
+
code: "BEIGNET_DEVTOOLS_ROUTE_POLICY_MISSING",
|
|
1493
1703
|
file,
|
|
1494
|
-
message: `${file}
|
|
1704
|
+
message: `${file} creates a devtools route without an access policy, so the route remains hidden. Add app-owned authorize(...), or explicitly set allowUnauthenticatedDevelopmentAccess: true only for a development server bound exclusively to loopback.`,
|
|
1495
1705
|
});
|
|
1496
1706
|
}
|
|
1497
1707
|
if (fileWithinDirectory(file, cronDirectory) &&
|
|
@@ -2316,7 +2526,7 @@ async function inspectFeatureArtifactDrift(targetDir, files, config, convention)
|
|
|
2316
2526
|
});
|
|
2317
2527
|
}
|
|
2318
2528
|
for (const file of files) {
|
|
2319
|
-
const match =
|
|
2529
|
+
const match = directFeatureTestMatch(file, featuresPath);
|
|
2320
2530
|
if (!match)
|
|
2321
2531
|
continue;
|
|
2322
2532
|
diagnostics.push({
|
|
@@ -2327,20 +2537,25 @@ async function inspectFeatureArtifactDrift(targetDir, files, config, convention)
|
|
|
2327
2537
|
});
|
|
2328
2538
|
}
|
|
2329
2539
|
for (const file of files) {
|
|
2330
|
-
if (
|
|
2331
|
-
|
|
2332
|
-
file.endsWith(".d.ts")) {
|
|
2540
|
+
if (!/\.(?:ts|tsx|mts|cts)$/.test(file) ||
|
|
2541
|
+
/\.d\.(?:ts|mts|cts)$/.test(file)) {
|
|
2333
2542
|
continue;
|
|
2334
2543
|
}
|
|
2335
|
-
const misplaced =
|
|
2544
|
+
const misplaced = directFeatureTestMatch(file, featuresPath)
|
|
2545
|
+
? undefined
|
|
2546
|
+
: misplacedFeatureLayer(file, config);
|
|
2336
2547
|
if (misplaced) {
|
|
2337
2548
|
diagnostics.push({
|
|
2338
2549
|
severity: "warning",
|
|
2339
2550
|
code: "BEIGNET_FEATURE_ARTIFACT_FOLDER",
|
|
2340
2551
|
file,
|
|
2341
|
-
message: `${file} uses ${misplaced.actual}, but Beignet expects ${misplaced.expected}. Keep feature-owned
|
|
2552
|
+
message: `${file} uses ${misplaced.actual}, but Beignet expects ${misplaced.expected}. Keep feature-owned files in the canonical feature folders so CLI generators, docs, and architecture lint stay aligned.`,
|
|
2342
2553
|
});
|
|
2343
2554
|
}
|
|
2555
|
+
if (isTestSourceFile(file))
|
|
2556
|
+
continue;
|
|
2557
|
+
if (!file.endsWith(".ts"))
|
|
2558
|
+
continue;
|
|
2344
2559
|
if (isNotificationDefinitionFile(file, featuresPath) ||
|
|
2345
2560
|
isInfraOrServerFile(file, config)) {
|
|
2346
2561
|
continue;
|
|
@@ -2357,6 +2572,9 @@ async function inspectFeatureArtifactDrift(targetDir, files, config, convention)
|
|
|
2357
2572
|
}
|
|
2358
2573
|
return diagnostics;
|
|
2359
2574
|
}
|
|
2575
|
+
function directFeatureTestMatch(file, featuresPath) {
|
|
2576
|
+
return new RegExp(`^${escapeRegExp(featuresPath)}/([^/]+)/[^/]+\\.(?:test|spec)\\.(?:ts|tsx|mts|cts)$`).exec(file);
|
|
2577
|
+
}
|
|
2360
2578
|
function isFeatureArtifactFile(file, featuresPath, folder) {
|
|
2361
2579
|
const match = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/${folder}/([^/]+)\\.ts$`).exec(file);
|
|
2362
2580
|
return Boolean(match && match[1] !== "index");
|
|
@@ -2375,28 +2593,23 @@ async function appHasUploadRoute(targetDir, files, config) {
|
|
|
2375
2593
|
}
|
|
2376
2594
|
return false;
|
|
2377
2595
|
}
|
|
2378
|
-
function
|
|
2379
|
-
const
|
|
2596
|
+
function misplacedFeatureLayer(file, config) {
|
|
2597
|
+
const featuresPath = directoryPath(config.paths.features);
|
|
2598
|
+
const match = new RegExp(`^${escapeRegExp(featuresPath)}/([^/]+)/(.+)$`).exec(file);
|
|
2380
2599
|
if (!match)
|
|
2381
2600
|
return undefined;
|
|
2382
|
-
const
|
|
2383
|
-
|
|
2384
|
-
command: "tasks/",
|
|
2385
|
-
commands: "tasks/",
|
|
2386
|
-
event: "domain/events/",
|
|
2387
|
-
events: "domain/events/",
|
|
2388
|
-
job: "jobs/",
|
|
2389
|
-
listener: "listeners/",
|
|
2390
|
-
notification: "notifications/",
|
|
2391
|
-
schedule: "schedules/",
|
|
2392
|
-
scheduled: "schedules/",
|
|
2393
|
-
task: "tasks/",
|
|
2394
|
-
upload: "uploads/",
|
|
2395
|
-
};
|
|
2396
|
-
const expected = mappings[folder];
|
|
2397
|
-
if (!expected)
|
|
2601
|
+
const canonicalPath = canonicalFeatureLayerPath(file, config);
|
|
2602
|
+
if (!canonicalPath)
|
|
2398
2603
|
return undefined;
|
|
2399
|
-
|
|
2604
|
+
const featurePrefix = `${featuresPath}/${match[1]}/`;
|
|
2605
|
+
const actualRelative = file.slice(featurePrefix.length);
|
|
2606
|
+
const expectedRelative = canonicalPath.slice(featurePrefix.length);
|
|
2607
|
+
const actual = actualRelative.includes("/")
|
|
2608
|
+
? `${actualRelative.split("/")[0]}/`
|
|
2609
|
+
: actualRelative;
|
|
2610
|
+
const expectedDirectory = path.posix.dirname(expectedRelative);
|
|
2611
|
+
const expected = expectedDirectory === "." ? expectedRelative : `${expectedDirectory}/`;
|
|
2612
|
+
return { actual, expected };
|
|
2400
2613
|
}
|
|
2401
2614
|
function isNotificationDefinitionFile(file, featuresPath) {
|
|
2402
2615
|
return isFeatureArtifactFile(file, featuresPath, "notifications");
|
|
@@ -2478,6 +2691,14 @@ async function inspectWorkflowRegistrationDrift(targetDir, files, config, conven
|
|
|
2478
2691
|
}
|
|
2479
2692
|
}
|
|
2480
2693
|
const infraDir = directoryPath(path.dirname(config.paths.portWiring));
|
|
2694
|
+
for (const file of drift.listeners.unsafeLifecycleFiles) {
|
|
2695
|
+
diagnostics.push({
|
|
2696
|
+
severity: "warning",
|
|
2697
|
+
code: "BEIGNET_LISTENER_LIFECYCLE_UNSAFE",
|
|
2698
|
+
file,
|
|
2699
|
+
message: `${file} calls registerListeners(...) without the complete provider lifecycle, so server startup can resolve before listeners are ready or shutdown can leak subscriptions. Register listeners in start(), return or await registration.ready, and return or await registration.unsubscribe() in stop().`,
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2481
2702
|
const listenerTarget = drift.listeners.wiringFile
|
|
2482
2703
|
? `${drift.listeners.wiringFile}, which already calls registerListeners(...)`
|
|
2483
2704
|
: drift.listeners.eventBusFile
|
|
@@ -2591,7 +2812,7 @@ async function workflowRegistrationDrift(targetDir, files, config) {
|
|
|
2591
2812
|
events: [],
|
|
2592
2813
|
jobs: [],
|
|
2593
2814
|
},
|
|
2594
|
-
listeners: { unregistered: [] },
|
|
2815
|
+
listeners: { unregistered: [], unsafeLifecycleFiles: [] },
|
|
2595
2816
|
};
|
|
2596
2817
|
if (registries.length === 0)
|
|
2597
2818
|
return drift;
|
|
@@ -2616,28 +2837,23 @@ async function workflowRegistrationDrift(targetDir, files, config) {
|
|
|
2616
2837
|
// Events without listeners have nothing to deliver on drain, so only
|
|
2617
2838
|
// record-only events stay out of the registry without a warning.
|
|
2618
2839
|
const listened = await listenedEventNames(targetDir, files, config);
|
|
2619
|
-
const
|
|
2620
|
-
const registered = value
|
|
2621
|
-
? identifiersFromArrayExpression(value.text)
|
|
2622
|
-
: new Set();
|
|
2840
|
+
const registered = outboxRegistryIdentifiers(outboxSource, "events");
|
|
2623
2841
|
drift.outbox.events = unregisteredWorkflowRegistries(eventRegistries, registered, (member) => listened.has(member));
|
|
2624
2842
|
}
|
|
2625
2843
|
if (jobRegistries.length > 0) {
|
|
2626
|
-
const
|
|
2627
|
-
const registered = value
|
|
2628
|
-
? identifiersFromArrayExpression(value.text)
|
|
2629
|
-
: new Set();
|
|
2844
|
+
const registered = outboxRegistryIdentifiers(outboxSource, "jobs");
|
|
2630
2845
|
drift.outbox.jobs = unregisteredWorkflowRegistries(jobRegistries, registered);
|
|
2631
2846
|
}
|
|
2632
2847
|
}
|
|
2633
2848
|
}
|
|
2634
2849
|
const listenerRegistries = byKind("listeners");
|
|
2635
2850
|
if (listenerRegistries.length > 0) {
|
|
2636
|
-
const wiring = await listenerWiringReferences(targetDir, files, config);
|
|
2851
|
+
const wiring = await listenerWiringReferences(targetDir, files, config, listenerRegistries);
|
|
2637
2852
|
drift.listeners = {
|
|
2638
2853
|
unregistered: unregisteredWorkflowRegistries(listenerRegistries, wiring.identifiers),
|
|
2639
2854
|
wiringFile: wiring.wiringFile,
|
|
2640
2855
|
eventBusFile: wiring.eventBusFile,
|
|
2856
|
+
unsafeLifecycleFiles: wiring.unsafeLifecycleFiles,
|
|
2641
2857
|
};
|
|
2642
2858
|
}
|
|
2643
2859
|
return drift;
|
|
@@ -2876,8 +3092,9 @@ async function listenedEventNames(targetDir, files, config) {
|
|
|
2876
3092
|
}
|
|
2877
3093
|
return listened;
|
|
2878
3094
|
}
|
|
2879
|
-
async function listenerWiringReferences(targetDir, files, config) {
|
|
3095
|
+
async function listenerWiringReferences(targetDir, files, config, listenerRegistries) {
|
|
2880
3096
|
const identifiers = new Set();
|
|
3097
|
+
const unsafeLifecycleFiles = new Set();
|
|
2881
3098
|
let wiringFile;
|
|
2882
3099
|
let eventBusFile;
|
|
2883
3100
|
let centralListenerRegistryReferenced = false;
|
|
@@ -2889,29 +3106,43 @@ async function listenerWiringReferences(targetDir, files, config) {
|
|
|
2889
3106
|
continue;
|
|
2890
3107
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
2891
3108
|
const namedImports = parseNamedImportSources(source);
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
3109
|
+
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
3110
|
+
const registerListenersBindings = importedRegisterListenersBindings(source, namedImports);
|
|
3111
|
+
const calls = registerListenersCalls(sourceFile, registerListenersBindings);
|
|
3112
|
+
let foundRelevantCall = false;
|
|
3113
|
+
for (const call of calls) {
|
|
3114
|
+
const argsText = call.arguments
|
|
3115
|
+
.map((argument) => argument.getText(sourceFile))
|
|
3116
|
+
.join(",");
|
|
2900
3117
|
const callIdentifiers = identifiersFromArrayExpression(argsText);
|
|
2901
|
-
|
|
2902
|
-
identifiers.add(identifier);
|
|
2903
|
-
}
|
|
2904
|
-
if (callReferencesCentralListenerRegistry({
|
|
3118
|
+
const referencesCentral = callReferencesCentralListenerRegistry({
|
|
2905
3119
|
callIdentifiers,
|
|
2906
3120
|
namedImports,
|
|
2907
3121
|
importerFile: file,
|
|
2908
3122
|
listenerRegistryFile,
|
|
2909
3123
|
files,
|
|
2910
|
-
})
|
|
3124
|
+
});
|
|
3125
|
+
const referencesFeature = callReferencesFeatureListenerRegistry({
|
|
3126
|
+
callIdentifiers,
|
|
3127
|
+
namedImports,
|
|
3128
|
+
importerFile: file,
|
|
3129
|
+
files,
|
|
3130
|
+
listenerRegistries,
|
|
3131
|
+
});
|
|
3132
|
+
if (!referencesCentral && !referencesFeature)
|
|
3133
|
+
continue;
|
|
3134
|
+
foundRelevantCall = true;
|
|
3135
|
+
for (const identifier of callIdentifiers) {
|
|
3136
|
+
identifiers.add(identifier);
|
|
3137
|
+
}
|
|
3138
|
+
if (referencesCentral) {
|
|
2911
3139
|
centralListenerRegistryReferenced = true;
|
|
2912
3140
|
}
|
|
3141
|
+
if (!hasSafeListenerRegistrationLifecycle(call, sourceFile)) {
|
|
3142
|
+
unsafeLifecycleFiles.add(file);
|
|
3143
|
+
}
|
|
2913
3144
|
}
|
|
2914
|
-
if (
|
|
3145
|
+
if (foundRelevantCall) {
|
|
2915
3146
|
wiringFile ??= file;
|
|
2916
3147
|
}
|
|
2917
3148
|
else if (!eventBusFile && /\bcreate\w*EventBus\s*\(/.test(source)) {
|
|
@@ -2928,7 +3159,256 @@ async function listenerWiringReferences(targetDir, files, config) {
|
|
|
2928
3159
|
}
|
|
2929
3160
|
}
|
|
2930
3161
|
}
|
|
2931
|
-
return {
|
|
3162
|
+
return {
|
|
3163
|
+
identifiers,
|
|
3164
|
+
wiringFile,
|
|
3165
|
+
eventBusFile,
|
|
3166
|
+
unsafeLifecycleFiles: [...unsafeLifecycleFiles].sort(),
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
function registerListenersCalls(sourceFile, bindings) {
|
|
3170
|
+
const calls = [];
|
|
3171
|
+
const visit = (node) => {
|
|
3172
|
+
if (ts.isCallExpression(node) &&
|
|
3173
|
+
((ts.isIdentifier(node.expression) &&
|
|
3174
|
+
bindings.has(node.expression.text)) ||
|
|
3175
|
+
(ts.isPropertyAccessExpression(node.expression) &&
|
|
3176
|
+
node.expression.name.text === "registerListeners" &&
|
|
3177
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
3178
|
+
bindings.has(`${node.expression.expression.text}.*`)))) {
|
|
3179
|
+
calls.push(node);
|
|
3180
|
+
}
|
|
3181
|
+
ts.forEachChild(node, visit);
|
|
3182
|
+
};
|
|
3183
|
+
visit(sourceFile);
|
|
3184
|
+
return calls;
|
|
3185
|
+
}
|
|
3186
|
+
function importedRegisterListenersBindings(source, namedImports) {
|
|
3187
|
+
const bindings = new Set();
|
|
3188
|
+
for (const [localName, imported] of namedImports) {
|
|
3189
|
+
if (imported.importedName === "registerListeners" &&
|
|
3190
|
+
imported.sourcePath === "@beignet/core/events") {
|
|
3191
|
+
bindings.add(localName);
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
const namespaceImport = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+["']@beignet\/core\/events["']/g;
|
|
3195
|
+
for (const match of source.matchAll(namespaceImport)) {
|
|
3196
|
+
bindings.add(`${match[1]}.*`);
|
|
3197
|
+
}
|
|
3198
|
+
return bindings;
|
|
3199
|
+
}
|
|
3200
|
+
function hasSafeListenerRegistrationLifecycle(call, sourceFile) {
|
|
3201
|
+
const startHook = enclosingListenerLifecycleHook(call, "start");
|
|
3202
|
+
const target = listenerRegistrationTarget(call, sourceFile);
|
|
3203
|
+
if (!startHook || !target)
|
|
3204
|
+
return false;
|
|
3205
|
+
const lifecycleOwner = listenerLifecycleOwner(startHook);
|
|
3206
|
+
if (!lifecycleOwner)
|
|
3207
|
+
return false;
|
|
3208
|
+
let observesReadiness = false;
|
|
3209
|
+
let observesCleanup = false;
|
|
3210
|
+
const visit = (node) => {
|
|
3211
|
+
if (!observesReadiness &&
|
|
3212
|
+
ts.isPropertyAccessExpression(node) &&
|
|
3213
|
+
node.name.text === "ready" &&
|
|
3214
|
+
listenerLifecycleTargetsMatch(listenerLifecycleTarget(node.expression, sourceFile), target) &&
|
|
3215
|
+
enclosingListenerLifecycleHook(node, "start") === startHook &&
|
|
3216
|
+
listenerLifecyclePromiseIsObserved(node, startHook)) {
|
|
3217
|
+
observesReadiness = true;
|
|
3218
|
+
}
|
|
3219
|
+
if (!observesCleanup &&
|
|
3220
|
+
ts.isCallExpression(node) &&
|
|
3221
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
3222
|
+
node.expression.name.text === "unsubscribe" &&
|
|
3223
|
+
listenerLifecycleTargetsMatch(listenerLifecycleTarget(node.expression.expression, sourceFile), target)) {
|
|
3224
|
+
const stopHook = enclosingListenerLifecycleHook(node, "stop");
|
|
3225
|
+
if (stopHook &&
|
|
3226
|
+
listenerLifecycleOwner(stopHook) === lifecycleOwner &&
|
|
3227
|
+
listenerLifecyclePromiseIsObserved(node, stopHook)) {
|
|
3228
|
+
observesCleanup = true;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
if (!observesReadiness || !observesCleanup) {
|
|
3232
|
+
ts.forEachChild(node, visit);
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
visit(lifecycleOwner);
|
|
3236
|
+
return observesReadiness && observesCleanup;
|
|
3237
|
+
}
|
|
3238
|
+
function listenerRegistrationTarget(call, sourceFile) {
|
|
3239
|
+
let expression = call;
|
|
3240
|
+
while (ts.isParenthesizedExpression(expression.parent) ||
|
|
3241
|
+
ts.isAsExpression(expression.parent) ||
|
|
3242
|
+
ts.isSatisfiesExpression(expression.parent) ||
|
|
3243
|
+
ts.isNonNullExpression(expression.parent)) {
|
|
3244
|
+
expression = expression.parent;
|
|
3245
|
+
}
|
|
3246
|
+
const parent = expression.parent;
|
|
3247
|
+
if (ts.isVariableDeclaration(parent) &&
|
|
3248
|
+
parent.initializer === expression &&
|
|
3249
|
+
ts.isIdentifier(parent.name)) {
|
|
3250
|
+
return { text: parent.name.text, binding: parent };
|
|
3251
|
+
}
|
|
3252
|
+
if (ts.isBinaryExpression(parent) &&
|
|
3253
|
+
parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
3254
|
+
parent.right === expression) {
|
|
3255
|
+
return listenerLifecycleTarget(parent.left, sourceFile);
|
|
3256
|
+
}
|
|
3257
|
+
return undefined;
|
|
3258
|
+
}
|
|
3259
|
+
function listenerLifecycleTarget(expression, sourceFile) {
|
|
3260
|
+
let current = expression;
|
|
3261
|
+
while (ts.isParenthesizedExpression(current) ||
|
|
3262
|
+
ts.isAsExpression(current) ||
|
|
3263
|
+
ts.isSatisfiesExpression(current) ||
|
|
3264
|
+
ts.isNonNullExpression(current)) {
|
|
3265
|
+
current = current.expression;
|
|
3266
|
+
}
|
|
3267
|
+
if (!ts.isIdentifier(current) && !ts.isPropertyAccessExpression(current)) {
|
|
3268
|
+
return undefined;
|
|
3269
|
+
}
|
|
3270
|
+
return {
|
|
3271
|
+
text: current.getText(sourceFile),
|
|
3272
|
+
binding: listenerLifecycleRootIdentifier(current)
|
|
3273
|
+
? resolveListenerLifecycleBinding(listenerLifecycleRootIdentifier(current))
|
|
3274
|
+
: undefined,
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
function listenerLifecycleRootIdentifier(expression) {
|
|
3278
|
+
let current = expression;
|
|
3279
|
+
while (ts.isPropertyAccessExpression(current)) {
|
|
3280
|
+
current = current.expression;
|
|
3281
|
+
}
|
|
3282
|
+
return ts.isIdentifier(current) ? current : undefined;
|
|
3283
|
+
}
|
|
3284
|
+
function resolveListenerLifecycleBinding(identifier) {
|
|
3285
|
+
let current = identifier;
|
|
3286
|
+
while (current) {
|
|
3287
|
+
if (ts.isBlock(current) || ts.isSourceFile(current)) {
|
|
3288
|
+
for (const statement of current.statements) {
|
|
3289
|
+
if (!ts.isVariableStatement(statement))
|
|
3290
|
+
continue;
|
|
3291
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
3292
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
3293
|
+
declaration.name.text === identifier.text) {
|
|
3294
|
+
return declaration;
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
if (ts.isFunctionLike(current)) {
|
|
3300
|
+
for (const parameter of current.parameters) {
|
|
3301
|
+
if (ts.isIdentifier(parameter.name) &&
|
|
3302
|
+
parameter.name.text === identifier.text) {
|
|
3303
|
+
return parameter;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
if (ts.isCatchClause(current) &&
|
|
3308
|
+
current.variableDeclaration &&
|
|
3309
|
+
ts.isIdentifier(current.variableDeclaration.name) &&
|
|
3310
|
+
current.variableDeclaration.name.text === identifier.text) {
|
|
3311
|
+
return current.variableDeclaration;
|
|
3312
|
+
}
|
|
3313
|
+
current = current.parent;
|
|
3314
|
+
}
|
|
3315
|
+
return undefined;
|
|
3316
|
+
}
|
|
3317
|
+
function listenerLifecycleTargetsMatch(left, right) {
|
|
3318
|
+
return (left?.text === right.text &&
|
|
3319
|
+
(left.binding !== undefined || right.binding !== undefined
|
|
3320
|
+
? left.binding === right.binding
|
|
3321
|
+
: true));
|
|
3322
|
+
}
|
|
3323
|
+
function enclosingListenerLifecycleHook(node, hookName) {
|
|
3324
|
+
let current = node.parent;
|
|
3325
|
+
while (current) {
|
|
3326
|
+
if (ts.isMethodDeclaration(current)) {
|
|
3327
|
+
return staticPropertyName(current.name) === hookName
|
|
3328
|
+
? current
|
|
3329
|
+
: undefined;
|
|
3330
|
+
}
|
|
3331
|
+
if (ts.isFunctionExpression(current) || ts.isArrowFunction(current)) {
|
|
3332
|
+
const parent = current.parent;
|
|
3333
|
+
return ts.isPropertyAssignment(parent) &&
|
|
3334
|
+
staticPropertyName(parent.name) === hookName
|
|
3335
|
+
? current
|
|
3336
|
+
: undefined;
|
|
3337
|
+
}
|
|
3338
|
+
if (ts.isFunctionDeclaration(current))
|
|
3339
|
+
return undefined;
|
|
3340
|
+
current = current.parent;
|
|
3341
|
+
}
|
|
3342
|
+
return undefined;
|
|
3343
|
+
}
|
|
3344
|
+
function listenerLifecycleOwner(hook) {
|
|
3345
|
+
if (ts.isMethodDeclaration(hook)) {
|
|
3346
|
+
return ts.isObjectLiteralExpression(hook.parent) ? hook.parent : undefined;
|
|
3347
|
+
}
|
|
3348
|
+
const property = hook.parent;
|
|
3349
|
+
return ts.isPropertyAssignment(property) &&
|
|
3350
|
+
ts.isObjectLiteralExpression(property.parent)
|
|
3351
|
+
? property.parent
|
|
3352
|
+
: undefined;
|
|
3353
|
+
}
|
|
3354
|
+
function listenerLifecyclePromiseIsObserved(node, hook) {
|
|
3355
|
+
let current = node;
|
|
3356
|
+
while (current && current !== hook) {
|
|
3357
|
+
const parent = current.parent;
|
|
3358
|
+
if ((ts.isAwaitExpression(parent) && parent.expression === current) ||
|
|
3359
|
+
(ts.isReturnStatement(parent) && parent.expression === current)) {
|
|
3360
|
+
return true;
|
|
3361
|
+
}
|
|
3362
|
+
if (ts.isParenthesizedExpression(parent) ||
|
|
3363
|
+
ts.isAsExpression(parent) ||
|
|
3364
|
+
ts.isSatisfiesExpression(parent) ||
|
|
3365
|
+
ts.isNonNullExpression(parent)) {
|
|
3366
|
+
current = parent;
|
|
3367
|
+
continue;
|
|
3368
|
+
}
|
|
3369
|
+
if (ts.isPropertyAccessExpression(parent) &&
|
|
3370
|
+
parent.expression === current) {
|
|
3371
|
+
const method = parent.name.text;
|
|
3372
|
+
const invocation = parent.parent;
|
|
3373
|
+
if (!["then", "catch", "finally"].includes(method) ||
|
|
3374
|
+
!ts.isCallExpression(invocation) ||
|
|
3375
|
+
invocation.expression !== parent ||
|
|
3376
|
+
method === "catch" ||
|
|
3377
|
+
(method === "then" && invocation.arguments.length > 1)) {
|
|
3378
|
+
return false;
|
|
3379
|
+
}
|
|
3380
|
+
current = invocation;
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
return false;
|
|
3384
|
+
}
|
|
3385
|
+
return false;
|
|
3386
|
+
}
|
|
3387
|
+
function callReferencesFeatureListenerRegistry(args) {
|
|
3388
|
+
for (const identifier of args.callIdentifiers) {
|
|
3389
|
+
for (const registry of args.listenerRegistries) {
|
|
3390
|
+
if (identifier === registry.registryName ||
|
|
3391
|
+
registry.members.includes(identifier)) {
|
|
3392
|
+
return true;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
const imported = args.namedImports.get(identifier);
|
|
3396
|
+
if (!imported)
|
|
3397
|
+
continue;
|
|
3398
|
+
const importedFile = sourceFileFromImport(imported.sourcePath, args.importerFile, args.files);
|
|
3399
|
+
if (!importedFile)
|
|
3400
|
+
continue;
|
|
3401
|
+
for (const registry of args.listenerRegistries) {
|
|
3402
|
+
if ((imported.importedName === registry.registryName &&
|
|
3403
|
+
importedFile === registry.indexFile) ||
|
|
3404
|
+
(registry.members.includes(imported.importedName) &&
|
|
3405
|
+
(importedFile === registry.indexFile ||
|
|
3406
|
+
registry.memberFiles.get(imported.importedName) === importedFile))) {
|
|
3407
|
+
return true;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
return false;
|
|
2932
3412
|
}
|
|
2933
3413
|
function callReferencesCentralListenerRegistry(args) {
|
|
2934
3414
|
if (args.importerFile === args.listenerRegistryFile &&
|
|
@@ -3114,6 +3594,45 @@ async function appHasOutboxDrainEntrypoint(targetDir, files, config) {
|
|
|
3114
3594
|
}
|
|
3115
3595
|
return false;
|
|
3116
3596
|
}
|
|
3597
|
+
async function inspectOutboxJobDispatcherDrift(targetDir, files, config) {
|
|
3598
|
+
if (!hasOutboxRegistry(files, config))
|
|
3599
|
+
return [];
|
|
3600
|
+
const source = await readFile(path.join(targetDir, config.paths.outbox), "utf8");
|
|
3601
|
+
const jobsState = outboxRegistryEntryState(source, "jobs");
|
|
3602
|
+
if (jobsState === "absent" || jobsState === "empty")
|
|
3603
|
+
return [];
|
|
3604
|
+
const analysis = await analyzePortWiringFiles({ targetDir, files, config });
|
|
3605
|
+
const declaresJobs = analysis.declared.has("jobs");
|
|
3606
|
+
const wiresJobs = analysis.bound.has("jobs") || analysis.deferred.has("jobs");
|
|
3607
|
+
if (declaresJobs && wiresJobs)
|
|
3608
|
+
return [];
|
|
3609
|
+
const file = jobsState === "indeterminate"
|
|
3610
|
+
? config.paths.outbox
|
|
3611
|
+
: !declaresJobs
|
|
3612
|
+
? config.paths.ports
|
|
3613
|
+
: config.paths.portWiring;
|
|
3614
|
+
const reason = !analysis.portsFileExists
|
|
3615
|
+
? `${config.paths.ports} does not exist at the configured path`
|
|
3616
|
+
: !declaresJobs
|
|
3617
|
+
? analysis.declarationIndeterminate
|
|
3618
|
+
? `${config.paths.ports} does not expose a statically verifiable jobs declaration`
|
|
3619
|
+
: `${config.paths.ports} does not declare a jobs port`
|
|
3620
|
+
: !analysis.portWiringFileExists
|
|
3621
|
+
? `${config.paths.portWiring} does not exist at the configured path`
|
|
3622
|
+
: analysis.indeterminate
|
|
3623
|
+
? `${config.paths.portWiring} does not expose statically verifiable jobs wiring`
|
|
3624
|
+
: `${config.paths.portWiring} does not bind or defer the jobs port`;
|
|
3625
|
+
return [
|
|
3626
|
+
{
|
|
3627
|
+
severity: "warning",
|
|
3628
|
+
code: "BEIGNET_OUTBOX_JOB_DISPATCHER_MISSING",
|
|
3629
|
+
file,
|
|
3630
|
+
message: jobsState === "indeterminate"
|
|
3631
|
+
? `The jobs entries in the outbox registry at ${config.paths.outbox} could not be statically resolved, and ${reason}. Keep the jobs property explicit so doctor can verify it, or add jobs: JobDispatcherPort to AppPorts and bind it directly or list "jobs" as deferred with an inline or provider-backed dispatcher. If the app uses custom paths, configure paths.ports and paths.portWiring in beignet.config.ts.`
|
|
3632
|
+
: `The outbox registry in ${config.paths.outbox} contains jobs, but ${reason}. Add jobs: JobDispatcherPort to AppPorts and bind it directly or list "jobs" as deferred with an inline or provider-backed dispatcher. If the app uses custom paths, configure paths.ports and paths.portWiring in beignet.config.ts.`,
|
|
3633
|
+
},
|
|
3634
|
+
];
|
|
3635
|
+
}
|
|
3117
3636
|
async function inspectPortProviderDrift(targetDir, files, config, convention) {
|
|
3118
3637
|
if (!convention.resourceGenerator)
|
|
3119
3638
|
return [];
|
|
@@ -5276,7 +5795,8 @@ async function contractsFromRoutesSource(targetDir, files, config, file, source,
|
|
|
5276
5795
|
return undefined;
|
|
5277
5796
|
const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
|
|
5278
5797
|
const registeredGroups = await registeredRouteGroupsForSource(targetDir, files, file, source, routeIdentifier);
|
|
5279
|
-
const
|
|
5798
|
+
const { contracts } = await readContracts(targetDir, files, config);
|
|
5799
|
+
const registeredContracts = contractsForRegisteredRouteGroups(contracts, routeGroups, registeredGroups);
|
|
5280
5800
|
return new Set(registeredContracts.map((contract) => contract.exportName));
|
|
5281
5801
|
}
|
|
5282
5802
|
function contractsFromRoutesIdentifier(source, exportName) {
|