@beignet/cli 0.0.26 → 0.0.28

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 (97) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +59 -15
  3. package/dist/check.d.ts +61 -0
  4. package/dist/check.d.ts.map +1 -0
  5. package/dist/check.js +164 -0
  6. package/dist/check.js.map +1 -0
  7. package/dist/choices.d.ts +8 -0
  8. package/dist/choices.d.ts.map +1 -1
  9. package/dist/choices.js +14 -0
  10. package/dist/choices.js.map +1 -1
  11. package/dist/db.d.ts +8 -0
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +4 -4
  14. package/dist/db.js.map +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +156 -21
  17. package/dist/index.js.map +1 -1
  18. package/dist/inspect.d.ts.map +1 -1
  19. package/dist/inspect.js +121 -3
  20. package/dist/inspect.js.map +1 -1
  21. package/dist/make/inbox.d.ts +63 -0
  22. package/dist/make/inbox.d.ts.map +1 -0
  23. package/dist/make/inbox.js +1747 -0
  24. package/dist/make/inbox.js.map +1 -0
  25. package/dist/make/payments.d.ts +61 -0
  26. package/dist/make/payments.d.ts.map +1 -0
  27. package/dist/make/payments.js +1900 -0
  28. package/dist/make/payments.js.map +1 -0
  29. package/dist/make/shared.d.ts +592 -0
  30. package/dist/make/shared.d.ts.map +1 -0
  31. package/dist/make/shared.js +2233 -0
  32. package/dist/make/shared.js.map +1 -0
  33. package/dist/make/tenancy.d.ts +101 -0
  34. package/dist/make/tenancy.d.ts.map +1 -0
  35. package/dist/make/tenancy.js +4409 -0
  36. package/dist/make/tenancy.js.map +1 -0
  37. package/dist/make.d.ts +8 -44
  38. package/dist/make.d.ts.map +1 -1
  39. package/dist/make.js +57 -3689
  40. package/dist/make.js.map +1 -1
  41. package/dist/mcp.d.ts.map +1 -1
  42. package/dist/mcp.js +45 -4
  43. package/dist/mcp.js.map +1 -1
  44. package/dist/provider-add.d.ts +19 -0
  45. package/dist/provider-add.d.ts.map +1 -0
  46. package/dist/provider-add.js +691 -0
  47. package/dist/provider-add.js.map +1 -0
  48. package/dist/provider-audit.d.ts.map +1 -1
  49. package/dist/provider-audit.js +77 -1
  50. package/dist/provider-audit.js.map +1 -1
  51. package/dist/registry-edits.d.ts +9 -0
  52. package/dist/registry-edits.d.ts.map +1 -1
  53. package/dist/registry-edits.js +32 -0
  54. package/dist/registry-edits.js.map +1 -1
  55. package/dist/task.d.ts +2 -0
  56. package/dist/task.d.ts.map +1 -1
  57. package/dist/task.js +2 -0
  58. package/dist/task.js.map +1 -1
  59. package/dist/templates/agents.d.ts.map +1 -1
  60. package/dist/templates/agents.js +29 -11
  61. package/dist/templates/agents.js.map +1 -1
  62. package/dist/templates/base.d.ts.map +1 -1
  63. package/dist/templates/base.js +42 -12
  64. package/dist/templates/base.js.map +1 -1
  65. package/dist/templates/db/sqlite.d.ts.map +1 -1
  66. package/dist/templates/db/sqlite.js +7 -1
  67. package/dist/templates/db/sqlite.js.map +1 -1
  68. package/dist/templates/server.d.ts.map +1 -1
  69. package/dist/templates/server.js +44 -37
  70. package/dist/templates/server.js.map +1 -1
  71. package/dist/templates/shared.d.ts +5 -0
  72. package/dist/templates/shared.d.ts.map +1 -1
  73. package/dist/templates/shared.js +5 -0
  74. package/dist/templates/shared.js.map +1 -1
  75. package/package.json +2 -2
  76. package/skills/app-structure/SKILL.md +1 -1
  77. package/src/check.ts +246 -0
  78. package/src/choices.ts +28 -0
  79. package/src/db.ts +4 -4
  80. package/src/index.ts +232 -21
  81. package/src/inspect.ts +168 -2
  82. package/src/make/inbox.ts +2015 -0
  83. package/src/make/payments.ts +2182 -0
  84. package/src/make/shared.ts +3638 -0
  85. package/src/make/tenancy.ts +4809 -0
  86. package/src/make.ts +388 -5355
  87. package/src/mcp.ts +66 -3
  88. package/src/provider-add.ts +926 -0
  89. package/src/provider-audit.ts +95 -3
  90. package/src/registry-edits.ts +47 -0
  91. package/src/task.ts +8 -10
  92. package/src/templates/agents.ts +29 -11
  93. package/src/templates/base.ts +43 -12
  94. package/src/templates/db/sqlite.ts +7 -1
  95. package/src/templates/server.ts +44 -37
  96. package/src/templates/shared.ts +5 -0
  97. package/src/test-helpers/generated-app.ts +168 -0
