@beignet/cli 0.0.52 → 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 +26 -0
- package/README.md +131 -1
- package/dist/analysis/layers.d.ts +2 -0
- package/dist/analysis/layers.d.ts.map +1 -1
- package/dist/analysis/layers.js +5 -0
- 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 +39 -10
- package/dist/analysis/port-wiring.js.map +1 -1
- 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 +166 -21
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map-changes.d.ts.map +1 -1
- package/dist/app-map-changes.js +19 -7
- package/dist/app-map-changes.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +58 -55
- package/dist/app-map.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/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +89 -21
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +106 -38
- 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/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/shared.js +3 -3
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +42 -0
- package/src/analysis/layers.ts +9 -0
- package/src/analysis/port-wiring.ts +66 -10
- package/src/analysis/source-index.ts +56 -4
- package/src/analysis/workspace-routes.ts +385 -0
- package/src/analysis/workspace.ts +281 -34
- package/src/app-map-changes.ts +31 -5
- package/src/app-map.ts +75 -75
- package/src/config.ts +33 -0
- package/src/index.ts +25 -0
- package/src/inspect.ts +123 -16
- package/src/lint.ts +146 -43
- package/src/make/shared.ts +1 -1
- package/src/make.ts +31 -1
- package/src/provider-add.ts +2 -2
- package/src/provider-audit.ts +30 -10
- package/src/templates/shared.ts +3 -3
package/src/lint.ts
CHANGED
|
@@ -3,11 +3,12 @@ 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
9
|
import {
|
|
10
10
|
isAnalysisSourceFile,
|
|
11
|
+
isAnalysisSourceFileExtension,
|
|
11
12
|
stripSourceFileExtension,
|
|
12
13
|
} from "./analysis/source-files.js";
|
|
13
14
|
import {
|
|
@@ -15,7 +16,12 @@ import {
|
|
|
15
16
|
moduleReferences,
|
|
16
17
|
} from "./analysis/source-index.js";
|
|
17
18
|
import {
|
|
19
|
+
type AnalysisWorkspace,
|
|
18
20
|
createAnalysisWorkspace,
|
|
21
|
+
findProjectForFile,
|
|
22
|
+
projectForFile,
|
|
23
|
+
projectPath,
|
|
24
|
+
projectSource,
|
|
19
25
|
resolveLocalModulePath,
|
|
20
26
|
} from "./analysis/workspace.js";
|
|
21
27
|
import { createPainter } from "./ansi.js";
|
|
@@ -81,12 +87,19 @@ export async function lintApp(
|
|
|
81
87
|
const importGraph: ImportGraph = new Map();
|
|
82
88
|
|
|
83
89
|
for (const file of sourceFiles) {
|
|
84
|
-
const sourceLayer = classifyPath(file,
|
|
90
|
+
const sourceLayer = classifyPath(file, workspace);
|
|
91
|
+
const source = projectSource(workspace, file);
|
|
85
92
|
const sourceFile = await workspace.readSourceFile(file);
|
|
86
93
|
sourceFileByPath.set(file, sourceFile);
|
|
87
94
|
importGraph.set(file, await parseImports(workspace, file));
|
|
88
95
|
|
|
89
|
-
const
|
|
96
|
+
const localCanonicalPath = canonicalFeatureLayerPath(
|
|
97
|
+
source.file,
|
|
98
|
+
source.project.config,
|
|
99
|
+
);
|
|
100
|
+
const canonicalLayerPath = localCanonicalPath
|
|
101
|
+
? projectPath(source.project, localCanonicalPath)
|
|
102
|
+
: undefined;
|
|
90
103
|
if (canonicalLayerPath) {
|
|
91
104
|
diagnostics.push({
|
|
92
105
|
severity: "error",
|
|
@@ -99,10 +112,17 @@ export async function lintApp(
|
|
|
99
112
|
});
|
|
100
113
|
}
|
|
101
114
|
|
|
115
|
+
for (const reference of importGraph.get(file) ?? []) {
|
|
116
|
+
const issue = workspaceImportViolation(workspace, file, reference);
|
|
117
|
+
if (issue) diagnostics.push(issue);
|
|
118
|
+
}
|
|
102
119
|
if (sourceLayer === "test" || sourceLayer === "unknown") continue;
|
|
103
120
|
|
|
104
|
-
if (isLegacySharedDomainPath(file, config)) {
|
|
105
|
-
const canonicalSharedDomain =
|
|
121
|
+
if (isLegacySharedDomainPath(source.file, source.project.config)) {
|
|
122
|
+
const canonicalSharedDomain = projectPath(
|
|
123
|
+
source.project,
|
|
124
|
+
sharedDomainPath(source.project.config),
|
|
125
|
+
);
|
|
106
126
|
diagnostics.push({
|
|
107
127
|
severity: "error",
|
|
108
128
|
code: "BEIGNET_CANONICAL_STRUCTURE",
|
|
@@ -119,7 +139,7 @@ export async function lintApp(
|
|
|
119
139
|
lintUnresolvedModuleReference(file, sourceLayer, reference) ??
|
|
120
140
|
(sourceLayer === "schema"
|
|
121
141
|
? undefined
|
|
122
|
-
: lintImport(file, sourceLayer, reference,
|
|
142
|
+
: lintImport(file, sourceLayer, reference, workspace));
|
|
123
143
|
if (diagnostic) {
|
|
124
144
|
diagnostics.push({
|
|
125
145
|
...diagnostic,
|
|
@@ -132,14 +152,14 @@ export async function lintApp(
|
|
|
132
152
|
|
|
133
153
|
diagnostics.push(
|
|
134
154
|
...lintTransitiveBoundaries({
|
|
135
|
-
|
|
155
|
+
workspace,
|
|
136
156
|
graph: importGraph,
|
|
137
157
|
}),
|
|
138
158
|
);
|
|
139
159
|
|
|
140
160
|
diagnostics.push(
|
|
141
161
|
...lintRuntimeBoundary({
|
|
142
|
-
|
|
162
|
+
workspace,
|
|
143
163
|
graph: importGraph,
|
|
144
164
|
sourceFileByPath,
|
|
145
165
|
}),
|
|
@@ -245,15 +265,20 @@ function lintImport(
|
|
|
245
265
|
file: string,
|
|
246
266
|
sourceLayer: SourceLayer,
|
|
247
267
|
reference: ImportReference,
|
|
248
|
-
|
|
268
|
+
workspace: AnalysisWorkspace,
|
|
249
269
|
): LintDiagnostic | undefined {
|
|
250
270
|
if (reference.kind === "type") return undefined;
|
|
251
271
|
|
|
252
272
|
const targetLayer = reference.resolvedPath
|
|
253
|
-
? classifyPath(reference.resolvedPath,
|
|
273
|
+
? classifyPath(reference.resolvedPath, workspace)
|
|
254
274
|
: undefined;
|
|
255
275
|
const featureImport = reference.resolvedPath
|
|
256
|
-
? featureImportViolation(
|
|
276
|
+
? featureImportViolation(
|
|
277
|
+
file,
|
|
278
|
+
sourceLayer,
|
|
279
|
+
reference.resolvedPath,
|
|
280
|
+
workspace,
|
|
281
|
+
)
|
|
257
282
|
: undefined;
|
|
258
283
|
if (featureImport) {
|
|
259
284
|
return {
|
|
@@ -580,16 +605,16 @@ function matchBeignetProviderPackage(importPath: string): string | undefined {
|
|
|
580
605
|
}
|
|
581
606
|
|
|
582
607
|
function lintTransitiveBoundaries({
|
|
583
|
-
|
|
608
|
+
workspace,
|
|
584
609
|
graph,
|
|
585
610
|
}: {
|
|
586
|
-
|
|
611
|
+
workspace: AnalysisWorkspace;
|
|
587
612
|
graph: ImportGraph;
|
|
588
613
|
}): LintDiagnostic[] {
|
|
589
614
|
const diagnostics: LintDiagnostic[] = [];
|
|
590
615
|
|
|
591
616
|
for (const root of graph.keys()) {
|
|
592
|
-
const rootLayer = classifyPath(root,
|
|
617
|
+
const rootLayer = classifyPath(root, workspace);
|
|
593
618
|
if (
|
|
594
619
|
![
|
|
595
620
|
"agent-capability",
|
|
@@ -609,7 +634,7 @@ function lintTransitiveBoundaries({
|
|
|
609
634
|
}
|
|
610
635
|
|
|
611
636
|
diagnostics.push(
|
|
612
|
-
...lintTransitiveBoundaryRoot({
|
|
637
|
+
...lintTransitiveBoundaryRoot({ workspace, graph, root, rootLayer }),
|
|
613
638
|
);
|
|
614
639
|
}
|
|
615
640
|
|
|
@@ -617,12 +642,12 @@ function lintTransitiveBoundaries({
|
|
|
617
642
|
}
|
|
618
643
|
|
|
619
644
|
function lintTransitiveBoundaryRoot({
|
|
620
|
-
|
|
645
|
+
workspace,
|
|
621
646
|
graph,
|
|
622
647
|
root,
|
|
623
648
|
rootLayer,
|
|
624
649
|
}: {
|
|
625
|
-
|
|
650
|
+
workspace: AnalysisWorkspace;
|
|
626
651
|
graph: ImportGraph;
|
|
627
652
|
root: string;
|
|
628
653
|
rootLayer: SourceLayer;
|
|
@@ -649,7 +674,7 @@ function lintTransitiveBoundaryRoot({
|
|
|
649
674
|
current.file === root
|
|
650
675
|
? undefined
|
|
651
676
|
: (lintUnresolvedModuleReference(root, rootLayer, reference) ??
|
|
652
|
-
lintImport(root, rootLayer, reference,
|
|
677
|
+
lintImport(root, rootLayer, reference, workspace));
|
|
653
678
|
|
|
654
679
|
if (violation) {
|
|
655
680
|
diagnostics.push({
|
|
@@ -664,7 +689,7 @@ function lintTransitiveBoundaryRoot({
|
|
|
664
689
|
reference.resolvedPath &&
|
|
665
690
|
graph.has(reference.resolvedPath) &&
|
|
666
691
|
isTransparentArchitectureLayer(
|
|
667
|
-
classifyPath(reference.resolvedPath,
|
|
692
|
+
classifyPath(reference.resolvedPath, workspace),
|
|
668
693
|
)
|
|
669
694
|
) {
|
|
670
695
|
queue.push({ file: reference.resolvedPath, chain });
|
|
@@ -680,11 +705,11 @@ function isTransparentArchitectureLayer(layer: SourceLayer): boolean {
|
|
|
680
705
|
}
|
|
681
706
|
|
|
682
707
|
function lintRuntimeBoundary({
|
|
683
|
-
|
|
708
|
+
workspace,
|
|
684
709
|
graph,
|
|
685
710
|
sourceFileByPath,
|
|
686
711
|
}: {
|
|
687
|
-
|
|
712
|
+
workspace: AnalysisWorkspace;
|
|
688
713
|
graph: ImportGraph;
|
|
689
714
|
sourceFileByPath: Map<string, ts.SourceFile>;
|
|
690
715
|
}): LintDiagnostic[] {
|
|
@@ -694,7 +719,7 @@ function lintRuntimeBoundary({
|
|
|
694
719
|
const rootKind = runtimeBoundaryRootKind(
|
|
695
720
|
file,
|
|
696
721
|
sourceFileByPath.get(file),
|
|
697
|
-
|
|
722
|
+
workspace,
|
|
698
723
|
);
|
|
699
724
|
if (!rootKind) continue;
|
|
700
725
|
|
|
@@ -702,7 +727,7 @@ function lintRuntimeBoundary({
|
|
|
702
727
|
...lintRuntimeBoundaryRoot({
|
|
703
728
|
root: file,
|
|
704
729
|
rootKind,
|
|
705
|
-
|
|
730
|
+
workspace,
|
|
706
731
|
graph,
|
|
707
732
|
}),
|
|
708
733
|
);
|
|
@@ -714,12 +739,12 @@ function lintRuntimeBoundary({
|
|
|
714
739
|
function lintRuntimeBoundaryRoot({
|
|
715
740
|
root,
|
|
716
741
|
rootKind,
|
|
717
|
-
|
|
742
|
+
workspace,
|
|
718
743
|
graph,
|
|
719
744
|
}: {
|
|
720
745
|
root: string;
|
|
721
746
|
rootKind: RuntimeBoundaryRootKind;
|
|
722
|
-
|
|
747
|
+
workspace: AnalysisWorkspace;
|
|
723
748
|
graph: ImportGraph;
|
|
724
749
|
}): LintDiagnostic[] {
|
|
725
750
|
const diagnostics: LintDiagnostic[] = [];
|
|
@@ -745,7 +770,11 @@ function lintRuntimeBoundaryRoot({
|
|
|
745
770
|
const chain = reference.resolvedPath
|
|
746
771
|
? [...current.chain, reference.resolvedPath]
|
|
747
772
|
: current.chain;
|
|
748
|
-
const violation = runtimeBoundaryViolation(
|
|
773
|
+
const violation = runtimeBoundaryViolation(
|
|
774
|
+
reference,
|
|
775
|
+
workspace,
|
|
776
|
+
rootKind,
|
|
777
|
+
);
|
|
749
778
|
|
|
750
779
|
if (violation) {
|
|
751
780
|
diagnostics.push({
|
|
@@ -773,14 +802,16 @@ function lintRuntimeBoundaryRoot({
|
|
|
773
802
|
function runtimeBoundaryRootKind(
|
|
774
803
|
file: string,
|
|
775
804
|
sourceFile: ts.SourceFile | undefined,
|
|
776
|
-
|
|
805
|
+
workspace: AnalysisWorkspace,
|
|
777
806
|
): RuntimeBoundaryRootKind | undefined {
|
|
778
|
-
const layer = classifyPath(file,
|
|
807
|
+
const layer = classifyPath(file, workspace);
|
|
779
808
|
if (layer === "contract") return "contract";
|
|
780
809
|
if (layer === "schema") return "schema";
|
|
781
810
|
if (layer === "client") return "client";
|
|
782
811
|
if (sourceFile && hasUseClientDirective(sourceFile)) return "client";
|
|
783
|
-
|
|
812
|
+
const source = projectSource(workspace, file);
|
|
813
|
+
if (isGeneratedClientAdapter(source.file, source.project.config))
|
|
814
|
+
return "client";
|
|
784
815
|
return undefined;
|
|
785
816
|
}
|
|
786
817
|
|
|
@@ -809,7 +840,7 @@ function isGeneratedClientAdapter(
|
|
|
809
840
|
|
|
810
841
|
function runtimeBoundaryViolation(
|
|
811
842
|
reference: ImportReference,
|
|
812
|
-
|
|
843
|
+
workspace: AnalysisWorkspace,
|
|
813
844
|
rootKind: RuntimeBoundaryRootKind,
|
|
814
845
|
): string | undefined {
|
|
815
846
|
if (!reference.staticallyResolvable) {
|
|
@@ -817,7 +848,7 @@ function runtimeBoundaryViolation(
|
|
|
817
848
|
}
|
|
818
849
|
|
|
819
850
|
if (reference.resolvedPath) {
|
|
820
|
-
const targetLayer = classifyPath(reference.resolvedPath,
|
|
851
|
+
const targetLayer = classifyPath(reference.resolvedPath, workspace);
|
|
821
852
|
const prohibitedLayer =
|
|
822
853
|
rootKind === "client"
|
|
823
854
|
? undefined
|
|
@@ -901,25 +932,30 @@ function featureImportViolation(
|
|
|
901
932
|
file: string,
|
|
902
933
|
sourceLayer: SourceLayer,
|
|
903
934
|
resolvedPath: string,
|
|
904
|
-
|
|
935
|
+
workspace: AnalysisWorkspace,
|
|
905
936
|
): string | undefined {
|
|
906
937
|
if (sourceLayer !== "domain") return undefined;
|
|
907
938
|
|
|
908
|
-
const sourceFeature = featureLayerInfo(file,
|
|
909
|
-
const targetFeature = featureLayerInfo(resolvedPath,
|
|
939
|
+
const sourceFeature = featureLayerInfo(file, workspace);
|
|
940
|
+
const targetFeature = featureLayerInfo(resolvedPath, workspace);
|
|
910
941
|
if (!sourceFeature || !targetFeature) return undefined;
|
|
911
942
|
if (sourceFeature.layer !== "domain" || targetFeature.layer !== "domain") {
|
|
912
943
|
return undefined;
|
|
913
944
|
}
|
|
914
|
-
if (
|
|
945
|
+
if (
|
|
946
|
+
sourceFeature.feature === targetFeature.feature &&
|
|
947
|
+
sourceFeature.owner === targetFeature.owner
|
|
948
|
+
)
|
|
949
|
+
return undefined;
|
|
915
950
|
if (targetFeature.feature === "shared") return undefined;
|
|
916
951
|
|
|
917
952
|
return `feature-specific domain files must not import another feature's domain (${stripKnownExtension(
|
|
918
953
|
resolvedPath,
|
|
919
|
-
)}). Move shared-kernel domain concepts to ${sharedDomainPath(config)}.`;
|
|
954
|
+
)}). Move shared-kernel domain concepts to ${sharedDomainPath(projectForFile(workspace, file).config)}.`;
|
|
920
955
|
}
|
|
921
956
|
|
|
922
957
|
const domainBannedPackages = new Set([
|
|
958
|
+
"@beignet/core/encryption",
|
|
923
959
|
"@aws-sdk/client-s3",
|
|
924
960
|
"@aws-sdk/lib-storage",
|
|
925
961
|
"@aws-sdk/s3-request-presigner",
|
|
@@ -928,12 +964,14 @@ const domainBannedPackages = new Set([
|
|
|
928
964
|
"@beignet/core/errors",
|
|
929
965
|
"@beignet/next",
|
|
930
966
|
"@beignet/core/ports",
|
|
967
|
+
"@beignet/react-form",
|
|
931
968
|
"@beignet/react-hook-form",
|
|
932
969
|
"@beignet/react-query",
|
|
933
970
|
"@hookform/resolvers",
|
|
934
971
|
"@libsql/client",
|
|
935
972
|
"@neondatabase/serverless",
|
|
936
973
|
"@planetscale/database",
|
|
974
|
+
"@tanstack/react-form",
|
|
937
975
|
"@tanstack/react-query",
|
|
938
976
|
"@upstash/ratelimit",
|
|
939
977
|
"@upstash/redis",
|
|
@@ -960,12 +998,14 @@ const coreBannedPackages = new Set([
|
|
|
960
998
|
"@aws-sdk/lib-storage",
|
|
961
999
|
"@aws-sdk/s3-request-presigner",
|
|
962
1000
|
"@beignet/core/client",
|
|
1001
|
+
"@beignet/react-form",
|
|
963
1002
|
"@beignet/react-hook-form",
|
|
964
1003
|
"@beignet/react-query",
|
|
965
1004
|
"@hookform/resolvers",
|
|
966
1005
|
"@libsql/client",
|
|
967
1006
|
"@neondatabase/serverless",
|
|
968
1007
|
"@planetscale/database",
|
|
1008
|
+
"@tanstack/react-form",
|
|
969
1009
|
"@tanstack/react-query",
|
|
970
1010
|
"@upstash/ratelimit",
|
|
971
1011
|
"@upstash/redis",
|
|
@@ -988,6 +1028,7 @@ const coreBannedPackages = new Set([
|
|
|
988
1028
|
]);
|
|
989
1029
|
|
|
990
1030
|
const serverRuntimePackages = new Set([
|
|
1031
|
+
"@beignet/core/encryption",
|
|
991
1032
|
"@beignet/devtools",
|
|
992
1033
|
"@beignet/next",
|
|
993
1034
|
"@beignet/core/openapi",
|
|
@@ -1033,19 +1074,22 @@ function featureRelativePath(
|
|
|
1033
1074
|
|
|
1034
1075
|
function featureLayerInfo(
|
|
1035
1076
|
filePath: string,
|
|
1036
|
-
|
|
1037
|
-
): { feature: string; layer: SourceLayer } | undefined {
|
|
1038
|
-
const
|
|
1077
|
+
workspace: AnalysisWorkspace,
|
|
1078
|
+
): { feature: string; owner: string; layer: SourceLayer } | undefined {
|
|
1079
|
+
const source = projectSource(workspace, filePath);
|
|
1080
|
+
const normalizedPath = stripKnownExtension(source.file);
|
|
1039
1081
|
const relativePath = featureRelativePath(
|
|
1040
1082
|
normalizedPath,
|
|
1041
|
-
directoryPath(config.paths.features),
|
|
1083
|
+
directoryPath(source.project.config.paths.features),
|
|
1042
1084
|
);
|
|
1043
1085
|
if (!relativePath) return undefined;
|
|
1044
|
-
|
|
1045
1086
|
const [feature] = relativePath.split("/");
|
|
1046
1087
|
if (!feature) return undefined;
|
|
1047
|
-
|
|
1048
|
-
|
|
1088
|
+
return {
|
|
1089
|
+
feature,
|
|
1090
|
+
owner: source.project.prefix,
|
|
1091
|
+
layer: classifyPath(filePath, workspace),
|
|
1092
|
+
};
|
|
1049
1093
|
}
|
|
1050
1094
|
|
|
1051
1095
|
function legacySharedDomainPath(config: ResolvedBeignetConfig): string {
|
|
@@ -1092,3 +1136,62 @@ function dedupeDiagnostics(diagnostics: LintDiagnostic[]): LintDiagnostic[] {
|
|
|
1092
1136
|
|
|
1093
1137
|
return deduped;
|
|
1094
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/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",
|
package/src/templates/shared.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type TemplateContext = {
|
|
|
32
32
|
|
|
33
33
|
export const externalVersions = {
|
|
34
34
|
biome: "^2.4.14",
|
|
35
|
-
next: "^16.3.
|
|
35
|
+
next: "^16.3.3",
|
|
36
36
|
react: "^19.2.5",
|
|
37
37
|
reactDom: "^19.2.5",
|
|
38
38
|
tsx: "^4.22.4",
|
|
@@ -41,7 +41,7 @@ export const externalVersions = {
|
|
|
41
41
|
typesReact: "^19.0.0",
|
|
42
42
|
typesReactDom: "^19.0.0",
|
|
43
43
|
zod: "^4.0.0",
|
|
44
|
-
tanstackReactQuery: "^5.
|
|
44
|
+
tanstackReactQuery: "^5.102.0",
|
|
45
45
|
hookformResolvers: "^5.0.0",
|
|
46
46
|
reactHookForm: "^7.74.0",
|
|
47
47
|
betterAuth: "1.7.1",
|
|
@@ -58,7 +58,7 @@ export const externalVersions = {
|
|
|
58
58
|
resend: "^6.14.0",
|
|
59
59
|
sentryNode: "^10.58.0",
|
|
60
60
|
stripe: "^22.2.1",
|
|
61
|
-
nodemailer: "^9.
|
|
61
|
+
nodemailer: "^9.1.1",
|
|
62
62
|
openFeatureServerSdk: "^1.22.0",
|
|
63
63
|
typesPg: "^8.11.0",
|
|
64
64
|
ioredis: "^5.0.0",
|