@beignet/cli 0.0.1 → 0.0.4
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 +231 -0
- package/README.md +497 -90
- 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 +72 -0
- package/dist/choices.d.ts.map +1 -0
- package/dist/choices.js +88 -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 +39 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -1
- package/dist/create-prompts.d.ts +42 -0
- package/dist/create-prompts.d.ts.map +1 -0
- package/dist/create-prompts.js +136 -0
- package/dist/create-prompts.js.map +1 -0
- package/dist/create.d.ts +12 -0
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +19 -24
- package/dist/create.js.map +1 -1
- package/dist/db.d.ts +37 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +146 -0
- package/dist/db.js.map +1 -0
- 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 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +710 -400
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +45 -2
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +2191 -100
- 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 +22 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +370 -38
- package/dist/lint.js.map +1 -1
- package/dist/make.d.ts +109 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +2225 -333
- 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.d.ts +12 -8
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +2144 -385
- package/dist/templates.js.map +1 -1
- 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 +137 -0
- package/src/completion.ts +169 -0
- package/src/config.ts +47 -0
- package/src/create-prompts.ts +182 -0
- package/src/create.ts +32 -28
- package/src/db.ts +222 -0
- package/src/github-annotations.ts +37 -0
- package/src/index.ts +1119 -535
- package/src/inspect.ts +3372 -134
- package/src/lib.ts +45 -0
- package/src/lint.ts +533 -45
- package/src/make.ts +3010 -397
- package/src/outbox.ts +249 -0
- package/src/schedule.ts +272 -0
- package/src/task.ts +169 -0
- package/src/templates.ts +2282 -462
- 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/src/create-bin.ts +0 -11
package/dist/index.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { buildApplication, buildCommand, buildRouteMap, run, text_en, } from "@stricli/core";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const templateChoices = ["next"];
|
|
12
|
-
const packageManagerChoices = [
|
|
13
|
-
"bun",
|
|
14
|
-
"npm",
|
|
15
|
-
"pnpm",
|
|
16
|
-
"yarn",
|
|
4
|
+
import { buildApplication, buildCommand, buildRouteMap, proposeCompletions, run, text_en, } from "@stricli/core";
|
|
5
|
+
import { completionShellChoices, featureChoices, integrationChoices, makeFeatureAddonChoices, packageManagerChoices, presetChoices, templateChoices, } from "./choices.js";
|
|
6
|
+
import { getCliVersion } from "./version.js";
|
|
7
|
+
const outputFormatChoices = [
|
|
8
|
+
"human",
|
|
9
|
+
"json",
|
|
10
|
+
"github",
|
|
17
11
|
];
|
|
18
12
|
const parseString = (input) => input;
|
|
13
|
+
const parsePositiveInteger = (input) => {
|
|
14
|
+
const value = Number(input);
|
|
15
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
16
|
+
throw new Error("Expected a positive integer.");
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
19
20
|
const forceFlag = {
|
|
20
21
|
kind: "boolean",
|
|
21
22
|
optional: true,
|
|
@@ -40,13 +41,60 @@ const parsedStringFlag = (brief) => ({
|
|
|
40
41
|
optional: true,
|
|
41
42
|
brief,
|
|
42
43
|
});
|
|
44
|
+
const cwdFlag = parsedStringFlag("App directory to inspect. Defaults to the current working directory.");
|
|
45
|
+
const formatFlag = {
|
|
46
|
+
kind: "enum",
|
|
47
|
+
values: outputFormatChoices,
|
|
48
|
+
optional: true,
|
|
49
|
+
brief: "Output format. Defaults to human, or github when GITHUB_ACTIONS=true.",
|
|
50
|
+
};
|
|
43
51
|
const makeFlagParameters = {
|
|
44
52
|
force: forceFlag,
|
|
45
53
|
dryRun: dryRunFlag,
|
|
46
54
|
json: jsonFlag,
|
|
47
55
|
};
|
|
48
|
-
const
|
|
56
|
+
const routesFlagParameters = {
|
|
57
|
+
json: jsonFlag,
|
|
58
|
+
cwd: cwdFlag,
|
|
59
|
+
};
|
|
60
|
+
const lintFlagParameters = {
|
|
61
|
+
json: jsonFlag,
|
|
62
|
+
cwd: cwdFlag,
|
|
63
|
+
format: formatFlag,
|
|
64
|
+
};
|
|
65
|
+
const dbFlagParameters = {
|
|
66
|
+
json: jsonFlag,
|
|
67
|
+
dryRun: dryRunFlag,
|
|
68
|
+
};
|
|
69
|
+
const taskRunFlagParameters = {
|
|
70
|
+
json: jsonFlag,
|
|
71
|
+
input: parsedStringFlag("JSON input passed to the task schema."),
|
|
72
|
+
module: parsedStringFlag("Task registry module. Defaults to server/tasks.ts."),
|
|
73
|
+
};
|
|
74
|
+
const outboxDrainFlagParameters = {
|
|
75
|
+
json: jsonFlag,
|
|
76
|
+
module: parsedStringFlag("Outbox registry module. Defaults to server/outbox.ts."),
|
|
77
|
+
batchSize: {
|
|
78
|
+
kind: "parsed",
|
|
79
|
+
parse: parsePositiveInteger,
|
|
80
|
+
optional: true,
|
|
81
|
+
brief: "Maximum messages to claim in one drain pass.",
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const scheduleRunFlagParameters = {
|
|
49
85
|
json: jsonFlag,
|
|
86
|
+
module: parsedStringFlag("Schedule registry module. Defaults to server/schedules.ts."),
|
|
87
|
+
payload: parsedStringFlag("JSON payload passed to the schedule schema."),
|
|
88
|
+
id: parsedStringFlag("Provider or app schedule run ID."),
|
|
89
|
+
attempt: {
|
|
90
|
+
kind: "parsed",
|
|
91
|
+
parse: parsePositiveInteger,
|
|
92
|
+
optional: true,
|
|
93
|
+
brief: "One-based provider attempt number.",
|
|
94
|
+
},
|
|
95
|
+
scheduledAt: parsedStringFlag("Provider scheduled timestamp."),
|
|
96
|
+
triggeredAt: parsedStringFlag("Schedule trigger timestamp."),
|
|
97
|
+
source: parsedStringFlag("Provider or app source label."),
|
|
50
98
|
};
|
|
51
99
|
const namePositional = {
|
|
52
100
|
kind: "tuple",
|
|
@@ -58,12 +106,29 @@ const namePositional = {
|
|
|
58
106
|
},
|
|
59
107
|
],
|
|
60
108
|
};
|
|
109
|
+
function resolveOutputFormat(flags) {
|
|
110
|
+
if (flags.json) {
|
|
111
|
+
if (flags.format && flags.format !== "json") {
|
|
112
|
+
throw new Error(`--json conflicts with --format ${flags.format}. Pass one of them.`);
|
|
113
|
+
}
|
|
114
|
+
return "json";
|
|
115
|
+
}
|
|
116
|
+
if (flags.format)
|
|
117
|
+
return flags.format;
|
|
118
|
+
return process.env.GITHUB_ACTIONS === "true" ? "github" : "human";
|
|
119
|
+
}
|
|
120
|
+
function useColor() {
|
|
121
|
+
return process.stdout.isTTY === true && !process.env.NO_COLOR;
|
|
122
|
+
}
|
|
61
123
|
const createCommand = buildCommand({
|
|
62
124
|
docs: {
|
|
63
125
|
brief: "Create a new Beignet app.",
|
|
64
126
|
fullDescription: `Available features: ${featureChoices.join(", ")}
|
|
65
127
|
|
|
66
|
-
Available integrations: ${integrationChoices.join(", ")}
|
|
128
|
+
Available integrations: ${integrationChoices.join(", ")}
|
|
129
|
+
|
|
130
|
+
Running create on an interactive terminal without selection flags opens a
|
|
131
|
+
prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
|
|
67
132
|
},
|
|
68
133
|
parameters: {
|
|
69
134
|
flags: {
|
|
@@ -76,8 +141,8 @@ Available integrations: ${integrationChoices.join(", ")}`,
|
|
|
76
141
|
preset: {
|
|
77
142
|
kind: "enum",
|
|
78
143
|
values: presetChoices,
|
|
79
|
-
|
|
80
|
-
brief: "Starter preset to generate.",
|
|
144
|
+
optional: true,
|
|
145
|
+
brief: "Starter preset to generate. Defaults to standard.",
|
|
81
146
|
},
|
|
82
147
|
packageManager: {
|
|
83
148
|
kind: "enum",
|
|
@@ -85,35 +150,29 @@ Available integrations: ${integrationChoices.join(", ")}`,
|
|
|
85
150
|
optional: true,
|
|
86
151
|
brief: "Package manager to use in next-step commands.",
|
|
87
152
|
},
|
|
88
|
-
feature: {
|
|
89
|
-
kind: "enum",
|
|
90
|
-
values: featureChoices,
|
|
91
|
-
optional: true,
|
|
92
|
-
variadic: true,
|
|
93
|
-
brief: "Add a starter feature. Repeatable.",
|
|
94
|
-
},
|
|
95
153
|
features: {
|
|
96
154
|
kind: "enum",
|
|
97
155
|
values: featureChoices,
|
|
98
156
|
optional: true,
|
|
99
157
|
variadic: ",",
|
|
100
|
-
brief: "Add comma-separated
|
|
101
|
-
},
|
|
102
|
-
integration: {
|
|
103
|
-
kind: "enum",
|
|
104
|
-
values: integrationChoices,
|
|
105
|
-
optional: true,
|
|
106
|
-
variadic: true,
|
|
107
|
-
brief: "Add a first-party integration. Repeatable.",
|
|
158
|
+
brief: "Add starter features as a comma-separated list.",
|
|
108
159
|
},
|
|
109
160
|
integrations: {
|
|
110
161
|
kind: "enum",
|
|
111
162
|
values: integrationChoices,
|
|
112
163
|
optional: true,
|
|
113
164
|
variadic: ",",
|
|
114
|
-
brief: "Add
|
|
165
|
+
brief: "Add first-party integrations as a comma-separated list.",
|
|
166
|
+
},
|
|
167
|
+
yes: {
|
|
168
|
+
kind: "boolean",
|
|
169
|
+
optional: true,
|
|
170
|
+
withNegated: false,
|
|
171
|
+
brief: "Skip interactive prompts and use the defaults.",
|
|
115
172
|
},
|
|
116
173
|
force: forceFlag,
|
|
174
|
+
dryRun: dryRunFlag,
|
|
175
|
+
json: jsonFlag,
|
|
117
176
|
},
|
|
118
177
|
positional: {
|
|
119
178
|
kind: "tuple",
|
|
@@ -122,28 +181,53 @@ Available integrations: ${integrationChoices.join(", ")}`,
|
|
|
122
181
|
parse: parseString,
|
|
123
182
|
placeholder: "directory",
|
|
124
183
|
brief: "Project directory to create.",
|
|
184
|
+
optional: true,
|
|
125
185
|
},
|
|
126
186
|
],
|
|
127
187
|
},
|
|
128
188
|
},
|
|
129
|
-
async
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
189
|
+
loader: async () => {
|
|
190
|
+
const { createProject } = await import("./create.js");
|
|
191
|
+
const { promptCreateSelections, shouldPromptInteractively } = await import("./create-prompts.js");
|
|
192
|
+
return async function runCreate(flags, directory) {
|
|
193
|
+
let selections = {
|
|
194
|
+
directory,
|
|
195
|
+
preset: flags.preset,
|
|
196
|
+
features: flags.features,
|
|
197
|
+
integrations: flags.integrations,
|
|
198
|
+
packageManager: flags.packageManager,
|
|
199
|
+
};
|
|
200
|
+
const interactive = shouldPromptInteractively({ ...selections, yes: flags.yes }, {
|
|
201
|
+
stdin: process.stdin.isTTY === true,
|
|
202
|
+
stdout: process.stdout.isTTY === true,
|
|
203
|
+
});
|
|
204
|
+
if (interactive) {
|
|
205
|
+
const answers = await promptCreateSelections(selections);
|
|
206
|
+
if (answers === undefined) {
|
|
207
|
+
this.process.stderr.write("Cancelled.\n");
|
|
208
|
+
this.process.exitCode = 2;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
selections = answers;
|
|
212
|
+
}
|
|
213
|
+
if (selections.directory === undefined) {
|
|
214
|
+
throw new Error("Project directory is required. Pass beignet create <directory>, or run beignet create in an interactive terminal.");
|
|
215
|
+
}
|
|
216
|
+
const integrations = uniqueValues([...(selections.integrations ?? [])]);
|
|
217
|
+
const result = await createProject({
|
|
218
|
+
name: selections.directory,
|
|
219
|
+
template: flags.template,
|
|
220
|
+
preset: selections.preset ?? "standard",
|
|
221
|
+
features: uniqueValues([...(selections.features ?? [])]),
|
|
222
|
+
packageManager: selections.packageManager,
|
|
223
|
+
integrations,
|
|
224
|
+
force: Boolean(flags.force),
|
|
225
|
+
dryRun: Boolean(flags.dryRun),
|
|
226
|
+
});
|
|
227
|
+
writeOutput(this, flags.json
|
|
228
|
+
? JSON.stringify(result, null, 2)
|
|
229
|
+
: nextSteps(result, { integrations }));
|
|
230
|
+
};
|
|
147
231
|
},
|
|
148
232
|
});
|
|
149
233
|
const routesCommand = buildCommand({
|
|
@@ -151,11 +235,14 @@ const routesCommand = buildCommand({
|
|
|
151
235
|
brief: "Inspect registered Beignet routes.",
|
|
152
236
|
},
|
|
153
237
|
parameters: {
|
|
154
|
-
flags:
|
|
238
|
+
flags: routesFlagParameters,
|
|
155
239
|
},
|
|
156
|
-
async
|
|
157
|
-
const
|
|
158
|
-
|
|
240
|
+
loader: async () => {
|
|
241
|
+
const { formatRoutes, inspectApp } = await import("./inspect.js");
|
|
242
|
+
return async function runRoutes(flags) {
|
|
243
|
+
const result = await inspectApp({ cwd: flags.cwd });
|
|
244
|
+
writeOutput(this, flags.json ? JSON.stringify(result, null, 2) : formatRoutes(result));
|
|
245
|
+
};
|
|
159
246
|
},
|
|
160
247
|
});
|
|
161
248
|
const doctorCommand = buildCommand({
|
|
@@ -177,19 +264,35 @@ const doctorCommand = buildCommand({
|
|
|
177
264
|
withNegated: false,
|
|
178
265
|
brief: "Apply low-risk fixes before reporting.",
|
|
179
266
|
},
|
|
267
|
+
cwd: cwdFlag,
|
|
268
|
+
format: formatFlag,
|
|
180
269
|
},
|
|
181
270
|
},
|
|
182
|
-
async
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
271
|
+
loader: async () => {
|
|
272
|
+
const { applyDoctorFixes, formatDoctor, formatDoctorGithub, inspectApp } = await import("./inspect.js");
|
|
273
|
+
return async function runDoctor(flags) {
|
|
274
|
+
const format = resolveOutputFormat(flags);
|
|
275
|
+
const fixes = flags.fix
|
|
276
|
+
? await applyDoctorFixes({
|
|
277
|
+
cwd: flags.cwd,
|
|
278
|
+
strict: Boolean(flags.strict),
|
|
279
|
+
})
|
|
280
|
+
: [];
|
|
281
|
+
const result = await inspectApp({
|
|
282
|
+
cwd: flags.cwd,
|
|
283
|
+
strict: Boolean(flags.strict),
|
|
284
|
+
});
|
|
285
|
+
result.fixes = fixes;
|
|
286
|
+
writeOutput(this, format === "json"
|
|
287
|
+
? JSON.stringify(result, null, 2)
|
|
288
|
+
: format === "github"
|
|
289
|
+
? formatDoctorGithub(result)
|
|
290
|
+
: formatDoctor(result, { color: useColor() }));
|
|
291
|
+
if (result.diagnostics.some((diagnostic) => diagnostic.severity === "error" ||
|
|
292
|
+
(flags.strict && diagnostic.severity === "warning"))) {
|
|
293
|
+
this.process.exitCode = 1;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
193
296
|
},
|
|
194
297
|
});
|
|
195
298
|
const lintCommand = buildCommand({
|
|
@@ -197,237 +300,380 @@ const lintCommand = buildCommand({
|
|
|
197
300
|
brief: "Check Beignet dependency direction conventions.",
|
|
198
301
|
},
|
|
199
302
|
parameters: {
|
|
200
|
-
flags:
|
|
303
|
+
flags: lintFlagParameters,
|
|
201
304
|
},
|
|
202
|
-
async
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
305
|
+
loader: async () => {
|
|
306
|
+
const { formatLint, formatLintGithub, lintApp } = await import("./lint.js");
|
|
307
|
+
return async function runLint(flags) {
|
|
308
|
+
const format = resolveOutputFormat(flags);
|
|
309
|
+
const result = await lintApp({ cwd: flags.cwd });
|
|
310
|
+
writeOutput(this, format === "json"
|
|
311
|
+
? JSON.stringify(result, null, 2)
|
|
312
|
+
: format === "github"
|
|
313
|
+
? formatLintGithub(result)
|
|
314
|
+
: formatLint(result, { color: useColor() }));
|
|
315
|
+
if (result.diagnostics.length > 0) {
|
|
316
|
+
this.process.exitCode = 1;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
208
319
|
},
|
|
209
320
|
});
|
|
210
|
-
|
|
321
|
+
function databaseCommand(command) {
|
|
322
|
+
return buildCommand({
|
|
323
|
+
docs: {
|
|
324
|
+
brief: `Run the app's db:${command} script.`,
|
|
325
|
+
},
|
|
326
|
+
parameters: {
|
|
327
|
+
flags: dbFlagParameters,
|
|
328
|
+
},
|
|
329
|
+
loader: async () => {
|
|
330
|
+
const { runDatabaseCommand } = await import("./db.js");
|
|
331
|
+
return async function runDb(flags) {
|
|
332
|
+
const result = await runDatabaseCommand({
|
|
333
|
+
command,
|
|
334
|
+
captureOutput: Boolean(flags.json),
|
|
335
|
+
dryRun: Boolean(flags.dryRun),
|
|
336
|
+
});
|
|
337
|
+
writeOutput(this, flags.json
|
|
338
|
+
? JSON.stringify(result, null, 2)
|
|
339
|
+
: databaseCommandNextSteps(result));
|
|
340
|
+
if (result.exitCode !== 0) {
|
|
341
|
+
this.process.exitCode = result.exitCode;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const dbRoutes = buildRouteMap({
|
|
211
348
|
docs: {
|
|
212
|
-
brief: "
|
|
213
|
-
},
|
|
214
|
-
parameters: {
|
|
215
|
-
flags: makeFlagParameters,
|
|
216
|
-
positional: namePositional,
|
|
349
|
+
brief: "Run Beignet database lifecycle commands.",
|
|
217
350
|
},
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
224
|
-
writeOutput(this, flags.json
|
|
225
|
-
? JSON.stringify(result, null, 2)
|
|
226
|
-
: makeResourceNextSteps(result));
|
|
351
|
+
routes: {
|
|
352
|
+
generate: databaseCommand("generate"),
|
|
353
|
+
migrate: databaseCommand("migrate"),
|
|
354
|
+
reset: databaseCommand("reset"),
|
|
355
|
+
seed: databaseCommand("seed"),
|
|
227
356
|
},
|
|
228
357
|
});
|
|
229
|
-
const
|
|
358
|
+
const taskRunCommand = buildCommand({
|
|
230
359
|
docs: {
|
|
231
|
-
brief: "
|
|
360
|
+
brief: "Run an app-owned operational task.",
|
|
232
361
|
},
|
|
233
362
|
parameters: {
|
|
234
|
-
flags:
|
|
363
|
+
flags: taskRunFlagParameters,
|
|
235
364
|
positional: namePositional,
|
|
236
365
|
},
|
|
237
|
-
async
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
366
|
+
loader: async () => {
|
|
367
|
+
const { runAppTask } = await import("./task.js");
|
|
368
|
+
return async function runTask(flags, name) {
|
|
369
|
+
const result = await runAppTask({
|
|
370
|
+
name,
|
|
371
|
+
input: flags.input,
|
|
372
|
+
modulePath: flags.module,
|
|
373
|
+
});
|
|
374
|
+
writeOutput(this, flags.json
|
|
375
|
+
? JSON.stringify(result, null, 2)
|
|
376
|
+
: appTaskRunNextSteps(result));
|
|
377
|
+
};
|
|
246
378
|
},
|
|
247
379
|
});
|
|
248
|
-
const
|
|
380
|
+
const taskRoutes = buildRouteMap({
|
|
249
381
|
docs: {
|
|
250
|
-
brief: "
|
|
251
|
-
},
|
|
252
|
-
parameters: {
|
|
253
|
-
flags: makeFlagParameters,
|
|
254
|
-
positional: namePositional,
|
|
382
|
+
brief: "Run Beignet app operational tasks.",
|
|
255
383
|
},
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
name,
|
|
259
|
-
force: Boolean(flags.force),
|
|
260
|
-
dryRun: Boolean(flags.dryRun),
|
|
261
|
-
});
|
|
262
|
-
writeOutput(this, flags.json
|
|
263
|
-
? JSON.stringify(result, null, 2)
|
|
264
|
-
: makeUseCaseNextSteps(result));
|
|
384
|
+
routes: {
|
|
385
|
+
run: taskRunCommand,
|
|
265
386
|
},
|
|
266
387
|
});
|
|
267
|
-
const
|
|
388
|
+
const outboxDrainCommand = buildCommand({
|
|
268
389
|
docs: {
|
|
269
|
-
brief: "
|
|
390
|
+
brief: "Run one app-owned outbox drain pass.",
|
|
270
391
|
},
|
|
271
392
|
parameters: {
|
|
272
|
-
flags:
|
|
273
|
-
positional: namePositional,
|
|
393
|
+
flags: outboxDrainFlagParameters,
|
|
274
394
|
},
|
|
275
|
-
async
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
395
|
+
loader: async () => {
|
|
396
|
+
const { runOutboxDrain } = await import("./outbox.js");
|
|
397
|
+
return async function runDrain(flags) {
|
|
398
|
+
const result = await runOutboxDrain({
|
|
399
|
+
modulePath: flags.module,
|
|
400
|
+
batchSize: flags.batchSize,
|
|
401
|
+
});
|
|
402
|
+
writeOutput(this, flags.json
|
|
403
|
+
? JSON.stringify(result, null, 2)
|
|
404
|
+
: outboxDrainNextSteps(result));
|
|
405
|
+
};
|
|
282
406
|
},
|
|
283
407
|
});
|
|
284
|
-
const
|
|
408
|
+
const outboxRoutes = buildRouteMap({
|
|
285
409
|
docs: {
|
|
286
|
-
brief: "
|
|
410
|
+
brief: "Run Beignet outbox operations.",
|
|
411
|
+
},
|
|
412
|
+
routes: {
|
|
413
|
+
drain: outboxDrainCommand,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
const scheduleRunCommand = buildCommand({
|
|
417
|
+
docs: {
|
|
418
|
+
brief: "Run an app-owned schedule.",
|
|
287
419
|
},
|
|
288
420
|
parameters: {
|
|
289
|
-
flags:
|
|
421
|
+
flags: scheduleRunFlagParameters,
|
|
290
422
|
positional: namePositional,
|
|
291
423
|
},
|
|
292
|
-
async
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
424
|
+
loader: async () => {
|
|
425
|
+
const { runAppSchedule } = await import("./schedule.js");
|
|
426
|
+
return async function runSchedule(flags, name) {
|
|
427
|
+
const result = await runAppSchedule({
|
|
428
|
+
name,
|
|
429
|
+
modulePath: flags.module,
|
|
430
|
+
payload: flags.payload,
|
|
431
|
+
id: flags.id,
|
|
432
|
+
attempt: flags.attempt,
|
|
433
|
+
scheduledAt: flags.scheduledAt,
|
|
434
|
+
triggeredAt: flags.triggeredAt,
|
|
435
|
+
source: flags.source,
|
|
436
|
+
});
|
|
437
|
+
writeOutput(this, flags.json
|
|
438
|
+
? JSON.stringify(result, null, 2)
|
|
439
|
+
: scheduleRunNextSteps(result));
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
const scheduleRoutes = buildRouteMap({
|
|
444
|
+
docs: {
|
|
445
|
+
brief: "Run Beignet schedule operations.",
|
|
446
|
+
},
|
|
447
|
+
routes: {
|
|
448
|
+
run: scheduleRunCommand,
|
|
299
449
|
},
|
|
300
450
|
});
|
|
301
|
-
const
|
|
451
|
+
const completionShellFlagParameters = {
|
|
452
|
+
shell: {
|
|
453
|
+
kind: "enum",
|
|
454
|
+
values: completionShellChoices,
|
|
455
|
+
optional: true,
|
|
456
|
+
brief: "Shell to manage completions for. Defaults to $SHELL.",
|
|
457
|
+
},
|
|
458
|
+
json: jsonFlag,
|
|
459
|
+
};
|
|
460
|
+
const completionInstallCommand = buildCommand({
|
|
302
461
|
docs: {
|
|
303
|
-
brief: "
|
|
462
|
+
brief: "Install beignet shell completions for bash or zsh.",
|
|
304
463
|
},
|
|
305
464
|
parameters: {
|
|
306
|
-
flags:
|
|
307
|
-
positional: namePositional,
|
|
465
|
+
flags: completionShellFlagParameters,
|
|
308
466
|
},
|
|
309
|
-
async
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
467
|
+
loader: async () => {
|
|
468
|
+
const { installCompletions } = await import("./completion.js");
|
|
469
|
+
return async function runInstall(flags) {
|
|
470
|
+
const result = await installCompletions({ shell: flags.shell });
|
|
471
|
+
writeOutput(this, flags.json
|
|
472
|
+
? JSON.stringify(result, null, 2)
|
|
473
|
+
: `${result.status === "updated" ? "Updated" : "Installed"} ${result.shell} completions in ${result.file}
|
|
474
|
+
|
|
475
|
+
Restart your shell or source the file to activate them.`);
|
|
476
|
+
};
|
|
318
477
|
},
|
|
319
478
|
});
|
|
320
|
-
const
|
|
479
|
+
const completionUninstallCommand = buildCommand({
|
|
321
480
|
docs: {
|
|
322
|
-
brief: "
|
|
481
|
+
brief: "Remove beignet shell completions for bash or zsh.",
|
|
323
482
|
},
|
|
324
483
|
parameters: {
|
|
325
|
-
flags:
|
|
326
|
-
|
|
484
|
+
flags: completionShellFlagParameters,
|
|
485
|
+
},
|
|
486
|
+
loader: async () => {
|
|
487
|
+
const { uninstallCompletions } = await import("./completion.js");
|
|
488
|
+
return async function runUninstall(flags) {
|
|
489
|
+
const result = await uninstallCompletions({ shell: flags.shell });
|
|
490
|
+
writeOutput(this, flags.json
|
|
491
|
+
? JSON.stringify(result, null, 2)
|
|
492
|
+
: result.status === "removed"
|
|
493
|
+
? `Removed ${result.shell} completions from ${result.file}`
|
|
494
|
+
: `No beignet completions found in ${result.file}`);
|
|
495
|
+
};
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
const completionRoutes = buildRouteMap({
|
|
499
|
+
docs: {
|
|
500
|
+
brief: "Manage beignet shell completions.",
|
|
327
501
|
},
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
force: Boolean(flags.force),
|
|
332
|
-
dryRun: Boolean(flags.dryRun),
|
|
333
|
-
});
|
|
334
|
-
writeOutput(this, flags.json
|
|
335
|
-
? JSON.stringify(result, null, 2)
|
|
336
|
-
: makePolicyNextSteps(result));
|
|
502
|
+
routes: {
|
|
503
|
+
install: completionInstallCommand,
|
|
504
|
+
uninstall: completionUninstallCommand,
|
|
337
505
|
},
|
|
338
506
|
});
|
|
339
|
-
const
|
|
507
|
+
const makeResourceCommand = buildCommand({
|
|
340
508
|
docs: {
|
|
341
|
-
brief: "Generate a
|
|
509
|
+
brief: "Generate a CRUD-shaped resource slice.",
|
|
342
510
|
},
|
|
343
511
|
parameters: {
|
|
344
|
-
flags:
|
|
512
|
+
flags: {
|
|
513
|
+
...makeFlagParameters,
|
|
514
|
+
auth: {
|
|
515
|
+
kind: "boolean",
|
|
516
|
+
optional: true,
|
|
517
|
+
withNegated: false,
|
|
518
|
+
brief: "Generate policy metadata and use-case authorization checks for mutating routes.",
|
|
519
|
+
},
|
|
520
|
+
tenant: {
|
|
521
|
+
kind: "boolean",
|
|
522
|
+
optional: true,
|
|
523
|
+
withNegated: false,
|
|
524
|
+
brief: "Generate tenant-scoped schemas, repository methods, and use-case checks.",
|
|
525
|
+
},
|
|
526
|
+
events: {
|
|
527
|
+
kind: "boolean",
|
|
528
|
+
optional: true,
|
|
529
|
+
withNegated: false,
|
|
530
|
+
brief: "Generate domain event definitions and publish resource events.",
|
|
531
|
+
},
|
|
532
|
+
softDelete: {
|
|
533
|
+
kind: "boolean",
|
|
534
|
+
optional: true,
|
|
535
|
+
withNegated: false,
|
|
536
|
+
brief: "Generate soft-delete persistence that archives rows with deletedAt.",
|
|
537
|
+
},
|
|
538
|
+
},
|
|
345
539
|
positional: namePositional,
|
|
346
540
|
},
|
|
347
|
-
async
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
541
|
+
loader: async () => {
|
|
542
|
+
const { makeResource } = await import("./make.js");
|
|
543
|
+
return async function runMakeResource(flags, name) {
|
|
544
|
+
const result = await makeResource({
|
|
545
|
+
name,
|
|
546
|
+
force: Boolean(flags.force),
|
|
547
|
+
dryRun: Boolean(flags.dryRun),
|
|
548
|
+
auth: Boolean(flags.auth),
|
|
549
|
+
tenant: Boolean(flags.tenant),
|
|
550
|
+
events: Boolean(flags.events),
|
|
551
|
+
softDelete: Boolean(flags.softDelete),
|
|
552
|
+
});
|
|
553
|
+
writeOutput(this, flags.json
|
|
554
|
+
? JSON.stringify(result, null, 2)
|
|
555
|
+
: makeResourceNextSteps(result));
|
|
556
|
+
};
|
|
354
557
|
},
|
|
355
558
|
});
|
|
356
|
-
const
|
|
559
|
+
const makeFeatureCommand = buildCommand({
|
|
357
560
|
docs: {
|
|
358
|
-
brief: "Generate a feature
|
|
561
|
+
brief: "Generate a contract-first feature slice.",
|
|
359
562
|
},
|
|
360
563
|
parameters: {
|
|
361
|
-
flags:
|
|
564
|
+
flags: {
|
|
565
|
+
...makeFlagParameters,
|
|
566
|
+
with: {
|
|
567
|
+
kind: "enum",
|
|
568
|
+
values: makeFeatureAddonChoices,
|
|
569
|
+
optional: true,
|
|
570
|
+
variadic: ",",
|
|
571
|
+
brief: "Add feature-owned artifacts. Supports policy, task/tasks, event/events, job/jobs, notification/notifications, ui, and upload/uploads.",
|
|
572
|
+
},
|
|
573
|
+
},
|
|
362
574
|
positional: namePositional,
|
|
363
575
|
},
|
|
364
|
-
async
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
576
|
+
loader: async () => {
|
|
577
|
+
const { makeFeature } = await import("./make.js");
|
|
578
|
+
return async function runMakeFeature(flags, name) {
|
|
579
|
+
const result = await makeFeature({
|
|
580
|
+
name,
|
|
581
|
+
with: flags.with,
|
|
582
|
+
force: Boolean(flags.force),
|
|
583
|
+
dryRun: Boolean(flags.dryRun),
|
|
584
|
+
});
|
|
585
|
+
writeOutput(this, flags.json
|
|
586
|
+
? JSON.stringify(result, null, 2)
|
|
587
|
+
: makeFeatureNextSteps(result));
|
|
588
|
+
};
|
|
371
589
|
},
|
|
372
590
|
});
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
591
|
+
function simpleMakeCommand(generator, brief, formatNextSteps) {
|
|
592
|
+
return buildCommand({
|
|
593
|
+
docs: {
|
|
594
|
+
brief,
|
|
595
|
+
},
|
|
596
|
+
parameters: {
|
|
597
|
+
flags: makeFlagParameters,
|
|
598
|
+
positional: namePositional,
|
|
599
|
+
},
|
|
600
|
+
loader: async () => {
|
|
601
|
+
const generators = await import("./make.js");
|
|
602
|
+
return async function runMake(flags, name) {
|
|
603
|
+
const result = await generators[generator]({
|
|
604
|
+
name,
|
|
605
|
+
force: Boolean(flags.force),
|
|
606
|
+
dryRun: Boolean(flags.dryRun),
|
|
607
|
+
});
|
|
608
|
+
writeOutput(this, flags.json
|
|
609
|
+
? JSON.stringify(result, null, 2)
|
|
610
|
+
: formatNextSteps(result));
|
|
611
|
+
};
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
}
|
|
377
615
|
const makeListenerCommand = buildCommand({
|
|
378
616
|
docs: {
|
|
379
617
|
brief: "Generate a feature event listener.",
|
|
380
618
|
},
|
|
381
619
|
parameters: {
|
|
382
|
-
flags:
|
|
620
|
+
flags: {
|
|
621
|
+
...makeFlagParameters,
|
|
622
|
+
event: parsedStringFlag("Event to listen to, for example posts/published."),
|
|
623
|
+
},
|
|
383
624
|
positional: namePositional,
|
|
384
625
|
},
|
|
385
|
-
async
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
626
|
+
loader: async () => {
|
|
627
|
+
const { makeListener } = await import("./make.js");
|
|
628
|
+
return async function runMakeListener(flags, name) {
|
|
629
|
+
if (!flags.event) {
|
|
630
|
+
throw new Error("beignet make listener requires --event feature/name, for example --event posts/published.");
|
|
631
|
+
}
|
|
632
|
+
const result = await makeListener({
|
|
633
|
+
name,
|
|
634
|
+
event: flags.event,
|
|
635
|
+
force: Boolean(flags.force),
|
|
636
|
+
dryRun: Boolean(flags.dryRun),
|
|
637
|
+
});
|
|
638
|
+
writeOutput(this, flags.json
|
|
639
|
+
? JSON.stringify(result, null, 2)
|
|
640
|
+
: makeListenerNextSteps(result));
|
|
641
|
+
};
|
|
398
642
|
},
|
|
399
643
|
});
|
|
400
|
-
const makeScheduleFlagParameters = {
|
|
401
|
-
...makeFlagParameters,
|
|
402
|
-
cron: parsedStringFlag("Cron expression. Defaults to 0 9 * * *."),
|
|
403
|
-
timezone: parsedStringFlag("IANA timezone, for example America/Chicago."),
|
|
404
|
-
route: {
|
|
405
|
-
kind: "boolean",
|
|
406
|
-
optional: true,
|
|
407
|
-
withNegated: false,
|
|
408
|
-
brief: "Generate a Next.js cron route for this schedule.",
|
|
409
|
-
},
|
|
410
|
-
};
|
|
411
644
|
const makeScheduleCommand = buildCommand({
|
|
412
645
|
docs: {
|
|
413
646
|
brief: "Generate a feature schedule.",
|
|
414
647
|
},
|
|
415
648
|
parameters: {
|
|
416
|
-
flags:
|
|
649
|
+
flags: {
|
|
650
|
+
...makeFlagParameters,
|
|
651
|
+
cron: parsedStringFlag("Cron expression. Defaults to 0 9 * * *."),
|
|
652
|
+
timezone: parsedStringFlag("IANA timezone, for example America/Chicago."),
|
|
653
|
+
route: {
|
|
654
|
+
kind: "boolean",
|
|
655
|
+
optional: true,
|
|
656
|
+
withNegated: false,
|
|
657
|
+
brief: "Generate a Next.js cron route for this schedule.",
|
|
658
|
+
},
|
|
659
|
+
},
|
|
417
660
|
positional: namePositional,
|
|
418
661
|
},
|
|
419
|
-
async
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
662
|
+
loader: async () => {
|
|
663
|
+
const { makeSchedule } = await import("./make.js");
|
|
664
|
+
return async function runMakeSchedule(flags, name) {
|
|
665
|
+
const result = await makeSchedule({
|
|
666
|
+
name,
|
|
667
|
+
cron: flags.cron,
|
|
668
|
+
timezone: flags.timezone,
|
|
669
|
+
route: Boolean(flags.route),
|
|
670
|
+
force: Boolean(flags.force),
|
|
671
|
+
dryRun: Boolean(flags.dryRun),
|
|
672
|
+
});
|
|
673
|
+
writeOutput(this, flags.json
|
|
674
|
+
? JSON.stringify(result, null, 2)
|
|
675
|
+
: makeScheduleNextSteps(result));
|
|
676
|
+
};
|
|
431
677
|
},
|
|
432
678
|
});
|
|
433
679
|
const makeRoutes = buildRouteMap({
|
|
@@ -435,17 +681,23 @@ const makeRoutes = buildRouteMap({
|
|
|
435
681
|
brief: "Generate Beignet app files.",
|
|
436
682
|
},
|
|
437
683
|
routes: {
|
|
438
|
-
adapter:
|
|
439
|
-
contract:
|
|
440
|
-
event:
|
|
441
|
-
|
|
684
|
+
adapter: simpleMakeCommand("makeAdapter", "Generate a port adapter.", makeAdapterNextSteps),
|
|
685
|
+
contract: simpleMakeCommand("makeContract", "Generate a contract group.", makeContractNextSteps),
|
|
686
|
+
event: simpleMakeCommand("makeEvent", "Generate a feature event.", makeEventNextSteps),
|
|
687
|
+
factory: simpleMakeCommand("makeFactory", "Generate a feature test data factory.", makeFactoryNextSteps),
|
|
688
|
+
feature: makeFeatureCommand,
|
|
689
|
+
job: simpleMakeCommand("makeJob", "Generate a feature job.", makeJobNextSteps),
|
|
690
|
+
notification: simpleMakeCommand("makeNotification", "Generate a feature notification.", makeNotificationNextSteps),
|
|
442
691
|
listener: makeListenerCommand,
|
|
443
|
-
policy:
|
|
444
|
-
port:
|
|
692
|
+
policy: simpleMakeCommand("makePolicy", "Generate an authorization policy.", makePolicyNextSteps),
|
|
693
|
+
port: simpleMakeCommand("makePort", "Generate an application port.", makePortNextSteps),
|
|
445
694
|
resource: makeResourceCommand,
|
|
446
695
|
schedule: makeScheduleCommand,
|
|
447
|
-
|
|
448
|
-
|
|
696
|
+
seed: simpleMakeCommand("makeSeed", "Generate a feature seed.", makeSeedNextSteps),
|
|
697
|
+
task: simpleMakeCommand("makeTask", "Generate a feature operational task.", makeTaskNextSteps),
|
|
698
|
+
test: simpleMakeCommand("makeTest", "Generate a use case test.", makeTestNextSteps),
|
|
699
|
+
upload: simpleMakeCommand("makeUpload", "Generate a feature upload.", makeUploadNextSteps),
|
|
700
|
+
useCase: simpleMakeCommand("makeUseCase", "Generate a use case.", makeUseCaseNextSteps),
|
|
449
701
|
},
|
|
450
702
|
});
|
|
451
703
|
const rootRoutes = buildRouteMap({
|
|
@@ -453,18 +705,26 @@ const rootRoutes = buildRouteMap({
|
|
|
453
705
|
brief: "Beignet CLI",
|
|
454
706
|
fullDescription: `Create apps, generate framework files, inspect routes, and check Beignet conventions.
|
|
455
707
|
|
|
456
|
-
create
|
|
708
|
+
Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
|
|
457
709
|
},
|
|
458
710
|
routes: {
|
|
711
|
+
completion: completionRoutes,
|
|
459
712
|
create: createCommand,
|
|
713
|
+
db: dbRoutes,
|
|
460
714
|
doctor: doctorCommand,
|
|
461
715
|
lint: lintCommand,
|
|
462
716
|
make: makeRoutes,
|
|
717
|
+
outbox: outboxRoutes,
|
|
463
718
|
routes: routesCommand,
|
|
719
|
+
schedule: scheduleRoutes,
|
|
720
|
+
task: taskRoutes,
|
|
464
721
|
},
|
|
465
722
|
});
|
|
466
723
|
const cli = buildApplication(rootRoutes, {
|
|
467
724
|
name: "beignet",
|
|
725
|
+
versionInfo: {
|
|
726
|
+
currentVersion: getCliVersion(),
|
|
727
|
+
},
|
|
468
728
|
scanner: {
|
|
469
729
|
caseStyle: "allow-kebab-for-camel",
|
|
470
730
|
},
|
|
@@ -478,7 +738,7 @@ const cli = buildApplication(rootRoutes, {
|
|
|
478
738
|
commandErrorResult: (error) => error.message,
|
|
479
739
|
},
|
|
480
740
|
},
|
|
481
|
-
determineExitCode: () =>
|
|
741
|
+
determineExitCode: () => 2,
|
|
482
742
|
});
|
|
483
743
|
function uniqueValues(values) {
|
|
484
744
|
return [...new Set(values)];
|
|
@@ -489,9 +749,45 @@ function writeOutput(context, output) {
|
|
|
489
749
|
function formatCliException(error) {
|
|
490
750
|
return error instanceof Error ? error.message : String(error);
|
|
491
751
|
}
|
|
752
|
+
function appTaskRunNextSteps(result) {
|
|
753
|
+
const output = result.output === undefined
|
|
754
|
+
? "undefined"
|
|
755
|
+
: JSON.stringify(result.output, null, 2);
|
|
756
|
+
return `Ran task ${result.name} in ${result.durationMs}ms
|
|
757
|
+
|
|
758
|
+
Output:
|
|
759
|
+
${output}`;
|
|
760
|
+
}
|
|
761
|
+
function outboxDrainNextSteps(result) {
|
|
762
|
+
return `Drained outbox in ${result.durationMs}ms
|
|
763
|
+
|
|
764
|
+
Result:
|
|
765
|
+
claimed: ${result.result.claimed}
|
|
766
|
+
delivered: ${result.result.delivered}
|
|
767
|
+
retried: ${result.result.retried}
|
|
768
|
+
deadLettered: ${result.result.deadLettered}`;
|
|
769
|
+
}
|
|
770
|
+
function scheduleRunNextSteps(result) {
|
|
771
|
+
return `Ran schedule ${result.name} in ${result.durationMs}ms
|
|
772
|
+
|
|
773
|
+
Run:
|
|
774
|
+
source: ${result.run.source}
|
|
775
|
+
id: ${result.run.id ?? "none"}
|
|
776
|
+
attempt: ${result.run.attempt ?? "none"}
|
|
777
|
+
scheduledAt: ${result.run.scheduledAt ?? "none"}
|
|
778
|
+
triggeredAt: ${result.run.triggeredAt ?? "none"}`;
|
|
779
|
+
}
|
|
492
780
|
function nextSteps(result, options = {}) {
|
|
781
|
+
if (result.dryRun) {
|
|
782
|
+
const plannedFiles = result.files.map((file) => ` ${file}`).join("\n");
|
|
783
|
+
return `Would create ${result.name} at ${result.targetDir}
|
|
784
|
+
|
|
785
|
+
Planned files:
|
|
786
|
+
${plannedFiles}`;
|
|
787
|
+
}
|
|
493
788
|
const pm = result.packageManager;
|
|
494
789
|
const run = pm === "npm" ? "npm run" : `${pm} run`;
|
|
790
|
+
const cli = packageRunnerFromPackageManager(pm);
|
|
495
791
|
const envFileStep = result.files.includes(".env.example")
|
|
496
792
|
? " cp .env.example .env.local\n"
|
|
497
793
|
: "";
|
|
@@ -499,218 +795,232 @@ function nextSteps(result, options = {}) {
|
|
|
499
795
|
const envStep = envRequiredIntegrations.length > 0
|
|
500
796
|
? " Fill .env.local for selected provider integrations before starting the app.\n"
|
|
501
797
|
: "";
|
|
798
|
+
const hasDatabase = result.files.some((file) => file.startsWith("infra/db/"));
|
|
799
|
+
const databaseStep = hasDatabase
|
|
800
|
+
? `
|
|
801
|
+
Prepare the database:
|
|
802
|
+
${cli} db generate
|
|
803
|
+
${cli} db migrate
|
|
804
|
+
`
|
|
805
|
+
: "";
|
|
806
|
+
const featureDatabaseSteps = hasDatabase
|
|
807
|
+
? `
|
|
808
|
+
${cli} db generate
|
|
809
|
+
${cli} db migrate`
|
|
810
|
+
: "";
|
|
502
811
|
return `Created ${result.name} at ${result.targetDir}
|
|
503
812
|
|
|
504
813
|
Next steps:
|
|
505
814
|
cd ${result.targetDir}
|
|
506
815
|
${pm} install
|
|
507
|
-
${envFileStep}${envStep}
|
|
508
|
-
|
|
816
|
+
${envFileStep}${envStep}${databaseStep}
|
|
817
|
+
Start the app:
|
|
818
|
+
${run} dev
|
|
819
|
+
|
|
820
|
+
Inspect the app:
|
|
821
|
+
${cli} routes
|
|
822
|
+
${cli} lint
|
|
823
|
+
${cli} doctor
|
|
824
|
+
|
|
825
|
+
Generate a feature:
|
|
826
|
+
${cli} make feature projects${featureDatabaseSteps}
|
|
827
|
+
${run} test
|
|
828
|
+
${run} typecheck
|
|
829
|
+
${cli} lint
|
|
830
|
+
${cli} doctor`;
|
|
831
|
+
}
|
|
832
|
+
function packageRunnerFromPackageManager(pm) {
|
|
833
|
+
switch (pm) {
|
|
834
|
+
case "npm":
|
|
835
|
+
return "npm run beignet --";
|
|
836
|
+
case "pnpm":
|
|
837
|
+
return "pnpm beignet";
|
|
838
|
+
case "yarn":
|
|
839
|
+
return "yarn beignet";
|
|
840
|
+
default:
|
|
841
|
+
return "bun beignet";
|
|
842
|
+
}
|
|
509
843
|
}
|
|
510
844
|
function isEnvRequiredProviderIntegration(integration) {
|
|
511
845
|
return (integration === "inngest" ||
|
|
512
846
|
integration === "resend" ||
|
|
513
847
|
integration === "upstash-rate-limit");
|
|
514
848
|
}
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
.map((file) => ` ${file}`)
|
|
521
|
-
|
|
522
|
-
|
|
849
|
+
function changedFileLines(result) {
|
|
850
|
+
return {
|
|
851
|
+
changedFiles: [...result.createdFiles, ...result.updatedFiles]
|
|
852
|
+
.map((file) => ` ${file}`)
|
|
853
|
+
.join("\n"),
|
|
854
|
+
skippedFiles: result.skippedFiles.map((file) => ` ${file}`).join("\n"),
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
function makeNextSteps(result, noun, steps) {
|
|
858
|
+
const { changedFiles, skippedFiles } = changedFileLines(result);
|
|
859
|
+
return `${result.dryRun ? "Would create" : "Created"} ${result.name} ${noun} in ${result.targetDir}
|
|
523
860
|
|
|
524
861
|
Changed files:
|
|
525
862
|
${changedFiles || " none"}
|
|
526
863
|
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
527
864
|
|
|
528
865
|
Next steps:
|
|
529
|
-
|
|
530
|
-
Run your app's typecheck and test commands.`;
|
|
866
|
+
${steps.map((step) => ` ${step}`).join("\n")}`;
|
|
531
867
|
}
|
|
532
|
-
function
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
868
|
+
function makeResourceNextSteps(result) {
|
|
869
|
+
return makeNextSteps(result, "resource", [
|
|
870
|
+
"Use make resource when the feature maps cleanly to a REST resource with repository-backed persistence.",
|
|
871
|
+
"Review the generated schemas and repository fields.",
|
|
872
|
+
"If this app uses Drizzle, run beignet db generate and beignet db migrate so the new schema is ready.",
|
|
873
|
+
"Run your app's test and typecheck commands, then beignet lint and beignet doctor.",
|
|
874
|
+
]);
|
|
875
|
+
}
|
|
876
|
+
function databaseCommandNextSteps(result) {
|
|
877
|
+
const command = [result.runner, ...result.args].join(" ");
|
|
878
|
+
const prefix = result.dryRun ? "Would run" : "Ran";
|
|
879
|
+
return `${prefix} ${result.script} in ${result.cwd}
|
|
544
880
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
881
|
+
Command:
|
|
882
|
+
${command}`;
|
|
883
|
+
}
|
|
884
|
+
function makeFeatureNextSteps(result) {
|
|
885
|
+
return makeNextSteps(result, "feature slice", [
|
|
886
|
+
"Use make feature for product capabilities and workflows. Use make resource when the concept is mostly CRUD-shaped.",
|
|
887
|
+
"Treat the generated name field as a placeholder and shape the contracts, use cases, and repository around the feature's real workflow.",
|
|
888
|
+
"If this app uses Drizzle, run beignet db generate and beignet db migrate so the new schema is ready.",
|
|
889
|
+
"Run your app's test and typecheck commands, then beignet lint and beignet doctor.",
|
|
890
|
+
]);
|
|
891
|
+
}
|
|
892
|
+
function makeContractNextSteps(result) {
|
|
893
|
+
return makeNextSteps(result, "contract", [
|
|
894
|
+
"Add route handlers for the generated contracts.",
|
|
895
|
+
"Register the exported contract list with OpenAPI if this app publishes docs.",
|
|
896
|
+
]);
|
|
897
|
+
}
|
|
898
|
+
function makeTaskNextSteps(result) {
|
|
899
|
+
return makeNextSteps(result, "task", [
|
|
900
|
+
"Replace the starter input schema and handler with the operational workflow.",
|
|
901
|
+
"Keep business rules in use cases and call them from the task handler.",
|
|
902
|
+
]);
|
|
548
903
|
}
|
|
549
904
|
function makeUseCaseNextSteps(result) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
.
|
|
553
|
-
|
|
554
|
-
.map((file) => ` ${file}`)
|
|
555
|
-
.join("\n");
|
|
556
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} use case in ${result.targetDir}
|
|
557
|
-
|
|
558
|
-
Changed files:
|
|
559
|
-
${changedFiles || " none"}
|
|
560
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
561
|
-
|
|
562
|
-
Next steps:
|
|
563
|
-
Replace the starter input and output schemas with domain-specific shapes.
|
|
564
|
-
Add a focused use case test before wiring it to an HTTP route.`;
|
|
905
|
+
return makeNextSteps(result, "use case", [
|
|
906
|
+
"Replace the starter input and output schemas with domain-specific shapes.",
|
|
907
|
+
"Add a focused use case test before wiring it to an HTTP route.",
|
|
908
|
+
]);
|
|
565
909
|
}
|
|
566
910
|
function makeTestNextSteps(result) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
.
|
|
570
|
-
|
|
571
|
-
.map((file) => ` ${file}`)
|
|
572
|
-
.join("\n");
|
|
573
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} test in ${result.targetDir}
|
|
574
|
-
|
|
575
|
-
Changed files:
|
|
576
|
-
${changedFiles || " none"}
|
|
577
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
578
|
-
|
|
579
|
-
Next steps:
|
|
580
|
-
Replace the starter input, context, and assertion with behavior-specific coverage.
|
|
581
|
-
Run your app's test command.`;
|
|
911
|
+
return makeNextSteps(result, "test", [
|
|
912
|
+
"Replace the starter input, context, and assertion with behavior-specific coverage.",
|
|
913
|
+
"Run your app's test command.",
|
|
914
|
+
]);
|
|
582
915
|
}
|
|
583
916
|
function makePortNextSteps(result) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
.
|
|
587
|
-
|
|
588
|
-
.map((file) => ` ${file}`)
|
|
589
|
-
.join("\n");
|
|
590
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} port in ${result.targetDir}
|
|
591
|
-
|
|
592
|
-
Changed files:
|
|
593
|
-
${changedFiles || " none"}
|
|
594
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
595
|
-
|
|
596
|
-
Next steps:
|
|
597
|
-
Replace the starter execute method with domain-specific operations.
|
|
598
|
-
Replace the generated infrastructure stub with a real port adapter.`;
|
|
917
|
+
return makeNextSteps(result, "port", [
|
|
918
|
+
"Replace the starter execute method with domain-specific operations.",
|
|
919
|
+
"Replace the generated infrastructure stub with a real port adapter.",
|
|
920
|
+
]);
|
|
599
921
|
}
|
|
600
922
|
function makePolicyNextSteps(result) {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
.
|
|
604
|
-
|
|
605
|
-
.map((file) => ` ${file}`)
|
|
606
|
-
.join("\n");
|
|
607
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} policy in ${result.targetDir}
|
|
608
|
-
|
|
609
|
-
Changed files:
|
|
610
|
-
${changedFiles || " none"}
|
|
611
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
612
|
-
|
|
613
|
-
Next steps:
|
|
614
|
-
Replace the starter abilities with domain-specific authorization rules.
|
|
615
|
-
Register the policy with createGate(...) and bind it in request context.`;
|
|
923
|
+
return makeNextSteps(result, "policy", [
|
|
924
|
+
"Replace the starter abilities with domain-specific authorization rules.",
|
|
925
|
+
"Register the policy with createGate(...) and bind it in request context.",
|
|
926
|
+
]);
|
|
616
927
|
}
|
|
617
928
|
function makeAdapterNextSteps(result) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
.
|
|
621
|
-
|
|
622
|
-
.map((file) => ` ${file}`)
|
|
623
|
-
.join("\n");
|
|
624
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} port adapter in ${result.targetDir}
|
|
625
|
-
|
|
626
|
-
Changed files:
|
|
627
|
-
${changedFiles || " none"}
|
|
628
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
629
|
-
|
|
630
|
-
Next steps:
|
|
631
|
-
Replace the generated throwing implementation with real infrastructure code.
|
|
632
|
-
Keep the adapter behind the port interface so use cases stay infrastructure-agnostic.`;
|
|
929
|
+
return makeNextSteps(result, "port adapter", [
|
|
930
|
+
"Replace the generated throwing implementation with real infrastructure code.",
|
|
931
|
+
"Keep the adapter behind the port interface so use cases stay infrastructure-agnostic.",
|
|
932
|
+
]);
|
|
633
933
|
}
|
|
634
934
|
function makeEventNextSteps(result) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
.
|
|
638
|
-
|
|
639
|
-
.map((file) => ` ${file}`)
|
|
640
|
-
.join("\n");
|
|
641
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} event in ${result.targetDir}
|
|
642
|
-
|
|
643
|
-
Changed files:
|
|
644
|
-
${changedFiles || " none"}
|
|
645
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
646
|
-
|
|
647
|
-
Next steps:
|
|
648
|
-
Replace the starter payload schema with the domain fact shape.
|
|
649
|
-
Emit the event from a use case with .emits(...) and events.record(...).`;
|
|
935
|
+
return makeNextSteps(result, "event", [
|
|
936
|
+
"Replace the starter payload schema with the domain fact shape.",
|
|
937
|
+
"Emit the event from a use case with .emits(...) and events.record(...).",
|
|
938
|
+
]);
|
|
650
939
|
}
|
|
651
940
|
function makeJobNextSteps(result) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
return
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
941
|
+
return makeNextSteps(result, "job", [
|
|
942
|
+
"Replace the starter payload and handler with the real background work.",
|
|
943
|
+
"Dispatch the job through ctx.ports.jobs from a use case, listener, or schedule.",
|
|
944
|
+
]);
|
|
945
|
+
}
|
|
946
|
+
function makeFactoryNextSteps(result) {
|
|
947
|
+
return makeNextSteps(result, "factory", [
|
|
948
|
+
"Replace the starter defaults with realistic test data for this feature.",
|
|
949
|
+
"Persist through app-owned repository ports so tests avoid raw database clients.",
|
|
950
|
+
]);
|
|
951
|
+
}
|
|
952
|
+
function makeSeedNextSteps(result) {
|
|
953
|
+
return makeNextSteps(result, "seed", [
|
|
954
|
+
"Replace the starter record with idempotent local or demo data.",
|
|
955
|
+
"Import the feature seed from your app-owned database seed entrypoint and run it with runSeeds(...).",
|
|
956
|
+
]);
|
|
957
|
+
}
|
|
958
|
+
function makeNotificationNextSteps(result) {
|
|
959
|
+
return makeNextSteps(result, "notification", [
|
|
960
|
+
"Replace the starter payload and channels with the real user-facing message.",
|
|
961
|
+
"Send the notification through ctx.ports.notifications so delivery can run inline in tests and through jobs in production.",
|
|
962
|
+
]);
|
|
667
963
|
}
|
|
668
964
|
function makeListenerNextSteps(result) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
.
|
|
672
|
-
|
|
673
|
-
.map((file) => ` ${file}`)
|
|
674
|
-
.join("\n");
|
|
675
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} listener in ${result.targetDir}
|
|
676
|
-
|
|
677
|
-
Changed files:
|
|
678
|
-
${changedFiles || " none"}
|
|
679
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
680
|
-
|
|
681
|
-
Next steps:
|
|
682
|
-
Replace the starter handler with the event reaction.
|
|
683
|
-
Register the listener collection from infrastructure startup.`;
|
|
965
|
+
return makeNextSteps(result, "listener", [
|
|
966
|
+
"Replace the starter handler with the event reaction.",
|
|
967
|
+
"Register the listener collection from infrastructure startup.",
|
|
968
|
+
]);
|
|
684
969
|
}
|
|
685
970
|
function makeScheduleNextSteps(result) {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
.
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
return
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
697
|
-
|
|
698
|
-
Next steps:
|
|
699
|
-
Replace the starter payload and handler with the scheduled workflow.
|
|
700
|
-
Trigger it from a cron route, worker, or provider adapter.`;
|
|
971
|
+
return makeNextSteps(result, "schedule", [
|
|
972
|
+
"Replace the starter payload and handler with the scheduled workflow.",
|
|
973
|
+
"Trigger it from a cron route, worker, or provider adapter.",
|
|
974
|
+
]);
|
|
975
|
+
}
|
|
976
|
+
function makeUploadNextSteps(result) {
|
|
977
|
+
return makeNextSteps(result, "upload", [
|
|
978
|
+
"Replace the starter metadata, constraints, key, authorization, and onComplete behavior.",
|
|
979
|
+
"Register the upload in createUploadRouter(...) and expose it with createUploadRoute(...).",
|
|
980
|
+
]);
|
|
701
981
|
}
|
|
702
982
|
export async function main(inputs = process.argv.slice(2), context = { process }) {
|
|
703
983
|
if (inputs.length === 0) {
|
|
704
984
|
await run(cli, ["--help"], context);
|
|
705
|
-
context.process.exitCode =
|
|
985
|
+
context.process.exitCode = 2;
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
// Shell completion scripts call `beignet completion propose <words...>`.
|
|
989
|
+
// Handle it before stricli parses the inputs: the trailing words are a
|
|
990
|
+
// partial command line, not flags for this CLI.
|
|
991
|
+
if (inputs[0] === "completion" && inputs[1] === "propose") {
|
|
992
|
+
await writeCompletionProposals(inputs.slice(2), context);
|
|
706
993
|
return;
|
|
707
994
|
}
|
|
708
995
|
await run(cli, inputs, context);
|
|
709
996
|
normalizeExitCode(context.process);
|
|
710
997
|
}
|
|
998
|
+
async function writeCompletionProposals(inputs, context) {
|
|
999
|
+
try {
|
|
1000
|
+
const proposals = await proposeCompletions(cli, inputs.length === 0 ? [""] : inputs, context);
|
|
1001
|
+
for (const proposal of proposals) {
|
|
1002
|
+
context.process.stdout.write(`${proposal.completion}\n`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
catch {
|
|
1006
|
+
// Completion failures must never break the shell; propose nothing.
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
// Stricli reports usage and internal failures as negative ExitCode values
|
|
1010
|
+
// (-5..-1). Node keeps the negative value on process.exitCode while Bun wraps
|
|
1011
|
+
// it to 251..255 on assignment, so both forms map to the usage exit code.
|
|
1012
|
+
const stricliErrorExitCodes = new Set([
|
|
1013
|
+
-5, -4, -3, -2, -1, 251, 252, 253, 254, 255,
|
|
1014
|
+
]);
|
|
711
1015
|
function normalizeExitCode(proc) {
|
|
712
|
-
if (typeof proc.exitCode
|
|
713
|
-
|
|
1016
|
+
if (typeof proc.exitCode !== "number") {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (stricliErrorExitCodes.has(proc.exitCode)) {
|
|
1020
|
+
proc.exitCode = 2;
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (proc.exitCode > 127) {
|
|
714
1024
|
proc.exitCode = 1;
|
|
715
1025
|
}
|
|
716
1026
|
}
|
|
@@ -729,7 +1039,7 @@ function isCliEntrypoint() {
|
|
|
729
1039
|
if (isCliEntrypoint()) {
|
|
730
1040
|
main().catch((error) => {
|
|
731
1041
|
console.error(error instanceof Error ? error.message : String(error));
|
|
732
|
-
process.exitCode =
|
|
1042
|
+
process.exitCode = 2;
|
|
733
1043
|
});
|
|
734
1044
|
}
|
|
735
1045
|
//# sourceMappingURL=index.js.map
|