@beignet/cli 0.0.34 → 0.0.36

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 (56) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +27 -0
  3. package/dist/config.d.ts +1 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +1 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/inspect.js +49 -13
  8. package/dist/inspect.js.map +1 -1
  9. package/dist/lint.js +10 -3
  10. package/dist/lint.js.map +1 -1
  11. package/dist/make/inbox.d.ts.map +1 -1
  12. package/dist/make/inbox.js +2 -3
  13. package/dist/make/inbox.js.map +1 -1
  14. package/dist/make/payments.d.ts.map +1 -1
  15. package/dist/make/payments.js +3 -5
  16. package/dist/make/payments.js.map +1 -1
  17. package/dist/make/tenancy.d.ts.map +1 -1
  18. package/dist/make/tenancy.js +2 -4
  19. package/dist/make/tenancy.js.map +1 -1
  20. package/dist/make.js +4 -5
  21. package/dist/make.js.map +1 -1
  22. package/dist/schedule.d.ts.map +1 -1
  23. package/dist/schedule.js +1 -1
  24. package/dist/schedule.js.map +1 -1
  25. package/dist/templates/agents.d.ts.map +1 -1
  26. package/dist/templates/agents.js +2 -0
  27. package/dist/templates/agents.js.map +1 -1
  28. package/dist/templates/index.d.ts.map +1 -1
  29. package/dist/templates/index.js +1 -0
  30. package/dist/templates/index.js.map +1 -1
  31. package/dist/templates/server.d.ts +1 -0
  32. package/dist/templates/server.d.ts.map +1 -1
  33. package/dist/templates/server.js +8 -2
  34. package/dist/templates/server.js.map +1 -1
  35. package/dist/templates/testing.d.ts.map +1 -1
  36. package/dist/templates/testing.js +55 -26
  37. package/dist/templates/testing.js.map +1 -1
  38. package/dist/templates/todos.d.ts.map +1 -1
  39. package/dist/templates/todos.js +2 -3
  40. package/dist/templates/todos.js.map +1 -1
  41. package/package.json +3 -2
  42. package/skills/app-structure/SKILL.md +12 -1
  43. package/src/config.ts +2 -0
  44. package/src/inspect.ts +82 -15
  45. package/src/lint.ts +15 -3
  46. package/src/make/inbox.ts +2 -3
  47. package/src/make/payments.ts +2 -6
  48. package/src/make/tenancy.ts +2 -8
  49. package/src/make.ts +4 -5
  50. package/src/schedule.ts +4 -3
  51. package/src/templates/agents.ts +2 -0
  52. package/src/templates/index.ts +1 -0
  53. package/src/templates/server.ts +8 -2
  54. package/src/templates/testing.ts +55 -26
  55. package/src/templates/todos.ts +2 -3
  56. package/src/test-helpers/generated-app.ts +1 -1
@@ -2041,13 +2041,7 @@ export const WorkspaceInviteNotification = defineNotification(
2041
2041
  }
2042
2042
 
2043
2043
  export function tenancyRoutesFile(config: ResolvedBeignetConfig): string {
2044
- const routeFilePath = path.join(
2045
- config.paths.features,
2046
- "workspaces/routes.ts",
2047
- );
2048
-
2049
- return `import { defineRouteGroup } from "@beignet/core/server";
2050
- import type { AppContext } from "${relativeModule(routeFilePath, config.paths.appContext)}";
2044
+ return `import { defineRouteGroup } from "${aliasModule(config.paths.routesBuilder)}";
2051
2045
  import * as workspaceContracts from "@/features/workspaces/contracts";
2052
2046
  import * as workspaceUseCases from "@/features/workspaces/use-cases";
2053
2047
  import { WORKSPACE_COOKIE_NAME } from "@/lib/tenant";
@@ -2064,7 +2058,7 @@ function workspaceCookie(workspaceId: string): string {
2064
2058
  \t].join("; ");
2065
2059
  }
2066
2060
 
