@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/app-map.ts
CHANGED
|
@@ -23,7 +23,10 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
type AnalysisWorkspace,
|
|
25
25
|
createAnalysisWorkspace,
|
|
26
|
+
projectPath,
|
|
27
|
+
projectSource,
|
|
26
28
|
} from "./analysis/workspace.js";
|
|
29
|
+
import { workspaceRouteContracts } from "./analysis/workspace-routes.js";
|
|
27
30
|
import type { AppMapNodeKind } from "./app-map-schema.js";
|
|
28
31
|
import { directoryPath } from "./config.js";
|
|
29
32
|
import {
|
|
@@ -215,9 +218,8 @@ export async function mapApp(
|
|
|
215
218
|
const definitions = new Map<string, DefinitionRecord>();
|
|
216
219
|
const definitionBySource = new Map<string, string>();
|
|
217
220
|
const registries: RegistryRecord[] = [];
|
|
218
|
-
const
|
|
219
|
-
const featureNames =
|
|
220
|
-
const testRoots = appTestRoots(workspace.config.paths);
|
|
221
|
+
const features = discoverFeatures(workspace);
|
|
222
|
+
const featureNames = [...features.keys()];
|
|
221
223
|
|
|
222
224
|
const addNode = (node: AppMapNode) => {
|
|
223
225
|
const existing = nodes.get(node.id);
|
|
@@ -235,11 +237,9 @@ export async function mapApp(
|
|
|
235
237
|
};
|
|
236
238
|
const addEdge = (edge: AppMapEdge) => edges.push(edge);
|
|
237
239
|
|
|
238
|
-
for (const feature of
|
|
239
|
-
const featureFiles =
|
|
240
|
-
|
|
241
|
-
featuresPath,
|
|
242
|
-
feature,
|
|
240
|
+
for (const [feature, featurePath] of features) {
|
|
241
|
+
const featureFiles = workspace.files.filter((file) =>
|
|
242
|
+
file.startsWith(`${featurePath}/`),
|
|
243
243
|
);
|
|
244
244
|
const roles = summarizeFeatureFiles(featureFiles);
|
|
245
245
|
addNode({
|
|
@@ -247,7 +247,7 @@ export async function mapApp(
|
|
|
247
247
|
kind: "feature",
|
|
248
248
|
name: feature,
|
|
249
249
|
feature,
|
|
250
|
-
source: { file:
|
|
250
|
+
source: { file: featurePath },
|
|
251
251
|
status: "declared",
|
|
252
252
|
details: roles,
|
|
253
253
|
});
|
|
@@ -255,10 +255,14 @@ export async function mapApp(
|
|
|
255
255
|
|
|
256
256
|
for (const file of workspace.files.filter(
|
|
257
257
|
(candidate) =>
|
|
258
|
-
isAppTestFile(candidate) &&
|
|
258
|
+
isAppTestFile(candidate) &&
|
|
259
|
+
isWithinAppTestRoots(
|
|
260
|
+
projectSource(workspace, candidate).file,
|
|
261
|
+
appTestRoots(projectSource(workspace, candidate).project.config.paths),
|
|
262
|
+
),
|
|
259
263
|
)) {
|
|
260
264
|
const id = `test:${file}`;
|
|
261
|
-
const feature = featureForFile(file,
|
|
265
|
+
const feature = featureForFile(file, workspace);
|
|
262
266
|
addNode({
|
|
263
267
|
id,
|
|
264
268
|
kind: "test",
|
|
@@ -276,7 +280,7 @@ export async function mapApp(
|
|
|
276
280
|
const contractIdsByExport = new Map<string, string[]>();
|
|
277
281
|
for (const contract of inspection.contracts) {
|
|
278
282
|
const id = `contract:${contract.file}#${contract.exportName}`;
|
|
279
|
-
const feature = featureForFile(contract.file,
|
|
283
|
+
const feature = featureForFile(contract.file, workspace);
|
|
280
284
|
contractIds.set(symbolKey(contract.file, contract.exportName), id);
|
|
281
285
|
const exports = contractIdsByExport.get(contract.exportName) ?? [];
|
|
282
286
|
exports.push(id);
|
|
@@ -321,8 +325,8 @@ export async function mapApp(
|
|
|
321
325
|
id,
|
|
322
326
|
kind: "route",
|
|
323
327
|
name: `${route.method} ${route.path}`,
|
|
324
|
-
...(featureForFile(route.file,
|
|
325
|
-
? { feature: featureForFile(route.file,
|
|
328
|
+
...(featureForFile(route.file, workspace)
|
|
329
|
+
? { feature: featureForFile(route.file, workspace) }
|
|
326
330
|
: {}),
|
|
327
331
|
source: {
|
|
328
332
|
file: route.handlerFile ?? route.file,
|
|
@@ -375,7 +379,7 @@ export async function mapApp(
|
|
|
375
379
|
const runtimeName = literalText(useCaseCall.arguments[0]);
|
|
376
380
|
if (runtimeName) {
|
|
377
381
|
const kind = callMemberName(useCaseCall) as "command" | "query";
|
|
378
|
-
const feature = featureForFile(file,
|
|
382
|
+
const feature = featureForFile(file, workspace);
|
|
379
383
|
const id = `use-case:${runtimeName}`;
|
|
380
384
|
const node: AppMapNode = {
|
|
381
385
|
id,
|
|
@@ -418,7 +422,7 @@ export async function mapApp(
|
|
|
418
422
|
: undefined;
|
|
419
423
|
const runtimeName = definitionRuntimeName(call, definitionCall);
|
|
420
424
|
if (kind && runtimeName) {
|
|
421
|
-
const feature = featureForFile(file,
|
|
425
|
+
const feature = featureForFile(file, workspace);
|
|
422
426
|
const id = `${kind}:${runtimeName}`;
|
|
423
427
|
const status = definitionStatus(
|
|
424
428
|
kind,
|
|
@@ -458,7 +462,7 @@ export async function mapApp(
|
|
|
458
462
|
|
|
459
463
|
const policyCall = findCall(initializer, new Set(["definePolicy"]));
|
|
460
464
|
if (policyCall && ts.isObjectLiteralExpression(policyCall.arguments[0])) {
|
|
461
|
-
const feature = featureForFile(file,
|
|
465
|
+
const feature = featureForFile(file, workspace);
|
|
462
466
|
const policyId = `policy:${file}#${exportName}`;
|
|
463
467
|
addNode({
|
|
464
468
|
id: policyId,
|
|
@@ -676,8 +680,11 @@ export async function mapApp(
|
|
|
676
680
|
contractIdsByExport,
|
|
677
681
|
routeIds,
|
|
678
682
|
definitionBySource,
|
|
679
|
-
featuresPath,
|
|
680
683
|
diagnostics: inspection.diagnostics,
|
|
684
|
+
registeredGroups:
|
|
685
|
+
workspace.projects.length > 1
|
|
686
|
+
? (await workspaceRouteContracts(workspace)).groups
|
|
687
|
+
: undefined,
|
|
681
688
|
});
|
|
682
689
|
|
|
683
690
|
await addOpenApiSurface({
|
|
@@ -711,7 +718,6 @@ export async function mapApp(
|
|
|
711
718
|
await addUnresolvedLocalImports({ workspace, unresolved });
|
|
712
719
|
await addFeatureDependencies({
|
|
713
720
|
workspace,
|
|
714
|
-
featuresPath,
|
|
715
721
|
addEdge,
|
|
716
722
|
featureNames: new Set(featureNames),
|
|
717
723
|
});
|
|
@@ -928,12 +934,12 @@ async function addRouteGroups(args: {
|
|
|
928
934
|
contractIdsByExport: Map<string, string[]>;
|
|
929
935
|
routeIds: Map<string, string>;
|
|
930
936
|
definitionBySource: Map<string, string>;
|
|
931
|
-
|
|
937
|
+
registeredGroups?: Set<string>;
|
|
932
938
|
diagnostics: InspectDiagnostic[];
|
|
933
939
|
}): Promise<void> {
|
|
934
940
|
const routeFiles = args.workspace.files.filter(
|
|
935
941
|
(file) =>
|
|
936
|
-
featureForFile(file, args.
|
|
942
|
+
featureForFile(file, args.workspace) !== undefined &&
|
|
937
943
|
isAnalysisSourceFile(file) &&
|
|
938
944
|
stripSourceFileExtension(file).endsWith("/routes"),
|
|
939
945
|
);
|
|
@@ -949,17 +955,20 @@ async function addRouteGroups(args: {
|
|
|
949
955
|
const options = call?.arguments[0];
|
|
950
956
|
if (!call || !options || !ts.isObjectLiteralExpression(options)) continue;
|
|
951
957
|
|
|
952
|
-
const feature = featureForFile(file, args.
|
|
958
|
+
const feature = featureForFile(file, args.workspace);
|
|
953
959
|
const groupName = literalText(
|
|
954
960
|
propertyAssignment(options, "name")?.initializer,
|
|
955
961
|
);
|
|
956
962
|
const id = `route-group:${file}#${exportName}`;
|
|
957
|
-
const missing =
|
|
958
|
-
(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
diagnostic
|
|
962
|
-
|
|
963
|
+
const missing =
|
|
964
|
+
(args.registeredGroups !== undefined &&
|
|
965
|
+
!args.registeredGroups.has(`${file}#${exportName}`)) ||
|
|
966
|
+
args.diagnostics.some(
|
|
967
|
+
(diagnostic) =>
|
|
968
|
+
diagnostic.code === "BEIGNET_ROUTE_GROUP_UNREGISTERED" &&
|
|
969
|
+
diagnostic.subject?.file === file &&
|
|
970
|
+
diagnostic.subject.exportName === exportName,
|
|
971
|
+
);
|
|
963
972
|
args.addNode({
|
|
964
973
|
id,
|
|
965
974
|
kind: "route-group",
|
|
@@ -1080,8 +1089,10 @@ async function addPorts(args: {
|
|
|
1080
1089
|
}): Promise<void> {
|
|
1081
1090
|
const portsFile = args.workspace.config.paths.ports;
|
|
1082
1091
|
if (!args.workspace.fileSet.has(portsFile)) return;
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1092
|
+
const analysis = await analyzePortWiring(
|
|
1093
|
+
args.workspace,
|
|
1094
|
+
args.workspace.projects.length > 1 ? args.sourceIndex : undefined,
|
|
1095
|
+
);
|
|
1085
1096
|
if (analysis.declarationIndeterminate) {
|
|
1086
1097
|
args.unresolved.push({
|
|
1087
1098
|
code: "app_ports_declaration_unresolved",
|
|
@@ -1098,11 +1109,12 @@ async function addPorts(args: {
|
|
|
1098
1109
|
}
|
|
1099
1110
|
for (const name of analysis.declared) {
|
|
1100
1111
|
const property = analysis.properties.get(name);
|
|
1112
|
+
const propertyFile = analysis.propertyFiles.get(name) ?? portsFile;
|
|
1101
1113
|
const feature = property
|
|
1102
1114
|
? await portFeature(
|
|
1103
1115
|
property.type,
|
|
1104
|
-
|
|
1105
|
-
args.workspace
|
|
1116
|
+
propertyFile,
|
|
1117
|
+
args.workspace,
|
|
1106
1118
|
args.sourceIndex,
|
|
1107
1119
|
)
|
|
1108
1120
|
: undefined;
|
|
@@ -1113,7 +1125,7 @@ async function addPorts(args: {
|
|
|
1113
1125
|
name,
|
|
1114
1126
|
...(feature ? { feature } : {}),
|
|
1115
1127
|
source: property
|
|
1116
|
-
? await args.sourceIndex.sourceLocation(
|
|
1128
|
+
? await args.sourceIndex.sourceLocation(propertyFile, property)
|
|
1117
1129
|
: { file: args.workspace.config.paths.portWiring },
|
|
1118
1130
|
status:
|
|
1119
1131
|
analysis.bound.has(name) || analysis.deferred.has(name)
|
|
@@ -1125,7 +1137,7 @@ async function addPorts(args: {
|
|
|
1125
1137
|
: analysis.deferred.has(name)
|
|
1126
1138
|
? "deferred"
|
|
1127
1139
|
: "unresolved",
|
|
1128
|
-
...(property?.type ? { type: property.type.getText(
|
|
1140
|
+
...(property?.type ? { type: property.type.getText() } : {}),
|
|
1129
1141
|
},
|
|
1130
1142
|
});
|
|
1131
1143
|
if (feature) {
|
|
@@ -1447,7 +1459,6 @@ async function addTables(args: {
|
|
|
1447
1459
|
|
|
1448
1460
|
async function addFeatureDependencies(args: {
|
|
1449
1461
|
workspace: AnalysisWorkspace;
|
|
1450
|
-
featuresPath: string;
|
|
1451
1462
|
addEdge(edge: AppMapEdge): void;
|
|
1452
1463
|
featureNames: Set<string>;
|
|
1453
1464
|
}): Promise<void> {
|
|
@@ -1466,11 +1477,11 @@ async function addFeatureDependencies(args: {
|
|
|
1466
1477
|
>();
|
|
1467
1478
|
|
|
1468
1479
|
for (const file of args.workspace.files.filter(isAnalysisSourceFile)) {
|
|
1469
|
-
const from = featureForFile(file, args.
|
|
1480
|
+
const from = featureForFile(file, args.workspace);
|
|
1470
1481
|
if (!from || !args.featureNames.has(from)) continue;
|
|
1471
1482
|
for (const reference of await localImportReferences(args.workspace, file)) {
|
|
1472
1483
|
if (!reference.resolvedFile) continue;
|
|
1473
|
-
const to = featureForFile(reference.resolvedFile, args.
|
|
1484
|
+
const to = featureForFile(reference.resolvedFile, args.workspace);
|
|
1474
1485
|
if (!to || to === from || !args.featureNames.has(to)) continue;
|
|
1475
1486
|
const key = `${from}->${to}`;
|
|
1476
1487
|
const current = dependencies.get(key) ?? {
|
|
@@ -1569,16 +1580,16 @@ async function addUnresolvedContractDeclarations(args: {
|
|
|
1569
1580
|
}
|
|
1570
1581
|
|
|
1571
1582
|
function appMapContractSourceFiles(workspace: AnalysisWorkspace): string[] {
|
|
1572
|
-
const contractsPath = directoryPath(workspace.config.paths.contracts);
|
|
1573
|
-
const featureOwned =
|
|
1574
|
-
contractsPath === directoryPath(workspace.config.paths.features);
|
|
1575
|
-
|
|
1576
1583
|
return workspace.files.filter((file) => {
|
|
1577
1584
|
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
1578
|
-
const
|
|
1585
|
+
const source = projectSource(workspace, file);
|
|
1586
|
+
const contractsPath = directoryPath(source.project.config.paths.contracts);
|
|
1587
|
+
const featureOwned =
|
|
1588
|
+
contractsPath === directoryPath(source.project.config.paths.features);
|
|
1589
|
+
const sourcePath = stripSourceFileExtension(source.file);
|
|
1579
1590
|
if (featureOwned) {
|
|
1580
1591
|
const prefix = `${contractsPath}/`;
|
|
1581
|
-
if (!file.startsWith(prefix)) return false;
|
|
1592
|
+
if (!source.file.startsWith(prefix)) return false;
|
|
1582
1593
|
const parts = sourcePath.slice(prefix.length).split("/");
|
|
1583
1594
|
return parts.length === 2 && parts[1] === "contracts";
|
|
1584
1595
|
}
|
|
@@ -1599,10 +1610,7 @@ async function registryRecord(args: {
|
|
|
1599
1610
|
if (!initializer) return undefined;
|
|
1600
1611
|
const registry = registryValues(initializer, args.exportName);
|
|
1601
1612
|
if (!registry) return undefined;
|
|
1602
|
-
const feature = featureForFile(
|
|
1603
|
-
args.file,
|
|
1604
|
-
directoryPath(args.workspace.config.paths.features),
|
|
1605
|
-
);
|
|
1613
|
+
const feature = featureForFile(args.file, args.workspace);
|
|
1606
1614
|
return {
|
|
1607
1615
|
file: args.file,
|
|
1608
1616
|
declaration: args.declaration,
|
|
@@ -1814,27 +1822,19 @@ function arrayExpressions(array: ts.ArrayLiteralExpression): ts.Expression[] {
|
|
|
1814
1822
|
});
|
|
1815
1823
|
}
|
|
1816
1824
|
|
|
1817
|
-
function
|
|
1818
|
-
|
|
1819
|
-
featuresPath: string,
|
|
1820
|
-
): string[] {
|
|
1821
|
-
const names = new Set<string>();
|
|
1822
|
-
const prefix = `${featuresPath}/`;
|
|
1825
|
+
function discoverFeatures(workspace: AnalysisWorkspace): Map<string, string> {
|
|
1826
|
+
const features = new Map<string, string>();
|
|
1823
1827
|
for (const file of workspace.files) {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1828
|
+
const feature = featureForFile(file, workspace);
|
|
1829
|
+
if (!feature) continue;
|
|
1830
|
+
const source = projectSource(workspace, file);
|
|
1831
|
+
const root = directoryPath(source.project.config.paths.features);
|
|
1832
|
+
const name = source.file.slice(`${root}/`.length).split("/")[0];
|
|
1833
|
+
features.set(feature, projectPath(source.project, `${root}/${name}`));
|
|
1827
1834
|
}
|
|
1828
|
-
return
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
function filesForFeature(
|
|
1832
|
-
files: string[],
|
|
1833
|
-
featuresPath: string,
|
|
1834
|
-
feature: string,
|
|
1835
|
-
): string[] {
|
|
1836
|
-
const prefix = `${featuresPath}/${feature}/`;
|
|
1837
|
-
return files.filter((file) => file.startsWith(prefix));
|
|
1835
|
+
return new Map(
|
|
1836
|
+
[...features].sort(([left], [right]) => left.localeCompare(right)),
|
|
1837
|
+
);
|
|
1838
1838
|
}
|
|
1839
1839
|
|
|
1840
1840
|
function summarizeFeatureFiles(files: string[]): Record<string, number> {
|
|
@@ -1849,18 +1849,20 @@ function summarizeFeatureFiles(files: string[]): Record<string, number> {
|
|
|
1849
1849
|
|
|
1850
1850
|
function featureForFile(
|
|
1851
1851
|
file: string,
|
|
1852
|
-
|
|
1852
|
+
workspace: AnalysisWorkspace,
|
|
1853
1853
|
): string | undefined {
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1854
|
+
const source = projectSource(workspace, file);
|
|
1855
|
+
const prefix = `${directoryPath(source.project.config.paths.features)}/`;
|
|
1856
|
+
if (!source.file.startsWith(prefix)) return undefined;
|
|
1857
|
+
const feature = source.file.slice(prefix.length).split("/")[0];
|
|
1858
|
+
if (!feature || feature === "shared") return undefined;
|
|
1859
|
+
return source.project.prefix ? `${source.project.name}:${feature}` : feature;
|
|
1858
1860
|
}
|
|
1859
1861
|
|
|
1860
1862
|
async function portFeature(
|
|
1861
1863
|
type: ts.TypeNode | undefined,
|
|
1862
1864
|
file: string,
|
|
1863
|
-
|
|
1865
|
+
workspace: AnalysisWorkspace,
|
|
1864
1866
|
sourceIndex: ReturnType<typeof createSourceIndex>,
|
|
1865
1867
|
): Promise<string | undefined> {
|
|
1866
1868
|
if (
|
|
@@ -1871,9 +1873,7 @@ async function portFeature(
|
|
|
1871
1873
|
return undefined;
|
|
1872
1874
|
}
|
|
1873
1875
|
const target = await sourceIndex.resolveName(file, type.typeName.text);
|
|
1874
|
-
return target
|
|
1875
|
-
? featureForFile(target.file, directoryPath(featuresPath))
|
|
1876
|
-
: undefined;
|
|
1876
|
+
return target ? featureForFile(target.file, workspace) : undefined;
|
|
1877
1877
|
}
|
|
1878
1878
|
|
|
1879
1879
|
function definitionStatus(
|
package/src/config.ts
CHANGED
|
@@ -67,6 +67,12 @@ export type BeignetProviderAuditConfig = {
|
|
|
67
67
|
ignoreRequiredEnv?: string[];
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
/** Source packages included in read-only application analysis. */
|
|
71
|
+
export type BeignetWorkspaceConfig = {
|
|
72
|
+
/** Package directories relative to this app, including transitive dependencies. */
|
|
73
|
+
packages: string[];
|
|
74
|
+
};
|
|
75
|
+
|
|
70
76
|
/**
|
|
71
77
|
* User-authored Beignet CLI config.
|
|
72
78
|
*/
|
|
@@ -75,6 +81,7 @@ export type BeignetConfig = {
|
|
|
75
81
|
paths?: Partial<BeignetPaths>;
|
|
76
82
|
database?: BeignetDatabaseConfig;
|
|
77
83
|
providerAudit?: BeignetProviderAuditConfig;
|
|
84
|
+
workspace?: BeignetWorkspaceConfig;
|
|
78
85
|
};
|
|
79
86
|
|
|
80
87
|
/**
|
|
@@ -90,6 +97,7 @@ export type ResolvedBeignetConfig = {
|
|
|
90
97
|
providerAudit: {
|
|
91
98
|
ignoreRequiredEnv: string[];
|
|
92
99
|
};
|
|
100
|
+
workspace: BeignetWorkspaceConfig;
|
|
93
101
|
configFile?: string;
|
|
94
102
|
};
|
|
95
103
|
|
|
@@ -134,6 +142,7 @@ export const defaultBeignetConfig: ResolvedBeignetConfig = {
|
|
|
134
142
|
providerAudit: {
|
|
135
143
|
ignoreRequiredEnv: [],
|
|
136
144
|
},
|
|
145
|
+
workspace: { packages: [] },
|
|
137
146
|
};
|
|
138
147
|
|
|
139
148
|
/**
|
|
@@ -174,6 +183,7 @@ export function resolveConfig(
|
|
|
174
183
|
paths: normalizeConfigPaths(paths),
|
|
175
184
|
database: normalizeDatabaseConfig(database),
|
|
176
185
|
providerAudit: normalizeProviderAuditConfig(providerAudit),
|
|
186
|
+
workspace: normalizeWorkspaceConfig(config.workspace),
|
|
177
187
|
configFile,
|
|
178
188
|
};
|
|
179
189
|
}
|
|
@@ -453,6 +463,29 @@ function normalizeIgnoredRequiredEnv(index: number, value: string): string {
|
|
|
453
463
|
return value.trim();
|
|
454
464
|
}
|
|
455
465
|
|
|
466
|
+
function normalizeWorkspaceConfig(
|
|
467
|
+
workspace: BeignetWorkspaceConfig | undefined,
|
|
468
|
+
): BeignetWorkspaceConfig {
|
|
469
|
+
if (workspace === undefined) return { packages: [] };
|
|
470
|
+
if (!isRecord(workspace) || !Array.isArray(workspace.packages)) {
|
|
471
|
+
throw new Error("Beignet config workspace.packages must be an array.");
|
|
472
|
+
}
|
|
473
|
+
const packages = workspace.packages.map((value, index) => {
|
|
474
|
+
if (
|
|
475
|
+
typeof value !== "string" ||
|
|
476
|
+
!value.trim() ||
|
|
477
|
+
path.isAbsolute(value.trim()) ||
|
|
478
|
+
path.win32.isAbsolute(value.trim())
|
|
479
|
+
) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`Beignet config workspace.packages[${index}] must be a relative package directory.`,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
return path.posix.normalize(normalizePath(value.trim())).replace(/\/$/, "");
|
|
485
|
+
});
|
|
486
|
+
return { packages: [...new Set(packages)].sort() };
|
|
487
|
+
}
|
|
488
|
+
|
|
456
489
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
457
490
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
458
491
|
}
|
package/src/index.ts
CHANGED
|
@@ -1853,6 +1853,30 @@ const scheduleRoutes = buildRouteMap({
|
|
|
1853
1853
|
},
|
|
1854
1854
|
});
|
|
1855
1855
|
|
|
1856
|
+
const encryptionKeyCommand = buildCommand<{ json?: boolean }, [], CliContext>({
|
|
1857
|
+
docs: {
|
|
1858
|
+
brief: "Print a new encryption key without changing environment files.",
|
|
1859
|
+
},
|
|
1860
|
+
parameters: {
|
|
1861
|
+
flags: { json: jsonFlag },
|
|
1862
|
+
},
|
|
1863
|
+
loader: async () => {
|
|
1864
|
+
const { generateEncryptionKey } = await import("@beignet/core/encryption");
|
|
1865
|
+
return async function runEncryptionKey(this: CliContext, flags) {
|
|
1866
|
+
const key = generateEncryptionKey();
|
|
1867
|
+
writeOutput(
|
|
1868
|
+
this,
|
|
1869
|
+
flags.json ? JSON.stringify({ schemaVersion: 1, key }) : key,
|
|
1870
|
+
);
|
|
1871
|
+
};
|
|
1872
|
+
},
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
const encryptionRoutes = buildRouteMap({
|
|
1876
|
+
docs: { brief: "Manage application encryption." },
|
|
1877
|
+
routes: { key: encryptionKeyCommand },
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1856
1880
|
const completionShellFlagParameters = {
|
|
1857
1881
|
shell: {
|
|
1858
1882
|
kind: "enum",
|
|
@@ -2449,6 +2473,7 @@ Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
|
|
|
2449
2473
|
create: createCommand,
|
|
2450
2474
|
db: dbRoutes,
|
|
2451
2475
|
doctor: doctorCommand,
|
|
2476
|
+
encryption: encryptionRoutes,
|
|
2452
2477
|
explain: explainRoutes,
|
|
2453
2478
|
lint: lintCommand,
|
|
2454
2479
|
map: mapCommand,
|