@beignet/cli 0.0.38 → 0.0.39

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 (77) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +120 -96
  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/create-prompts.d.ts +5 -6
  8. package/dist/create-prompts.d.ts.map +1 -1
  9. package/dist/create-prompts.js +15 -15
  10. package/dist/create-prompts.js.map +1 -1
  11. package/dist/create.d.ts +11 -2
  12. package/dist/create.d.ts.map +1 -1
  13. package/dist/create.js +38 -28
  14. package/dist/create.js.map +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +149 -47
  17. package/dist/index.js.map +1 -1
  18. package/dist/inspect.js +54 -1
  19. package/dist/inspect.js.map +1 -1
  20. package/dist/lib.d.ts +1 -1
  21. package/dist/lib.d.ts.map +1 -1
  22. package/dist/make/inbox.js +3 -3
  23. package/dist/make/inbox.js.map +1 -1
  24. package/dist/make/payments.js +3 -3
  25. package/dist/make/payments.js.map +1 -1
  26. package/dist/make/shared.d.ts +10 -3
  27. package/dist/make/shared.d.ts.map +1 -1
  28. package/dist/make/shared.js +7 -8
  29. package/dist/make/shared.js.map +1 -1
  30. package/dist/make.d.ts +3 -2
  31. package/dist/make.d.ts.map +1 -1
  32. package/dist/make.js +455 -185
  33. package/dist/make.js.map +1 -1
  34. package/dist/mcp.d.ts.map +1 -1
  35. package/dist/mcp.js +15 -11
  36. package/dist/mcp.js.map +1 -1
  37. package/dist/provider-add.d.ts +21 -3
  38. package/dist/provider-add.d.ts.map +1 -1
  39. package/dist/provider-add.js +101 -65
  40. package/dist/provider-add.js.map +1 -1
  41. package/dist/task.js +1 -1
  42. package/dist/task.js.map +1 -1
  43. package/dist/templates/base.d.ts +1 -1
  44. package/dist/templates/base.d.ts.map +1 -1
  45. package/dist/templates/base.js +15 -15
  46. package/dist/templates/base.js.map +1 -1
  47. package/dist/templates/index.d.ts +2 -2
  48. package/dist/templates/index.d.ts.map +1 -1
  49. package/dist/templates/index.js +5 -5
  50. package/dist/templates/index.js.map +1 -1
  51. package/dist/templates/server.d.ts.map +1 -1
  52. package/dist/templates/server.js +17 -13
  53. package/dist/templates/server.js.map +1 -1
  54. package/dist/templates/shared.d.ts +4 -4
  55. package/dist/templates/shared.d.ts.map +1 -1
  56. package/dist/templates/shared.js +6 -6
  57. package/dist/templates/shared.js.map +1 -1
  58. package/package.json +2 -2
  59. package/skills/app-structure/SKILL.md +13 -8
  60. package/src/choices.ts +38 -88
  61. package/src/create-prompts.ts +20 -21
  62. package/src/create.ts +54 -36
  63. package/src/index.ts +193 -65
  64. package/src/inspect.ts +101 -1
  65. package/src/lib.ts +1 -1
  66. package/src/make/inbox.ts +3 -3
  67. package/src/make/payments.ts +3 -3
  68. package/src/make/shared.ts +16 -10
  69. package/src/make.ts +578 -176
  70. package/src/mcp.ts +20 -13
  71. package/src/provider-add.ts +159 -79
  72. package/src/task.ts +1 -1
  73. package/src/templates/base.ts +16 -16
  74. package/src/templates/index.ts +6 -6
  75. package/src/templates/server.ts +17 -13
  76. package/src/templates/shared.ts +10 -10
  77. package/src/test-helpers/generated-app.ts +1 -0
@@ -34,19 +34,24 @@ Use:
34
34
  ```bash
35
35
  beignet make feature projects
36
36
  beignet make feature projects --recipe full-slice
37
- beignet make resource projects/project --auth --tenant
38
- beignet make event posts/published
39
- beignet make listener posts/enqueue-published-email --event posts/published
40
- beignet make job billing/sync
41
- beignet make schedule billing/nightly --cron "0 2 * * *"
42
- beignet make task admin/backfill
43
- beignet make seed demo/users
44
- beignet make upload issues/attachment
37
+ beignet make resource projects --authorization --tenant-scoped
38
+ beignet make event posts.published
39
+ beignet make listener posts.enqueue-published-email --event posts.published
40
+ beignet make job billing.sync
41
+ beignet make schedule billing.nightly --cron "0 2 * * *"
42
+ beignet make task admin.backfill
43
+ beignet make seed demo.users
44
+ beignet make upload issues.attachment
45
+ beignet make upload issues.attachment --ui
45
46
  ```