2067
- export const workspaceRoutes = defineRouteGroup<AppContext>()({
2061
+ export const workspaceRoutes = defineRouteGroup({
2068
2062
  \tname: "workspaces",
2069
2063
  \troutes: [
2070
2064
  \t\t{
package/src/make.ts CHANGED
@@ -4871,8 +4871,8 @@ function listenerFile(
4871
4871
  return `import { defineListener } from "${aliasModule(config.paths.listenersBuilder)}";
4872
4872
  import { ${names.event.eventExportName} } from "${relativeModule(filePath, eventFilePath(names.event, config))}";
4873
4873
 
4874
- export const ${names.listenerExportName} = defineListener(${names.event.eventExportName}, {
4875
- \tname: "${names.listenerName}",
4874
+ export const ${names.listenerExportName} = defineListener("${names.listenerName}", {
4875
+ \tevent: ${names.event.eventExportName},
4876
4876
  \tasync handle({ payload, ctx }) {
4877
4877
  \t\tctx.ports.logger.info("Listener handled", {
4878
4878
  \t\t\tlistenerName: "${names.listenerName}",
@@ -5249,8 +5249,7 @@ function routeGroupFile(
5249
5249
  const useCasesPath = resourceUseCaseIndexPath(names, config);
5250
5250
 
5251
5251
  return `import "@beignet/core/server-only";
5252
- import { defineRouteGroup } from "@beignet/core/server";
5253
- import type { AppContext } from "${relativeModule(routeFilePath, config.paths.appContext)}";
5252
+ import { defineRouteGroup } from "${relativeModule(routeFilePath, config.paths.routesBuilder)}";
5254
5253
  import {
5255
5254
  create${names.singularPascal},
5256
5255
  ${mode === "resource" ? ` delete${names.singularPascal},\n get${names.singularPascal},\n` : ""} list${names.pluralPascal},
@@ -5260,7 +5259,7 @@ import {
5260
5259
  ${mode === "resource" ? ` delete${names.singularPascal}UseCase,\n get${names.singularPascal}UseCase,\n` : ""} list${names.pluralPascal}UseCase,
5261
5260
  ${mode === "resource" ? ` update${names.singularPascal}UseCase,\n` : ""}} from "${relativeModule(routeFilePath, useCasesPath)}";
5262
5261
 
5263
- export const ${names.singularCamel}Routes = defineRouteGroup<AppContext>()({
5262
+ export const ${names.singularCamel}Routes = defineRouteGroup({
5264
5263
  name: "${names.pluralKebab}",
5265
5264
  routes: [
5266
5265
  { contract: list${names.pluralPascal}, useCase: list${names.pluralPascal}UseCase },
package/src/schedule.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import type { ProviderInstrumentationPort } from "@beignet/core/providers";
3
4
  import type {
4
5
  ScheduleDef,
5
- ScheduleInstrumentation,
6
6
  ScheduleRunOptions,
7
7
  StandardSchema,
8
8
  } from "@beignet/core/schedules";
@@ -54,7 +54,8 @@ type ScheduleContext = {
54
54
  traceId?: string;
55
55
  ports?: {
56
56
  logger?: ScheduleLogger;
57
- devtools?: ScheduleInstrumentation;
57
+ devtools?: ProviderInstrumentationPort;
58
+ instrumentation?: ProviderInstrumentationPort;
58
59
  };
59
60
  };
60
61
 
@@ -107,7 +108,7 @@ export async function runAppSchedule(
107
108
  );
108
109
  const runner = createInlineScheduleRunner<unknown>({
109
110
  ctx,
110
- instrumentation: scheduleContext.ports?.devtools,
111
+ instrumentation: scheduleContext.ports,
111
112
  instrumentationContext: {
112
113
  requestId: scheduleContext.requestId,
113
114
  traceId: scheduleContext.traceId,
@@ -48,6 +48,8 @@ is a silent failure — the file exists but never runs:
48
48
 
49
49
  - Feature route groups in \`features/<feature>/routes.ts\` must be composed in
50
50
  \`server/routes.ts\` via \`defineRoutes([...])\`.
51
+ - Feature route files import the app-bound \`defineRouteGroup\` from
52
+ \`lib/routes.ts\`; that builder owns the shared \`AppContext\` binding.
51
53
  - Schedules must be added to the \`schedules\` array in \`server/schedules.ts\`.
52
54
  - Tasks must be added to \`defineTasks([...])\` in \`server/tasks.ts\`.
53
55
  - Outbox events and jobs must be registered in \`defineOutboxRegistry({...})\`
@@ -136,6 +136,7 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
136
136
  { path: "lib/tenant.ts", content: serverFiles.tenantHelper },
137
137
  ...testSupportTemplateFiles,
138
138
  { path: "lib/use-case.ts", content: serverFiles.useCaseBuilder },
139
+ { path: "lib/routes.ts", content: serverFiles.routesBuilder },
139
140
  { path: "server/index.ts", content: server(ctx) },
140
141
  { path: "server/context.ts", content: serverFiles.serverContext },
141
142
  { path: "server/routes.ts", content: serverFiles.serverRoutes },
@@ -10,7 +10,7 @@ export function serverProviders(ctx: TemplateContext): string {
10
10
  const imports = [
11
11
  'import "@beignet/core/server-only";',
12
12
  'import { createDevtoolsProvider } from "@beignet/devtools";',
13
- 'import { createAuthBetterAuthProvider } from "@beignet/provider-auth-better-auth";',
13
+ 'import { createBetterAuthProvider } from "@beignet/provider-auth-better-auth";',
14
14
  `import { ${db.providerFactory} } from "@beignet/provider-db-drizzle/${db.subpath}";`,
15
15
  hasIntegration(ctx, "inngest")
16
16
  ? 'import { createInngestJobsProvider } from "@beignet/provider-jobs-inngest";'
@@ -30,7 +30,7 @@ export function serverProviders(ctx: TemplateContext): string {
30
30
 
31
31
  const entries = [
32
32
  "\tcreateDevtoolsProvider(),",
33
- "\tcreateAuthBetterAuthProvider<AuthUser, AuthSessionMetadata>(auth),",
33
+ "\tcreateBetterAuthProvider<AuthUser, AuthSessionMetadata>(auth),",
34
34
  "\tcreatePinoLoggerProvider(),",
35
35
  `\t${db.providerInstance},`,
36
36
  "\tstarterDatabaseProvider,",
@@ -396,6 +396,12 @@ import { createUseCase } from "@beignet/core/application";
396
396
  import type { AppContext } from "@/app-context";
397
397
 
398
398
  export const useCase = createUseCase<AppContext>();
399
+ `,
400
+ routesBuilder: `import "@beignet/core/server-only";
401
+ import { createRoutes } from "@beignet/core/server";
402
+ import type { AppContext } from "@/app-context";
403
+
404
+ export const { defineRoute, defineRouteGroup } = createRoutes<AppContext>();
399
405
  `,
400
406
  authHelpers: `import type { AppContext } from "@/app-context";
401
407
  import type { AuthSession, AuthUser } from "@/ports/auth";
@@ -406,6 +406,7 @@ export const testRunnerTemplateFile: TemplateFile = {
406
406
  content: `import { spawnSync } from "node:child_process";
407
407
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
408
408
  import path from "node:path";
409
+ import { pathToFileURL } from "node:url";
409
410
 
410
411
  const defaultTestRoots = [
411
412
  "app",
@@ -428,11 +429,18 @@ const ignoredDirectories = new Set([
428
429
  ]);
429
430
  const testFilePattern = /\\.(test|spec)\\.[cm]?[tj]sx?$/;
430
431
 
432
+ const configFileNames = [
433
+ "beignet.config.ts",
434
+ "beignet.config.json",
435
+ "beignet.config.mjs",
436
+ "beignet.config.js",
437
+ ];
438
+
431
439
  const explicitArgs = process.argv.slice(2);
432
440
  const testArgs =
433
441
  explicitArgs.length > 0
434
442
  ? resolveTestArgs(process.cwd(), explicitArgs)
435
- : discoverTestFiles(process.cwd());
443
+ : await discoverTestFiles(process.cwd());
436
444
 
437
445
  if (testArgs.length === 0) {
438
446
  console.log("No test files found.");
@@ -456,9 +464,9 @@ if (result.error) {
456
464
 
457
465
  process.exit(result.status ?? 1);
458
466
 
459
- function discoverTestFiles(cwd: string): string[] {
467
+ async function discoverTestFiles(cwd: string): Promise<string[]> {
460
468
  const files: string[] = [];
461
- for (const root of testRoots(cwd)) {
469
+ for (const root of await testRoots(cwd)) {
462
470
  const rootPath = path.join(cwd, root);
463
471
  if (existsSync(rootPath)) {
464
472
  walk(rootPath, cwd, files);
@@ -494,32 +502,53 @@ function resolveTestArgs(cwd: string, args: string[]): string[] {
494
502
  return Array.from(new Set(resolved));
495
503
  }
496
504
 
497
- function testRoots(cwd: string): string[] {
498
- return Array.from(new Set([...defaultTestRoots, ...configuredRoots(cwd)]));
505
+ async function testRoots(cwd: string): Promise<string[]> {
506
+ return Array.from(
507
+ new Set([...defaultTestRoots, ...(await configuredRoots(cwd))]),
508
+ );
499
509
  }
500
510
 
501
- function configuredRoots(cwd: string): string[] {
502
- const configPath = path.join(cwd, "beignet.config.json");
503
- if (!existsSync(configPath)) return [];
504
- try {
505
- const config = JSON.parse(readFileSync(configPath, "utf8")) as {
506
- paths?: Record<string, string>;
507
- };
508
- const paths = config.paths ?? {};
509
- return [
510
- paths.tests,
511
- paths.features,
512
- paths.routes ? path.dirname(paths.routes) : undefined,
513
- paths.server ? path.dirname(paths.server) : undefined,
514
- paths.infrastructurePorts
515
- ? path.dirname(paths.infrastructurePorts)
516
- : undefined,
517
- paths.ports ? path.dirname(paths.ports) : undefined,
518
- paths.useCaseBuilder ? path.dirname(paths.useCaseBuilder) : undefined,
519
- ].filter((root): root is string => Boolean(root) && root !== ".");
520
- } catch {
521
- return [];
511
+ async function configuredRoots(cwd: string): Promise<string[]> {
512
+ const configFiles = configFileNames.filter((file) =>
513
+ existsSync(path.join(cwd, file)),
514
+ );
515
+ if (configFiles.length === 0) return [];
516
+ if (configFiles.length > 1) {
517
+ throw new Error(
518
+ "Multiple Beignet config files found: " +
519
+ configFiles.join(", ") +
520
+ ". Keep only one config file.",
521
+ );
522
522
  }
523
+
524
+ const configFile = configFiles[0];
525
+ const configPath = path.join(cwd, configFile);
526
+ const config = configFile.endsWith(".json")
527
+ ? JSON.parse(readFileSync(configPath, "utf8"))
528
+ : ((await import(pathToFileURL(configPath).href)) as { default?: unknown })
529
+ .default;
530
+ if (!isObjectRecord(config)) {
531
+ throw new Error(configFile + " must export a Beignet config object.");
532
+ }
533
+ const paths = isObjectRecord(config.paths) ? config.paths : {};
534
+ return [
535
+ paths.tests,
536
+ paths.features,
537
+ parentPath(paths.routes),
538
+ parentPath(paths.server),
539
+ parentPath(paths.infrastructurePorts),
540
+ parentPath(paths.ports),
541
+ parentPath(paths.useCaseBuilder),
542
+ parentPath(paths.routesBuilder),
543
+ ].filter((root): root is string => Boolean(root) && root !== ".");
544
+ }
545
+
546
+ function parentPath(value: unknown): string | undefined {
547
+ return typeof value === "string" ? path.dirname(value) : undefined;
548
+ }
549
+
550
+ function isObjectRecord(value: unknown): value is Record<string, unknown> {
551
+ return value !== null && typeof value === "object" && !Array.isArray(value);
523
552
  }
524
553
 
525
554
  function walk(directory: string, cwd: string, files: string[]): void {
@@ -175,8 +175,7 @@ export const todoPolicy = definePolicy({
175
175
  });
176
176
  `,
177
177
  routes: `import "@beignet/core/server-only";
178
- import { defineRouteGroup } from "@beignet/core/server";
179
- import type { AppContext } from "@/app-context";
178
+ import { defineRouteGroup } from "@/lib/routes";
180
179
  import {
181
180
  createTodo,
182
181
  deleteTodo,
@@ -190,7 +189,7 @@ import {
190
189
  updateTodoUseCase,
191
190
  } from "@/features/todos/use-cases";
192
191
 
193
- export const todoRoutes = defineRouteGroup<AppContext>()({
192
+ export const todoRoutes = defineRouteGroup({
194
193
  name: "todos",
195
194
  routes: [
196
195
  { contract: listTodos, useCase: listTodosUseCase },
@@ -116,7 +116,7 @@ export async function linkSmokeDependencies(
116
116
  "packages/provider-mail-resend/node_modules/resend",
117
117
  ),
118
118
  "next-themes": path.join(repoRoot, "packages/cli/node_modules/next-themes"),
119
- tsx: path.join(repoRoot, "node_modules/tsx"),
119
+ tsx: path.join(repoRoot, "packages/cli/node_modules/tsx"),
120
120
  };
121
121
 
122
122
  for (const packageName of externalPackages) {