@beignet/cli 0.0.3 → 0.0.5

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 (138) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/README.md +379 -61
  3. package/dist/ansi.d.ts +10 -0
  4. package/dist/ansi.d.ts.map +1 -0
  5. package/dist/ansi.js +20 -0
  6. package/dist/ansi.js.map +1 -0
  7. package/dist/choices.d.ts +49 -0
  8. package/dist/choices.d.ts.map +1 -0
  9. package/dist/choices.js +53 -0
  10. package/dist/choices.js.map +1 -0
  11. package/dist/completion.d.ts +47 -0
  12. package/dist/completion.d.ts.map +1 -0
  13. package/dist/completion.js +123 -0
  14. package/dist/completion.js.map +1 -0
  15. package/dist/config.d.ts +8 -0
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/config.js +8 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/create-prompts.d.ts +41 -0
  20. package/dist/create-prompts.d.ts.map +1 -0
  21. package/dist/create-prompts.js +78 -0
  22. package/dist/create-prompts.js.map +1 -0
  23. package/dist/create.d.ts +10 -5
  24. package/dist/create.d.ts.map +1 -1
  25. package/dist/create.js +28 -47
  26. package/dist/create.js.map +1 -1
  27. package/dist/db.d.ts +1 -0
  28. package/dist/db.d.ts.map +1 -1
  29. package/dist/db.js +37 -2
  30. package/dist/db.js.map +1 -1
  31. package/dist/github-annotations.d.ts +18 -0
  32. package/dist/github-annotations.d.ts.map +1 -0
  33. package/dist/github-annotations.js +22 -0
  34. package/dist/github-annotations.js.map +1 -0
  35. package/dist/index.d.ts +1 -9
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +665 -603
  38. package/dist/index.js.map +1 -1
  39. package/dist/inspect.d.ts +21 -2
  40. package/dist/inspect.d.ts.map +1 -1
  41. package/dist/inspect.js +1938 -131
  42. package/dist/inspect.js.map +1 -1
  43. package/dist/lib.d.ts +20 -0
  44. package/dist/lib.d.ts.map +1 -0
  45. package/dist/lib.js +17 -0
  46. package/dist/lib.js.map +1 -0
  47. package/dist/lint.d.ts +10 -1
  48. package/dist/lint.d.ts.map +1 -1
  49. package/dist/lint.js +340 -33
  50. package/dist/lint.js.map +1 -1
  51. package/dist/make.d.ts +20 -3
  52. package/dist/make.d.ts.map +1 -1
  53. package/dist/make.js +1896 -382
  54. package/dist/make.js.map +1 -1
  55. package/dist/outbox.d.ts +24 -0
  56. package/dist/outbox.d.ts.map +1 -0
  57. package/dist/outbox.js +138 -0
  58. package/dist/outbox.js.map +1 -0
  59. package/dist/schedule.d.ts +36 -0
  60. package/dist/schedule.d.ts.map +1 -0
  61. package/dist/schedule.js +155 -0
  62. package/dist/schedule.js.map +1 -0
  63. package/dist/task.d.ts +26 -0
  64. package/dist/task.d.ts.map +1 -0
  65. package/dist/task.js +106 -0
  66. package/dist/task.js.map +1 -0
  67. package/dist/templates/base.d.ts +13 -0
  68. package/dist/templates/base.d.ts.map +1 -0
  69. package/dist/templates/base.js +300 -0
  70. package/dist/templates/base.js.map +1 -0
  71. package/dist/templates/db.d.ts +14 -0
  72. package/dist/templates/db.d.ts.map +1 -0
  73. package/dist/templates/db.js +962 -0
  74. package/dist/templates/db.js.map +1 -0
  75. package/dist/templates/index.d.ts +14 -0
  76. package/dist/templates/index.d.ts.map +1 -0
  77. package/dist/templates/index.js +123 -0
  78. package/dist/templates/index.js.map +1 -0
  79. package/dist/templates/server.d.ts +26 -0
  80. package/dist/templates/server.d.ts.map +1 -0
  81. package/dist/templates/server.js +520 -0
  82. package/dist/templates/server.js.map +1 -0
  83. package/dist/templates/shadcn.d.ts +5 -0
  84. package/dist/templates/shadcn.d.ts.map +1 -0
  85. package/dist/templates/shadcn.js +555 -0
  86. package/dist/templates/shadcn.js.map +1 -0
  87. package/dist/templates/shared.d.ts +49 -0
  88. package/dist/templates/shared.d.ts.map +1 -0
  89. package/dist/templates/shared.js +57 -0
  90. package/dist/templates/shared.js.map +1 -0
  91. package/dist/templates/shell.d.ts +5 -0
  92. package/dist/templates/shell.d.ts.map +1 -0
  93. package/dist/templates/shell.js +1190 -0
  94. package/dist/templates/shell.js.map +1 -0
  95. package/dist/templates/todos.d.ts +17 -0
  96. package/dist/templates/todos.d.ts.map +1 -0
  97. package/dist/templates/todos.js +607 -0
  98. package/dist/templates/todos.js.map +1 -0
  99. package/dist/version.d.ts +8 -0
  100. package/dist/version.d.ts.map +1 -0
  101. package/dist/version.js +18 -0
  102. package/dist/version.js.map +1 -0
  103. package/package.json +9 -8
  104. package/src/ansi.ts +30 -0
  105. package/src/choices.ts +83 -0
  106. package/src/completion.ts +169 -0
  107. package/src/config.ts +16 -0
  108. package/src/create-prompts.ts +114 -0
  109. package/src/create.ts +40 -64
  110. package/src/db.ts +60 -4
  111. package/src/github-annotations.ts +37 -0
  112. package/src/index.ts +1075 -817
  113. package/src/inspect.ts +2859 -115
  114. package/src/lib.ts +45 -0
  115. package/src/lint.ts +493 -39
  116. package/src/make.ts +2389 -406
  117. package/src/outbox.ts +249 -0
  118. package/src/schedule.ts +272 -0
  119. package/src/task.ts +169 -0
  120. package/src/templates/base.ts +377 -0
  121. package/src/templates/db.ts +963 -0
  122. package/src/templates/index.ts +148 -0
  123. package/src/templates/server.ts +528 -0
  124. package/src/templates/shadcn.ts +570 -0
  125. package/src/templates/shared.ts +90 -0
  126. package/src/templates/shell.ts +1219 -0
  127. package/src/templates/todos.ts +607 -0
  128. package/src/version.ts +20 -0
  129. package/dist/create-bin.d.ts +0 -3
  130. package/dist/create-bin.d.ts.map +0 -1
  131. package/dist/create-bin.js +0 -9
  132. package/dist/create-bin.js.map +0 -1
  133. package/dist/templates.d.ts +0 -55
  134. package/dist/templates.d.ts.map +0 -1
  135. package/dist/templates.js +0 -3520
  136. package/dist/templates.js.map +0 -1
  137. package/src/create-bin.ts +0 -11
  138. package/src/templates.ts +0 -3774
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@beignet/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "description": "CLI for creating and maintaining Beignet apps.",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
6
+ "main": "./dist/lib.js",
7
+ "types": "./dist/lib.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "default": "./dist/index.js"
10
+ "types": "./dist/lib.d.ts",
11
+ "default": "./dist/lib.js"
12
12
  },
