@beignet/cli 0.0.21 → 0.0.23

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 (50) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +25 -3
  3. package/dist/choices.d.ts +8 -0
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +4 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +8 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspect.js +152 -0
  11. package/dist/inspect.js.map +1 -1
  12. package/dist/lib.d.ts +2 -2
  13. package/dist/lib.d.ts.map +1 -1
  14. package/dist/lib.js +1 -1
  15. package/dist/lib.js.map +1 -1
  16. package/dist/make.d.ts +4 -3
  17. package/dist/make.d.ts.map +1 -1
  18. package/dist/make.js +333 -30
  19. package/dist/make.js.map +1 -1
  20. package/dist/mcp.d.ts.map +1 -1
  21. package/dist/mcp.js +6 -2
  22. package/dist/mcp.js.map +1 -1
  23. package/dist/templates/agents.d.ts.map +1 -1
  24. package/dist/templates/agents.js +9 -5
  25. package/dist/templates/agents.js.map +1 -1
  26. package/dist/templates/base.d.ts.map +1 -1
  27. package/dist/templates/base.js +1 -0
  28. package/dist/templates/base.js.map +1 -1
  29. package/dist/templates/index.d.ts.map +1 -1
  30. package/dist/templates/index.js +1 -0
  31. package/dist/templates/index.js.map +1 -1
  32. package/dist/templates/server.d.ts +1 -0
  33. package/dist/templates/server.d.ts.map +1 -1
  34. package/dist/templates/server.js +30 -0
  35. package/dist/templates/server.js.map +1 -1
  36. package/dist/templates/shell.d.ts.map +1 -1
  37. package/dist/templates/shell.js +63 -29
  38. package/dist/templates/shell.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/choices.ts +10 -0
  41. package/src/index.ts +11 -0
  42. package/src/inspect.ts +266 -0
  43. package/src/lib.ts +2 -1
  44. package/src/make.ts +433 -38
  45. package/src/mcp.ts +7 -1
  46. package/src/templates/agents.ts +9 -5
  47. package/src/templates/base.ts +1 -0
  48. package/src/templates/index.ts +1 -0
  49. package/src/templates/server.ts +30 -0
  50. package/src/templates/shell.ts +64 -29
package/src/inspect.ts CHANGED
@@ -1894,6 +1894,23 @@ type ProviderPackageMetadataSource = {
1894
1894
  metadata: unknown;
1895
1895
  };
1896
1896
 
1897
+ const readinessRelevantProviderPackages = new Set([
1898
+ "@beignet/provider-auth-better-auth",
1899
+ "@beignet/provider-db-drizzle",
1900
+ "@beignet/provider-error-reporting-sentry",
1901
+ "@beignet/provider-flags-openfeature",
1902
+ "@beignet/provider-jobs-bullmq",
1903
+ "@beignet/provider-jobs-inngest",
1904
+ "@beignet/provider-locks-redis",
1905
+ "@beignet/provider-mail-resend",
1906
+ "@beignet/provider-mail-smtp",
1907
+ "@beignet/provider-payments-stripe",
1908
+ "@beignet/provider-rate-limit-upstash",
1909
+ "@beignet/provider-redis",
1910
+ "@beignet/provider-search-meilisearch",
1911
+ "@beignet/provider-storage-s3",
1912
+ ]);
1913
+
1897
1914
  async function inspectProductionReadiness(
1898
1915
  targetDir: string,
1899
1916
  files: string[],
@@ -2074,9 +2091,53 @@ async function inspectProductionReadiness(
2074
2091
  }
2075
2092
  }
2076
2093
 
2094
+ const installedReadinessProviders = [...installedPackages].filter(
2095
+ (packageName) => readinessRelevantProviderPackages.has(packageName),
2096
+ );
2097
+ if (
2098
+ installedReadinessProviders.length > 0 &&
2099
+ !(await hasReadinessCheck(targetDir, files, config, sourceCache))
2100
+ ) {
2101
+ diagnostics.push({
2102
+ severity: "hint",
2103
+ code: "BEIGNET_READINESS_ROUTE_MISSING",
2104
+ file: directoryPath(config.paths.routes),
2105
+ message: `${installedReadinessProviders.join(", ")} ${installedReadinessProviders.length === 1 ? "is" : "are"} installed, but no app-owned readiness route or dependency health checks were found. Consider exposing /api/ready with bounded dependency checks before deploying provider-backed infrastructure.`,
2106
+ });
2107
+ }
2108
+
2077
2109
  return diagnostics;