46
47
 
47
48
  Generators place files under the owning feature, update central registries
48
49
  when possible, and keep `doctor` clean.
49
50
 
51
+ Use `make upload --ui` in full-stack apps when the upload should include a
52
+ shared client/server constraint manifest, typed React upload client, uploader,
53
+ and component test. Omit `--ui` for API-only or backend-only workflows.
54
+
50
55
  Use the `full-slice` recipe when you need a richer reference feature with
51
56
  policy, client helpers, workflow artifacts, events, listener registration,
52
57
  jobs, schedules, notifications, uploads, seeds, and outbox wiring. After
package/src/choices.ts CHANGED
@@ -14,18 +14,21 @@ export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
14
14
  */
15
15
  export type TemplateName = "next";
16
16
  /**
17
- * Optional provider integrations that can be scaffolded into a new app.
17
+ * Optional providers rendered directly by the starter template.
18
18
  *
19
19
  * Better Auth, Drizzle/libSQL, and pino ship in every starter, so only
20
- * integrations that add an external service remain selectable.
20
+ * providers that add an external service remain selectable.
21
21
  */
22
- export type IntegrationName = "inngest" | "resend" | "upstash-rate-limit";
22
+ export type StarterProviderName =
23
+ | "jobs-inngest"
24
+ | "mail-resend"
25
+ | "rate-limit-upstash";
23
26
  /**
24
27
  * Databases supported by the starter's Drizzle persistence layer.
25
28
  */
26
29
  export type DatabaseName = "sqlite" | "postgres" | "mysql";
27
30
  /**
28
- * Provider setup presets supported by `beignet providers add`.
31
+ * Provider setup presets supported by `beignet provider add`.
29
32
  */
30
33
  export type ProviderPresetName =
31
34
  | "flags-openfeature"
@@ -35,13 +38,13 @@ export type ProviderPresetName =
35
38
  | "mail-smtp"
36
39
  | "payments-stripe"
37
40
  | "search-meilisearch"
38
- | "sentry"
39
- | "upstash-rate-limit"
40
- | "redis-cache"
41
+ | "error-reporting-sentry"
42
+ | "rate-limit-upstash"
43
+ | "cache-redis"
41
44
  | "event-bus-redis"
42
- | "redis-locks"
43
- | "s3-storage"
44
- | "vercel-blob-storage";
45
+ | "locks-redis"
46
+ | "storage-s3"
47
+ | "storage-vercel-blob";
45
48
 
46
49
  /**
47
50
  * Supported scaffold template choices.
@@ -61,13 +64,13 @@ export const packageManagerChoices = [
61
64
  ] as const satisfies readonly PackageManager[];
62
65
 
63
66
  /**
64
- * Supported scaffold integration choices.
67
+ * Supported providers rendered directly by the starter.
65
68
  */
66
- export const integrationChoices = [
67
- "inngest",
68
- "resend",
69
- "upstash-rate-limit",
70
- ] as const satisfies readonly IntegrationName[];
69
+ export const starterProviderChoices = [
70
+ "jobs-inngest",
71
+ "mail-resend",
72
+ "rate-limit-upstash",
73
+ ] as const satisfies readonly StarterProviderName[];
71
74
 
72
75
  /**
73
76
  * Supported scaffold database choices.
@@ -89,89 +92,45 @@ export const providerPresetChoices = [
89
92
  "mail-smtp",
90
93
  "payments-stripe",
91
94
  "search-meilisearch",
92
- "sentry",
93
- "upstash-rate-limit",
94
- "redis-cache",
95
+ "error-reporting-sentry",
96
+ "rate-limit-upstash",
97
+ "cache-redis",
95
98
  "event-bus-redis",
96
- "redis-locks",
97
- "s3-storage",
98
- "vercel-blob-storage",
99
+ "locks-redis",
100
+ "storage-s3",
101
+ "storage-vercel-blob",
99
102
  ] as const satisfies readonly ProviderPresetName[];
100
103
 
101
104
  /**
102
- * Values accepted by `beignet create --integrations`: the starter template
103
- * integrations plus the full `beignet providers add` preset catalog.
105
+ * Values accepted by `beignet create --providers`.
104
106
  */
105
- export type CreateIntegrationName = IntegrationName | ProviderPresetName;
107
+ export type CreateProviderName = ProviderPresetName;
106
108
 