package/dist/inspect.js CHANGED
@@ -4,6 +4,7 @@ import { parseProviderPackageMetadata } from "@beignet/core/providers";
4
4
  import { createPainter } from "./ansi.js";
5
5
  import { clientDirPath, clientFormsPath, clientIndexPath, defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
6
6
  import { formatGithubAnnotation, } from "./github-annotations.js";
7
+ import { providersFilePath, wireListenersProviderSource, } from "./make/shared.js";
7
8
  import { detectedProviderVariants, diagnosticPackageJsonFile, formatProviderMetadataIssues, installedPackageNames, providerDoctorRulesForInstalledPackages, providerOperationalConfigFiles, providerRequiredEnvExists, providerRequiredTableExists, providerVariantRegistrationSeverity, readProviderListEntries, readProviderPackageMetadataSource, registrationTokenHint, serverProviderFiles, } from "./provider-audit.js";
8
9
  import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryValueInfo, } from "./registry-edits.js";
9
10
  import { getCliVersion } from "./version.js";
@@ -61,6 +62,9 @@ export async function applyDoctorFixes(options = {}) {
61
62
  if (taskFix)
62
63
  fixes.push(taskFix);
63
64
  fixes.push(...(await fixUnregisteredOutboxEntries(targetDir, files, drift, config)));
65
+ const listenerFix = await fixUnregisteredListeners(targetDir, files, drift, config);
66
+ if (listenerFix)
67
+ fixes.push(listenerFix);
64
68
  const openApiFix = await fixDirectOpenApiArrayDrift(targetDir, files, config, convention, contracts, matchedRoutes);
65
69
  if (openApiFix)
66
70
  fixes.push(openApiFix);
@@ -595,6 +599,22 @@ function parseRouteExports(source, handlerFile, routePath, config) {
595
599
  const exports = [];
596
600
  const imports = parseNamedImports(source, config);
597
601
  const exportRegex = /export const\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*=\s*([^;\n]+)/g;
602
+ const apiRouteExportRegex = /export const\s*\{([^}]+)\}\s*=\s*createApiRoute\s*\(/g;
603
+ for (const match of source.matchAll(apiRouteExportRegex)) {
604
+ for (const member of match[1].split(",")) {
605
+ const parts = member.split(":");
606
+ const method = (parts[1] ?? parts[0])?.trim();
607
+ if (!method || !isHttpMethod(method))
608
+ continue;
609
+ exports.push({
610
+ method,
611
+ handlerFile,
612
+ contractRef: routePath,
613
+ catchAllPrefix: catchAllRoutePrefix(routePath),
614
+ source: "next-route",
615
+ });
616
+ }
617
+ }
598
618
  for (const match of source.matchAll(exportRegex)) {
599
619
  const method = match[1];
600
620
  const expression = match[2];
@@ -822,7 +842,7 @@ async function inspectDiagnostics(targetDir, files, config, convention, contract
822
842
  for (const routeHandler of matchedRoutes.unmatchedRouteHandlers) {
823
843
  diagnostics.push(unmatchedRouteDiagnostic(routeHandler));
824
844
  }
825
- diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
845
+ diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files, config, convention)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
826
846
  return dedupeDiagnostics(diagnostics);
827
847
  }
828
848
  async function inspectCanonicalConformance(targetDir, files, config, convention, strict) {
@@ -2314,7 +2334,7 @@ function isWorkflowTestFile(file) {
2314
2334
  file.startsWith("tests/") ||
2315
2335
  file.includes("/tests/"));
2316
2336
  }
2317
- async function inspectServerlessFootguns(targetDir, files) {
2337
+ async function inspectServerlessFootguns(targetDir, files, config, convention) {
2318
2338
  const diagnostics = [];
2319
2339
  for (const file of files) {
2320
2340
  if (!file.endsWith(".ts") ||
@@ -2326,6 +2346,16 @@ async function inspectServerlessFootguns(targetDir, files) {
2326
2346
  const isProviderFile = /(^|\/)provider(s)?\.ts$/.test(file) ||
2327
2347
  /(^|\/)[^/]*provider[^/]*\.ts$/.test(file);
2328
2348
  const source = await readFile(path.join(targetDir, file), "utf8");
2349
+ if (convention.resourceGenerator &&
2350
+ isNextRouteFile(file, config) &&
2351
+ importsEagerServer(source, file, files, config)) {
2352
+ diagnostics.push({
2353
+ severity: "warning",
2354
+ code: "BEIGNET_NEXT_ROUTE_EAGER_SERVER_IMPORT",
2355
+ file,
2356
+ message: `${file} imports the eager server singleton. Next.js imports route modules during production builds, so use createNextServerLoader(...) in ${config.paths.server} and pass getServer to createApiRoute(...), createHealthRoute(...), or the focused route helper instead.`,
2357
+ });
2358
+ }
2329
2359
  if (isInfraOrServerFile &&
2330
2360
  isProviderFile &&
2331
2361
  /\bset(?:Interval|Timeout)\s*\(/.test(source)) {
@@ -2370,6 +2400,26 @@ async function inspectServerlessFootguns(targetDir, files) {
2370
2400
  }
2371
2401
  return diagnostics;
2372
2402
  }
2403
+ function isNextRouteFile(file, config) {
2404
+ const routesPath = directoryPath(config.paths.routes);
2405
+ return (file.startsWith(`${routesPath}/`) &&
2406
+ file.endsWith("/route.ts") &&
2407
+ !file.endsWith(".test.ts"));
2408
+ }
2409
+ function importsEagerServer(source, importerFile, files, config) {
2410
+ const importRegex = /import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
2411
+ for (const match of source.matchAll(importRegex)) {
2412
+ const members = match[1]
2413
+ .split(",")
2414
+ .map((member) => parseImportMember(member));
2415
+ if (!members.some((member) => member?.importedName === "server"))
2416
+ continue;
2417
+ const sourceFile = sourceFileFromImport(match[2], importerFile, files);
2418
+ if (sourceFile === config.paths.server)
2419
+ return true;
2420
+ }
2421
+ return false;
2422
+ }
2373
2423
  function hasOutboxRegistry(files) {
2374
2424
  return files.some(isOutboxRegistryFile);
2375
2425
  }
@@ -2533,7 +2583,9 @@ async function inspectDatabaseLifecycleDrift(targetDir, files, config, conventio
2533
2583
  {
2534
2584
  script: "db:seed",
2535
2585
  command: "seed",
2536
- file: `${infrastructurePath}/db/seed.ts`,
2586
+ // The seed entrypoint lives beside the server runtime because it boots
2587
+ // the app through getServer(), which infra/ must not import.
2588
+ file: `${directoryPath(path.dirname(config.paths.server))}/seed.ts`,
2537
2589
  code: "BEIGNET_DB_SEED_ENTRYPOINT_MISSING",
2538
2590
  },
2539
2591
  {
@@ -3341,6 +3393,72 @@ async function fixUnregisteredTasks(targetDir, files, drift, config) {
3341
3393
  append: (source, entry, importLine) => appendToNamedArray(source, "tasks", entry, importLine),
3342
3394
  });
3343
3395
  }
3396
+ /**
3397
+ * Wire fully unregistered feature listener registries into the providers
3398
+ * file with the same `<registry>Provider` block `beignet make listener`
3399
+ * generates, so both write paths stay byte-compatible.
3400
+ *
3401
+ * Bails without writing when the providers file or its exported array is
3402
+ * missing or customized, when the app has no eventBus port to register
3403
+ * against, or when the registry name is already imported from another file.
3404
+ * The diagnostic stays in every bail case.
3405
+ */
3406
+ async function fixUnregisteredListeners(targetDir, files, drift, config) {
3407
+ const candidates = drift.listeners.unregistered.filter((entry) => entry.fullyUnregistered);
3408
+ if (candidates.length === 0)
3409
+ return undefined;
3410
+ const providersFile = providersFilePath(config);
3411
+ const contextFile = normalizePath(path.join(path.dirname(config.paths.server), "context.ts"));
3412
+ const wiringDependencies = [
3413
+ providersFile,
3414
+ contextFile,
3415
+ config.paths.appContext,
3416
+ config.paths.ports,
3417
+ ];
3418
+ if (wiringDependencies.some((file) => !files.includes(file))) {
3419
+ return undefined;
3420
+ }
3421
+ // The generated wiring registers listeners on ports.eventBus; without the
3422
+ // port the fix would produce code that cannot typecheck.
3423
+ const portsSource = await readFile(path.join(targetDir, config.paths.ports), "utf8");
3424
+ if (!/\beventBus\s*[:?]/.test(portsSource))
3425
+ return undefined;
3426
+ const providersPath = path.join(targetDir, providersFile);
3427
+ const original = await readFile(providersPath, "utf8");
3428
+ let next = original;
3429
+ const registeredNames = [];
3430
+ for (const { registry } of candidates) {
3431
+ const imported = parseNamedImportSources(next).get(registry.registryName);
3432
+ if (imported) {
3433
+ const importedFile = sourceFileFromImport(imported.sourcePath, providersFile, files);
3434
+ if (importedFile !== registry.indexFile)
3435
+ return undefined;
3436
+ }
3437
+ const result = wireListenersProviderSource(next, {
3438
+ registryName: registry.registryName,
3439
+ featureKebab: listenerFeatureKebab(registry.indexFile),
3440
+ listenerModule: `@/${modulePath(registry.indexFile)}`,
3441
+ config,
3442
+ });
3443
+ if (result.kind === "missing")
3444
+ return undefined;
3445
+ if (result.kind === "updated") {
3446
+ next = result.source;
3447
+ registeredNames.push(registry.registryName);
3448
+ }
3449
+ }
3450
+ if (next === original || registeredNames.length === 0)
3451
+ return undefined;
3452
+ await writeFile(providersPath, next);
3453
+ return {
3454
+ code: "BEIGNET_LISTENER_UNREGISTERED",
3455
+ file: providersFile,
3456
+ message: `Registered ${registeredNames.join(", ")} with registerListeners(...) in ${providersFile}.`,
3457
+ };
3458
+ }
3459
+ function listenerFeatureKebab(indexFile) {
3460
+ return path.posix.basename(path.posix.dirname(path.posix.dirname(normalizePath(indexFile))));
3461
+ }
3344
3462
  async function fixUnregisteredOutboxEntries(targetDir, files, drift, config) {
3345
3463
  if (!drift.outbox.exists)
3346
3464
  return [];