@beignet/cli 0.0.41 → 0.0.43
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 +23 -0
- package/README.md +96 -25
- package/dist/analysis/source-index.d.ts +1 -1
- package/dist/analysis/source-index.d.ts.map +1 -1
- package/dist/analysis/source-index.js +4 -2
- package/dist/analysis/source-index.js.map +1 -1
- package/dist/analysis/workspace.d.ts +3 -2
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +51 -39
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +65 -4
- package/dist/app-map.js.map +1 -1
- package/dist/choices.d.ts +18 -0
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +35 -0
- package/dist/choices.js.map +1 -1
- package/dist/db.d.ts +18 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -7
- package/dist/db.js.map +1 -1
- package/dist/doctor-fixes.d.ts +64 -0
- package/dist/doctor-fixes.d.ts.map +1 -0
- package/dist/doctor-fixes.js +142 -0
- package/dist/doctor-fixes.js.map +1 -0
- package/dist/explain.d.ts +3 -1
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +136 -42
- package/dist/explain.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +92 -25
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +38 -9
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +385 -251
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +6 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +3 -2
- package/dist/lib.js.map +1 -1
- package/dist/make/inbox.js +4 -4
- package/dist/make/payments.js +6 -6
- package/dist/make/shared.js +3 -3
- package/dist/make/shared.js.map +1 -1
- package/dist/make/tenancy.d.ts.map +1 -1
- package/dist/make/tenancy.js +12 -14
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +33 -23
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +121 -13
- package/dist/mcp.js.map +1 -1
- package/dist/registry-edits.d.ts +7 -1
- package/dist/registry-edits.d.ts.map +1 -1
- package/dist/registry-edits.js +61 -43
- package/dist/registry-edits.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +34 -11
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +5 -5
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/mysql.js +3 -3
- package/dist/templates/db/postgres.js +3 -3
- package/dist/templates/db/sqlite.js +3 -3
- package/dist/templates/server.js +4 -4
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +2 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +7 -4
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/todos.js +2 -2
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +43 -10
- package/src/analysis/source-index.ts +12 -3
- package/src/analysis/workspace.ts +94 -52
- package/src/app-map.ts +82 -4
- package/src/choices.ts +57 -0
- package/src/db.ts +45 -15
- package/src/doctor-fixes.ts +252 -0
- package/src/explain.ts +151 -43
- package/src/index.ts +130 -35
- package/src/inspect.ts +537 -295
- package/src/lib.ts +28 -1
- package/src/make/inbox.ts +4 -4
- package/src/make/payments.ts +6 -6
- package/src/make/shared.ts +3 -3
- package/src/make/tenancy.ts +12 -14
- package/src/make.ts +36 -24
- package/src/mcp.ts +187 -13
- package/src/registry-edits.ts +81 -39
- package/src/templates/agents.ts +34 -11
- package/src/templates/base.ts +5 -4
- package/src/templates/db/mysql.ts +3 -3
- package/src/templates/db/postgres.ts +3 -3
- package/src/templates/db/sqlite.ts +3 -3
- package/src/templates/server.ts +4 -4
- package/src/templates/shared.ts +14 -6
- package/src/templates/todos.ts +2 -2
package/src/explain.ts
CHANGED
|
@@ -8,14 +8,10 @@ import {
|
|
|
8
8
|
mapApp,
|
|
9
9
|
projectAppMap,
|
|
10
10
|
} from "./app-map.js";
|
|
11
|
+
import { appMapNodeKinds } from "./app-map-schema.js";
|
|
11
12
|
|
|
12
13
|
/** Targets supported by `beignet explain` and the MCP `explain` tool. */
|
|
13
|
-
export const explainTargetKinds = [
|
|
14
|
-
"feature",
|
|
15
|
-
"route",
|
|
16
|
-
"provider",
|
|
17
|
-
"diagnostic",
|
|
18
|
-
] as const;
|
|
14
|
+
export const explainTargetKinds = [...appMapNodeKinds, "diagnostic"] as const;
|
|
19
15
|
|
|
20
16
|
/** One supported explanation target kind. */
|
|
21
17
|
export type ExplainTargetKind = (typeof explainTargetKinds)[number];
|
|
@@ -32,6 +28,8 @@ export type ExplainTarget = {
|
|
|
32
28
|
kind: ExplainTargetKind;
|
|
33
29
|
id: string;
|
|
34
30
|
name: string;
|
|
31
|
+
runtimeName?: string;
|
|
32
|
+
feature?: string;
|
|
35
33
|
source?: AppMapSource;
|
|
36
34
|
status?: AppMapNodeStatus;
|
|
37
35
|
details?: Record<string, unknown>;
|
|
@@ -318,8 +316,13 @@ function resolveTargetNode(
|
|
|
318
316
|
const nodeKind: AppMapNodeKind = kind;
|
|
319
317
|
const candidates = appMap.nodes.filter((node) => node.kind === nodeKind);
|
|
320
318
|
const query = normalized(target);
|
|
319
|
+
const idMatches = candidates.filter((node) => normalized(node.id) === query);
|
|
320
|
+
if (idMatches.length === 1) return idMatches[0];
|
|
321
|
+
|
|
321
322
|
let matches = candidates.filter((node) =>
|
|
322
|
-
aliasesForNode(node).some(
|
|
323
|
+
aliasesForNode(node).some(
|
|
324
|
+
(alias) => normalized(alias) === query && normalized(node.id) !== query,
|
|
325
|
+
),
|
|
323
326
|
);
|
|
324
327
|
|
|
325
328
|
if (kind === "provider" && matches.length === 0) {
|
|
@@ -353,18 +356,27 @@ function resolveTargetNode(
|
|
|
353
356
|
);
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
const available = candidates
|
|
357
|
-
.map((node) => node.name)
|
|
359
|
+
const available = [...new Set(candidates.map(canonicalNodeSelector))]
|
|
358
360
|
.sort()
|
|
359
361
|
.slice(0, 20);
|
|
360
362
|
throw new Error(
|
|
361
|
-
`Could not explain ${kind} "${target}". ${available.length > 0 ? `Available ${kind}
|
|
363
|
+
`Could not explain ${kind} "${target}". ${available.length > 0 ? `Available ${kind} targets: ${available.join(", ")}.` : `This app has no mapped ${kind} targets.`}`,
|
|
362
364
|
);
|
|
363
365
|
}
|
|
364
366
|
|
|
367
|
+
function canonicalNodeSelector(node: AppMapNode): string {
|
|
368
|
+
return node.runtimeName ?? node.name;
|
|
369
|
+
}
|
|
370
|
+
|
|
365
371
|
function aliasesForNode(node: AppMapNode): string[] {
|
|
366
|
-
const aliases = [node.id, node.name];
|
|
372
|
+
const aliases = [node.id, node.name, node.source.file];
|
|
367
373
|
if (node.runtimeName) aliases.push(node.runtimeName);
|
|
374
|
+
if (node.source.exportName) {
|
|
375
|
+
aliases.push(
|
|
376
|
+
node.source.exportName,
|
|
377
|
+
`${node.source.file}#${node.source.exportName}`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
368
380
|
if (node.id.includes("#"))
|
|
369
381
|
aliases.push(node.id.slice(node.id.lastIndexOf("#") + 1));
|
|
370
382
|
if (node.kind === "route" && typeof node.details?.path === "string") {
|
|
@@ -412,6 +424,8 @@ function nodeTarget(
|
|
|
412
424
|
kind,
|
|
413
425
|
id: node.id,
|
|
414
426
|
name: node.name,
|
|
427
|
+
...(node.runtimeName ? { runtimeName: node.runtimeName } : {}),
|
|
428
|
+
...(node.feature ? { feature: node.feature } : {}),
|
|
415
429
|
source: node.source,
|
|
416
430
|
...(node.status ? { status: node.status } : {}),
|
|
417
431
|
...(node.details ? { details: node.details } : {}),
|
|
@@ -654,16 +668,7 @@ function suggestedActions(
|
|
|
654
668
|
findings: ExplainFinding[],
|
|
655
669
|
targetDir: string,
|
|
656
670
|
): ExplainSuggestedAction[] {
|
|
657
|
-
const inspectCommand =
|
|
658
|
-
target.kind === "feature"
|
|
659
|
-
? `beignet map --feature ${target.name} --json`
|
|
660
|
-
: target.kind === "route"
|
|
661
|
-
? "beignet routes --json"
|
|
662
|
-
: target.kind === "provider"
|
|
663
|
-
? "beignet provider audit --json"
|
|
664
|
-
: findings.some((finding) => finding.source === "lint")
|
|
665
|
-
? "beignet lint --json"
|
|
666
|
-
: "beignet doctor --strict --json";
|
|
671
|
+
const inspectCommand = inspectCommandForTarget(target, findings);
|
|
667
672
|
const actions: ExplainSuggestedAction[] = [
|
|
668
673
|
{
|
|
669
674
|
kind: "inspect",
|
|
@@ -688,6 +693,35 @@ function suggestedActions(
|
|
|
688
693
|
return actions;
|
|
689
694
|
}
|
|
690
695
|
|
|
696
|
+
function inspectCommandForTarget(
|
|
697
|
+
target: ExplainTarget,
|
|
698
|
+
findings: ExplainFinding[],
|
|
699
|
+
): string {
|
|
700
|
+
if (target.kind === "feature") {
|
|
701
|
+
return `beignet map --feature ${target.name} --json`;
|
|
702
|
+
}
|
|
703
|
+
if (
|
|
704
|
+
target.kind === "contract" ||
|
|
705
|
+
target.kind === "route" ||
|
|
706
|
+
target.kind === "route-group" ||
|
|
707
|
+
target.kind === "openapi"
|
|
708
|
+
) {
|
|
709
|
+
return "beignet routes --json";
|
|
710
|
+
}
|
|
711
|
+
if (target.kind === "port" || target.kind === "provider") {
|
|
712
|
+
return "beignet provider audit --json";
|
|
713
|
+
}
|
|
714
|
+
if (target.kind === "diagnostic") {
|
|
715
|
+
return findings.every((finding) => finding.source === "lint")
|
|
716
|
+
? "beignet lint --json"
|
|
717
|
+
: "beignet doctor --strict --json";
|
|
718
|
+
}
|
|
719
|
+
if (target.feature) {
|
|
720
|
+
return `beignet map --feature ${target.feature} --json`;
|
|
721
|
+
}
|
|
722
|
+
return `beignet map --kind ${target.kind} --json`;
|
|
723
|
+
}
|
|
724
|
+
|
|
691
725
|
function providerRegistrationSources(target: ExplainTarget): AppMapSource[] {
|
|
692
726
|
if (target.kind !== "provider") return [];
|
|
693
727
|
const sources = target.details?.registrationSources;
|
|
@@ -722,29 +756,103 @@ function explainSummary(
|
|
|
722
756
|
return `${target.name} is ${target.status ?? "mapped"} with ${countLabel(edges.length, "relationship")}, ${countLabel(fileCount, "source")}, and ${countLabel(findings.length, "relevant finding")}.`;
|
|
723
757
|
}
|
|
724
758
|
|
|
759
|
+
const conventionsByTargetKind = {
|
|
760
|
+
feature: [
|
|
761
|
+
"Feature-owned contracts, use cases, policies, ports, workflows, clients, components, and tests stay under features/<feature>/.",
|
|
762
|
+
"Feature route groups and workflow artifacts register through the central server entrypoints.",
|
|
763
|
+
],
|
|
764
|
+
contract: [
|
|
765
|
+
"Contracts own HTTP method, path, schemas, responses, metadata, and route errors.",
|
|
766
|
+
"Contracts stay transport-focused and bind to application behavior through feature routes.",
|
|
767
|
+
],
|
|
768
|
+
route: [
|
|
769
|
+
"Routes bind contracts to use cases and policies without importing concrete infrastructure.",
|
|
770
|
+
"Feature route groups compose centrally in server/routes.ts and runtime route adapters stay thin.",
|
|
771
|
+
],
|
|
772
|
+
"route-group": [
|
|
773
|
+
"Feature route groups are declared in features/<feature>/routes.ts and composed centrally in server/routes.ts.",
|
|
774
|
+
"Route groups bind contracts to use cases while preserving the app's dependency direction.",
|
|
775
|
+
],
|
|
776
|
+
"use-case": [
|
|
777
|
+
"Use cases own validated application workflows and may depend on ports, policies, and domain code, not concrete infrastructure or UI.",
|
|
778
|
+
"Use cases remain callable outside HTTP so routes, tasks, jobs, and agent capabilities can reuse the same behavior.",
|
|
779
|
+
],
|
|
780
|
+
policy: [
|
|
781
|
+
"Feature policies own business authorization for resources owned by that feature.",
|
|
782
|
+
"Authorization remains enforced in application workflows even when contract metadata also declares an ability.",
|
|
783
|
+
],
|
|
784
|
+
ability: [
|
|
785
|
+
"An ability belongs to the policy of the feature that owns the resource being authorized.",
|
|
786
|
+
"Contracts may name authorization abilities, while policies and use cases enforce the business rule.",
|
|
787
|
+
],
|
|
788
|
+
event: [
|
|
789
|
+
"Events represent facts that happened and belong to the feature that owns the fact.",
|
|
790
|
+
"Durable event publication registers through the app's workflow and outbox boundaries.",
|
|
791
|
+
],
|
|
792
|
+
listener: [
|
|
793
|
+
"Listeners react to registered events and delegate application work through ports, jobs, notifications, or use cases.",
|
|
794
|
+
"Feature listeners register through the central listener registry used by runtime wiring and integrity checks.",
|
|
795
|
+
],
|
|
796
|
+
job: [
|
|
797
|
+
"Jobs represent background work and remain explicit at provider-backed execution boundaries.",
|
|
798
|
+
"Feature jobs register through central runtime or outbox registries before workers can dispatch them.",
|
|
799
|
+
],
|
|
800
|
+
schedule: [
|
|
801
|
+
"Schedules represent time-based triggers and delegate work to application workflows.",
|
|
802
|
+
"Feature schedules register centrally so runtime wiring, doctor, and beignet schedule run inspect the same surface.",
|
|
803
|
+
],
|
|
804
|
+
task: [
|
|
805
|
+
"Tasks are explicit operational entrypoints for maintenance, backfills, and other bounded application work.",
|
|
806
|
+
"Feature tasks register centrally so beignet task run and runtime integrity checks use the same definitions.",
|
|
807
|
+
],
|
|
808
|
+
notification: [
|
|
809
|
+
"Notifications represent user-facing communication intent, separate from provider-specific delivery.",
|
|
810
|
+
"Queued notification definitions register with the central notification and job runtime.",
|
|
811
|
+
],
|
|
812
|
+
upload: [
|
|
813
|
+
"Uploads define feature-owned validation and storage workflows while storage remains behind an app port.",
|
|
814
|
+
"Upload route adapters stay thin and reuse the registered feature upload definition.",
|
|
815
|
+
],
|
|
816
|
+
"agent-capability": [
|
|
817
|
+
"Agent capabilities expose curated, validated inputs and outputs by adapting existing application use cases.",
|
|
818
|
+
"Delegated context and authorization resolve centrally before an agent capability executes.",
|
|
819
|
+
],
|
|
820
|
+
registry: [
|
|
821
|
+
"Registries explicitly compose feature declarations into the runtime surface inspected by doctor and app-map.",
|
|
822
|
+
"Central registries remain the source of truth for runtime wiring and integrity checks.",
|
|
823
|
+
],
|
|
824
|
+
port: [
|
|
825
|
+
"Ports define app-facing dependency interfaces; infrastructure adapters provide their concrete implementations.",
|
|
826
|
+
"App ports wire through infra/port-wiring.ts and remain available to use cases through application context.",
|
|
827
|
+
],
|
|
828
|
+
provider: [
|
|
829
|
+
"Ports define app-facing interfaces, providers adapt external systems, and app infrastructure wires them together.",
|
|
830
|
+
"Lifecycle providers register in server/providers.ts and provider-backed app ports wire through infra/port-wiring.ts.",
|
|
831
|
+
],
|
|
832
|
+
table: [
|
|
833
|
+
"Database tables remain infrastructure-owned persistence details rather than domain or use-case dependencies.",
|
|
834
|
+
"Configured schema sources and migrations must describe the same deployed database surface.",
|
|
835
|
+
],
|
|
836
|
+
openapi: [
|
|
837
|
+
"OpenAPI documents derive from the same registered contract surface served by the application.",
|
|
838
|
+
"Documented methods, paths, schemas, errors, and operation IDs stay aligned with their contracts.",
|
|
839
|
+
],
|
|
840
|
+
entrypoint: [
|
|
841
|
+
"Runtime entrypoints compose the server or expose thin framework adapters over registered routes.",
|
|
842
|
+
"Entrypoints reuse central context, routes, providers, and workflow registries instead of duplicating application behavior.",
|
|
843
|
+
],
|
|
844
|
+
test: [
|
|
845
|
+
"Feature behavior tests stay under features/<feature>/tests and exercise use cases, routes, policies, workflows, and UI.",
|
|
846
|
+
"Infrastructure and server integration tests may remain adjacent to the modules they exercise.",
|
|
847
|
+
],
|
|
848
|
+
diagnostic: [
|
|
849
|
+
"Doctor findings describe framework convention drift; lint findings describe dependency-direction violations.",
|
|
850
|
+
"Fix the app structure or wiring named by the finding, then run the complete validation loop.",
|
|
851
|
+
],
|
|
852
|
+
} as const satisfies Record<ExplainTargetKind, readonly string[]>;
|
|
853
|
+
|
|
725
854
|
function conventionsFor(kind: ExplainTargetKind): string[] {
|
|
726
|
-
|
|
727
|
-
case "feature":
|
|
728
|
-
return [
|
|
729
|
-
"Feature-owned contracts, use cases, policies, ports, workflows, clients, components, and tests stay under features/<feature>/.",
|
|
730
|
-
"Feature route groups and workflow artifacts register through the central server entrypoints.",
|
|
731
|
-
];
|
|
732
|
-
case "route":
|
|
733
|
-
return [
|
|
734
|
-
"Contracts own HTTP method, path, schemas, responses, metadata, and route errors; routes bind contracts to use cases.",
|
|
735
|
-
"Feature route groups compose centrally in server/routes.ts and runtime route adapters stay thin.",
|
|
736
|
-
];
|
|
737
|
-
case "provider":
|
|
738
|
-
return [
|
|
739
|
-
"Ports define app-facing interfaces, providers adapt external systems, and app infrastructure wires them together.",
|
|
740
|
-
"Lifecycle providers register in server/providers.ts and provider-backed app ports wire through infra/port-wiring.ts.",
|
|
741
|
-
];
|
|
742
|
-
case "diagnostic":
|
|
743
|
-
return [
|
|
744
|
-
"Doctor findings describe framework convention drift; lint findings describe dependency-direction violations.",
|
|
745
|
-
"Fix the app structure or wiring named by the finding, then run the complete validation loop.",
|
|
746
|
-
];
|
|
747
|
-
}
|
|
855
|
+
return [...conventionsByTargetKind[kind]];
|
|
748
856
|
}
|
|
749
857
|
|
|
750
858
|
function scopeFiles(nodes: AppMapNode[], edges: AppMapEdge[]): Set<string> {
|
package/src/index.ts
CHANGED
|
@@ -19,9 +19,16 @@ import {
|
|
|
19
19
|
type CreateProviderName,
|
|
20
20
|
completionShellChoices,
|
|
21
21
|
createProviderChoices,
|
|
22
|
+
type DatabaseCommand,
|
|
22
23
|
type DatabaseName,
|
|
24
|
+
type DatabaseSchemaDialect,
|
|
25
|
+
type DatabaseSchemaTable,
|
|
26
|
+
type DoctorFixOperationId,
|
|
23
27
|
databaseChoices,
|
|
28
|
+
databaseSchemaDialectChoices,
|
|
29
|
+
databaseSchemaTableChoices,
|
|
24
30
|
databaseStartCommand,
|
|
31
|
+
doctorFixOperationIds,
|
|
25
32
|
type MakeFeatureAddon,
|
|
26
33
|
type MakeFeatureRecipe,
|
|
27
34
|
makeFeatureAddonChoices,
|
|
@@ -143,21 +150,6 @@ type DbFlags = {
|
|
|
143
150
|
cwd?: string;
|
|
144
151
|
};
|
|
145
152
|
|
|
146
|
-
type DatabaseSchemaDialect = "sqlite" | "postgres" | "mysql";
|
|
147
|
-
type DatabaseSchemaTable = "audit" | "idempotency" | "outbox";
|
|
148
|
-
|
|
149
|
-
const databaseSchemaDialectChoices = [
|
|
150
|
-
"sqlite",
|
|
151
|
-
"postgres",
|
|
152
|
-
"mysql",
|
|
153
|
-
] as const satisfies readonly DatabaseSchemaDialect[];
|
|
154
|
-
|
|
155
|
-
const databaseSchemaTableChoices = [
|
|
156
|
-
"audit",
|
|
157
|
-
"idempotency",
|
|
158
|
-
"outbox",
|
|
159
|
-
] as const satisfies readonly DatabaseSchemaTable[];
|
|
160
|
-
|
|
161
153
|
type DbSchemaSyncFlags = DbFlags & {
|
|
162
154
|
dialect?: DatabaseSchemaDialect;
|
|
163
155
|
tables?: readonly DatabaseSchemaTable[];
|
|
@@ -258,6 +250,9 @@ type DoctorFlags = {
|
|
|
258
250
|
json?: boolean;
|
|
259
251
|
strict?: boolean;
|
|
260
252
|
fix?: boolean;
|
|
253
|
+
dryRun?: boolean;
|
|
254
|
+
plan?: string;
|
|
255
|
+
only?: readonly DoctorFixOperationId[];
|
|
261
256
|
cwd?: string;
|
|
262
257
|
format?: OutputFormat;
|
|
263
258
|
};
|
|
@@ -925,16 +920,37 @@ function explainCommand(kind: ExplainTargetKind) {
|
|
|
925
920
|
});
|
|
926
921
|
}
|
|
927
922
|
|
|
923
|
+
const explainRouteCommands = {
|
|
924
|
+
ability: explainCommand("ability"),
|
|
925
|
+
"agent-capability": explainCommand("agent-capability"),
|
|
926
|
+
contract: explainCommand("contract"),
|
|
927
|
+
diagnostic: explainCommand("diagnostic"),
|
|
928
|
+
entrypoint: explainCommand("entrypoint"),
|
|
929
|
+
event: explainCommand("event"),
|
|
930
|
+
feature: explainCommand("feature"),
|
|
931
|
+
job: explainCommand("job"),
|
|
932
|
+
listener: explainCommand("listener"),
|
|
933
|
+
notification: explainCommand("notification"),
|
|
934
|
+
openapi: explainCommand("openapi"),
|
|
935
|
+
policy: explainCommand("policy"),
|
|
936
|
+
port: explainCommand("port"),
|
|
937
|
+
provider: explainCommand("provider"),
|
|
938
|
+
registry: explainCommand("registry"),
|
|
939
|
+
route: explainCommand("route"),
|
|
940
|
+
"route-group": explainCommand("route-group"),
|
|
941
|
+
schedule: explainCommand("schedule"),
|
|
942
|
+
table: explainCommand("table"),
|
|
943
|
+
task: explainCommand("task"),
|
|
944
|
+
test: explainCommand("test"),
|
|
945
|
+
upload: explainCommand("upload"),
|
|
946
|
+
"use-case": explainCommand("use-case"),
|
|
947
|
+
} satisfies Record<ExplainTargetKind, ReturnType<typeof explainCommand>>;
|
|
948
|
+
|
|
928
949
|
const explainRoutes = buildRouteMap({
|
|
929
950
|
docs: {
|
|
930
951
|
brief: "Explain mapped Beignet app concepts with source evidence.",
|
|
931
952
|
},
|
|
932
|
-
routes:
|
|
933
|
-
diagnostic: explainCommand("diagnostic"),
|
|
934
|
-
feature: explainCommand("feature"),
|
|
935
|
-
provider: explainCommand("provider"),
|
|
936
|
-
route: explainCommand("route"),
|
|
937
|
-
},
|
|
953
|
+
routes: explainRouteCommands,
|
|
938
954
|
});
|
|
939
955
|
|
|
940
956
|
const doctorCommand = buildCommand<DoctorFlags, [], CliContext>({
|
|
@@ -956,27 +972,103 @@ const doctorCommand = buildCommand<DoctorFlags, [], CliContext>({
|
|
|
956
972
|
withNegated: false,
|
|
957
973
|
brief: "Apply low-risk fixes before reporting.",
|
|
958
974
|
},
|
|
975
|
+
dryRun: {
|
|
976
|
+
...dryRunFlag,
|
|
977
|
+
brief: "Preview an exact doctor fix plan without writing files.",
|
|
978
|
+
},
|
|
979
|
+
plan: parsedStringFlag(
|
|
980
|
+
"Apply only when the current repair plan matches this plan ID.",
|
|
981
|
+
),
|
|
982
|
+
only: {
|
|
983
|
+
kind: "enum",
|
|
984
|
+
values: doctorFixOperationIds,
|
|
985
|
+
optional: true,
|
|
986
|
+
variadic: ",",
|
|
987
|
+
brief: "Apply only these repair operation IDs. Requires --plan.",
|
|
988
|
+
},
|
|
959
989
|
cwd: cwdFlag,
|
|
960
990
|
format: formatFlag,
|
|
961
|
-
},
|
|
991
|
+
} satisfies FlagParametersForType<DoctorFlags, CliContext>,
|
|
962
992
|
},
|
|
963
993
|
loader: async () => {
|
|
964
|
-
const {
|
|
965
|
-
|
|
994
|
+
const {
|
|
995
|
+
applyDoctorFixPlan,
|
|
996
|
+
applyDoctorFixesWithResult,
|
|
997
|
+
createDoctorFixInspectionResult,
|
|
998
|
+
formatDoctor,
|
|
999
|
+
formatDoctorFixPlan,
|
|
1000
|
+
formatDoctorFixPlanGithub,
|
|
1001
|
+
formatDoctorGithub,
|
|
1002
|
+
inspectApp,
|
|
1003
|
+
planDoctorFixes,
|
|
1004
|
+
} = await import("./inspect.js");
|
|
966
1005
|
|
|
967
1006
|
return async function runDoctor(this: CliContext, flags: DoctorFlags) {
|
|
968
1007
|
const format = resolveOutputFormat(flags);
|
|
969
|
-
const
|
|
970
|
-
|
|
1008
|
+
const only = flags.only ?? [];
|
|
1009
|
+
if (flags.dryRun && !flags.fix) {
|
|
1010
|
+
throw new Error("doctor --dry-run requires --fix.");
|
|
1011
|
+
}
|
|
1012
|
+
if (flags.plan && !flags.fix) {
|
|
1013
|
+
throw new Error("doctor --plan requires --fix.");
|
|
1014
|
+
}
|
|
1015
|
+
if (flags.dryRun && flags.plan) {
|
|
1016
|
+
throw new Error("doctor --dry-run cannot be combined with --plan.");
|
|
1017
|
+
}
|
|
1018
|
+
if (flags.dryRun && only.length > 0) {
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
"doctor --dry-run previews the complete plan; use --only when applying it.",
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (only.length > 0 && !flags.plan) {
|
|
1024
|
+
throw new Error("doctor --only requires --plan.");
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (flags.dryRun) {
|
|
1028
|
+
const plan = await planDoctorFixes({
|
|
1029
|
+
cwd: flags.cwd,
|
|
1030
|
+
strict: Boolean(flags.strict),
|
|
1031
|
+
});
|
|
1032
|
+
writeOutput(
|
|
1033
|
+
this,
|
|
1034
|
+
format === "json"
|
|
1035
|
+
? JSON.stringify(plan, null, 2)
|
|
1036
|
+
: format === "github"
|
|
1037
|
+
? formatDoctorFixPlanGithub(plan)
|
|
1038
|
+
: formatDoctorFixPlan(plan, { color: useColor() }),
|
|
1039
|
+
);
|
|
1040
|
+
if (
|
|
1041
|
+
plan.diagnostics.some(
|
|
1042
|
+
(diagnostic) =>
|
|
1043
|
+
diagnostic.severity === "error" ||
|
|
1044
|
+
(flags.strict && diagnostic.severity === "warning"),
|
|
1045
|
+
)
|
|
1046
|
+
) {
|
|
1047
|
+
this.process.exitCode = 1;
|
|
1048
|
+
}
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const applied = flags.plan
|
|
1053
|
+
? await applyDoctorFixPlan({
|
|
971
1054
|
cwd: flags.cwd,
|
|
972
1055
|
strict: Boolean(flags.strict),
|
|
1056
|
+
planId: flags.plan,
|
|
1057
|
+
...(only.length > 0 ? { fixIds: only } : {}),
|
|
973
1058
|
})
|
|
974
|
-
:
|
|
975
|
-
|
|
1059
|
+
: flags.fix
|
|
1060
|
+
? await applyDoctorFixesWithResult({
|
|
1061
|
+
cwd: flags.cwd,
|
|
1062
|
+
strict: Boolean(flags.strict),
|
|
1063
|
+
})
|
|
1064
|
+
: undefined;
|
|
1065
|
+
const inspected = await inspectApp({
|
|
976
1066
|
cwd: flags.cwd,
|
|
977
1067
|
strict: Boolean(flags.strict),
|
|
978
1068
|
});
|
|
979
|
-
result
|
|
1069
|
+
const result = applied
|
|
1070
|
+
? createDoctorFixInspectionResult(inspected, applied)
|
|
1071
|
+
: inspected;
|
|
980
1072
|
writeOutput(
|
|
981
1073
|
this,
|
|
982
1074
|
format === "json"
|
|
@@ -1241,9 +1333,7 @@ const providerRoutes = buildRouteMap({
|
|
|
1241
1333
|
},
|
|
1242
1334
|
});
|
|
1243
1335
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
function databaseCommand(command: DatabaseCommandName) {
|
|
1336
|
+
function databaseCommand(command: DatabaseCommand) {
|
|
1247
1337
|
return buildCommand<DbFlags, [], CliContext>({
|
|
1248
1338
|
docs: {
|
|
1249
1339
|
brief: `Run the app's db:${command} script.`,
|
|
@@ -1252,13 +1342,18 @@ function databaseCommand(command: DatabaseCommandName) {
|
|
|
1252
1342
|
flags: dbFlagParameters,
|
|
1253
1343
|
},
|
|
1254
1344
|
loader: async () => {
|
|
1255
|
-
const { runDatabaseCommand } =
|
|
1345
|
+
const { defaultDatabaseCommandMaxOutputBytes, runDatabaseCommand } =
|
|
1346
|
+
await import("./db.js");
|
|
1256
1347
|
|
|
1257
1348
|
return async function runDb(this: CliContext, flags: DbFlags) {
|
|
1349
|
+
const captureOutput = Boolean(flags.json);
|
|
1258
1350
|
const result = await runDatabaseCommand({
|
|
1259
1351
|
command,
|
|
1260
1352
|
cwd: flags.cwd,
|
|
1261
|
-
captureOutput
|
|
1353
|
+
captureOutput,
|
|
1354
|
+
maxOutputBytes: captureOutput
|
|
1355
|
+
? defaultDatabaseCommandMaxOutputBytes
|
|
1356
|
+
: undefined,
|
|
1262
1357
|
dryRun: Boolean(flags.dryRun),
|
|
1263
1358
|
});
|
|
1264
1359
|
|
|
@@ -1329,7 +1424,7 @@ const dbRoutes = buildRouteMap({
|
|
|
1329
1424
|
reset: databaseCommand("reset"),
|
|
1330
1425
|
schema: dbSchemaRoutes,
|
|
1331
1426
|
seed: databaseCommand("seed"),
|
|
1332
|
-
},
|
|
1427
|
+
} satisfies Record<DatabaseCommand | "schema", unknown>,
|
|
1333
1428
|
});
|
|
1334
1429
|
|
|
1335
1430
|
const taskRunCommand = buildCommand<TaskRunFlags, [string], CliContext>({
|