107
109
  /**
108
- * Supported `beignet create --integrations` choices. Template integrations
109
- * come first; the rest of the provider preset catalog follows.
110
+ * Supported `beignet create --providers` choices.
110
111
  */
111
- export const createIntegrationChoices = [
112
- "inngest",
113
- "resend",
114
- "upstash-rate-limit",
115
- "flags-openfeature",
116
- "jobs-bullmq",
117
- "jobs-inngest",
118
- "mail-resend",
119
- "mail-smtp",
120
- "payments-stripe",
121
- "redis-cache",
122
- "event-bus-redis",
123
- "redis-locks",
124
- "s3-storage",
125
- "search-meilisearch",
126
- "sentry",
127
- "vercel-blob-storage",
128
- ] as const satisfies readonly CreateIntegrationName[];
129
-
130
- /**
131
- * Provider presets whose create-time wiring already ships as a starter
132
- * template integration. Selecting the preset name at create time scaffolds
133
- * the equivalent template integration, so preset and integration stay
134
- * consistent and `beignet providers add` remains a no-op afterwards.
135
- */
136
- const presetTemplateIntegrationAliases: Partial<
137
- Record<ProviderPresetName, IntegrationName>
138
- > = {
139
- "jobs-inngest": "inngest",
140
- "mail-resend": "resend",
141
- "upstash-rate-limit": "upstash-rate-limit",
142
- };
112
+ export const createProviderChoices = providerPresetChoices;
143
113
 
144
114
  /**
145
- * Split `--integrations` selections into starter template integrations and
146
- * provider presets applied with the `beignet providers add` machinery after
147
- * the scaffold is written. Preset names with a template equivalent are
148
- * normalized to the template integration; duplicates collapse.
115
+ * Split `--providers` selections into providers rendered directly by the
116
+ * starter and presets applied after the scaffold is written.
149
117
  */
