@beignet/cli 0.0.51 → 0.0.53
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 +46 -0
- package/README.md +182 -19
- package/dist/analysis/layers.d.ts +2 -0
- package/dist/analysis/layers.d.ts.map +1 -1
- package/dist/analysis/layers.js +9 -2
- package/dist/analysis/layers.js.map +1 -1
- package/dist/analysis/port-wiring.d.ts +4 -2
- package/dist/analysis/port-wiring.d.ts.map +1 -1
- package/dist/analysis/port-wiring.js +41 -11
- package/dist/analysis/port-wiring.js.map +1 -1
- package/dist/analysis/source-files.d.ts +15 -0
- package/dist/analysis/source-files.d.ts.map +1 -0
- package/dist/analysis/source-files.js +52 -0
- package/dist/analysis/source-files.js.map +1 -0
- package/dist/analysis/source-index.d.ts +2 -1
- package/dist/analysis/source-index.d.ts.map +1 -1
- package/dist/analysis/source-index.js +34 -5
- package/dist/analysis/source-index.js.map +1 -1
- package/dist/analysis/workspace-routes.d.ts +8 -0
- package/dist/analysis/workspace-routes.d.ts.map +1 -0
- package/dist/analysis/workspace-routes.js +291 -0
- package/dist/analysis/workspace-routes.js.map +1 -0
- package/dist/analysis/workspace.d.ts +24 -3
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +184 -43
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map-changes.d.ts.map +1 -1
- package/dist/app-map-changes.js +24 -14
- package/dist/app-map-changes.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +87 -81
- package/dist/app-map.js.map +1 -1
- package/dist/check.d.ts +12 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +92 -25
- package/dist/check.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -1
- package/dist/git-changes.d.ts +17 -1
- package/dist/git-changes.d.ts.map +1 -1
- package/dist/git-changes.js +118 -84
- package/dist/git-changes.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +424 -129
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +108 -43
- package/dist/lint.js.map +1 -1
- package/dist/make/shared.js +1 -1
- package/dist/make.js +18 -1
- package/dist/make.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/provider-add.js +2 -2
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +22 -6
- package/dist/provider-audit.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +36 -13
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.js +5 -5
- package/dist/templates/db/mysql.d.ts.map +1 -1
- package/dist/templates/db/mysql.js +51 -22
- package/dist/templates/db/mysql.js.map +1 -1
- package/dist/templates/db/postgres.d.ts.map +1 -1
- package/dist/templates/db/postgres.js +60 -20
- package/dist/templates/db/postgres.js.map +1 -1
- package/dist/templates/db/sqlite.d.ts.map +1 -1
- package/dist/templates/db/sqlite.js +51 -23
- package/dist/templates/db/sqlite.js.map +1 -1
- package/dist/templates/shadcn.js +1 -1
- package/dist/templates/shadcn.js.map +1 -1
- package/dist/templates/shared.js +6 -6
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.d.ts.map +1 -1
- package/dist/templates/testing.js +7 -19
- package/dist/templates/testing.js.map +1 -1
- package/dist/test-discovery.d.ts +9 -0
- package/dist/test-discovery.d.ts.map +1 -0
- package/dist/test-discovery.js +40 -0
- package/dist/test-discovery.js.map +1 -0
- package/package.json +8 -5
- package/skills/app-structure/SKILL.md +74 -7
- package/src/analysis/layers.ts +13 -2
- package/src/analysis/port-wiring.ts +68 -11
- package/src/analysis/source-files.ts +61 -0
- package/src/analysis/source-index.ts +56 -4
- package/src/analysis/workspace-routes.ts +385 -0
- package/src/analysis/workspace.ts +302 -59
- package/src/app-map-changes.ts +41 -13
- package/src/app-map.ts +114 -103
- package/src/check.ts +115 -25
- package/src/config.ts +33 -0
- package/src/git-changes.ts +218 -86
- package/src/index.ts +33 -5
- package/src/inspect.ts +608 -156
- package/src/lint.ts +152 -48
- package/src/make/shared.ts +1 -1
- package/src/make.ts +31 -1
- package/src/mcp.ts +1 -1
- package/src/provider-add.ts +2 -2
- package/src/provider-audit.ts +30 -10
- package/src/templates/agents.ts +36 -13
- package/src/templates/base.ts +5 -5
- package/src/templates/db/mysql.ts +51 -22
- package/src/templates/db/postgres.ts +60 -20
- package/src/templates/db/sqlite.ts +51 -23
- package/src/templates/shadcn.ts +1 -1
- package/src/templates/shared.ts +6 -6
- package/src/templates/testing.ts +12 -19
- package/src/test-discovery.ts +53 -0
package/src/lint.ts
CHANGED
|
@@ -3,15 +3,25 @@ import path from "node:path";
|
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
import {
|
|
5
5
|
canonicalFeatureLayerPath,
|
|
6
|
-
|
|
6
|
+
classifyWorkspaceSourcePath as classifyPath,
|
|
7
7
|
type SourceLayer,
|
|
8
8
|
} from "./analysis/layers.js";
|
|
9
|
+
import {
|
|
10
|
+
isAnalysisSourceFile,
|
|
11
|
+
isAnalysisSourceFileExtension,
|
|
12
|
+
stripSourceFileExtension,
|
|
13
|
+
} from "./analysis/source-files.js";
|
|
9
14
|
import {
|
|
10
15
|
type ModuleReference,
|
|
11
16
|
moduleReferences,
|
|
12
17
|
} from "./analysis/source-index.js";
|
|
13
18
|
import {
|
|
19
|
+
type AnalysisWorkspace,
|
|
14
20
|
createAnalysisWorkspace,
|
|
21
|
+
findProjectForFile,
|
|
22
|
+
projectForFile,
|
|
23
|
+
projectPath,
|
|
24
|
+
projectSource,
|
|
15
25
|
resolveLocalModulePath,
|
|
16
26
|
} from "./analysis/workspace.js";
|
|
17
27
|
import { createPainter } from "./ansi.js";
|
|
@@ -77,12 +87,19 @@ export async function lintApp(
|
|
|
77
87
|
const importGraph: ImportGraph = new Map();
|
|
78
88
|
|
|
79
89
|
for (const file of sourceFiles) {
|
|
80
|
-
const sourceLayer = classifyPath(file,
|
|
90
|
+
const sourceLayer = classifyPath(file, workspace);
|
|
91
|
+
const source = projectSource(workspace, file);
|
|
81
92
|
const sourceFile = await workspace.readSourceFile(file);
|
|
82
93
|
sourceFileByPath.set(file, sourceFile);
|
|
83
94
|
importGraph.set(file, await parseImports(workspace, file));
|
|
84
95
|
|
|
85
|
-
const
|
|
96
|
+
const localCanonicalPath = canonicalFeatureLayerPath(
|
|
97
|
+
source.file,
|
|
98
|
+
source.project.config,
|
|
99
|
+
);
|
|
100
|
+
const canonicalLayerPath = localCanonicalPath
|
|
101
|
+
? projectPath(source.project, localCanonicalPath)
|
|
102
|
+
: undefined;
|
|
86
103
|
if (canonicalLayerPath) {
|
|
87
104
|
diagnostics.push({
|
|
88
105
|
severity: "error",
|
|
@@ -95,10 +112,17 @@ export async function lintApp(
|
|
|
95
112
|
});
|
|
96
113
|
}
|
|
97
114
|
|
|
115
|
+
for (const reference of importGraph.get(file) ?? []) {
|
|
116
|
+
const issue = workspaceImportViolation(workspace, file, reference);
|
|
117
|
+
if (issue) diagnostics.push(issue);
|
|
118
|
+
}
|
|
98
119
|
if (sourceLayer === "test" || sourceLayer === "unknown") continue;
|
|
99
120
|
|
|
100
|
-
if (isLegacySharedDomainPath(file, config)) {
|
|
101
|
-
const canonicalSharedDomain =
|
|
121
|
+
if (isLegacySharedDomainPath(source.file, source.project.config)) {
|
|
122
|
+
const canonicalSharedDomain = projectPath(
|
|
123
|
+
source.project,
|
|
124
|
+
sharedDomainPath(source.project.config),
|
|
125
|
+
);
|
|
102
126
|
diagnostics.push({
|
|
103
127
|
severity: "error",
|
|
104
128
|
code: "BEIGNET_CANONICAL_STRUCTURE",
|
|
@@ -115,7 +139,7 @@ export async function lintApp(
|
|
|
115
139
|
lintUnresolvedModuleReference(file, sourceLayer, reference) ??
|
|
116
140
|
(sourceLayer === "schema"
|
|
117
141
|
? undefined
|
|
118
|
-
: lintImport(file, sourceLayer, reference,
|
|
142
|
+
: lintImport(file, sourceLayer, reference, workspace));
|
|
119
143
|
if (diagnostic) {
|
|
120
144
|
diagnostics.push({
|
|
121
145
|
...diagnostic,
|
|
@@ -128,14 +152,14 @@ export async function lintApp(
|
|
|
128
152
|
|
|
129
153
|
diagnostics.push(
|
|
130
154
|
...lintTransitiveBoundaries({
|
|
131
|
-
|
|
155
|
+
workspace,
|
|
132
156
|
graph: importGraph,
|
|
133
157
|
}),
|
|
134
158
|
);
|
|
135
159
|
|
|
136
160
|
diagnostics.push(
|
|
137
161
|
...lintRuntimeBoundary({
|
|
138
|
-
|
|
162
|
+
workspace,
|
|
139
163
|
graph: importGraph,
|
|
140
164
|
sourceFileByPath,
|
|
141
165
|
}),
|
|
@@ -202,10 +226,7 @@ export function formatLintGithub(result: LintAppResult): string {
|
|
|
202
226
|
}
|
|
203
227
|
|
|
204
228
|
function lintSourceFiles(files: string[]): string[] {
|
|
205
|
-
return files.filter(
|
|
206
|
-
if (/\.d\.(c|m)?ts$/.test(file)) return false;
|
|
207
|
-
return /\.(c|m)?tsx?$/.test(file);
|
|
208
|
-
});
|
|
229
|
+
return files.filter(isAnalysisSourceFile);
|
|
209
230
|
}
|
|
210
231
|
|
|
211
232
|
async function parseImports(
|
|
@@ -237,22 +258,27 @@ function packageName(importPath: string): string {
|
|
|
237
258
|
}
|
|
238
259
|
|
|
239
260
|
function stripKnownExtension(filePath: string): string {
|
|
240
|
-
return filePath
|
|
261
|
+
return stripSourceFileExtension(filePath);
|
|
241
262
|
}
|
|
242
263
|
|
|
243
264
|
function lintImport(
|
|
244
265
|
file: string,
|
|
245
266
|
sourceLayer: SourceLayer,
|
|
246
267
|
reference: ImportReference,
|
|
247
|
-
|
|
268
|
+
workspace: AnalysisWorkspace,
|
|
248
269
|
): LintDiagnostic | undefined {
|
|
249
270
|
if (reference.kind === "type") return undefined;
|
|
250
271
|
|
|
251
272
|
const targetLayer = reference.resolvedPath
|
|
252
|
-
? classifyPath(reference.resolvedPath,
|
|
273
|
+
? classifyPath(reference.resolvedPath, workspace)
|
|
253
274
|
: undefined;
|
|
254
275
|
const featureImport = reference.resolvedPath
|
|
255
|
-
? featureImportViolation(
|
|
276
|
+
? featureImportViolation(
|
|
277
|
+
file,
|
|
278
|
+
sourceLayer,
|
|
279
|
+
reference.resolvedPath,
|
|
280
|
+
workspace,
|
|
281
|
+
)
|
|
256
282
|
: undefined;
|
|
257
283
|
if (featureImport) {
|
|
258
284
|
return {
|
|
@@ -579,16 +605,16 @@ function matchBeignetProviderPackage(importPath: string): string | undefined {
|
|
|
579
605
|
}
|
|
580
606
|
|
|
581
607
|
function lintTransitiveBoundaries({
|
|
582
|
-
|
|
608
|
+
workspace,
|
|
583
609
|
graph,
|
|
584
610
|
}: {
|
|
585
|
-
|
|
611
|
+
workspace: AnalysisWorkspace;
|
|
586
612
|
graph: ImportGraph;
|
|
587
613
|
}): LintDiagnostic[] {
|
|
588
614
|
const diagnostics: LintDiagnostic[] = [];
|
|
589
615
|
|
|
590
616
|
for (const root of graph.keys()) {
|
|
591
|
-
const rootLayer = classifyPath(root,
|
|
617
|
+
const rootLayer = classifyPath(root, workspace);
|
|
592
618
|
if (
|
|
593
619
|
![
|
|
594
620
|
"agent-capability",
|
|
@@ -608,7 +634,7 @@ function lintTransitiveBoundaries({
|
|
|
608
634
|
}
|
|
609
635
|
|
|
610
636
|
diagnostics.push(
|
|
611
|
-
...lintTransitiveBoundaryRoot({
|
|
637
|
+
...lintTransitiveBoundaryRoot({ workspace, graph, root, rootLayer }),
|
|
612
638
|
);
|
|
613
639
|
}
|
|
614
640
|
|
|
@@ -616,12 +642,12 @@ function lintTransitiveBoundaries({
|
|
|
616
642
|
}
|
|
617
643
|
|
|
618
644
|
function lintTransitiveBoundaryRoot({
|
|
619
|
-
|
|
645
|
+
workspace,
|
|
620
646
|
graph,
|
|
621
647
|
root,
|
|
622
648
|
rootLayer,
|
|
623
649
|
}: {
|
|
624
|
-
|
|
650
|
+
workspace: AnalysisWorkspace;
|
|
625
651
|
graph: ImportGraph;
|
|
626
652
|
root: string;
|
|
627
653
|
rootLayer: SourceLayer;
|
|
@@ -648,7 +674,7 @@ function lintTransitiveBoundaryRoot({
|
|
|
648
674
|
current.file === root
|
|
649
675
|
? undefined
|
|
650
676
|
: (lintUnresolvedModuleReference(root, rootLayer, reference) ??
|
|
651
|
-
lintImport(root, rootLayer, reference,
|
|
677
|
+
lintImport(root, rootLayer, reference, workspace));
|
|
652
678
|
|
|
653
679
|
if (violation) {
|
|
654
680
|
diagnostics.push({
|
|
@@ -663,7 +689,7 @@ function lintTransitiveBoundaryRoot({
|
|
|
663
689
|
reference.resolvedPath &&
|
|
664
690
|
graph.has(reference.resolvedPath) &&
|
|
665
691
|
isTransparentArchitectureLayer(
|
|
666
|
-
classifyPath(reference.resolvedPath,
|
|
692
|
+
classifyPath(reference.resolvedPath, workspace),
|
|
667
693
|
)
|
|
668
694
|
) {
|
|
669
695
|
queue.push({ file: reference.resolvedPath, chain });
|
|
@@ -679,11 +705,11 @@ function isTransparentArchitectureLayer(layer: SourceLayer): boolean {
|
|
|
679
705
|
}
|
|
680
706
|
|
|
681
707
|
function lintRuntimeBoundary({
|
|
682
|
-
|
|
708
|
+
workspace,
|
|
683
709
|
graph,
|
|
684
710
|
sourceFileByPath,
|
|
685
711
|
}: {
|
|
686
|
-
|
|
712
|
+
workspace: AnalysisWorkspace;
|
|
687
713
|
graph: ImportGraph;
|
|
688
714
|
sourceFileByPath: Map<string, ts.SourceFile>;
|
|
689
715
|
}): LintDiagnostic[] {
|
|
@@ -693,7 +719,7 @@ function lintRuntimeBoundary({
|
|
|
693
719
|
const rootKind = runtimeBoundaryRootKind(
|
|
694
720
|
file,
|
|
695
721
|
sourceFileByPath.get(file),
|
|
696
|
-
|
|
722
|
+
workspace,
|
|
697
723
|
);
|
|
698
724
|
if (!rootKind) continue;
|
|
699
725
|
|
|
@@ -701,7 +727,7 @@ function lintRuntimeBoundary({
|
|
|
701
727
|
...lintRuntimeBoundaryRoot({
|
|
702
728
|
root: file,
|
|
703
729
|
rootKind,
|
|
704
|
-
|
|
730
|
+
workspace,
|
|
705
731
|
graph,
|
|
706
732
|
}),
|
|
707
733
|
);
|
|
@@ -713,12 +739,12 @@ function lintRuntimeBoundary({
|
|
|
713
739
|
function lintRuntimeBoundaryRoot({
|
|
714
740
|
root,
|
|
715
741
|
rootKind,
|
|
716
|
-
|
|
742
|
+
workspace,
|
|
717
743
|
graph,
|
|
718
744
|
}: {
|
|
719
745
|
root: string;
|
|
720
746
|
rootKind: RuntimeBoundaryRootKind;
|
|
721
|
-
|
|
747
|
+
workspace: AnalysisWorkspace;
|
|
722
748
|
graph: ImportGraph;
|
|
723
749
|
}): LintDiagnostic[] {
|
|
724
750
|
const diagnostics: LintDiagnostic[] = [];
|
|
@@ -744,7 +770,11 @@ function lintRuntimeBoundaryRoot({
|
|
|
744
770
|
const chain = reference.resolvedPath
|
|
745
771
|
? [...current.chain, reference.resolvedPath]
|
|
746
772
|
: current.chain;
|
|
747
|
-
const violation = runtimeBoundaryViolation(
|
|
773
|
+
const violation = runtimeBoundaryViolation(
|
|
774
|
+
reference,
|
|
775
|
+
workspace,
|
|
776
|
+
rootKind,
|
|
777
|
+
);
|
|
748
778
|
|
|
749
779
|
if (violation) {
|
|
750
780
|
diagnostics.push({
|
|
@@ -772,14 +802,16 @@ function lintRuntimeBoundaryRoot({
|
|
|
772
802
|
function runtimeBoundaryRootKind(
|
|
773
803
|
file: string,
|
|
774
804
|
sourceFile: ts.SourceFile | undefined,
|
|
775
|
-
|
|
805
|
+
workspace: AnalysisWorkspace,
|
|
776
806
|
): RuntimeBoundaryRootKind | undefined {
|
|
777
|
-
const layer = classifyPath(file,
|
|
807
|
+
const layer = classifyPath(file, workspace);
|
|
778
808
|
if (layer === "contract") return "contract";
|
|
779
809
|
if (layer === "schema") return "schema";
|
|
780
810
|
if (layer === "client") return "client";
|
|
781
811
|
if (sourceFile && hasUseClientDirective(sourceFile)) return "client";
|
|
782
|
-
|
|
812
|
+
const source = projectSource(workspace, file);
|
|
813
|
+
if (isGeneratedClientAdapter(source.file, source.project.config))
|
|
814
|
+
return "client";
|
|
783
815
|
return undefined;
|
|
784
816
|
}
|
|
785
817
|
|
|
@@ -808,7 +840,7 @@ function isGeneratedClientAdapter(
|
|
|
808
840
|
|
|
809
841
|
function runtimeBoundaryViolation(
|
|
810
842
|
reference: ImportReference,
|
|
811
|
-
|
|
843
|
+
workspace: AnalysisWorkspace,
|
|
812
844
|
rootKind: RuntimeBoundaryRootKind,
|
|
813
845
|
): string | undefined {
|
|
814
846
|
if (!reference.staticallyResolvable) {
|
|
@@ -816,7 +848,7 @@ function runtimeBoundaryViolation(
|
|
|
816
848
|
}
|
|
817
849
|
|
|
818
850
|
if (reference.resolvedPath) {
|
|
819
|
-
const targetLayer = classifyPath(reference.resolvedPath,
|
|
851
|
+
const targetLayer = classifyPath(reference.resolvedPath, workspace);
|
|
820
852
|
const prohibitedLayer =
|
|
821
853
|
rootKind === "client"
|
|
822
854
|
? undefined
|
|
@@ -900,25 +932,30 @@ function featureImportViolation(
|
|
|
900
932
|
file: string,
|
|
901
933
|
sourceLayer: SourceLayer,
|
|
902
934
|
resolvedPath: string,
|
|
903
|
-
|
|
935
|
+
workspace: AnalysisWorkspace,
|
|
904
936
|
): string | undefined {
|
|
905
937
|
if (sourceLayer !== "domain") return undefined;
|
|
906
938
|
|
|
907
|
-
const sourceFeature = featureLayerInfo(file,
|
|
908
|
-
const targetFeature = featureLayerInfo(resolvedPath,
|
|
939
|
+
const sourceFeature = featureLayerInfo(file, workspace);
|
|
940
|
+
const targetFeature = featureLayerInfo(resolvedPath, workspace);
|
|
909
941
|
if (!sourceFeature || !targetFeature) return undefined;
|
|
910
942
|
if (sourceFeature.layer !== "domain" || targetFeature.layer !== "domain") {
|
|
911
943
|
return undefined;
|
|
912
944
|
}
|
|
913
|
-
if (
|
|
945
|
+
if (
|
|
946
|
+
sourceFeature.feature === targetFeature.feature &&
|
|
947
|
+
sourceFeature.owner === targetFeature.owner
|
|
948
|
+
)
|
|
949
|
+
return undefined;
|
|
914
950
|
if (targetFeature.feature === "shared") return undefined;
|
|
915
951
|
|
|
916
952
|
return `feature-specific domain files must not import another feature's domain (${stripKnownExtension(
|
|
917
953
|
resolvedPath,
|
|
918
|
-
)}). Move shared-kernel domain concepts to ${sharedDomainPath(config)}.`;
|
|
954
|
+
)}). Move shared-kernel domain concepts to ${sharedDomainPath(projectForFile(workspace, file).config)}.`;
|
|
919
955
|
}
|
|
920
956
|
|
|
921
957
|
const domainBannedPackages = new Set([
|
|
958
|
+
"@beignet/core/encryption",
|
|
922
959
|
"@aws-sdk/client-s3",
|
|
923
960
|
"@aws-sdk/lib-storage",
|
|
924
961
|
"@aws-sdk/s3-request-presigner",
|
|
@@ -927,12 +964,14 @@ const domainBannedPackages = new Set([
|
|
|
927
964
|
"@beignet/core/errors",
|
|
928
965
|
"@beignet/next",
|
|
929
966
|
"@beignet/core/ports",
|
|
967
|
+
"@beignet/react-form",
|
|
930
968
|
"@beignet/react-hook-form",
|
|
931
969
|
"@beignet/react-query",
|
|
932
970
|
"@hookform/resolvers",
|
|
933
971
|
"@libsql/client",
|
|
934
972
|
"@neondatabase/serverless",
|
|
935
973
|
"@planetscale/database",
|
|
974
|
+
"@tanstack/react-form",
|
|
936
975
|
"@tanstack/react-query",
|
|
937
976
|
"@upstash/ratelimit",
|
|
938
977
|
"@upstash/redis",
|
|
@@ -959,12 +998,14 @@ const coreBannedPackages = new Set([
|
|
|
959
998
|
"@aws-sdk/lib-storage",
|
|
960
999
|
"@aws-sdk/s3-request-presigner",
|
|
961
1000
|
"@beignet/core/client",
|
|
1001
|
+
"@beignet/react-form",
|
|
962
1002
|
"@beignet/react-hook-form",
|
|
963
1003
|
"@beignet/react-query",
|
|
964
1004
|
"@hookform/resolvers",
|
|
965
1005
|
"@libsql/client",
|
|
966
1006
|
"@neondatabase/serverless",
|
|
967
1007
|
"@planetscale/database",
|
|
1008
|
+
"@tanstack/react-form",
|
|
968
1009
|
"@tanstack/react-query",
|
|
969
1010
|
"@upstash/ratelimit",
|
|
970
1011
|
"@upstash/redis",
|
|
@@ -987,6 +1028,7 @@ const coreBannedPackages = new Set([
|
|
|
987
1028
|
]);
|
|
988
1029
|
|
|
989
1030
|
const serverRuntimePackages = new Set([
|
|
1031
|
+
"@beignet/core/encryption",
|
|
990
1032
|
"@beignet/devtools",
|
|
991
1033
|
"@beignet/next",
|
|
992
1034
|
"@beignet/core/openapi",
|
|
@@ -1032,19 +1074,22 @@ function featureRelativePath(
|
|
|
1032
1074
|
|
|
1033
1075
|
function featureLayerInfo(
|
|
1034
1076
|
filePath: string,
|
|
1035
|
-
|
|
1036
|
-
): { feature: string; layer: SourceLayer } | undefined {
|
|
1037
|
-
const
|
|
1077
|
+
workspace: AnalysisWorkspace,
|
|
1078
|
+
): { feature: string; owner: string; layer: SourceLayer } | undefined {
|
|
1079
|
+
const source = projectSource(workspace, filePath);
|
|
1080
|
+
const normalizedPath = stripKnownExtension(source.file);
|
|
1038
1081
|
const relativePath = featureRelativePath(
|
|
1039
1082
|
normalizedPath,
|
|
1040
|
-
directoryPath(config.paths.features),
|
|
1083
|
+
directoryPath(source.project.config.paths.features),
|
|
1041
1084
|
);
|
|
1042
1085
|
if (!relativePath) return undefined;
|
|
1043
|
-
|
|
1044
1086
|
const [feature] = relativePath.split("/");
|
|
1045
1087
|
if (!feature) return undefined;
|
|
1046
|
-
|
|
1047
|
-
|
|
1088
|
+
return {
|
|
1089
|
+
feature,
|
|
1090
|
+
owner: source.project.prefix,
|
|
1091
|
+
layer: classifyPath(filePath, workspace),
|
|
1092
|
+
};
|
|
1048
1093
|
}
|
|
1049
1094
|
|
|
1050
1095
|
function legacySharedDomainPath(config: ResolvedBeignetConfig): string {
|
|
@@ -1091,3 +1136,62 @@ function dedupeDiagnostics(diagnostics: LintDiagnostic[]): LintDiagnostic[] {
|
|
|
1091
1136
|
|
|
1092
1137
|
return deduped;
|
|
1093
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
function workspaceImportViolation(
|
|
1141
|
+
workspace: AnalysisWorkspace,
|
|
1142
|
+
file: string,
|
|
1143
|
+
reference: ImportReference,
|
|
1144
|
+
): LintDiagnostic | undefined {
|
|
1145
|
+
if (workspace.projects.length === 1 || !reference.staticallyResolvable)
|
|
1146
|
+
return undefined;
|
|
1147
|
+
const importer = projectForFile(workspace, file);
|
|
1148
|
+
const namedPackage = workspace.projects.find(
|
|
1149
|
+
(project) =>
|
|
1150
|
+
project.name &&
|
|
1151
|
+
(reference.importPath === project.name ||
|
|
1152
|
+
reference.importPath.startsWith(`${project.name}/`)),
|
|
1153
|
+
);
|
|
1154
|
+
const target = reference.resolvedPath
|
|
1155
|
+
? findProjectForFile(workspace, reference.resolvedPath)
|
|
1156
|
+
: namedPackage;
|
|
1157
|
+
let message: string | undefined;
|
|
1158
|
+
if (
|
|
1159
|
+
importer.localDependencies.has(packageName(reference.importPath)) &&
|
|
1160
|
+
!namedPackage
|
|
1161
|
+
) {
|
|
1162
|
+
message = `Add the source directory for ${packageName(reference.importPath)} to workspace.packages so Beignet can inspect this local dependency.`;
|
|
1163
|
+
} else if (reference.resolvedPath && !target) {
|
|
1164
|
+
message = `Cannot inspect ${reference.importPath}: its source is outside the configured workspace. Add the owning package directory to workspace.packages.`;
|
|
1165
|
+
} else if (
|
|
1166
|
+
target &&
|
|
1167
|
+
target !== importer &&
|
|
1168
|
+
(!target.name || !importer.dependencies.has(target.name))
|
|
1169
|
+
) {
|
|
1170
|
+
message = `Declare ${target.name ?? target.prefix} in ${projectPath(importer, "package.json")} before importing its source.`;
|
|
1171
|
+
} else if (
|
|
1172
|
+
(namedPackage &&
|
|
1173
|
+
(!path.posix.extname(reference.importPath) ||
|
|
1174
|
+
isAnalysisSourceFileExtension(
|
|
1175
|
+
path.posix.extname(reference.importPath),
|
|
1176
|
+
)) &&
|
|
1177
|
+
(!reference.resolvedPath ||
|
|
1178
|
+
!workspace.fileSet.has(reference.resolvedPath))) ||
|
|
1179
|
+
(target?.prefix &&
|
|
1180
|
+
reference.resolvedPath &&
|
|
1181
|
+
(!workspace.fileSet.has(reference.resolvedPath) ||
|
|
1182
|
+
!isAnalysisSourceFile(reference.resolvedPath)))
|
|
1183
|
+
) {
|
|
1184
|
+
message = `Cannot inspect ${reference.importPath}. Install workspace dependencies and expose source files through package exports and the importing package's TypeScript configuration. Build output is not inspected.`;
|
|
1185
|
+
}
|
|
1186
|
+
return message
|
|
1187
|
+
? {
|
|
1188
|
+
severity: "error",
|
|
1189
|
+
code: "BEIGNET_WORKSPACE_IMPORT",
|
|
1190
|
+
file,
|
|
1191
|
+
importPath: reference.importPath,
|
|
1192
|
+
line: reference.line,
|
|
1193
|
+
column: reference.column,
|
|
1194
|
+
message,
|
|
1195
|
+
}
|
|
1196
|
+
: undefined;
|
|
1197
|
+
}
|
package/src/make/shared.ts
CHANGED
|
@@ -1130,7 +1130,7 @@ export function beignetDependencyVersion(packageJson: PackageJsonLike): string {
|
|
|
1130
1130
|
export function tanstackReactQueryDependencyVersion(
|
|
1131
1131
|
packageJson: PackageJsonLike,
|
|
1132
1132
|
): string {
|
|
1133
|
-
return packageJson.dependencies?.["@tanstack/react-query"] ?? "^5.
|
|
1133
|
+
return packageJson.dependencies?.["@tanstack/react-query"] ?? "^5.102.0";
|
|
1134
1134
|
}
|
|
1135
1135
|
|
|
1136
1136
|
export async function updatePackageJson(
|
package/src/make.ts
CHANGED
|
@@ -3173,6 +3173,13 @@ async function missingFeatureUiFormsFiles(
|
|
|
3173
3173
|
config: ResolvedBeignetConfig,
|
|
3174
3174
|
): Promise<GeneratedFile[]> {
|
|
3175
3175
|
const formsPath = clientFormsPath(config);
|
|
3176
|
+
|
|
3177
|
+
if (await appDependsOn(targetDir, "@beignet/react-form")) {
|
|
3178
|
+
throw new Error(
|
|
3179
|
+
"beignet make feature --with ui generates React Hook Form components, but this app depends on @beignet/react-form. Generate the feature without --with ui and write the TanStack Form component by hand; the @beignet/react-form#forms skill shows the pattern.",
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3176
3183
|
const existing = await readOptionalFile(path.join(targetDir, formsPath));
|
|
3177
3184
|
|
|
3178
3185
|
if (existing === undefined) {
|
|
@@ -3196,6 +3203,29 @@ export const rhf = createReactHookForm();
|
|
|
3196
3203
|
return [];
|
|
3197
3204
|
}
|
|
3198
3205
|
|
|
3206
|
+
async function appDependsOn(
|
|
3207
|
+
targetDir: string,
|
|
3208
|
+
packageName: string,
|
|
3209
|
+
): Promise<boolean> {
|
|
3210
|
+
const source = await readOptionalFile(path.join(targetDir, "package.json"));
|
|
3211
|
+
if (source === undefined) return false;
|
|
3212
|
+
|
|
3213
|
+
let packageJson: {
|
|
3214
|
+
dependencies?: Record<string, string>;
|
|
3215
|
+
devDependencies?: Record<string, string>;
|
|
3216
|
+
};
|
|
3217
|
+
try {
|
|
3218
|
+
packageJson = JSON.parse(source);
|
|
3219
|
+
} catch {
|
|
3220
|
+
return false;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
return Boolean(
|
|
3224
|
+
packageJson.dependencies?.[packageName] ??
|
|
3225
|
+
packageJson.devDependencies?.[packageName],
|
|
3226
|
+
);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3199
3229
|
async function assertUploadUiApp(
|
|
3200
3230
|
targetDir: string,
|
|
3201
3231
|
config: ResolvedBeignetConfig,
|
|
@@ -4187,7 +4217,7 @@ export function decode${names.singularPascal}Cursor(cursor: string): ${names.sin
|
|
|
4187
4217
|
export const List${names.pluralPascal}InputSchema = z
|
|
4188
4218
|
.object({
|
|
4189
4219
|
limit: z.number().int().min(1).max(100).default(20),
|
|
4190
|
-
cursor: z.string().optional()
|
|
4220
|
+
cursor: z.string().optional(),
|
|
4191
4221
|
name: z.string().trim().min(1).max(120).optional(),
|
|
4192
4222
|
sortBy: ${names.singularPascal}SortBySchema.default("createdAt"),
|
|
4193
4223
|
sortDirection: ${names.singularPascal}SortDirectionSchema.default("desc"),
|
package/src/mcp.ts
CHANGED
|
@@ -687,7 +687,7 @@ export function buildBeignetMcpServer(options: McpServerOptions): McpServer {
|
|
|
687
687
|
"check",
|
|
688
688
|
{
|
|
689
689
|
description:
|
|
690
|
-
"Run the full Beignet app validation loop: Beignet lint, strict doctor, optional disconnected or connected preflight, and the app's lint, typecheck, and test scripts. Returns the same versioned JSON as beignet check --json, including every step, bounded failure output, durations, and overall ok status. Does not apply Beignet fixes, but app-owned scripts run with their normal side effects.",
|
|
690
|
+
"Run the full Beignet app validation loop: Beignet lint, strict doctor, optional disconnected or connected preflight, and the app's lint, typecheck, and test scripts. Returns the same versioned JSON as beignet check --json, including every step, bounded failure output, durations, source-state provenance, and overall ok status. An ending fingerprint that differs from the baseline makes the result stale and unsuccessful. Does not apply Beignet fixes, but app-owned scripts run with their normal side effects.",
|
|
691
691
|
inputSchema: {
|
|
692
692
|
preflight: z
|
|
693
693
|
.boolean()
|
package/src/provider-add.ts
CHANGED
|
@@ -584,7 +584,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
584
584
|
key: "storage",
|
|
585
585
|
typeName: "StoragePort",
|
|
586
586
|
importName: "StoragePort",
|
|
587
|
-
importFrom: "@beignet/core/
|
|
587
|
+
importFrom: "@beignet/core/ports",
|
|
588
588
|
},
|
|
589
589
|
envVars: [
|
|
590
590
|
"STORAGE_S3_BUCKET",
|
|
@@ -649,7 +649,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
649
649
|
key: "storage",
|
|
650
650
|
typeName: "StoragePort",
|
|
651
651
|
importName: "StoragePort",
|
|
652
|
-
importFrom: "@beignet/core/
|
|
652
|
+
importFrom: "@beignet/core/ports",
|
|
653
653
|
},
|
|
654
654
|
envVars: ["BLOB_READ_WRITE_TOKEN"],
|
|
655
655
|
envExample: [
|
package/src/provider-audit.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type ProviderPackageMetadata,
|
|
6
6
|
parseProviderPackageMetadata,
|
|
7
7
|
} from "@beignet/core/providers";
|
|
8
|
+
import { analyzePortWiringFiles } from "./analysis/port-wiring.js";
|
|
8
9
|
import {
|
|
9
10
|
type BeignetDatabaseTables,
|
|
10
11
|
defaultBeignetConfig,
|
|
@@ -169,6 +170,10 @@ export async function auditProviders(
|
|
|
169
170
|
const portsSource = files.includes(config.paths.ports)
|
|
170
171
|
? await readCachedSource(targetDir, config.paths.ports, sourceCache)
|
|
171
172
|
: "";
|
|
173
|
+
const declaredAppPorts =
|
|
174
|
+
config.workspace.packages.length > 0
|
|
175
|
+
? (await analyzePortWiringFiles({ targetDir, files, config })).declared
|
|
176
|
+
: undefined;
|
|
172
177
|
const infrastructurePath = directoryPath(
|
|
173
178
|
path.dirname(config.paths.portWiring),
|
|
174
179
|
);
|
|
@@ -211,6 +216,7 @@ export async function auditProviders(
|
|
|
211
216
|
schemaDir,
|
|
212
217
|
sourceCache,
|
|
213
218
|
portsSource,
|
|
219
|
+
declaredAppPorts,
|
|
214
220
|
providerEntries,
|
|
215
221
|
providerEntryRecords,
|
|
216
222
|
sourceFile: source.file,
|
|
@@ -334,10 +340,12 @@ export async function readProviderPackageMetadataSource(
|
|
|
334
340
|
const packageJson = await readJsonIfExists<{
|
|
335
341
|
beignet?: { provider?: unknown };
|
|
336
342
|
}>(candidate);
|
|
343
|
+
if (packageJson === undefined) continue;
|
|
337
344
|
const metadata = packageJson?.beignet?.provider;
|
|
338
345
|
if (metadata !== undefined) {
|
|
339
346
|
return { packageName, file: candidate, metadata };
|
|
340
347
|
}
|
|
348
|
+
return undefined;
|
|
341
349
|
}
|
|
342
350
|
|
|
343
351
|
return undefined;
|
|
@@ -1169,14 +1177,21 @@ function providerPackageJsonCandidates(
|
|
|
1169
1177
|
targetDir: string,
|
|
1170
1178
|
packageName: string,
|
|
1171
1179
|
): string[] {
|
|
1172
|
-
const candidates = [
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
+
const candidates: string[] = [];
|
|
1181
|
+
let directory = path.resolve(targetDir);
|
|
1182
|
+
while (true) {
|
|
1183
|
+
candidates.push(
|
|
1184
|
+
path.join(
|
|
1185
|
+
directory,
|
|
1186
|
+
"node_modules",
|
|
1187
|
+
...packageName.split("/"),
|
|
1188
|
+
"package.json",
|
|
1189
|
+
),
|
|
1190
|
+
);
|
|
1191
|
+
const parent = path.dirname(directory);
|
|
1192
|
+
if (parent === directory) break;
|
|
1193
|
+
directory = parent;
|
|
1194
|
+
}
|
|
1180
1195
|
|
|
1181
1196
|
if (packageName.startsWith("@beignet/")) {
|
|
1182
1197
|
candidates.push(
|
|
@@ -1217,6 +1232,7 @@ async function providerAuditEntryForRule(options: {
|
|
|
1217
1232
|
schemaDir: string;
|
|
1218
1233
|
sourceCache: Map<string, string>;
|
|
1219
1234
|
portsSource: string;
|
|
1235
|
+
declaredAppPorts?: ReadonlySet<string>;
|
|
1220
1236
|
providerEntries: string[];
|
|
1221
1237
|
providerEntryRecords: ProviderListEntryRecord[];
|
|
1222
1238
|
sourceFile: string;
|
|
@@ -1288,6 +1304,7 @@ async function providerAuditEntryForRule(options: {
|
|
|
1288
1304
|
const appPorts = appPortsAudit(
|
|
1289
1305
|
options.rule.appPorts ?? [],
|
|
1290
1306
|
options.portsSource,
|
|
1307
|
+
options.declaredAppPorts,
|
|
1291
1308
|
);
|
|
1292
1309
|
const notes = providerAuditNotes({
|
|
1293
1310
|
metadataStatus: "valid",
|
|
@@ -1629,11 +1646,14 @@ function aggregateVariantRequirement(
|
|
|
1629
1646
|
function appPortsAudit(
|
|
1630
1647
|
required: readonly { name: string; type: string }[],
|
|
1631
1648
|
portsSource: string,
|
|
1649
|
+
declaredAppPorts?: ReadonlySet<string>,
|
|
1632
1650
|
): ProviderAppPortsAudit {
|
|
1633
1651
|
if (required.length === 0) return emptyAppPorts();
|
|
1634
1652
|
|
|
1635
|
-
const missing = required.filter(
|
|
1636
|
-
|
|
1653
|
+
const missing = required.filter((port) =>
|
|
1654
|
+
declaredAppPorts
|
|
1655
|
+
? !declaredAppPorts.has(port.name)
|
|
1656
|
+
: !portsSource.includes(`${port.name}:`),
|
|
1637
1657
|
);
|
|
1638
1658
|
return {
|
|
1639
1659
|
status: missing.length === 0 ? "present" : "missing",
|