@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.
- package/CHANGELOG.md +219 -0
- package/README.md +379 -61
- package/dist/ansi.d.ts +10 -0
- package/dist/ansi.d.ts.map +1 -0
- package/dist/ansi.js +20 -0
- package/dist/ansi.js.map +1 -0
- package/dist/choices.d.ts +49 -0
- package/dist/choices.d.ts.map +1 -0
- package/dist/choices.js +53 -0
- package/dist/choices.js.map +1 -0
- package/dist/completion.d.ts +47 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +123 -0
- package/dist/completion.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/create-prompts.d.ts +41 -0
- package/dist/create-prompts.d.ts.map +1 -0
- package/dist/create-prompts.js +78 -0
- package/dist/create-prompts.js.map +1 -0
- package/dist/create.d.ts +10 -5
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +28 -47
- package/dist/create.js.map +1 -1
- package/dist/db.d.ts +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +37 -2
- package/dist/db.js.map +1 -1
- package/dist/github-annotations.d.ts +18 -0
- package/dist/github-annotations.d.ts.map +1 -0
- package/dist/github-annotations.js +22 -0
- package/dist/github-annotations.js.map +1 -0
- package/dist/index.d.ts +1 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +665 -603
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +21 -2
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +1938 -131
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +20 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +17 -0
- package/dist/lib.js.map +1 -0
- package/dist/lint.d.ts +10 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +340 -33
- package/dist/lint.js.map +1 -1
- package/dist/make.d.ts +20 -3
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +1896 -382
- package/dist/make.js.map +1 -1
- package/dist/outbox.d.ts +24 -0
- package/dist/outbox.d.ts.map +1 -0
- package/dist/outbox.js +138 -0
- package/dist/outbox.js.map +1 -0
- package/dist/schedule.d.ts +36 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +155 -0
- package/dist/schedule.js.map +1 -0
- package/dist/task.d.ts +26 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +106 -0
- package/dist/task.js.map +1 -0
- package/dist/templates/base.d.ts +13 -0
- package/dist/templates/base.d.ts.map +1 -0
- package/dist/templates/base.js +300 -0
- package/dist/templates/base.js.map +1 -0
- package/dist/templates/db.d.ts +14 -0
- package/dist/templates/db.d.ts.map +1 -0
- package/dist/templates/db.js +962 -0
- package/dist/templates/db.js.map +1 -0
- package/dist/templates/index.d.ts +14 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +123 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/server.d.ts +26 -0
- package/dist/templates/server.d.ts.map +1 -0
- package/dist/templates/server.js +520 -0
- package/dist/templates/server.js.map +1 -0
- package/dist/templates/shadcn.d.ts +5 -0
- package/dist/templates/shadcn.d.ts.map +1 -0
- package/dist/templates/shadcn.js +555 -0
- package/dist/templates/shadcn.js.map +1 -0
- package/dist/templates/shared.d.ts +49 -0
- package/dist/templates/shared.d.ts.map +1 -0
- package/dist/templates/shared.js +57 -0
- package/dist/templates/shared.js.map +1 -0
- package/dist/templates/shell.d.ts +5 -0
- package/dist/templates/shell.d.ts.map +1 -0
- package/dist/templates/shell.js +1190 -0
- package/dist/templates/shell.js.map +1 -0
- package/dist/templates/todos.d.ts +17 -0
- package/dist/templates/todos.d.ts.map +1 -0
- package/dist/templates/todos.js +607 -0
- package/dist/templates/todos.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +18 -0
- package/dist/version.js.map +1 -0
- package/package.json +9 -8
- package/src/ansi.ts +30 -0
- package/src/choices.ts +83 -0
- package/src/completion.ts +169 -0
- package/src/config.ts +16 -0
- package/src/create-prompts.ts +114 -0
- package/src/create.ts +40 -64
- package/src/db.ts +60 -4
- package/src/github-annotations.ts +37 -0
- package/src/index.ts +1075 -817
- package/src/inspect.ts +2859 -115
- package/src/lib.ts +45 -0
- package/src/lint.ts +493 -39
- package/src/make.ts +2389 -406
- package/src/outbox.ts +249 -0
- package/src/schedule.ts +272 -0
- package/src/task.ts +169 -0
- package/src/templates/base.ts +377 -0
- package/src/templates/db.ts +963 -0
- package/src/templates/index.ts +148 -0
- package/src/templates/server.ts +528 -0
- package/src/templates/shadcn.ts +570 -0
- package/src/templates/shared.ts +90 -0
- package/src/templates/shell.ts +1219 -0
- package/src/templates/todos.ts +607 -0
- package/src/version.ts +20 -0
- package/dist/create-bin.d.ts +0 -3
- package/dist/create-bin.d.ts.map +0 -1
- package/dist/create-bin.js +0 -9
- package/dist/create-bin.js.map +0 -1
- package/dist/templates.d.ts +0 -55
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js +0 -3520
- package/dist/templates.js.map +0 -1
- package/src/create-bin.ts +0 -11
- 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
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for creating and maintaining Beignet apps.",
|
|
6
|
-
"main": "./dist/
|
|
7
|
-
"types": "./dist/
|
|
6
|
+
"main": "./dist/lib.js",
|
|
7
|
+
"types": "./dist/lib.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./dist/
|
|
11
|
-
"default": "./dist/
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
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
|
|
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
|
|
52
|
-
const
|
|
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
|
-
|
|
69
|
-
features,
|
|
70
|
+
api,
|
|
70
71
|
integrations,
|
|
71
72
|
});
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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 {
|