150
- export function splitCreateIntegrations(
151
- values: readonly CreateIntegrationName[],
152
- ): {
153
- integrations: IntegrationName[];
118
+ export function splitCreateProviders(values: readonly CreateProviderName[]): {
119
+ starterProviders: StarterProviderName[];
154
120
  presets: ProviderPresetName[];
155
121
  } {
156
- const integrations = new Set<IntegrationName>();
122
+ const starterProviders = new Set<StarterProviderName>();
157
123
  const presets = new Set<ProviderPresetName>();
158
124
 
159
125
  for (const value of values) {
160
- if ((integrationChoices as readonly string[]).includes(value)) {
161
- integrations.add(value as IntegrationName);
162
- continue;
163
- }
164
-
165
- const preset = value as ProviderPresetName;
166
- const alias = presetTemplateIntegrationAliases[preset];
167
- if (alias) {
168
- integrations.add(alias);
126
+ if ((starterProviderChoices as readonly string[]).includes(value)) {
127
+ starterProviders.add(value as StarterProviderName);
169
128
  } else {
170
- presets.add(preset);
129
+ presets.add(value);
171
130
  }
172
131
  }
173
132
 
174
- return { integrations: [...integrations], presets: [...presets] };
133
+ return { starterProviders: [...starterProviders], presets: [...presets] };
175
134
  }
176
135
 
177
136
  /**
@@ -246,24 +205,15 @@ export const completionShellChoices = [
246
205
  export const makeFeatureAddonChoices = [
247
206
  "policy",
248
207
  "factory",
249
- "factories",
250
208
  "event",
251
- "events",
252
209
  "job",
253
- "jobs",
254
210
  "listener",
255
- "listeners",
256
211
  "notification",
257
- "notifications",
258
212
  "schedule",
259
- "schedules",
260
213
  "seed",
261
- "seeds",
262
214
  "task",
263
- "tasks",
264
215
  "ui",
265
216
  "upload",
266
- "uploads",
267
217
  ] as const;
268
218
 
269
219
  /**
@@ -7,26 +7,25 @@
7
7
  */
8
8
 
9
9
  import {
10
- type CreateIntegrationName,
10
+ type CreateProviderName,
11
11
  type DatabaseName,
12
12
  databaseChoices,
13
- type IntegrationName,
14
- integrationChoices,
15
13
  type PackageManager,
14
+ type StarterProviderName,
15
+ starterProviderChoices,
16
16
  } from "./choices.js";
17
17
 
18
18
  /**
19
19
  * Selections shared by create flags and interactive prompt answers.
20
20
  *
21
- * `integrations` accepts the starter template integrations plus the full
22
- * `beignet providers add` preset catalog; the interactive prompt shows the
23
- * curated template list and points at the wider catalog.
21
+ * `providers` accepts the full `beignet provider add` preset catalog; the
22
+ * interactive prompt shows the providers rendered directly by the starter.
24
23
  */
25
24
  export type CreateSelections = {
26
25
  directory?: string;
27
26
  api?: boolean;
28
27
  db?: DatabaseName;
29
- integrations?: readonly CreateIntegrationName[];
28
+ providers?: readonly CreateProviderName[];
30
29
  packageManager?: PackageManager;
31
30
  };
32
31
 
@@ -43,7 +42,7 @@ type CreatePromptTty = {
43
42
  * Decide whether `beignet create` should run the interactive prompt flow.
44
43
  *
45
44
  * Prompts run only on a full TTY, only when `--yes` is absent, and only when
46
- * no selection flag was passed. Any explicit api, database, integration, or
45
+ * no selection flag was passed. Any explicit api, database, provider, or
47
46
  * package manager selection means the invocation is scripted, so the
48
47
  * non-interactive path stays byte-identical for agents and CI.
49
48
  */
@@ -57,16 +56,16 @@ export function shouldPromptInteractively(
57
56
  const selectionFlagPassed =
58
57
  flags.api !== undefined ||
59
58
  flags.db !== undefined ||
60
- (flags.integrations?.length ?? 0) > 0 ||
59
+ (flags.providers?.length ?? 0) > 0 ||
61
60
  flags.packageManager !== undefined;
62
61
 
63
62
  return !selectionFlagPassed;
64
63
  }
65
64
 
66
- const integrationHints: Record<IntegrationName, string> = {
67
- inngest: "Inngest-backed background jobs",
68
- resend: "Resend-backed mail provider",
69
- "upstash-rate-limit": "Upstash-backed rate limiting",
65
+ const providerHints: Record<StarterProviderName, string> = {
66
+ "jobs-inngest": "Inngest-backed background jobs",
67
+ "mail-resend": "Resend-backed mail provider",
68
+ "rate-limit-upstash": "Upstash-backed rate limiting",
70
69
  };
71
70
 
72
71
  const databaseHints: Record<DatabaseName, string> = {
@@ -118,17 +117,17 @@ export async function promptCreateSelections(
118
117
  });
119
118
  if (prompts.isCancel(db)) return undefined;
120
119
 
121
- const integrations = await prompts.multiselect<IntegrationName>({
120
+ const providers = await prompts.multiselect<StarterProviderName>({
122
121
  message:
123
- "Add provider integrations? (press enter to skip; the full preset catalog is available via --integrations or beignet providers add)",
124
- options: integrationChoices.map((integration) => ({
125
- value: integration,
126
- label: integration,
127
- hint: integrationHints[integration],
122
+ "Add providers? (press enter to skip; the full catalog is available via --providers or beignet provider add)",
123
+ options: starterProviderChoices.map((provider) => ({
124
+ value: provider,
125
+ label: provider,
126
+ hint: providerHints[provider],
128
127
  })),
129
128
  required: false,
130
129
  });
131
- if (prompts.isCancel(integrations)) return undefined;
130
+ if (prompts.isCancel(providers)) return undefined;
132
131
 
133
132
  prompts.outro(`Creating ${directory}`);
134
133
 
@@ -136,7 +135,7 @@ export async function promptCreateSelections(
136
135
  directory,
137
136
  api,
138
137
  db,
139
- integrations,
138
+ providers,
140
139
  packageManager: initial.packageManager,
141
140
  };
142
141
  }
package/src/create.ts CHANGED
@@ -4,9 +4,10 @@ import {
4
4
  type DatabaseName,
5
5
  databaseChoices,
6
6
  getTemplateFiles,
7
- type IntegrationName,
8
- integrationChoices,
9
7
  type PackageManager,
8
+ type StarterProviderName,
9
+ starterProviderChoices,
10
+ type TemplateFile,
10
11
  type TemplateName,
11
12
  } from "./templates/index.js";
12
13
  import { getCliVersion } from "./version.js";
@@ -23,7 +24,7 @@ export type CreateOptions = {
23
24
  */
24
25
  api?: boolean;
25
26
  packageManager?: PackageManager;
26
- integrations?: IntegrationName[];
27
+ providers?: StarterProviderName[];
27
28
  /**
28
29
  * Database backing the starter's Drizzle persistence layer.
29
30
  * Defaults to "sqlite".
@@ -47,19 +48,46 @@ export type CreateResult = {
47
48
  database: DatabaseName;
48
49
  };
49
50
 
51
+ export type CreateProjectPlan = {
52
+ result: CreateResult;
53
+ files: TemplateFile[];
54
+ };
55
+
50
56
  /**
51
57
  * Create a Beignet app from the starter template.
52
58
  */
53
59
  export async function createProject(
54
60
  options: CreateOptions,
55
61
  ): Promise<CreateResult> {
62
+ const plan = await planCreateProject(options);
63
+
64
+ if (!plan.result.dryRun) {
65
+ await mkdir(plan.result.targetDir, { recursive: true });
66
+
67
+ for (const file of plan.files) {
68
+ const destination = path.join(plan.result.targetDir, file.path);
69
+ await mkdir(path.dirname(destination), { recursive: true });
70
+ await writeFile(destination, file.content);
71
+ }
72
+ }
73
+
74
+ return plan.result;
75
+ }
76
+
77
+ /**
78
+ * Render a scaffold without writing it so create-time provider presets can
79
+ * compose against the exact same file contents as a real create.
80
+ */
81
+ export async function planCreateProject(
82
+ options: CreateOptions,
83
+ ): Promise<CreateProjectPlan> {
56
84
  const cwd = options.cwd ?? process.cwd();
57
85
  const name = normalizeProjectName(options.name);
58
86
  const targetDir = path.resolve(cwd, options.name);
59
87
  const packageManager = options.packageManager ?? "bun";
60
88
  const template = options.template ?? "next";
61
89
  const api = Boolean(options.api);
62
- const integrations = resolveIntegrations(options.integrations ?? []);
90
+ const providers = resolveStarterProviders(options.providers ?? []);
63
91
  const database = options.db ?? "sqlite";
64
92
 
65
93
  if (template !== "next") {
@@ -83,58 +111,48 @@ export async function createProject(
83
111
  packageManager,
84
112
  beignetVersion: options.beignetVersion ?? getDefaultBeignetVersion(),
85
113
  api,
86
- integrations,
114
+ providers,
87
115
  database,
88
116
  });
89
117
 
90
- if (!dryRun) {
91
- await mkdir(targetDir, { recursive: true });
92
-
93
- for (const file of files) {
94
- const destination = path.join(targetDir, file.path);
95
- await mkdir(path.dirname(destination), { recursive: true });
96
- await writeFile(destination, file.content);
97
- }
98
- }
99
-
100
118
  return {
101
- schemaVersion: 1,
102
- name,
103
- targetDir,
104
- dryRun,
105
- files: files.map((file) => file.path),
106
- packageManager,
107
- database,
119
+ result: {
120
+ schemaVersion: 1,
121
+ name,
122
+ targetDir,
123
+ dryRun,
124
+ files: files.map((file) => file.path),
125
+ packageManager,
126
+ database,
127
+ },
128
+ files,
108
129
  };
109
130
  }
110
131
 
111
132
  /**
112
- * Providers that ship in every starter. They used to be `--integrations`
113
- * choices, so passing them gets a pointed error instead of a generic one.
133
+ * Providers that ship in every starter are not selectable additions.
114
134
  */
115
135
  const baseProviderNames = new Set(["better-auth", "drizzle-sqlite", "pino"]);
116
136
 
117
- function resolveIntegrations(
118
- explicitIntegrations: IntegrationName[],
119
- ): IntegrationName[] {
120
- for (const integration of explicitIntegrations) {
121
- const integrationName: string = integration;
122
- if (baseProviderNames.has(integrationName)) {
137
+ function resolveStarterProviders(
138
+ explicitProviders: StarterProviderName[],
139
+ ): StarterProviderName[] {
140
+ for (const provider of explicitProviders) {
141
+ const providerName: string = provider;
142
+ if (baseProviderNames.has(providerName)) {
123
143
  const databaseHint =
124
- integrationName === "drizzle-sqlite"
144
+ providerName === "drizzle-sqlite"
125
145
  ? " Choose the starter database with --db sqlite|postgres|mysql instead."
126
146
  : "";
127
147
  throw new Error(
128
- `${integration} is part of every Beignet starter and no longer an --integrations choice.${databaseHint}`,
148
+ `${provider} is part of every Beignet starter and is not a --providers choice.${databaseHint}`,
129
149
  );
130
150
  }
131
151
  }
132
152
 
133
- const integrations = new Set<IntegrationName>(explicitIntegrations);
153
+ const providers = new Set<StarterProviderName>(explicitProviders);
134
154
 
135
- return integrationChoices.filter((integration) =>
136
- integrations.has(integration),
137
- );
155
+ return starterProviderChoices.filter((provider) => providers.has(provider));
138
156
  }
139
157
 
140
158
  function getDefaultBeignetVersion(): string {