@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/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") {
|
|
635
|
+
return undefined;
|
|
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) {
|
|
640
|
+
return undefined;
|
|
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)
|
|
524
658
|
return undefined;
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
const pathMatch = /path\s*:\s*["']([^"']+)["']/.exec(configSource);
|
|
528
|
-
if (!methodMatch || !pathMatch)
|
|
659
|
+
const options = objectLiteral(factoryCall.arguments[0]);
|
|
660
|
+
if (!options)
|
|
529
661
|
return undefined;
|
|
530
|
-
const
|
|
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;
|
|
558
734
|
}
|
|
735
|
+
if (ts.isIdentifier(expression)) {
|
|
736
|
+
return { baseIdentifier: expression.text, calls };
|
|
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");
|
|
@@ -2616,17 +2829,11 @@ async function workflowRegistrationDrift(targetDir, files, config) {
|
|
|
2616
2829
|
// Events without listeners have nothing to deliver on drain, so only
|
|
2617
2830
|
// record-only events stay out of the registry without a warning.
|
|
2618
2831
|
const listened = await listenedEventNames(targetDir, files, config);
|
|
2619
|
-
const
|
|
2620
|
-
const registered = value
|
|
2621
|
-
? identifiersFromArrayExpression(value.text)
|
|
2622
|
-
: new Set();
|
|
2832
|
+
const registered = outboxRegistryIdentifiers(outboxSource, "events");
|
|
2623
2833
|
drift.outbox.events = unregisteredWorkflowRegistries(eventRegistries, registered, (member) => listened.has(member));
|
|
2624
2834
|
}
|
|
2625
2835
|
if (jobRegistries.length > 0) {
|
|
2626
|
-
const
|
|
2627
|
-
const registered = value
|
|
2628
|
-
? identifiersFromArrayExpression(value.text)
|
|
2629
|
-
: new Set();
|
|
2836
|
+
const registered = outboxRegistryIdentifiers(outboxSource, "jobs");
|
|
2630
2837
|
drift.outbox.jobs = unregisteredWorkflowRegistries(jobRegistries, registered);
|
|
2631
2838
|
}
|
|
2632
2839
|
}
|
|
@@ -3114,6 +3321,45 @@ async function appHasOutboxDrainEntrypoint(targetDir, files, config) {
|
|
|
3114
3321
|
}
|
|
3115
3322
|
return false;
|
|
3116
3323
|
}
|
|
3324
|
+
async function inspectOutboxJobDispatcherDrift(targetDir, files, config) {
|
|
3325
|
+
if (!hasOutboxRegistry(files, config))
|
|
3326
|
+
return [];
|
|
3327
|
+
const source = await readFile(path.join(targetDir, config.paths.outbox), "utf8");
|
|
3328
|
+
const jobsState = outboxRegistryEntryState(source, "jobs");
|
|
3329
|
+
if (jobsState === "absent" || jobsState === "empty")
|
|
3330
|
+
return [];
|
|
3331
|
+
const analysis = await analyzePortWiringFiles({ targetDir, files, config });
|
|
3332
|
+
const declaresJobs = analysis.declared.has("jobs");
|
|
3333
|
+
const wiresJobs = analysis.bound.has("jobs") || analysis.deferred.has("jobs");
|
|
3334
|
+
if (declaresJobs && wiresJobs)
|
|
3335
|
+
return [];
|
|
3336
|
+
const file = jobsState === "indeterminate"
|
|
3337
|
+
? config.paths.outbox
|
|
3338
|
+
: !declaresJobs
|
|
3339
|
+
? config.paths.ports
|
|
3340
|
+
: config.paths.portWiring;
|
|
3341
|
+
const reason = !analysis.portsFileExists
|
|
3342
|
+
? `${config.paths.ports} does not exist at the configured path`
|
|
3343
|
+
: !declaresJobs
|
|
3344
|
+
? analysis.declarationIndeterminate
|
|
3345
|
+
? `${config.paths.ports} does not expose a statically verifiable jobs declaration`
|
|
3346
|
+
: `${config.paths.ports} does not declare a jobs port`
|
|
3347
|
+
: !analysis.portWiringFileExists
|
|
3348
|
+
? `${config.paths.portWiring} does not exist at the configured path`
|
|
3349
|
+
: analysis.indeterminate
|
|
3350
|
+
? `${config.paths.portWiring} does not expose statically verifiable jobs wiring`
|
|
3351
|
+
: `${config.paths.portWiring} does not bind or defer the jobs port`;
|
|
3352
|
+
return [
|
|
3353
|
+
{
|
|
3354
|
+
severity: "warning",
|
|
3355
|
+
code: "BEIGNET_OUTBOX_JOB_DISPATCHER_MISSING",
|
|
3356
|
+
file,
|
|
3357
|
+
message: jobsState === "indeterminate"
|
|
3358
|
+
? `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.`
|
|
3359
|
+
: `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.`,
|
|
3360
|
+
},
|
|
3361
|
+
];
|
|
3362
|
+
}
|
|
3117
3363
|
async function inspectPortProviderDrift(targetDir, files, config, convention) {
|
|
3118
3364
|
if (!convention.resourceGenerator)
|
|
3119
3365
|
return [];
|
|
@@ -5276,7 +5522,8 @@ async function contractsFromRoutesSource(targetDir, files, config, file, source,
|
|
|
5276
5522
|
return undefined;
|
|
5277
5523
|
const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
|
|
5278
5524
|
const registeredGroups = await registeredRouteGroupsForSource(targetDir, files, file, source, routeIdentifier);
|
|
5279
|
-
const
|
|
5525
|
+
const { contracts } = await readContracts(targetDir, files, config);
|
|
5526
|
+
const registeredContracts = contractsForRegisteredRouteGroups(contracts, routeGroups, registeredGroups);
|
|
5280
5527
|
return new Set(registeredContracts.map((contract) => contract.exportName));
|
|
5281
5528
|
}
|
|
5282
5529
|
function contractsFromRoutesIdentifier(source, exportName) {
|