@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/src/check.ts ADDED
@@ -0,0 +1,246 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createPainter } from "./ansi.js";
4
+ import { detectPackageManager, spawnCommand } from "./db.js";
5
+ import {
6
+ applyDoctorFixes,
7
+ formatDoctor,
8
+ type InspectFix,
9
+ inspectApp,
10
+ } from "./inspect.js";
11
+ import { formatLint, lintApp } from "./lint.js";
12
+
13
+ /**
14
+ * One step of `beignet check`.
15
+ *
16
+ * `output` carries the failing step's formatted diagnostics or captured
17
+ * script output so the summary is actionable without a re-run.
18
+ */
19
+ export type CheckStep = {
20
+ name: string;
21
+ command: string;
22
+ status: "passed" | "failed" | "skipped";
23
+ detail?: string;
24
+ output?: string;
25
+ durationMs: number;
26
+ };
27
+
28
+ export type CheckAppResult = {
29
+ schemaVersion: 1;
30
+ targetDir: string;
31
+ runner: string;
32
+ ok: boolean;
33
+ fixes: InspectFix[];
34
+ steps: CheckStep[];
35
+ };
36
+
37
+ export type CheckAppOptions = {
38
+ cwd?: string;
39
+ fix?: boolean;
40
+ color?: boolean;
41
+ /** Called after each step completes, for incremental progress output. */
42
+ onStep?: (step: CheckStep) => void;
43
+ };
44
+
45
+ const scriptStepNames = ["lint", "typecheck", "test"] as const;
46
+
47
+ /**
48
+ * Run the whole app validation loop as one command: `beignet lint`,
49
+ * `beignet doctor --strict` (optionally with fixes), and the app's own
50
+ * `lint`, `typecheck`, and `test` package scripts through the detected
51
+ * package manager.
52
+ *
53
+ * Every step runs even when an earlier one fails, so a single run reports
54
+ * everything that needs fixing. Missing package scripts are reported as
55
+ * skipped, never as failures.
56
+ */
57
+ export async function checkApp(
58
+ options: CheckAppOptions = {},
59
+ ): Promise<CheckAppResult> {
60
+ const targetDir = path.resolve(options.cwd ?? process.cwd());
61
+ const runner = await detectPackageManager(targetDir);
62
+ const steps: CheckStep[] = [];
63
+ const record = (step: CheckStep): void => {
64
+ steps.push(step);
65
+ options.onStep?.(step);
66
+ };
67
+
68
+ record(await lintStep(targetDir, options));
69
+ const { step: doctorStep, fixes } = await runDoctorStep(targetDir, options);
70
+ record(doctorStep);
71
+
72
+ const scripts = await readPackageScripts(targetDir);
73
+ for (const script of scriptStepNames) {
74
+ record(await scriptStep(targetDir, runner, script, scripts));
75
+ }
76
+
77
+ return {
78
+ schemaVersion: 1,
79
+ targetDir,
80
+ runner,
81
+ ok: steps.every((step) => step.status !== "failed"),
82
+ fixes,
83
+ steps,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Format a completed step as a single status line, with the failing step's
89
+ * output indented underneath.
90
+ */
91
+ export function formatCheckStep(
92
+ step: CheckStep,
93
+ options: { color?: boolean } = {},
94
+ ): string {
95
+ const paint = createPainter(options.color);
96
+ const mark =
97
+ step.status === "passed"
98
+ ? paint("✓", "green")
99
+ : step.status === "failed"
100
+ ? paint("✗", "red")
101
+ : paint("-", "cyan");
102
+ const detail = step.detail ? ` (${step.detail})` : "";
103
+ const line = `${mark} ${step.name}${detail}`;
104
+ if (step.status !== "failed" || !step.output?.trim()) return line;
105
+
106
+ const indented = step.output
107
+ .trimEnd()
108
+ .split("\n")
109
+ .map((outputLine) => ` ${outputLine}`)
110
+ .join("\n");
111
+ return `${line}\n${indented}`;
112
+ }
113
+
114
+ /**
115
+ * Format the closing summary line, e.g. `3 passed, 1 failed, 1 skipped`.
116
+ */
117
+ export function formatCheckSummary(
118
+ result: CheckAppResult,
119
+ options: { color?: boolean } = {},
120
+ ): string {
121
+ const paint = createPainter(options.color);
122
+ const counts = {
123
+ passed: result.steps.filter((step) => step.status === "passed").length,
124
+ failed: result.steps.filter((step) => step.status === "failed").length,
125
+ skipped: result.steps.filter((step) => step.status === "skipped").length,
126
+ };
127
+ const parts = [`${counts.passed} passed`];
128
+ if (counts.failed > 0) parts.push(paint(`${counts.failed} failed`, "red"));
129
+ if (counts.skipped > 0) parts.push(`${counts.skipped} skipped`);
130
+ return parts.join(", ");
131
+ }
132
+
133
+ /**
134
+ * Format a full result at once (JSON-free callers and tests).
135
+ */
136
+ export function formatCheck(
137
+ result: CheckAppResult,
138
+ options: { color?: boolean } = {},
139
+ ): string {
140
+ const lines = result.steps.map((step) => formatCheckStep(step, options));
141
+ return `${lines.join("\n")}\n\n${formatCheckSummary(result, options)}`;
142
+ }
143
+
144
+ async function lintStep(
145
+ targetDir: string,
146
+ options: CheckAppOptions,
147
+ ): Promise<CheckStep> {
148
+ const startedAt = Date.now();
149
+ const result = await lintApp({ cwd: targetDir });
150
+ const failed = result.diagnostics.length > 0;
151
+
152
+ return {
153
+ name: "beignet lint",
154
+ command: "beignet lint",
155
+ status: failed ? "failed" : "passed",
156
+ ...(failed ? { output: formatLint(result, { color: options.color }) } : {}),
157
+ durationMs: Date.now() - startedAt,
158
+ };
159
+ }
160
+
161
+ async function runDoctorStep(
162
+ targetDir: string,
163
+ options: CheckAppOptions,
164
+ ): Promise<{ step: CheckStep; fixes: InspectFix[] }> {
165
+ const startedAt = Date.now();
166
+ const fixes = options.fix
167
+ ? await applyDoctorFixes({ cwd: targetDir, strict: true })
168
+ : [];
169
+ const result = await inspectApp({ cwd: targetDir, strict: true });
170
+ result.fixes = fixes;
171
+ const failed = result.diagnostics.some(
172
+ (diagnostic) =>
173
+ diagnostic.severity === "error" || diagnostic.severity === "warning",
174
+ );
175
+
176
+ return {
177
+ step: {
178
+ name: "beignet doctor --strict",
179
+ command: "beignet doctor --strict",
180
+ status: failed ? "failed" : "passed",
181
+ ...(fixes.length > 0
182
+ ? {
183
+ detail: `${fixes.length} ${fixes.length === 1 ? "fix" : "fixes"} applied`,
184
+ }
185
+ : {}),
186
+ ...(failed
187
+ ? { output: formatDoctor(result, { color: options.color }) }
188
+ : {}),
189
+ durationMs: Date.now() - startedAt,
190
+ },
191
+ fixes,
192
+ };
193
+ }
194
+
195
+ async function scriptStep(
196
+ targetDir: string,
197
+ runner: string,
198
+ script: (typeof scriptStepNames)[number],
199
+ scripts: Record<string, string>,
200
+ ): Promise<CheckStep> {
201
+ const command = `${runner} run ${script}`;
202
+ if (!scripts[script]) {
203
+ return {
204
+ name: script,
205
+ command,
206
+ status: "skipped",
207
+ detail: `no "${script}" script in package.json`,
208
+ durationMs: 0,
209
+ };
210
+ }
211
+
212
+ const startedAt = Date.now();
213
+ const result = await spawnCommand(runner, ["run", script], targetDir, {
214
+ captureOutput: true,
215
+ });
216
+ const failed = result.exitCode !== 0;
217
+
218
+ return {
219
+ name: script,
220
+ command,
221
+ status: failed ? "failed" : "passed",
222
+ detail: command,
223
+ ...(failed
224
+ ? { output: [result.stdout, result.stderr].filter(Boolean).join("\n") }
225
+ : {}),
226
+ durationMs: Date.now() - startedAt,
227
+ };
228
+ }
229
+
230
+ async function readPackageScripts(
231
+ targetDir: string,
232
+ ): Promise<Record<string, string>> {
233
+ let source: string;
234
+ try {
235
+ source = await readFile(path.join(targetDir, "package.json"), "utf8");
236
+ } catch {
237
+ throw new Error(
238
+ "Could not find package.json. Run beignet check from the app root.",
239
+ );
240
+ }
241
+
242
+ const parsed = JSON.parse(source) as {
243
+ scripts?: Record<string, string>;
244
+ };
245
+ return parsed.scripts ?? {};
246
+ }
package/src/choices.ts CHANGED
@@ -24,6 +24,19 @@ export type IntegrationName = "inngest" | "resend" | "upstash-rate-limit";
24
24
  * Databases supported by the starter's Drizzle persistence layer.
25
25
  */
26
26
  export type DatabaseName = "sqlite" | "postgres" | "mysql";
27
+ /**
28
+ * Provider setup presets supported by `beignet providers add`.
29
+ */
30
+ export type ProviderPresetName =
31
+ | "flags-openfeature"
32
+ | "mail-resend"
33
+ | "mail-smtp"
34
+ | "search-meilisearch"
35
+ | "sentry"
36
+ | "upstash-rate-limit"
37
+ | "redis-cache"
38
+ | "redis-locks"
39
+ | "s3-storage";
27
40
 
28
41
  /**
29
42
  * Supported scaffold template choices.
@@ -60,6 +73,21 @@ export const databaseChoices = [
60
73
  "mysql",
61
74
  ] as const satisfies readonly DatabaseName[];
62
75
 
76
+ /**
77
+ * Supported provider setup preset choices.
78
+ */
79
+ export const providerPresetChoices = [
80
+ "flags-openfeature",
81
+ "mail-resend",
82
+ "mail-smtp",
83
+ "search-meilisearch",
84
+ "sentry",
85
+ "upstash-rate-limit",
86
+ "redis-cache",
87
+ "redis-locks",
88
+ "s3-storage",
89
+ ] as const satisfies readonly ProviderPresetName[];
90
+
63
91
  /**
64
92
  * Local database name derived from the app name.
65
93
  *
package/src/db.ts CHANGED
@@ -212,7 +212,7 @@ async function assertDatabaseCommandPreflight(
212
212
  }
213
213
 
214
214
  const standardEntrypoints: Partial<Record<DatabaseCommand, string>> = {
215
- seed: "infra/db/seed.ts",
215
+ seed: "server/seed.ts",
216
216
  reset: "infra/db/reset.ts",
217
217
  };
218
218
  const standardEntrypoint = standardEntrypoints[command];
@@ -463,7 +463,7 @@ function missingDatabaseScriptMessage(
463
463
  }
464
464
 
465
465
  const entrypoint =
466
- command === "seed" ? "infra/db/seed.ts" : "infra/db/reset.ts";
466
+ command === "seed" ? "server/seed.ts" : "infra/db/reset.ts";
467
467
  return `Missing package.json script "${script}". Standard Drizzle apps use "${script}": "bun ${entrypoint}" so beignet db ${command} can run the app-owned ${command} entrypoint.`;
468
468
  }
469
469
 
@@ -482,7 +482,7 @@ async function readPackageJson(cwd: string): Promise<PackageJson> {
482
482
  }
483
483
  }
484
484
 
485
- async function detectPackageManager(cwd: string): Promise<string> {
485
+ export async function detectPackageManager(cwd: string): Promise<string> {
486
486
  if (await exists(path.join(cwd, "bun.lock"))) return "bun";
487
487
  if (await exists(path.join(cwd, "bun.lockb"))) return "bun";
488
488
  if (await exists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
@@ -500,7 +500,7 @@ async function exists(filePath: string): Promise<boolean> {
500
500
  }
501
501
  }
502
502
 
503
- function spawnCommand(
503
+ export function spawnCommand(
504
504
  command: string,
505
505
  args: readonly string[],
506
506
  cwd: string,