2078
2110
  }
2079
2111
 
2112
+ async function hasReadinessCheck(
2113
+ targetDir: string,
2114
+ files: string[],
2115
+ config: ResolvedBeignetConfig,
2116
+ sourceCache: Map<string, string>,
2117
+ ): Promise<boolean> {
2118
+ const routesDir = directoryPath(config.paths.routes);
2119
+ if (files.includes(`${routesDir}/ready/route.ts`)) return true;
2120
+
2121
+ for (const file of files) {
2122
+ if (!file.endsWith(".ts") || file.endsWith(".test.ts")) continue;
2123
+ if (!isRouteServerOrInfraFile(file, config)) continue;
2124
+
2125
+ const source = await readCachedSource(targetDir, file, sourceCache);
2126
+ if (containsReadinessCheck(source)) {
2127
+ return true;
2128
+ }
2129
+ }
2130
+
2131
+ return false;
2132
+ }
2133
+
2134
+ function containsReadinessCheck(source: string): boolean {
2135
+ if (containsCallExpression(source, "runHealthChecks")) return true;
2136
+ if (!containsCallExpression(source, "createHealthHandler")) return false;
2137
+
2138
+ return /\bchecks\s*:/.test(source) || /\.checkHealth\s*\(/.test(source);
2139
+ }
2140
+
2080
2141
  async function inspectEntitlementsProductionReadiness(
2081
2142
  targetDir: string,
2082
2143
  files: string[],
@@ -4030,6 +4091,7 @@ async function inspectDatabaseLifecycleDrift(
4030
4091
  if (!convention.resourceGenerator) return [];
4031
4092
 
4032
4093
  const diagnostics: InspectDiagnostic[] = [];
4094
+ const sourceCache = new Map<string, string>();
4033
4095
  const resources = await collectResourceNames(targetDir, files, config);
4034
4096
  const infrastructurePath = directoryPath(
4035
4097
  path.dirname(config.paths.infrastructurePorts),
@@ -4088,6 +4150,17 @@ async function inspectDatabaseLifecycleDrift(
4088
4150
  message: `This app has Drizzle database artifacts, but package.json does not define "${script}". Add an app-owned ${script} script so beignet db ${script.replace("db:", "")} can run the database lifecycle command.`,
4089
4151
  });
4090
4152
  }
4153
+
4154
+ diagnostics.push(
4155
+ ...(await inspectDurableDrizzleTableDrift(
4156
+ targetDir,
4157
+ files,
4158
+ config,
4159
+ infrastructurePath,
4160
+ schemaDir,
4161
+ sourceCache,
4162
+ )),
4163
+ );
4091
4164
  }
4092
4165
 
4093
4166
  const schemaFilePath = `${infrastructurePath}/db/schema.ts`;
@@ -4244,6 +4317,199 @@ async function inspectDatabaseLifecycleDrift(
4244
4317
  return diagnostics;
4245
4318
  }
4246
4319
 
4320
+ type DurableDrizzleTableRequirement = {
4321
+ capability: string;
4322
+ defaultTableName: string;
4323
+ code: string;
4324
+ factoryPattern: string;
4325
+ setupPattern?: string;
4326
+ };
4327
+
4328
+ const durableDrizzleTableRequirements: readonly DurableDrizzleTableRequirement[] =
4329
+ [
4330
+ {
4331
+ capability: "idempotency",
4332
+ defaultTableName: "idempotency_records",
4333
+ code: "BEIGNET_IDEMPOTENCY_TABLE_MISSING",
4334
+ factoryPattern: "createDrizzle[A-Za-z]+IdempotencyPort",
4335
+ setupPattern: "createDrizzle[A-Za-z]+IdempotencySetupStatements",
4336
+ },
4337
+ {
4338
+ capability: "outbox",
4339
+ defaultTableName: "outbox_messages",
4340
+ code: "BEIGNET_OUTBOX_TABLE_MISSING",
4341
+ factoryPattern: "createDrizzle[A-Za-z]+OutboxPort",
4342
+ setupPattern: "createDrizzle[A-Za-z]+OutboxSetupStatements",
4343
+ },
4344
+ {
4345
+ capability: "audit",
4346
+ defaultTableName: "audit_log",
4347
+ code: "BEIGNET_AUDIT_TABLE_MISSING",
4348
+ factoryPattern: "createDrizzle(?:[A-Za-z]+AuditLogPort|AuditLog)",
4349
+ setupPattern: "createDrizzle[A-Za-z]+AuditLogSetupStatements",
4350
+ },
4351
+ ];
4352
+
4353
+ async function inspectDurableDrizzleTableDrift(
4354
+ targetDir: string,
4355
+ files: string[],
4356
+ config: ResolvedBeignetConfig,
4357
+ infrastructurePath: string,
4358
+ schemaDir: string,
4359
+ sourceCache: Map<string, string>,
4360
+ ): Promise<InspectDiagnostic[]> {
4361
+ const diagnostics: InspectDiagnostic[] = [];
4362
+ const usageFiles = durableDrizzleUsageFiles(files, config);
4363
+
4364
+ for (const requirement of durableDrizzleTableRequirements) {
4365
+ const tableNames = await configuredDurableDrizzleTableNames(
4366
+ targetDir,
4367
+ usageFiles,
4368
+ requirement,
4369
+ sourceCache,
4370
+ );
4371
+ if (tableNames.size === 0) continue;
4372
+
4373
+ for (const tableName of tableNames) {
4374
+ if (
4375
+ await durableDrizzleTableExists(
4376
+ targetDir,
4377
+ files,
4378
+ infrastructurePath,
4379
+ schemaDir,
4380
+ requirement,
4381
+ tableName,
4382
+ sourceCache,
4383
+ )
4384
+ ) {
4385
+ continue;
4386
+ }
4387
+
4388
+ diagnostics.push({
4389
+ severity: "warning",
4390
+ code: requirement.code,
4391
+ file: schemaDir,
4392
+ message: `This app wires a Drizzle-backed ${requirement.capability} port, but no ${tableName} table setup was found in ${schemaDir}/, drizzle/, or app database setup files. Add the table to your Drizzle schema/migrations, call the provider setup statements, or remove the stale ${requirement.capability} wiring before deployment.`,
4393
+ });
4394
+ }
4395
+ }
4396
+
4397
+ return diagnostics;
4398
+ }
4399
+
4400
+ function durableDrizzleUsageFiles(
4401
+ files: string[],
4402
+ config: ResolvedBeignetConfig,
4403
+ ): string[] {
4404
+ return files.filter(
4405
+ (file) =>
4406
+ /\.(?:ts|tsx|mts|cts)$/.test(file) &&
4407
+ !isTestSourceFile(file) &&
4408
+ isInfraOrServerFile(file, config),
4409
+ );
4410
+ }
4411
+
4412
+ async function configuredDurableDrizzleTableNames(
4413
+ targetDir: string,
4414
+ files: string[],
4415
+ requirement: DurableDrizzleTableRequirement,
4416
+ sourceCache: Map<string, string>,
4417
+ ): Promise<Set<string>> {
4418
+ const tableNames = new Set<string>();
4419
+
4420
+ for (const file of files) {
4421
+ const source = await readCachedSource(targetDir, file, sourceCache);
4422
+ for (const tableName of tableNamesFromCalls(
4423
+ source,
4424
+ requirement.factoryPattern,
4425
+ requirement.defaultTableName,
4426
+ )) {
4427
+ tableNames.add(tableName);
4428
+ }
4429
+ }
4430
+
4431
+ return tableNames;
4432
+ }
4433
+
4434
+ async function durableDrizzleTableExists(
4435
+ targetDir: string,
4436
+ files: string[],
4437
+ infrastructurePath: string,
4438
+ schemaDir: string,
4439
+ requirement: DurableDrizzleTableRequirement,
4440
+ tableName: string,
4441
+ sourceCache: Map<string, string>,
4442
+ ): Promise<boolean> {
4443
+ const schemaFiles = files.filter(
4444
+ (file) =>
4445
+ file.startsWith(`${schemaDir}/`) &&
4446
+ /\.(?:ts|mts|cts)$/.test(file) &&
4447
+ !isTestSourceFile(file),
4448
+ );
4449
+ for (const file of schemaFiles) {
4450
+ const source = await readCachedSource(targetDir, file, sourceCache);
4451
+ if (source.includes(tableName)) return true;
4452
+ }
4453
+
4454
+ const setupFiles = files.filter(
4455
+ (file) =>
4456
+ !isTestSourceFile(file) &&
4457
+ (file.startsWith("drizzle/") ||
4458
+ file.startsWith(`${infrastructurePath}/db/`)) &&
4459
+ /\.(?:ts|js|mts|mjs|cts|cjs|sql)$/.test(file),
4460
+ );
4461
+ for (const file of setupFiles) {
4462
+ const source = await readCachedSource(targetDir, file, sourceCache);
4463
+ if (sourceCreatesTable(source, tableName)) return true;
4464
+
4465
+ if (!requirement.setupPattern) continue;
4466
+ if (
4467
+ tableNamesFromCalls(
4468
+ source,
4469
+ requirement.setupPattern,
4470
+ requirement.defaultTableName,
4471
+ ).has(tableName)
4472
+ ) {
4473
+ return true;
4474
+ }
4475
+ }
4476
+
4477
+ return false;
4478
+ }
4479
+
4480
+ function sourceCreatesTable(source: string, tableName: string): boolean {
4481
+ return new RegExp(
4482
+ `\\bCREATE\\s+TABLE\\b[\\s\\S]{0,200}${escapeRegExp(tableName)}\\b`,
4483
+ "i",
4484
+ ).test(source);
4485
+ }
4486
+
4487
+ function tableNamesFromCalls(
4488
+ source: string,
4489
+ calleePattern: string,
4490
+ defaultTableName: string,
4491
+ ): Set<string> {
4492
+ const tableNames = new Set<string>();
4493
+ const callPattern = new RegExp(
4494
+ `\\b${calleePattern}\\s*\\(([\\s\\S]{0,800}?)\\)`,
4495
+ "g",
4496
+ );
4497
+
4498
+ for (const match of source.matchAll(callPattern)) {
4499
+ const matchIndex = match.index ?? 0;
4500
+ const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
4501
+ if (/\bfunction\s+$/.test(prefix)) continue;
4502
+
4503
+ const argsSource = match[1] ?? "";
4504
+ const tableName = /\btableName\s*:\s*["'`]([^"'`]+)["'`]/.exec(
4505
+ argsSource,
4506
+ )?.[1];
4507
+ tableNames.add(tableName ?? defaultTableName);
4508
+ }
4509
+
4510
+ return tableNames;
4511
+ }
4512
+
4247
4513
  function hasRepositoryAdapterFile(
4248
4514
  files: string[],
4249
4515
  infrastructureDir: string,
package/src/lib.ts CHANGED
@@ -18,7 +18,7 @@ export {
18
18
  inspectApp,
19
19
  } from "./inspect.js";
20
20
  export { formatLint, formatLintGithub, lintApp } from "./lint.js";
21
- export type { MakeFeatureAddon } from "./make.js";
21
+ export type { MakeFeatureAddon, MakeFeatureRecipe } from "./make.js";
22
22
  export {
23
23
  makeAdapter,
24
24
  makeContract,
@@ -26,6 +26,7 @@ export {
26
26
  makeFactory,
27
27
  makeFeature,
28
28
  makeFeatureAddonChoices,
29
+ makeFeatureRecipeChoices,
29
30
  makeJob,
30
31
  makeListener,
31
32
  makeNotification,