@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.
Files changed (92) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +154 -102
  3. package/dist/choices.d.ts +17 -21
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +21 -67
  6. package/dist/choices.js.map +1 -1
  7. package/dist/config.d.ts +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +4 -2
  10. package/dist/config.js.map +1 -1
  11. package/dist/create-prompts.d.ts +5 -6
  12. package/dist/create-prompts.d.ts.map +1 -1
  13. package/dist/create-prompts.js +15 -15
  14. package/dist/create-prompts.js.map +1 -1
  15. package/dist/create.d.ts +11 -2
  16. package/dist/create.d.ts.map +1 -1
  17. package/dist/create.js +38 -28
  18. package/dist/create.js.map +1 -1
  19. package/dist/framework.d.ts +3 -0
  20. package/dist/framework.d.ts.map +1 -0
  21. package/dist/framework.js +6 -0
  22. package/dist/framework.js.map +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +149 -47
  25. package/dist/index.js.map +1 -1
  26. package/dist/inspect.d.ts +4 -2
  27. package/dist/inspect.d.ts.map +1 -1
  28. package/dist/inspect.js +174 -34
  29. package/dist/inspect.js.map +1 -1
  30. package/dist/lib.d.ts +1 -1
  31. package/dist/lib.d.ts.map +1 -1
  32. package/dist/make/inbox.js +3 -3
  33. package/dist/make/inbox.js.map +1 -1
  34. package/dist/make/payments.d.ts.map +1 -1
  35. package/dist/make/payments.js +5 -3
  36. package/dist/make/payments.js.map +1 -1
  37. package/dist/make/shared.d.ts +10 -3
  38. package/dist/make/shared.d.ts.map +1 -1
  39. package/dist/make/shared.js +7 -8
  40. package/dist/make/shared.js.map +1 -1
  41. package/dist/make.d.ts +3 -2
  42. package/dist/make.d.ts.map +1 -1
  43. package/dist/make.js +549 -186
  44. package/dist/make.js.map +1 -1
  45. package/dist/mcp.d.ts.map +1 -1
  46. package/dist/mcp.js +15 -11
  47. package/dist/mcp.js.map +1 -1
  48. package/dist/provider-add.d.ts +21 -3
  49. package/dist/provider-add.d.ts.map +1 -1
  50. package/dist/provider-add.js +101 -65
  51. package/dist/provider-add.js.map +1 -1
  52. package/dist/task.js +1 -1
  53. package/dist/task.js.map +1 -1
  54. package/dist/templates/base.d.ts +1 -1
  55. package/dist/templates/base.d.ts.map +1 -1
  56. package/dist/templates/base.js +15 -15
  57. package/dist/templates/base.js.map +1 -1
  58. package/dist/templates/index.d.ts +2 -2
  59. package/dist/templates/index.d.ts.map +1 -1
  60. package/dist/templates/index.js +5 -5
  61. package/dist/templates/index.js.map +1 -1
  62. package/dist/templates/server.d.ts.map +1 -1
  63. package/dist/templates/server.js +17 -13
  64. package/dist/templates/server.js.map +1 -1
  65. package/dist/templates/shadcn.js +1 -1
  66. package/dist/templates/shared.d.ts +4 -4
  67. package/dist/templates/shared.d.ts.map +1 -1
  68. package/dist/templates/shared.js +6 -6
  69. package/dist/templates/shared.js.map +1 -1
  70. package/package.json +2 -2
  71. package/skills/app-structure/SKILL.md +29 -9
  72. package/src/choices.ts +38 -88
  73. package/src/config.ts +7 -3
  74. package/src/create-prompts.ts +20 -21
  75. package/src/create.ts +54 -36
  76. package/src/framework.ts +13 -0
  77. package/src/index.ts +193 -65
  78. package/src/inspect.ts +290 -39
  79. package/src/lib.ts +1 -1
  80. package/src/make/inbox.ts +3 -3
  81. package/src/make/payments.ts +10 -3
  82. package/src/make/shared.ts +16 -10
  83. package/src/make.ts +720 -176
  84. package/src/mcp.ts +20 -13
  85. package/src/provider-add.ts +159 -79
  86. package/src/task.ts +1 -1
  87. package/src/templates/base.ts +16 -16
  88. package/src/templates/index.ts +6 -6
  89. package/src/templates/server.ts +17 -13
  90. package/src/templates/shadcn.ts +1 -1
  91. package/src/templates/shared.ts +10 -10
  92. 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 routeFiles = files.filter(
208
- (file) =>
209
- file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
210
- file.endsWith("/route.ts"),
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 routeFiles = files.filter(
250
- (file) =>
251
- file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
252
- file.endsWith("/route.ts"),
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
- : `${route.handlerFile ?? "unknown"}:${route.handlerExport ?? route.method}`,
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 ? "standard" : nextLayout ? "next" : "custom",
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
- if (!convention.nextLayout) {
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 standard Beignet app layout. CLI inspection expects ${directoryPath(config.paths.contracts)}/, ${directoryPath(config.paths.routes)}/, and ${config.paths.server}. Missing: ${convention.missingNextLayout.join(", ")}. Add the missing app files or configure their paths in beignet.config.ts.`,
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
- for (const route of matchedRoutes.routes) {
1362
- if (route.handlerSource !== "missing") continue;
1363
- diagnostics.push({
1364
- severity: "error",
1365
- code: "BEIGNET_ROUTE_MISSING",
1366
- file: route.file,
1367
- contract: route.exportName,
1368
- message: `${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.`,
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
- for (const routeHandler of matchedRoutes.unmatchedRouteHandlers) {
1373
- diagnostics.push(unmatchedRouteDiagnostic(routeHandler));
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 createNextServer and hooks.`,
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
- !/\bmaxSizeBytes\s*:/.test(source)
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 (!(await hasPaymentWebhookRoute(targetDir, files, config, sourceCache))) {
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 = parseNamedImports(serverSource, config, config.paths.server);
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?.contractFile || !files.includes(imported.contractFile)) {
6369
- continue;
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, imported.contractFile), {
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 { IntegrationName } from "./templates/index.js";
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/demo-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
- auth: false,
252
- tenant: false,
251
+ authorization: false,
252
+ tenantScoped: false,
253
253
  events: false,
254
254
  softDelete: false,
255
255
  },
@@ -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/demo-billing-account");
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
- auth: false,
245
- tenant: false,
251
+ authorization: false,
252
+ tenantScoped: false,
246
253
  events: false,
247
254
  softDelete: false,
248
255
  },
@@ -45,8 +45,8 @@ export type ResourceNames = {
45
45
  export type ResourceGenerationMode = "feature" | "resource";
46
46
 
47
47
  export type ResourceGenerationOptions = {
48
- auth: boolean;
49
- tenant: boolean;
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 integrations already contribute the port) are left untouched. All
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 an integration already contributes the port.
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?.auth) {
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/name format, for example posts/published.`,
2926
+ `${label} name must use feature.name format, for example posts.published.`,
2921
2927
  );
2922
2928
  }
2923
2929