@beignet/cli 0.0.38 → 0.0.40
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 +18 -0
- package/README.md +154 -102
- package/dist/choices.d.ts +17 -21
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +21 -67
- package/dist/choices.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -2
- package/dist/config.js.map +1 -1
- package/dist/create-prompts.d.ts +5 -6
- package/dist/create-prompts.d.ts.map +1 -1
- package/dist/create-prompts.js +15 -15
- package/dist/create-prompts.js.map +1 -1
- package/dist/create.d.ts +11 -2
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +38 -28
- package/dist/create.js.map +1 -1
- package/dist/framework.d.ts +3 -0
- package/dist/framework.d.ts.map +1 -0
- package/dist/framework.js +6 -0
- package/dist/framework.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +149 -47
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +4 -2
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +174 -34
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/make/inbox.js +3 -3
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.d.ts.map +1 -1
- package/dist/make/payments.js +5 -3
- package/dist/make/payments.js.map +1 -1
- package/dist/make/shared.d.ts +10 -3
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +7 -8
- package/dist/make/shared.js.map +1 -1
- package/dist/make.d.ts +3 -2
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +549 -186
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +15 -11
- package/dist/mcp.js.map +1 -1
- package/dist/provider-add.d.ts +21 -3
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +101 -65
- package/dist/provider-add.js.map +1 -1
- package/dist/task.js +1 -1
- package/dist/task.js.map +1 -1
- package/dist/templates/base.d.ts +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +15 -15
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts +2 -2
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +5 -5
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +17 -13
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shadcn.js +1 -1
- package/dist/templates/shared.d.ts +4 -4
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +6 -6
- package/dist/templates/shared.js.map +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +29 -9
- package/src/choices.ts +38 -88
- package/src/config.ts +7 -3
- package/src/create-prompts.ts +20 -21
- package/src/create.ts +54 -36
- package/src/framework.ts +13 -0
- package/src/index.ts +193 -65
- package/src/inspect.ts +290 -39
- package/src/lib.ts +1 -1
- package/src/make/inbox.ts +3 -3
- package/src/make/payments.ts +10 -3
- package/src/make/shared.ts +16 -10
- package/src/make.ts +720 -176
- package/src/mcp.ts +20 -13
- package/src/provider-add.ts +159 -79
- package/src/task.ts +1 -1
- package/src/templates/base.ts +16 -16
- package/src/templates/index.ts +6 -6
- package/src/templates/server.ts +17 -13
- package/src/templates/shadcn.ts +1 -1
- package/src/templates/shared.ts +10 -10
- package/src/test-helpers/generated-app.ts +2 -1
package/src/inspect.ts
CHANGED
|
@@ -105,7 +105,7 @@ type InspectedCatalogErrorRef = {
|
|
|
105
105
|
export type InspectedRoute = InspectedContract & {
|
|
106
106
|
handlerFile?: string;
|
|
107
107
|
handlerExport?: HttpMethod;
|
|
108
|
-
handlerSource: "next-route" | "route-local" | "missing";
|
|
108
|
+
handlerSource: "next-route" | "route-local" | "web-server" | "missing";
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
/**
|
|
@@ -132,11 +132,13 @@ export type InspectFix = {
|
|
|
132
132
|
* Detected app convention and missing convention files.
|
|
133
133
|
*/
|
|
134
134
|
export type InspectConvention = {
|
|
135
|
-
kind: "standard" | "next" | "custom";
|
|
135
|
+
kind: "standard" | "next" | "web" | "custom";
|
|
136
136
|
nextLayout: boolean;
|
|
137
|
+
webLayout: boolean;
|
|
137
138
|
resourceGenerator: boolean;
|
|
138
139
|
configFile?: string;
|
|
139
140
|
missingNextLayout: string[];
|
|
141
|
+
missingWebLayout: string[];
|
|
140
142
|
missingResourceGenerator: string[];
|
|
141
143
|
};
|
|
142
144
|
|
|
@@ -204,13 +206,12 @@ export async function inspectApp(
|
|
|
204
206
|
: await loadBeignetConfig(targetDir, files);
|
|
205
207
|
const convention = inspectConvention(files, config);
|
|
206
208
|
const contracts = await readContracts(targetDir, files, config);
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
const matchedRoutes = await inspectRouteSurface(
|
|
210
|
+
targetDir,
|
|
211
|
+
files,
|
|
212
|
+
config,
|
|
213
|
+
contracts,
|
|
211
214
|
);
|
|
212
|
-
const routeExports = await readRouteExports(targetDir, routeFiles, config);
|
|
213
|
-
const matchedRoutes = matchRoutes(contracts, routeExports);
|
|
214
215
|
const diagnostics = await inspectDiagnostics(
|
|
215
216
|
targetDir,
|
|
216
217
|
files,
|
|
@@ -246,13 +247,12 @@ export async function applyDoctorFixes(
|
|
|
246
247
|
: await loadBeignetConfig(targetDir, files);
|
|
247
248
|
const convention = inspectConvention(files, config);
|
|
248
249
|
const contracts = await readContracts(targetDir, files, config);
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
const matchedRoutes = await inspectRouteSurface(
|
|
251
|
+
targetDir,
|
|
252
|
+
files,
|
|
253
|
+
config,
|
|
254
|
+
contracts,
|
|
253
255
|
);
|
|
254
|
-
const routeExports = await readRouteExports(targetDir, routeFiles, config);
|
|
255
|
-
const matchedRoutes = matchRoutes(contracts, routeExports);
|
|
256
256
|
const fixes: InspectFix[] = [];
|
|
257
257
|
|
|
258
258
|
const packageFix = await fixPackageScripts(targetDir, files, convention);
|
|
@@ -318,7 +318,9 @@ export function formatRoutes(result: InspectAppResult): string {
|
|
|
318
318
|
handler:
|
|
319
319
|
route.handlerSource === "missing"
|
|
320
320
|
? "missing"
|
|
321
|
-
:
|
|
321
|
+
: route.handlerSource === "web-server"
|
|
322
|
+
? `${route.handlerFile ?? "unknown"}:registry`
|
|
323
|
+
: `${route.handlerFile ?? "unknown"}:${route.handlerExport ?? route.method}`,
|
|
322
324
|
}));
|
|
323
325
|
|
|
324
326
|
return table([
|
|
@@ -556,6 +558,10 @@ function inspectConvention(
|
|
|
556
558
|
`${directoryPath(config.paths.routes)}/`,
|
|
557
559
|
config.paths.server,
|
|
558
560
|
]);
|
|
561
|
+
const missingWebLayout = missingRequirements(files, [
|
|
562
|
+
`${directoryPath(config.paths.contracts)}/`,
|
|
563
|
+
config.paths.server,
|
|
564
|
+
]);
|
|
559
565
|
const missingResourceGenerator = missingRequirements(files, [
|
|
560
566
|
config.paths.appContext,
|
|
561
567
|
config.paths.portWiring,
|
|
@@ -564,14 +570,23 @@ function inspectConvention(
|
|
|
564
570
|
config.paths.useCaseBuilder,
|
|
565
571
|
]);
|
|
566
572
|
const nextLayout = missingNextLayout.length === 0;
|
|
573
|
+
const webLayout = missingWebLayout.length === 0;
|
|
567
574
|
const resourceGenerator = missingResourceGenerator.length === 0;
|
|
568
575
|
|
|
569
576
|
return {
|
|
570
|
-
kind: resourceGenerator
|
|
577
|
+
kind: resourceGenerator
|
|
578
|
+
? "standard"
|
|
579
|
+
: config.framework === "web" && webLayout
|
|
580
|
+
? "web"
|
|
581
|
+
: nextLayout
|
|
582
|
+
? "next"
|
|
583
|
+
: "custom",
|
|
571
584
|
nextLayout,
|
|
585
|
+
webLayout,
|
|
572
586
|
resourceGenerator,
|
|
573
587
|
configFile: config.configFile,
|
|
574
588
|
missingNextLayout,
|
|
589
|
+
missingWebLayout,
|
|
575
590
|
missingResourceGenerator,
|
|
576
591
|
};
|
|
577
592
|
}
|
|
@@ -1289,6 +1304,73 @@ function matchRoutes(
|
|
|
1289
1304
|
};
|
|
1290
1305
|
}
|
|
1291
1306
|
|
|
1307
|
+
async function inspectRouteSurface(
|
|
1308
|
+
targetDir: string,
|
|
1309
|
+
files: string[],
|
|
1310
|
+
config: ResolvedBeignetConfig,
|
|
1311
|
+
contracts: InspectedContract[],
|
|
1312
|
+
): Promise<MatchedRoutesResult> {
|
|
1313
|
+
if (config.framework === "next") {
|
|
1314
|
+
const routeFiles = files.filter(
|
|
1315
|
+
(file) =>
|
|
1316
|
+
file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
|
|
1317
|
+
file.endsWith("/route.ts"),
|
|
1318
|
+
);
|
|
1319
|
+
const routeExports = await readRouteExports(targetDir, routeFiles, config);
|
|
1320
|
+
return matchRoutes(contracts, routeExports);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
if (!files.includes(config.paths.server)) {
|
|
1324
|
+
return {
|
|
1325
|
+
routes: contracts.map((contract) => ({
|
|
1326
|
+
...contract,
|
|
1327
|
+
handlerSource: "missing",
|
|
1328
|
+
})),
|
|
1329
|
+
unmatchedRouteHandlers: [],
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const serverSource = await readFile(
|
|
1334
|
+
path.join(targetDir, config.paths.server),
|
|
1335
|
+
"utf8",
|
|
1336
|
+
);
|
|
1337
|
+
const routeGroups = await readFeatureRouteGroups(targetDir, files, config);
|
|
1338
|
+
const registeredGroups = await registeredRouteGroupsForServer(
|
|
1339
|
+
targetDir,
|
|
1340
|
+
files,
|
|
1341
|
+
config,
|
|
1342
|
+
serverSource,
|
|
1343
|
+
);
|
|
1344
|
+
const registeredContracts = contractsForRegisteredRouteGroups(
|
|
1345
|
+
contracts,
|
|
1346
|
+
routeGroups,
|
|
1347
|
+
registeredGroups,
|
|
1348
|
+
);
|
|
1349
|
+
const registeredKeys = new Set(registeredContracts.map(contractKey));
|
|
1350
|
+
const handlerFile =
|
|
1351
|
+
routeRegistryFileFromServerSource(
|
|
1352
|
+
serverSource,
|
|
1353
|
+
config.paths.server,
|
|
1354
|
+
files,
|
|
1355
|
+
) ?? config.paths.server;
|
|
1356
|
+
|
|
1357
|
+
return {
|
|
1358
|
+
routes: contracts.map((contract) =>
|
|
1359
|
+
registeredKeys.has(contractKey(contract))
|
|
1360
|
+
? {
|
|
1361
|
+
...contract,
|
|
1362
|
+
handlerFile,
|
|
1363
|
+
handlerSource: "web-server" as const,
|
|
1364
|
+
}
|
|
1365
|
+
: {
|
|
1366
|
+
...contract,
|
|
1367
|
+
handlerSource: "missing" as const,
|
|
1368
|
+
},
|
|
1369
|
+
),
|
|
1370
|
+
unmatchedRouteHandlers: [],
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1292
1374
|
function findContract(
|
|
1293
1375
|
contracts: InspectedContract[],
|
|
1294
1376
|
routeExport: RouteExport,
|
|
@@ -1349,28 +1431,53 @@ async function inspectDiagnostics(
|
|
|
1349
1431
|
strict: boolean,
|
|
1350
1432
|
): Promise<InspectDiagnostic[]> {
|
|
1351
1433
|
const diagnostics: InspectDiagnostic[] = [];
|
|
1352
|
-
|
|
1353
|
-
|
|
1434
|
+
const frameworkLayout =
|
|
1435
|
+
config.framework === "next" ? convention.nextLayout : convention.webLayout;
|
|
1436
|
+
const missingFrameworkLayout =
|
|
1437
|
+
config.framework === "next"
|
|
1438
|
+
? convention.missingNextLayout
|
|
1439
|
+
: convention.missingWebLayout;
|
|
1440
|
+
|
|
1441
|
+
if (!frameworkLayout) {
|
|
1442
|
+
const expectedLayout =
|
|
1443
|
+
config.framework === "next"
|
|
1444
|
+
? `${directoryPath(config.paths.contracts)}/, ${directoryPath(config.paths.routes)}/, and ${config.paths.server}`
|
|
1445
|
+
: `${directoryPath(config.paths.contracts)}/ and ${config.paths.server}`;
|
|
1446
|
+
const layoutName =
|
|
1447
|
+
config.framework === "next"
|
|
1448
|
+
? "standard Beignet app layout"
|
|
1449
|
+
: "standard Beignet web app layout";
|
|
1354
1450
|
diagnostics.push({
|
|
1355
1451
|
severity: "warning",
|
|
1356
1452
|
code: "BEIGNET_APP_LAYOUT_NOT_FOUND",
|
|
1357
|
-
message: `This directory does not match the
|
|
1453
|
+
message: `This directory does not match the ${layoutName}. CLI inspection expects ${expectedLayout}. Missing: ${missingFrameworkLayout.join(", ")}. Add the missing app files or configure their paths in beignet.config.ts.`,
|
|
1358
1454
|
});
|
|
1359
1455
|
}
|
|
1360
1456
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1457
|
+
diagnostics.push(
|
|
1458
|
+
...(await inspectFrameworkAdapter(targetDir, files, config)),
|
|
1459
|
+
);
|
|
1460
|
+
|
|
1461
|
+
if (config.framework === "next" || !convention.resourceGenerator) {
|
|
1462
|
+
for (const route of matchedRoutes.routes) {
|
|
1463
|
+
if (route.handlerSource !== "missing") continue;
|
|
1464
|
+
diagnostics.push({
|
|
1465
|
+
severity: "error",
|
|
1466
|
+
code: "BEIGNET_ROUTE_MISSING",
|
|
1467
|
+
file: route.file,
|
|
1468
|
+
contract: route.exportName,
|
|
1469
|
+
message:
|
|
1470
|
+
config.framework === "next"
|
|
1471
|
+
? `${route.exportName} (${route.method} ${route.path}) has no matching Next route handler. Add ${methodArticle(route.method)} ${route.method} export to ${directoryPath(config.paths.routes)}/[[...path]]/route.ts or a matching route file, or remove the unused contract.`
|
|
1472
|
+
: `${route.exportName} (${route.method} ${route.path}) is not registered in the route surface passed to createFetchServer(...). Register the contract through defineRoutes(...) or remove the unused contract.`,
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1370
1475
|
}
|
|
1371
1476
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1477
|
+
if (config.framework === "next") {
|
|
1478
|
+
for (const routeHandler of matchedRoutes.unmatchedRouteHandlers) {
|
|
1479
|
+
diagnostics.push(unmatchedRouteDiagnostic(routeHandler));
|
|
1480
|
+
}
|
|
1374
1481
|
}
|
|
1375
1482
|
|
|
1376
1483
|
diagnostics.push(
|
|
@@ -1460,6 +1567,39 @@ async function inspectDiagnostics(
|
|
|
1460
1567
|
return dedupeDiagnostics(diagnostics);
|
|
1461
1568
|
}
|
|
1462
1569
|
|
|
1570
|
+
async function inspectFrameworkAdapter(
|
|
1571
|
+
targetDir: string,
|
|
1572
|
+
files: string[],
|
|
1573
|
+
config: ResolvedBeignetConfig,
|
|
1574
|
+
): Promise<InspectDiagnostic[]> {
|
|
1575
|
+
if (!files.includes(config.paths.server)) return [];
|
|
1576
|
+
|
|
1577
|
+
const source = await readFile(
|
|
1578
|
+
path.join(targetDir, config.paths.server),
|
|
1579
|
+
"utf8",
|
|
1580
|
+
);
|
|
1581
|
+
const expectedFactory =
|
|
1582
|
+
config.framework === "next" ? "createNextServer" : "createFetchServer";
|
|
1583
|
+
const otherFactory =
|
|
1584
|
+
config.framework === "next" ? "createFetchServer" : "createNextServer";
|
|
1585
|
+
|
|
1586
|
+
if (
|
|
1587
|
+
containsCallExpression(source, expectedFactory) ||
|
|
1588
|
+
!containsCallExpression(source, otherFactory)
|
|
1589
|
+
) {
|
|
1590
|
+
return [];
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
return [
|
|
1594
|
+
{
|
|
1595
|
+
severity: "error",
|
|
1596
|
+
code: "BEIGNET_FRAMEWORK_MISMATCH",
|
|
1597
|
+
file: config.paths.server,
|
|
1598
|
+
message: `${config.configFile ?? "beignet.config.*"} selects framework: "${config.framework}", but ${config.paths.server} calls ${otherFactory}(...). Use ${expectedFactory}(...) or update the configured framework profile.`,
|
|
1599
|
+
},
|
|
1600
|
+
];
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1463
1603
|
async function inspectCanonicalConformance(
|
|
1464
1604
|
targetDir: string,
|
|
1465
1605
|
files: string[],
|
|
@@ -1626,11 +1766,13 @@ async function inspectCanonicalConformance(
|
|
|
1626
1766
|
}
|
|
1627
1767
|
|
|
1628
1768
|
if (!importsAppContext(serverSource, config.paths.server, config, files)) {
|
|
1769
|
+
const serverFactory =
|
|
1770
|
+
config.framework === "next" ? "createNextServer" : "createFetchServer";
|
|
1629
1771
|
diagnostics.push({
|
|
1630
1772
|
severity: "warning",
|
|
1631
1773
|
code: "BEIGNET_SERVER_APP_CONTEXT",
|
|
1632
1774
|
file: config.paths.server,
|
|
1633
|
-
message: `${config.paths.server} should import type { AppContext } from ${config.paths.appContext} and thread that context through
|
|
1775
|
+
message: `${config.paths.server} should import type { AppContext } from ${config.paths.appContext} and thread that context through ${serverFactory} and hooks.`,
|
|
1634
1776
|
});
|
|
1635
1777
|
}
|
|
1636
1778
|
|
|
@@ -2115,7 +2257,13 @@ async function inspectProductionReadiness(
|
|
|
2115
2257
|
if (
|
|
2116
2258
|
isFeatureUploadDefinition(file, config) &&
|
|
2117
2259
|
containsCallExpression(source, "defineUpload") &&
|
|
2118
|
-
|
|
2260
|
+
!(await uploadHasExplicitSizeLimit(
|
|
2261
|
+
targetDir,
|
|
2262
|
+
file,
|
|
2263
|
+
source,
|
|
2264
|
+
files,
|
|
2265
|
+
sourceCache,
|
|
2266
|
+
))
|
|
2119
2267
|
) {
|
|
2120
2268
|
diagnostics.push({
|
|
2121
2269
|
severity: "warning",
|
|
@@ -2327,7 +2475,7 @@ async function inspectProductionReadiness(
|
|
|
2327
2475
|
}
|
|
2328
2476
|
|
|
2329
2477
|
if (installedPackages.has("@beignet/provider-auth-better-auth")) {
|
|
2330
|
-
if (!hasBetterAuthRoute(files, config)) {
|
|
2478
|
+
if (config.framework === "next" && !hasBetterAuthRoute(files, config)) {
|
|
2331
2479
|
diagnostics.push({
|
|
2332
2480
|
severity: "warning",
|
|
2333
2481
|
code: "BEIGNET_AUTH_ROUTE_MISSING",
|
|
@@ -2373,6 +2521,100 @@ async function inspectProductionReadiness(
|
|
|
2373
2521
|
return diagnostics;
|
|
2374
2522
|
}
|
|
2375
2523
|
|
|
2524
|
+
async function uploadHasExplicitSizeLimit(
|
|
2525
|
+
targetDir: string,
|
|
2526
|
+
file: string,
|
|
2527
|
+
source: string,
|
|
2528
|
+
files: string[],
|
|
2529
|
+
sourceCache: Map<string, string>,
|
|
2530
|
+
): Promise<boolean> {
|
|
2531
|
+
if (/\bmaxSizeBytes\s*:/.test(source)) return true;
|
|
2532
|
+
|
|
2533
|
+
const fileReference = source.match(
|
|
2534
|
+
/\bfile\s*:\s*([A-Za-z_$][A-Za-z0-9_$]*)\b/,
|
|
2535
|
+
)?.[1];
|
|
2536
|
+
if (!fileReference) return false;
|
|
2537
|
+
|
|
2538
|
+
const importPattern = /import\s*\{([\s\S]*?)\}\s*from\s*["']([^"']+)["']/g;
|
|
2539
|
+
for (const match of source.matchAll(importPattern)) {
|
|
2540
|
+
const moduleName = match[2];
|
|
2541
|
+
if (!moduleName?.startsWith(".")) continue;
|
|
2542
|
+
|
|
2543
|
+
const importedName = importedNameForLocalBinding(
|
|
2544
|
+
match[1] ?? "",
|
|
2545
|
+
fileReference,
|
|
2546
|
+
);
|
|
2547
|
+
if (!importedName) continue;
|
|
2548
|
+
|
|
2549
|
+
const importedFile = resolveLocalTypeScriptModule(file, moduleName, files);
|
|
2550
|
+
if (!importedFile) continue;
|
|
2551
|
+
|
|
2552
|
+
const importedSource = await readCachedSource(
|
|
2553
|
+
targetDir,
|
|
2554
|
+
importedFile,
|
|
2555
|
+
sourceCache,
|
|
2556
|
+
);
|
|
2557
|
+
if (
|
|
2558
|
+
exportedObjectHasProperty(importedSource, importedName, "maxSizeBytes")
|
|
2559
|
+
) {
|
|
2560
|
+
return true;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
return false;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
function importedNameForLocalBinding(
|
|
2568
|
+
importList: string,
|
|
2569
|
+
localName: string,
|
|
2570
|
+
): string | undefined {
|
|
2571
|
+
for (const specifier of importList.split(",")) {
|
|
2572
|
+
const [importedName, aliasedLocalName] = specifier
|
|
2573
|
+
.trim()
|
|
2574
|
+
.replace(/^type\s+/, "")
|
|
2575
|
+
.split(/\s+as\s+/);
|
|
2576
|
+
if (!importedName) continue;
|
|
2577
|
+
if ((aliasedLocalName ?? importedName) === localName) return importedName;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
return undefined;
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
function exportedObjectHasProperty(
|
|
2584
|
+
source: string,
|
|
2585
|
+
exportName: string,
|
|
2586
|
+
propertyName: string,
|
|
2587
|
+
): boolean {
|
|
2588
|
+
const declaration = new RegExp(
|
|
2589
|
+
`\\bexport\\s+const\\s+${escapeRegExp(exportName)}\\s*=\\s*\\{`,
|
|
2590
|
+
).exec(source);
|
|
2591
|
+
if (!declaration) return false;
|
|
2592
|
+
|
|
2593
|
+
const openIndex = source.indexOf(
|
|
2594
|
+
"{",
|
|
2595
|
+
declaration.index + declaration[0].length - 1,
|
|
2596
|
+
);
|
|
2597
|
+
const closeIndex = matchingDelimiterIndex(source, openIndex, "{", "}");
|
|
2598
|
+
if (closeIndex < 0) return false;
|
|
2599
|
+
|
|
2600
|
+
const initializer = source.slice(openIndex, closeIndex + 1);
|
|
2601
|
+
return new RegExp(`\\b${escapeRegExp(propertyName)}\\s*:`).test(initializer);
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
function resolveLocalTypeScriptModule(
|
|
2605
|
+
importer: string,
|
|
2606
|
+
moduleName: string,
|
|
2607
|
+
files: string[],
|
|
2608
|
+
): string | undefined {
|
|
2609
|
+
const base = normalizePath(
|
|
2610
|
+
path.posix.normalize(
|
|
2611
|
+
path.posix.join(path.posix.dirname(importer), moduleName),
|
|
2612
|
+
),
|
|
2613
|
+
);
|
|
2614
|
+
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}/index.ts`];
|
|
2615
|
+
return candidates.find((candidate) => files.includes(candidate));
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2376
2618
|
async function hasReadinessCheck(
|
|
2377
2619
|
targetDir: string,
|
|
2378
2620
|
files: string[],
|
|
@@ -2489,7 +2731,10 @@ async function inspectPaymentsProductionReadiness(
|
|
|
2489
2731
|
});
|
|
2490
2732
|
}
|
|
2491
2733
|
|
|
2492
|
-
if (
|
|
2734
|
+
if (
|
|
2735
|
+
config.framework === "next" &&
|
|
2736
|
+
!(await hasPaymentWebhookRoute(targetDir, files, config, sourceCache))
|
|
2737
|
+
) {
|
|
2493
2738
|
diagnostics.push({
|
|
2494
2739
|
severity: "warning",
|
|
2495
2740
|
code: "BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
|
|
@@ -4160,6 +4405,7 @@ async function inspectServerlessFootguns(
|
|
|
4160
4405
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
4161
4406
|
|
|
4162
4407
|
if (
|
|
4408
|
+
config.framework === "next" &&
|
|
4163
4409
|
convention.resourceGenerator &&
|
|
4164
4410
|
isNextRouteFile(file, config) &&
|
|
4165
4411
|
importsEagerServer(source, file, files, config)
|
|
@@ -6361,15 +6607,19 @@ async function registeredRouteGroupsForServer(
|
|
|
6361
6607
|
serverSource: string,
|
|
6362
6608
|
): Promise<Set<string>> {
|
|
6363
6609
|
const registeredGroups = registeredRouteGroups(serverSource);
|
|
6364
|
-
const imports =
|
|
6610
|
+
const imports = parseNamedImportSources(serverSource);
|
|
6365
6611
|
|
|
6366
6612
|
for (const identifier of routeOptionIdentifiers(serverSource)) {
|
|
6367
6613
|
const imported = imports.get(identifier);
|
|
6368
|
-
if (!imported
|
|
6369
|
-
|
|
6370
|
-
|
|
6614
|
+
if (!imported) continue;
|
|
6615
|
+
const importedFile = sourceFileFromImport(
|
|
6616
|
+
imported.sourcePath,
|
|
6617
|
+
config.paths.server,
|
|
6618
|
+
files,
|
|
6619
|
+
);
|
|
6620
|
+
if (!importedFile || !files.includes(importedFile)) continue;
|
|
6371
6621
|
|
|
6372
|
-
const source = await readFile(path.join(targetDir,
|
|
6622
|
+
const source = await readFile(path.join(targetDir, importedFile), {
|
|
6373
6623
|
encoding: "utf8",
|
|
6374
6624
|
});
|
|
6375
6625
|
for (const routeGroup of registeredRouteGroups(source)) {
|
|
@@ -7358,6 +7608,7 @@ async function inspectResourceSlices(
|
|
|
7358
7608
|
);
|
|
7359
7609
|
}
|
|
7360
7610
|
if (
|
|
7611
|
+
config.framework === "next" &&
|
|
7361
7612
|
!hasRequirement(files, routeFile) &&
|
|
7362
7613
|
!hasRequirement(files, catchAllRouteFile)
|
|
7363
7614
|
) {
|
package/src/lib.ts
CHANGED
|
@@ -43,4 +43,4 @@ export {
|
|
|
43
43
|
export { runOutboxDrain } from "./outbox.js";
|
|
44
44
|
export { runAppSchedule } from "./schedule.js";
|
|
45
45
|
export { runAppTask } from "./task.js";
|
|
46
|
-
export type {
|
|
46
|
+
export type { StarterProviderName } from "./templates/index.js";
|
package/src/make/inbox.ts
CHANGED
|
@@ -160,7 +160,7 @@ export async function makeInbox(
|
|
|
160
160
|
.map((file) => file.path);
|
|
161
161
|
const files = generatedFiles.map((file) => file.path);
|
|
162
162
|
const dryRun = Boolean(options.dryRun);
|
|
163
|
-
const inboxSeedNames = seedNames("inbox
|
|
163
|
+
const inboxSeedNames = seedNames("inbox.demo-inbox");
|
|
164
164
|
const seedScript = { "db:seed": `tsx ${seedEntrypointPath(config)}` };
|
|
165
165
|
|
|
166
166
|
const recordSeedEntrypoint = (result: "created" | "updated" | "skipped") => {
|
|
@@ -248,8 +248,8 @@ export async function updateInboxWiring(
|
|
|
248
248
|
await updatePortsIndex(targetDir, inboxNames, config, {
|
|
249
249
|
...options,
|
|
250
250
|
generationOptions: {
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
authorization: false,
|
|
252
|
+
tenantScoped: false,
|
|
253
253
|
events: false,
|
|
254
254
|
softDelete: false,
|
|
255
255
|
},
|
package/src/make/payments.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type ResolvedBeignetConfig,
|
|
7
7
|
resolveConfig,
|
|
8
8
|
} from "../config.js";
|
|
9
|
+
import { assertNextFramework } from "../framework.js";
|
|
9
10
|
import { appendToNamedArray, insertAfterImports } from "../registry-edits.js";
|
|
10
11
|
import { testSupportTemplateFiles } from "../templates/testing.js";
|
|
11
12
|
import {
|
|
@@ -71,6 +72,12 @@ export async function makePayments(
|
|
|
71
72
|
? resolveConfig(options.config)
|
|
72
73
|
: await loadBeignetConfig(targetDir);
|
|
73
74
|
|
|
75
|
+
assertNextFramework(
|
|
76
|
+
config,
|
|
77
|
+
"beignet make payments",
|
|
78
|
+
"Next.js App Router billing and webhook handlers",
|
|
79
|
+
);
|
|
80
|
+
|
|
74
81
|
await assertStandardApp(targetDir, config);
|
|
75
82
|
|
|
76
83
|
const persistence = await detectResourcePersistence(targetDir, config);
|
|
@@ -93,7 +100,7 @@ export async function makePayments(
|
|
|
93
100
|
force: Boolean(options.force),
|
|
94
101
|
});
|
|
95
102
|
const billingNames = resourceNames("billing");
|
|
96
|
-
const billingSeedNames = seedNames("billing
|
|
103
|
+
const billingSeedNames = seedNames("billing.demo-billing-account");
|
|
97
104
|
const plannedBillingUpdates = await updatePaymentsWiring(
|
|
98
105
|
targetDir,
|
|
99
106
|
billingNames,
|
|
@@ -241,8 +248,8 @@ export async function updatePaymentsWiring(
|
|
|
241
248
|
await updatePortsIndex(targetDir, billingNames, config, {
|
|
242
249
|
...options,
|
|
243
250
|
generationOptions: {
|
|
244
|
-
|
|
245
|
-
|
|
251
|
+
authorization: false,
|
|
252
|
+
tenantScoped: false,
|
|
246
253
|
events: false,
|
|
247
254
|
softDelete: false,
|
|
248
255
|
},
|
package/src/make/shared.ts
CHANGED
|
@@ -45,8 +45,8 @@ export type ResourceNames = {
|
|
|
45
45
|
export type ResourceGenerationMode = "feature" | "resource";
|
|
46
46
|
|
|
47
47
|
export type ResourceGenerationOptions = {
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
authorization: boolean;
|
|
49
|
+
tenantScoped: boolean;
|
|
50
50
|
events: boolean;
|
|
51
51
|
softDelete: boolean;
|
|
52
52
|
};
|
|
@@ -220,6 +220,13 @@ export type UploadNames = FeatureArtifactNames & {
|
|
|
220
220
|
uploadExportName: string;
|
|
221
221
|
metadataSchemaName: string;
|
|
222
222
|
metadataTypeName: string;
|
|
223
|
+
fileConstraintsExportName: string;
|
|
224
|
+
manifestExportName: string;
|
|
225
|
+
manifestFileName: string;
|
|
226
|
+
reactUploadsExportName: string;
|
|
227
|
+
clientFileName: string;
|
|
228
|
+
componentExportName: string;
|
|
229
|
+
componentFileName: string;
|
|
223
230
|
};
|
|
224
231
|
|
|
225
232
|
export type FeatureUiNames = ResourceNames & {
|
|
@@ -502,7 +509,7 @@ export function wireListenersProviderSource(
|
|
|
502
509
|
*
|
|
503
510
|
* Each wiring is independent and skipped silently when its port key already
|
|
504
511
|
* appears in the ports file, so apps that wired their own adapter (or scaffolds
|
|
505
|
-
* whose
|
|
512
|
+
* whose providers already contribute the port) are left untouched. All
|
|
506
513
|
* anchors are computed before any file is written; an anchor miss aborts with a
|
|
507
514
|
* copy-pasteable manual instruction so the generator never leaves partial
|
|
508
515
|
* wiring behind. Callers validate anchors with `dryRun: true` before writing
|
|
@@ -529,7 +536,7 @@ export async function wirePortProviders(
|
|
|
529
536
|
}
|
|
530
537
|
|
|
531
538
|
// A port key that already appears in the ports file means the app wired its
|
|
532
|
-
// own adapter or
|
|
539
|
+
// own adapter or a provider already contributes the port.
|
|
533
540
|
const active = wirings.filter(
|
|
534
541
|
(wiring) => !new RegExp(`\\b${wiring.portKey}\\b`).test(portsOriginal),
|
|
535
542
|
);
|
|
@@ -1179,7 +1186,7 @@ export async function updatePortsIndex(
|
|
|
1179
1186
|
next = insertAfterImports(next, importLine);
|
|
1180
1187
|
}
|
|
1181
1188
|
|
|
1182
|
-
if (options.generationOptions?.
|
|
1189
|
+
if (options.generationOptions?.authorization) {
|
|
1183
1190
|
const policyPath = path.join(
|
|
1184
1191
|
config.paths.features,
|
|
1185
1192
|
names.pluralKebab,
|
|
@@ -2911,13 +2918,12 @@ export function artifactNameParts(
|
|
|
2911
2918
|
): [string, string] {
|
|
2912
2919
|
const parts = input
|
|
2913
2920
|
.trim()
|
|
2914
|
-
.split("
|
|
2915
|
-
.map((part) => part.trim())
|
|
2916
|
-
.filter(Boolean);
|
|
2921
|
+
.split(".")
|
|
2922
|
+
.map((part) => part.trim());
|
|
2917
2923
|
|
|
2918
|
-
if (parts.length !== 2) {
|
|
2924
|
+
if (parts.length !== 2 || parts.some((part) => part.length === 0)) {
|
|
2919
2925
|
throw new Error(
|
|
2920
|
-
`${label} name must use feature
|
|
2926
|
+
`${label} name must use feature.name format, for example posts.published.`,
|
|
2921
2927
|
);
|
|
2922
2928
|
}
|
|
2923
2929
|
|