13
13
  "./config": {
14
14
  "types": "./dist/config.d.ts",
@@ -16,8 +16,7 @@
16
16
  }
17
17
  },
18
18
  "bin": {
19
- "beignet": "dist/index.js",
20
- "create-beignet": "dist/create-bin.js"
19
+ "beignet": "dist/index.js"
21
20
  },
22
21
  "files": [
23
22
  "dist",
@@ -29,7 +28,7 @@
29
28
  "CHANGELOG.md"
30
29
  ],
31
30
  "scripts": {
32
- "build": "tsc && chmod +x dist/index.js dist/create-bin.js",
31
+ "build": "tsc && chmod +x dist/index.js",
33
32
  "prepack": "bun run build",
34
33
  "dev": "tsc --watch",
35
34
  "clean": "rm -rf dist coverage .turbo",
@@ -67,6 +66,8 @@
67
66
  "typescript": "^5.3.0"
68
67
  },
69
68
  "dependencies": {
69
+ "@beignet/core": "^0.0.5",
70
+ "@clack/prompts": "^1.5.1",
70
71
  "@stricli/core": "^1.2.7",
71
72
  "jiti": "^2.7.0"
72
73
  }
package/src/ansi.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Minimal ANSI color helpers for human-readable CLI output.
3
+ */
4
+ export type AnsiColor = "red" | "yellow" | "cyan" | "green" | "bold";
5
+
6
+ const ansiEscape = String.fromCharCode(27);
7
+
8
+ const ansiCodes: Record<AnsiColor, string> = {
9
+ red: `${ansiEscape}[31m`,
10
+ yellow: `${ansiEscape}[33m`,
11
+ cyan: `${ansiEscape}[36m`,
12
+ green: `${ansiEscape}[32m`,
13
+ bold: `${ansiEscape}[1m`,
14
+ };
15
+
16
+ const ansiReset = `${ansiEscape}[0m`;
17
+
18
+ /**
19
+ * Create a paint function that wraps text in ANSI codes when enabled and
20
+ * returns the text unchanged otherwise.
21
+ */
22
+ export function createPainter(
23
+ enabled: boolean | undefined,
24
+ ): (text: string, color: AnsiColor) => string {
25
+ if (!enabled) {
26
+ return (text) => text;
27
+ }
28
+
29
+ return (text, color) => `${ansiCodes[color]}${text}${ansiReset}`;
30
+ }
package/src/choices.ts ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Choice lists and string-literal types shared by the CLI entrypoint and the
3
+ * heavier scaffold/generator modules. Keeping them here lets the bin define
4
+ * flag enums without loading templates or generators at startup.
5
+ */
6
+
7
+ /**
8
+ * Package managers supported by the app template generator.
9
+ */
10
+ export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
11
+ /**
12
+ * Application templates supported by the app template generator.
13
+ */
14
+ export type TemplateName = "next";
15
+ /**
16
+ * Optional provider integrations that can be scaffolded into a new app.
17
+ *
18
+ * Better Auth, Drizzle/libSQL, and pino ship in every starter, so only
19
+ * integrations that add an external service remain selectable.
20
+ */
21
+ export type IntegrationName = "inngest" | "resend" | "upstash-rate-limit";
22
+
23
+ /**
24
+ * Supported scaffold template choices.
25
+ */
26
+ export const templateChoices = [
27
+ "next",
28
+ ] as const satisfies readonly TemplateName[];
29
+
30
+ /**
31
+ * Supported scaffold package manager choices.
32
+ */
33
+ export const packageManagerChoices = [
34
+ "bun",
35
+ "npm",
36
+ "pnpm",
37
+ "yarn",
38
+ ] as const satisfies readonly PackageManager[];
39
+
40
+ /**
41
+ * Supported scaffold integration choices.
42
+ */
43
+ export const integrationChoices = [
44
+ "inngest",
45
+ "resend",
46
+ "upstash-rate-limit",
47
+ ] as const satisfies readonly IntegrationName[];
48
+
49
+ /**
50
+ * Shells supported by `beignet completion install` and `uninstall`.
51
+ */
52
+ export type CompletionShell = "bash" | "zsh";
53
+
54
+ /**
55
+ * Supported completion shell choices.
56
+ */
57
+ export const completionShellChoices = [
58
+ "bash",
59
+ "zsh",
60
+ ] as const satisfies readonly CompletionShell[];
61
+
62
+ /**
63
+ * Optional artifacts supported by `beignet make feature --with`.
64
+ */
65
+ export const makeFeatureAddonChoices = [
66
+ "policy",
67
+ "event",
68
+ "events",
69
+ "job",
70
+ "jobs",
71
+ "notification",
72
+ "notifications",
73
+ "task",
74
+ "tasks",
75
+ "ui",
76
+ "upload",
77
+ "uploads",
78
+ ] as const;
79
+
80
+ /**
81
+ * Optional artifact accepted by `beignet make feature --with`.
82
+ */
83
+ export type MakeFeatureAddon = (typeof makeFeatureAddonChoices)[number];
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Shell completion install/uninstall for the `beignet` bin.
3
+ *
4
+ * The shell scripts call the hidden `beignet completion propose` entrypoint,
5
+ * which prints one proposal per line through stricli's completion engine. The
6
+ * route map and flag definitions live eagerly in index.ts, so proposals work
7
+ * without loading any lazy command module.
8
+ */
9
+
10
+ import { readFile, writeFile } from "node:fs/promises";
11
+ import { homedir } from "node:os";
12
+ import path from "node:path";
13
+ import type { CompletionShell } from "./choices.js";
14
+
15
+ const beginMarker = "# >>> beignet completions >>>";
16
+ const endMarker = "# <<< beignet completions <<<";
17
+
18
+ const bashScript = `_beignet_completions() {
19
+ local -a completions
20
+ while IFS= read -r line; do
21
+ [ -n "$line" ] && completions+=("$line")
22
+ done < <(beignet completion propose "\${COMP_WORDS[@]:1:COMP_CWORD}" 2>/dev/null)
23
+ COMPREPLY=("\${completions[@]}")
24
+ }
25
+ complete -F _beignet_completions beignet`;
26
+
27
+ const zshScript = `_beignet_completions() {
28
+ local -a completions
29
+ completions=("\${(@f)$(beignet completion propose "\${(@)words[2,CURRENT]}" 2>/dev/null)}")
30
+ compadd -- "\${completions[@]}"
31
+ }
32
+ compdef _beignet_completions beignet`;
33
+
34
+ /**
35
+ * Result of installing or uninstalling shell completions.
36
+ */
37
+ export type CompletionResult = {
38
+ schemaVersion: 1;
39
+ shell: CompletionShell;
40
+ file: string;
41
+ status: "installed" | "updated" | "removed" | "not-installed";
42
+ };
43
+
44
+ type CompletionOptions = {
45
+ shell?: CompletionShell;
46
+ /**
47
+ * Home directory override for tests.
48
+ */
49
+ home?: string;
50
+ };
51
+
52
+ /**
53
+ * Detect the completion shell from a `$SHELL`-style path.
54
+ */
55
+ export function detectCompletionShell(
56
+ shellPath = process.env.SHELL,
57
+ ): CompletionShell | undefined {
58
+ const shellName = shellPath ? path.basename(shellPath) : "";
59
+ if (shellName === "bash") return "bash";
60
+ if (shellName === "zsh") return "zsh";
61
+ return undefined;
62
+ }
63
+
64
+ /**
65
+ * The completion script block written to the shell rc file.
66
+ */
67
+ export function completionScript(shell: CompletionShell): string {
68
+ return shell === "bash" ? bashScript : zshScript;
69
+ }
70
+
71
+ /**
72
+ * The rc file that owns completions for the given shell.
73
+ */
74
+ export function completionRcFile(
75
+ shell: CompletionShell,
76
+ home = homedir(),
77
+ ): string {
78
+ return path.join(home, shell === "bash" ? ".bashrc" : ".zshrc");
79
+ }
80
+
81
+ /**
82
+ * Install (or refresh) `beignet` completions in the shell rc file.
83
+ */
84
+ export async function installCompletions(
85
+ options: CompletionOptions = {},
86
+ ): Promise<CompletionResult> {
87
+ const shell = resolveShell(options.shell);
88
+ const file = completionRcFile(shell, options.home);
89
+ const source = await readRcFile(file);
90
+ const stripped = removeCompletionBlock(source);
91
+ const block = `${beginMarker}\n${completionScript(shell)}\n${endMarker}`;
92
+ const next =
93
+ stripped === "" ? `${block}\n` : `${trimEnd(stripped)}\n\n${block}\n`;
94
+
95
+ await writeFile(file, next);
96
+
97
+ return {
98
+ schemaVersion: 1,
99
+ shell,
100
+ file,
101
+ status: stripped === source ? "installed" : "updated",
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Remove `beignet` completions from the shell rc file.
107
+ */
108
+ export async function uninstallCompletions(
109
+ options: CompletionOptions = {},
110
+ ): Promise<CompletionResult> {
111
+ const shell = resolveShell(options.shell);
112
+ const file = completionRcFile(shell, options.home);
113
+ const source = await readRcFile(file);
114
+ const stripped = removeCompletionBlock(source);
115
+
116
+ if (stripped === source) {
117
+ return { schemaVersion: 1, shell, file, status: "not-installed" };
118
+ }
119
+
120
+ await writeFile(file, stripped === "" ? "" : `${trimEnd(stripped)}\n`);
121
+ return { schemaVersion: 1, shell, file, status: "removed" };
122
+ }
123
+
124
+ function resolveShell(shell: CompletionShell | undefined): CompletionShell {
125
+ if (shell) return shell;
126
+
127
+ const detected = detectCompletionShell();
128
+ if (!detected) {
129
+ throw new Error(
130
+ `Could not detect a supported shell from $SHELL (${process.env.SHELL ?? "unset"}). Pass --shell bash or --shell zsh.`,
131
+ );
132
+ }
133
+
134
+ return detected;
135
+ }
136
+
137
+ async function readRcFile(file: string): Promise<string> {
138
+ try {
139
+ return await readFile(file, "utf8");
140
+ } catch (error) {
141
+ if (
142
+ error instanceof Error &&
143
+ "code" in error &&
144
+ (error as { code?: string }).code === "ENOENT"
145
+ ) {
146
+ return "";
147
+ }
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ function removeCompletionBlock(source: string): string {
153
+ const beginIndex = source.indexOf(beginMarker);
154
+ if (beginIndex === -1) return source;
155
+
156
+ const endIndex = source.indexOf(endMarker, beginIndex);
157
+ if (endIndex === -1) return source;
158
+
159
+ const before = trimEnd(source.slice(0, beginIndex));
160
+ const after = source.slice(endIndex + endMarker.length).replace(/^\n+/, "");
161
+
162
+ if (before === "") return after;
163
+ if (after === "") return `${before}\n`;
164
+ return `${before}\n\n${after}`;
165
+ }
166
+
167
+ function trimEnd(source: string): string {
168
+ return source.replace(/\s+$/, "");
169
+ }
package/src/config.ts CHANGED
@@ -17,6 +17,9 @@ export type BeignetPaths = {
17
17
  routes: string;
18
18
  server: string;
19
19
  openapiRoute: string;
20
+ tasks: string;
21
+ outbox: string;
22
+ schedules: string;
20
23
  appContext: string;
21
24
  ports: string;
22
25
  infrastructurePorts: string;
@@ -24,6 +27,11 @@ export type BeignetPaths = {
24
27
  policies: string;
25
28
  useCases: string;
26
29
  useCaseBuilder: string;
30
+ listenersBuilder: string;
31
+ jobsBuilder: string;
32
+ schedulesBuilder: string;
33
+ notificationsBuilder: string;
34
+ tasksBuilder: string;
27
35
  tests: string;
28
36
  };
29
37
 
@@ -54,6 +62,9 @@ export const defaultBeignetConfig: ResolvedBeignetConfig = {
54
62
  routes: "app/api",
55
63
  server: "server/index.ts",
56
64
  openapiRoute: "app/api/openapi/route.ts",
65
+ tasks: "server/tasks.ts",
66
+ outbox: "server/outbox.ts",
67
+ schedules: "server/schedules.ts",
57
68
  appContext: "app-context.ts",
58
69
  ports: "ports/index.ts",
59
70
  infrastructurePorts: "infra/app-ports.ts",
@@ -61,6 +72,11 @@ export const defaultBeignetConfig: ResolvedBeignetConfig = {
61
72
  policies: "features",
62
73
  useCases: "features",
63
74
  useCaseBuilder: "lib/use-case.ts",
75
+ listenersBuilder: "lib/listeners.ts",
76
+ jobsBuilder: "lib/jobs.ts",
77
+ schedulesBuilder: "lib/schedules.ts",
78
+ notificationsBuilder: "lib/notifications.ts",
79
+ tasksBuilder: "lib/tasks.ts",
64
80
  tests: "tests",
65
81
  },
66
82
  };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Interactive selection flow for `beignet create`.
3
+ *
4
+ * The decision helper is pure so the TTY gating stays unit-testable, and
5
+ * `@clack/prompts` is loaded lazily inside the prompt flow so non-interactive
6
+ * runs and CLI startup never pay for it.
7
+ */
8
+
9
+ import {
10
+ type IntegrationName,
11
+ integrationChoices,
12
+ type PackageManager,
13
+ } from "./choices.js";
14
+
15
+ /**
16
+ * Selections shared by create flags and interactive prompt answers.
17
+ */
18
+ export type CreateSelections = {
19
+ directory?: string;
20
+ api?: boolean;
21
+ integrations?: readonly IntegrationName[];
22
+ packageManager?: PackageManager;
23
+ };
24
+
25
+ type CreatePromptFlags = CreateSelections & {
26
+ yes?: boolean;
27
+ };
28
+
29
+ type CreatePromptTty = {
30
+ stdin: boolean;
31
+ stdout: boolean;
32
+ };
33
+
34
+ /**
35
+ * Decide whether `beignet create` should run the interactive prompt flow.
36
+ *
37
+ * Prompts run only on a full TTY, only when `--yes` is absent, and only when
38
+ * no selection flag was passed. Any explicit api, integration, or package
39
+ * manager selection means the invocation is scripted, so the non-interactive
40
+ * path stays byte-identical for agents and CI.
41
+ */
42
+ export function shouldPromptInteractively(
43
+ flags: CreatePromptFlags,
44
+ tty: CreatePromptTty,
45
+ ): boolean {
46
+ if (!tty.stdin || !tty.stdout) return false;
47
+ if (flags.yes) return false;
48
+
49
+ const selectionFlagPassed =
50
+ flags.api !== undefined ||
51
+ (flags.integrations?.length ?? 0) > 0 ||
52
+ flags.packageManager !== undefined;
53
+
54
+ return !selectionFlagPassed;
55
+ }
56
+
57
+ const integrationHints: Record<IntegrationName, string> = {
58
+ inngest: "Inngest-backed background jobs",
59
+ resend: "Resend-backed mail provider",
60
+ "upstash-rate-limit": "Upstash-backed rate limiting",
61
+ };
62
+
63
+ /**
64
+ * Run the interactive `beignet create` prompt flow.
65
+ *
66
+ * Returns the completed selections, or undefined when the user cancels.
67
+ */
68
+ export async function promptCreateSelections(
69
+ initial: CreateSelections,
70
+ ): Promise<CreateSelections | undefined> {
71
+ const prompts = await import("@clack/prompts");
72
+
73
+ prompts.intro("beignet create");
74
+
75
+ let directory = initial.directory;
76
+ if (directory === undefined) {
77
+ const answer = await prompts.text({
78
+ message: "Where should the app be created?",
79
+ placeholder: "my-app",
80
+ validate: (value) =>
81
+ value === undefined || value.trim() === ""
82
+ ? "Enter a project directory."
83
+ : undefined,
84
+ });
85
+ if (prompts.isCancel(answer)) return undefined;
86
+ directory = answer;
87
+ }
88
+
89
+ const api = await prompts.confirm({
90
+ message: "API-only app (no UI shell)?",
91
+ initialValue: false,
92
+ });
93
+ if (prompts.isCancel(api)) return undefined;
94
+
95
+ const integrations = await prompts.multiselect<IntegrationName>({
96
+ message: "Add provider integrations? (press enter to skip)",
97
+ options: integrationChoices.map((integration) => ({
98
+ value: integration,
99
+ label: integration,
100
+ hint: integrationHints[integration],
101
+ })),
102
+ required: false,
103
+ });
104
+ if (prompts.isCancel(integrations)) return undefined;
105
+
106
+ prompts.outro(`Creating ${directory}`);
107
+
108
+ return {
109
+ directory,
110
+ api,
111
+ integrations,
112
+ packageManager: initial.packageManager,
113
+ };
114
+ }
package/src/create.ts CHANGED
@@ -1,16 +1,13 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { mkdir, readdir, writeFile } from "node:fs/promises";
3
2
  import path from "node:path";
4
3
  import {
5
- type FeatureName,
6
- featureChoices,
7
4
  getTemplateFiles,
8
5
  type IntegrationName,
9
6
  integrationChoices,
10
7
  type PackageManager,
11
- type PresetName,
12
8
  type TemplateName,
13
- } from "./templates.js";
9
+ } from "./templates/index.js";
10
+ import { getCliVersion } from "./version.js";
14
11
 
15
12
  /**
16
13
  * Options for creating a new Beignet app.
@@ -19,26 +16,31 @@ export type CreateOptions = {
19
16
  name: string;
20
17
  cwd?: string;
21
18
  template?: TemplateName;
22
- preset?: PresetName;
23
- features?: FeatureName[];
19
+ /**
20
+ * Scaffold an API-only app without the UI shell.
21
+ */
22
+ api?: boolean;
24
23
  packageManager?: PackageManager;
25
24
  integrations?: IntegrationName[];
26
25
  force?: boolean;
26
+ dryRun?: boolean;
27
27
  beignetVersion?: string;
28
28
  };
29
29
 
30
30
  /**
31
- * Result returned after a new app is scaffolded.
31
+ * Result returned after a new app is scaffolded or previewed.
32
32
  */
33
33
  export type CreateResult = {
34
+ schemaVersion: 1;
34
35
  name: string;
35
36
  targetDir: string;
37
+ dryRun: boolean;
36
38
  files: string[];
37
39
  packageManager: PackageManager;
38
40
  };
39
41
 
40
42
  /**
41
- * Create a Beignet app from a starter template.
43
+ * Create a Beignet app from the starter template.
42
44
  */
43
45
  export async function createProject(
44
46
  options: CreateOptions,
@@ -48,9 +50,8 @@ export async function createProject(
48
50
  const targetDir = path.resolve(cwd, options.name);
49
51
  const packageManager = options.packageManager ?? "bun";
50
52
  const template = options.template ?? "next";
51
- const preset = options.preset ?? "standard";
52
- const features = resolveFeatures(preset, options.features ?? []);
53
- const integrations = resolveIntegrations(preset, options.integrations ?? []);
53
+ const api = Boolean(options.api);
54
+ const integrations = resolveIntegrations(options.integrations ?? []);
54
55
 
55
56
  if (template !== "next") {
56
57
  throw new Error(
@@ -58,90 +59,65 @@ export async function createProject(
58
59
  );
59
60
  }
60
61
 
62
+ const dryRun = Boolean(options.dryRun);
63
+
61
64
  await assertCanWrite(targetDir, Boolean(options.force));
62
- await mkdir(targetDir, { recursive: true });
63
65
 
64
66
  const files = getTemplateFiles({
65
67
  name,
66
68
  packageManager,
67
69
  beignetVersion: options.beignetVersion ?? getDefaultBeignetVersion(),
68
- preset,
69
- features,
70
+ api,
70
71
  integrations,
71
72
  });
72
73
 
73
- for (const file of files) {
74
- const destination = path.join(targetDir, file.path);
75
- await mkdir(path.dirname(destination), { recursive: true });
76
- await writeFile(destination, file.content);
74
+ if (!dryRun) {
75
+ await mkdir(targetDir, { recursive: true });
76
+
77
+ for (const file of files) {
78
+ const destination = path.join(targetDir, file.path);
79
+ await mkdir(path.dirname(destination), { recursive: true });
80
+ await writeFile(destination, file.content);
81
+ }
77
82
  }
78
83
 
79
84
  return {
85
+ schemaVersion: 1,
80
86
  name,
81
87
  targetDir,
88
+ dryRun,
82
89
  files: files.map((file) => file.path),
83
90
  packageManager,
84
91
  };
85
92
  }
86
93
 
87
- function resolveFeatures(
88
- preset: PresetName,
89
- explicitFeatures: FeatureName[],
90
- ): FeatureName[] {
91
- const features = new Set<FeatureName>();
92
-
93
- if (preset === "standard") {
94
- for (const feature of featureChoices) {
95
- features.add(feature);
96
- }
97
- }
98
-
99
- for (const feature of explicitFeatures) {
100
- features.add(feature);
101
- }
102
-
103
- if (features.has("forms")) {
104
- features.add("react-query");
105
- }
106
- if (features.has("react-query")) {
107
- features.add("client");
108
- }
109
-
110
- return featureChoices.filter((feature) => features.has(feature));
111
- }
94
+ /**
95
+ * Providers that ship in every starter. They used to be `--integrations`
96
+ * choices, so passing them gets a pointed error instead of a generic one.
97
+ */
98
+ const baseProviderNames = new Set(["better-auth", "drizzle-turso", "pino"]);
112
99
 
113
100
  function resolveIntegrations(
114
- preset: PresetName,
115
101
  explicitIntegrations: IntegrationName[],
116
102
  ): IntegrationName[] {
117
- const integrations = new Set<IntegrationName>();
118
-
119
- if (preset === "standard") {
120
- integrations.add("better-auth");
121
- integrations.add("drizzle-turso");
122
- integrations.add("pino");
123
- }
124
-
125
103
  for (const integration of explicitIntegrations) {
126
- integrations.add(integration);
104
+ if (baseProviderNames.has(integration)) {
105
+ throw new Error(
106
+ `${integration} is part of every Beignet starter and no longer an --integrations choice.`,
107
+ );
108
+ }
127
109
  }
128
110
 
111
+ const integrations = new Set<IntegrationName>(explicitIntegrations);
112
+
129
113
  return integrationChoices.filter((integration) =>
130
114
  integrations.has(integration),
131
115
  );
132
116
  }
133
117
 
134
118
  function getDefaultBeignetVersion(): string {
135
- try {
136
- const packageJsonUrl = new URL("../package.json", import.meta.url);
137
- const packageJson = JSON.parse(readFileSync(packageJsonUrl, "utf8")) as {
138
- version?: string;
139
- };
140
-
141
- return packageJson.version ? `^${packageJson.version}` : "latest";
142
- } catch {
143
- return "latest";
144
- }
119
+ const version = getCliVersion();
120
+ return version === "0.0.0" ? "latest" : `^${version}`;
145
121
  }
146
122
 
147
123
  function normalizeProjectName(name: string): string {