@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/src/index.ts
CHANGED
|
@@ -7,80 +7,59 @@ import {
|
|
|
7
7
|
buildRouteMap,
|
|
8
8
|
type CommandContext,
|
|
9
9
|
type FlagParametersForType,
|
|
10
|
+
proposeCompletions,
|
|
10
11
|
run,
|
|
11
12
|
type StricliDynamicCommandContext,
|
|
12
13
|
type StricliProcess,
|
|
13
14
|
text_en,
|
|
14
15
|
} from "@stricli/core";
|
|
15
|
-
import type { CreateOptions } from "./create.js";
|
|
16
|
-
import { createProject } from "./create.js";
|
|
17
|
-
import {
|
|
18
|
-
applyDoctorFixes,
|
|
19
|
-
formatDoctor,
|
|
20
|
-
formatRoutes,
|
|
21
|
-
inspectApp,
|
|
22
|
-
} from "./inspect.js";
|
|
23
|
-
import { formatLint, lintApp } from "./lint.js";
|
|
24
|
-
import {
|
|
25
|
-
makeAdapter,
|
|
26
|
-
makeContract,
|
|
27
|
-
makeEvent,
|
|
28
|
-
makeJob,
|
|
29
|
-
makeListener,
|
|
30
|
-
makePolicy,
|
|
31
|
-
makePort,
|
|
32
|
-
makeResource,
|
|
33
|
-
makeSchedule,
|
|
34
|
-
makeTest,
|
|
35
|
-
makeUseCase,
|
|
36
|
-
} from "./make.js";
|
|
37
16
|
import {
|
|
17
|
+
type CompletionShell,
|
|
18
|
+
completionShellChoices,
|
|
38
19
|
type FeatureName,
|
|
39
20
|
featureChoices,
|
|
40
21
|
type IntegrationName,
|
|
41
22
|
integrationChoices,
|
|
23
|
+
type MakeFeatureAddon,
|
|
24
|
+
makeFeatureAddonChoices,
|
|
42
25
|
type PackageManager,
|
|
43
26
|
type PresetName,
|
|
27
|
+
packageManagerChoices,
|
|
44
28
|
presetChoices,
|
|
45
29
|
type TemplateName,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
createProject,
|
|
52
|
-
formatDoctor,
|
|
53
|
-
formatLint,
|
|
54
|
-
formatRoutes,
|
|
55
|
-
type IntegrationName,
|
|
56
|
-
inspectApp,
|
|
57
|
-
lintApp,
|
|
58
|
-
makeAdapter,
|
|
59
|
-
makeContract,
|
|
60
|
-
makeEvent,
|
|
61
|
-
makeJob,
|
|
62
|
-
makeListener,
|
|
63
|
-
makePolicy,
|
|
64
|
-
makePort,
|
|
65
|
-
makeResource,
|
|
66
|
-
makeSchedule,
|
|
67
|
-
makeTest,
|
|
68
|
-
makeUseCase,
|
|
69
|
-
};
|
|
30
|
+
templateChoices,
|
|
31
|
+
} from "./choices.js";
|
|
32
|
+
// Type-only: the runtime module stays behind the create command's lazy loader.
|
|
33
|
+
import type { CreateSelections } from "./create-prompts.js";
|
|
34
|
+
import { getCliVersion } from "./version.js";
|
|
70
35
|
|
|
71
36
|
type CliContext = CommandContext & {
|
|
72
37
|
readonly process: StricliProcess;
|
|
73
38
|
};
|
|
74
39
|
|
|
40
|
+
type OutputFormat = "human" | "json" | "github";
|
|
41
|
+
|
|
42
|
+
const outputFormatChoices = [
|
|
43
|
+
"human",
|
|
44
|
+
"json",
|
|
45
|
+
"github",
|
|
46
|
+
] as const satisfies readonly OutputFormat[];
|
|
47
|
+
|
|
75
48
|
type CreateFlags = {
|
|
76
49
|
template: TemplateName;
|
|
77
|
-
preset
|
|
50
|
+
preset?: PresetName;
|
|
78
51
|
packageManager?: PackageManager;
|
|
79
|
-
feature?: readonly FeatureName[];
|
|
80
52
|
features?: readonly FeatureName[];
|
|
81
|
-
integration?: readonly IntegrationName[];
|
|
82
53
|
integrations?: readonly IntegrationName[];
|
|
54
|
+
yes?: boolean;
|
|
83
55
|
force?: boolean;
|
|
56
|
+
dryRun?: boolean;
|
|
57
|
+
json?: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type CompletionFlags = {
|
|
61
|
+
shell?: CompletionShell;
|
|
62
|
+
json?: boolean;
|
|
84
63
|
};
|
|
85
64
|
|
|
86
65
|
type MakeFlags = {
|
|
@@ -89,6 +68,17 @@ type MakeFlags = {
|
|
|
89
68
|
json?: boolean;
|
|
90
69
|
};
|
|
91
70
|
|
|
71
|
+
type MakeResourceFlags = MakeFlags & {
|
|
72
|
+
auth?: boolean;
|
|
73
|
+
tenant?: boolean;
|
|
74
|
+
events?: boolean;
|
|
75
|
+
softDelete?: boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type MakeFeatureFlags = MakeFlags & {
|
|
79
|
+
with?: readonly MakeFeatureAddon[];
|
|
80
|
+
};
|
|
81
|
+
|
|
92
82
|
type MakeListenerFlags = MakeFlags & {
|
|
93
83
|
event?: string;
|
|
94
84
|
};
|
|
@@ -99,25 +89,61 @@ type MakeScheduleFlags = MakeFlags & {
|
|
|
99
89
|
route?: boolean;
|
|
100
90
|
};
|
|
101
91
|
|
|
102
|
-
type
|
|
92
|
+
type RoutesFlags = {
|
|
93
|
+
json?: boolean;
|
|
94
|
+
cwd?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type LintFlags = {
|
|
98
|
+
json?: boolean;
|
|
99
|
+
cwd?: string;
|
|
100
|
+
format?: OutputFormat;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type DbFlags = {
|
|
103
104
|
json?: boolean;
|
|
105
|
+
dryRun?: boolean;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
type TaskRunFlags = {
|
|
109
|
+
json?: boolean;
|
|
110
|
+
input?: string;
|
|
111
|
+
module?: string;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
type OutboxDrainFlags = {
|
|
115
|
+
json?: boolean;
|
|
116
|
+
module?: string;
|
|
117
|
+
batchSize?: number;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
type ScheduleRunFlags = {
|
|
121
|
+
json?: boolean;
|
|
122
|
+
module?: string;
|
|
123
|
+
payload?: string;
|
|
124
|
+
id?: string;
|
|
125
|
+
attempt?: number;
|
|
126
|
+
scheduledAt?: string;
|
|
127
|
+
triggeredAt?: string;
|
|
128
|
+
source?: string;
|
|
104
129
|
};
|
|
105
130
|
|
|
106
131
|
type DoctorFlags = {
|
|
107
132
|
json?: boolean;
|
|
108
133
|
strict?: boolean;
|
|
109
134
|
fix?: boolean;
|
|
135
|
+
cwd?: string;
|
|
136
|
+
format?: OutputFormat;
|
|
110
137
|
};
|
|
111
138
|
|
|
112
|
-
const templateChoices = ["next"] as const satisfies readonly TemplateName[];
|
|
113
|
-
const packageManagerChoices = [
|
|
114
|
-
"bun",
|
|
115
|
-
"npm",
|
|
116
|
-
"pnpm",
|
|
117
|
-
"yarn",
|
|
118
|
-
] as const satisfies readonly PackageManager[];
|
|
119
|
-
|
|
120
139
|
const parseString = (input: string): string => input;
|
|
140
|
+
const parsePositiveInteger = (input: string): number => {
|
|
141
|
+
const value = Number(input);
|
|
142
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
143
|
+
throw new Error("Expected a positive integer.");
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
};
|
|
121
147
|
|
|
122
148
|
const forceFlag = {
|
|
123
149
|
kind: "boolean",
|
|
@@ -148,15 +174,78 @@ const parsedStringFlag = (brief: string) =>
|
|
|
148
174
|
brief,
|
|
149
175
|
}) as const;
|
|
150
176
|
|
|
177
|
+
const cwdFlag = parsedStringFlag(
|
|
178
|
+
"App directory to inspect. Defaults to the current working directory.",
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const formatFlag = {
|
|
182
|
+
kind: "enum",
|
|
183
|
+
values: outputFormatChoices,
|
|
184
|
+
optional: true,
|
|
185
|
+
brief:
|
|
186
|
+
"Output format. Defaults to human, or github when GITHUB_ACTIONS=true.",
|
|
187
|
+
} as const;
|
|
188
|
+
|
|
151
189
|
const makeFlagParameters = {
|
|
152
190
|
force: forceFlag,
|
|
153
191
|
dryRun: dryRunFlag,
|
|
154
192
|
json: jsonFlag,
|
|
155
193
|
} satisfies FlagParametersForType<MakeFlags, CliContext>;
|
|
156
194
|
|
|
157
|
-
const
|
|
195
|
+
const routesFlagParameters = {
|
|
196
|
+
json: jsonFlag,
|
|
197
|
+
cwd: cwdFlag,
|
|
198
|
+
} satisfies FlagParametersForType<RoutesFlags, CliContext>;
|
|
199
|
+
|
|
200
|
+
const lintFlagParameters = {
|
|
201
|
+
json: jsonFlag,
|
|
202
|
+
cwd: cwdFlag,
|
|
203
|
+
format: formatFlag,
|
|
204
|
+
} satisfies FlagParametersForType<LintFlags, CliContext>;
|
|
205
|
+
|
|
206
|
+
const dbFlagParameters = {
|
|
158
207
|
json: jsonFlag,
|
|
159
|
-
|
|
208
|
+
dryRun: dryRunFlag,
|
|
209
|
+
} satisfies FlagParametersForType<DbFlags, CliContext>;
|
|
210
|
+
|
|
211
|
+
const taskRunFlagParameters = {
|
|
212
|
+
json: jsonFlag,
|
|
213
|
+
input: parsedStringFlag("JSON input passed to the task schema."),
|
|
214
|
+
module: parsedStringFlag(
|
|
215
|
+
"Task registry module. Defaults to server/tasks.ts.",
|
|
216
|
+
),
|
|
217
|
+
} satisfies FlagParametersForType<TaskRunFlags, CliContext>;
|
|
218
|
+
|
|
219
|
+
const outboxDrainFlagParameters = {
|
|
220
|
+
json: jsonFlag,
|
|
221
|
+
module: parsedStringFlag(
|
|
222
|
+
"Outbox registry module. Defaults to server/outbox.ts.",
|
|
223
|
+
),
|
|
224
|
+
batchSize: {
|
|
225
|
+
kind: "parsed",
|
|
226
|
+
parse: parsePositiveInteger,
|
|
227
|
+
optional: true,
|
|
228
|
+
brief: "Maximum messages to claim in one drain pass.",
|
|
229
|
+
},
|
|
230
|
+
} satisfies FlagParametersForType<OutboxDrainFlags, CliContext>;
|
|
231
|
+
|
|
232
|
+
const scheduleRunFlagParameters = {
|
|
233
|
+
json: jsonFlag,
|
|
234
|
+
module: parsedStringFlag(
|
|
235
|
+
"Schedule registry module. Defaults to server/schedules.ts.",
|
|
236
|
+
),
|
|
237
|
+
payload: parsedStringFlag("JSON payload passed to the schedule schema."),
|
|
238
|
+
id: parsedStringFlag("Provider or app schedule run ID."),
|
|
239
|
+
attempt: {
|
|
240
|
+
kind: "parsed",
|
|
241
|
+
parse: parsePositiveInteger,
|
|
242
|
+
optional: true,
|
|
243
|
+
brief: "One-based provider attempt number.",
|
|
244
|
+
},
|
|
245
|
+
scheduledAt: parsedStringFlag("Provider scheduled timestamp."),
|
|
246
|
+
triggeredAt: parsedStringFlag("Schedule trigger timestamp."),
|
|
247
|
+
source: parsedStringFlag("Provider or app source label."),
|
|
248
|
+
} satisfies FlagParametersForType<ScheduleRunFlags, CliContext>;
|
|
160
249
|
|
|
161
250
|
const namePositional = {
|
|
162
251
|
kind: "tuple",
|
|
@@ -169,12 +258,40 @@ const namePositional = {
|
|
|
169
258
|
],
|
|
170
259
|
} as const;
|
|
171
260
|
|
|
172
|
-
|
|
261
|
+
function resolveOutputFormat(flags: {
|
|
262
|
+
json?: boolean;
|
|
263
|
+
format?: OutputFormat;
|
|
264
|
+
}): OutputFormat {
|
|
265
|
+
if (flags.json) {
|
|
266
|
+
if (flags.format && flags.format !== "json") {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`--json conflicts with --format ${flags.format}. Pass one of them.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return "json";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (flags.format) return flags.format;
|
|
275
|
+
return process.env.GITHUB_ACTIONS === "true" ? "github" : "human";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function useColor(): boolean {
|
|
279
|
+
return process.stdout.isTTY === true && !process.env.NO_COLOR;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const createCommand = buildCommand<
|
|
283
|
+
CreateFlags,
|
|
284
|
+
[string | undefined],
|
|
285
|
+
CliContext
|
|
286
|
+
>({
|
|
173
287
|
docs: {
|
|
174
288
|
brief: "Create a new Beignet app.",
|
|
175
289
|
fullDescription: `Available features: ${featureChoices.join(", ")}
|
|
176
290
|
|
|
177
|
-
Available integrations: ${integrationChoices.join(", ")}
|
|
291
|
+
Available integrations: ${integrationChoices.join(", ")}
|
|
292
|
+
|
|
293
|
+
Running create on an interactive terminal without selection flags opens a
|
|
294
|
+
prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
|
|
178
295
|
},
|
|
179
296
|
parameters: {
|
|
180
297
|
flags: {
|
|
@@ -187,8 +304,8 @@ Available integrations: ${integrationChoices.join(", ")}`,
|
|
|
187
304
|
preset: {
|
|
188
305
|
kind: "enum",
|
|
189
306
|
values: presetChoices,
|
|
190
|
-
|
|
191
|
-
brief: "Starter preset to generate.",
|
|
307
|
+
optional: true,
|
|
308
|
+
brief: "Starter preset to generate. Defaults to standard.",
|
|
192
309
|
},
|
|
193
310
|
packageManager: {
|
|
194
311
|
kind: "enum",
|
|
@@ -196,35 +313,29 @@ Available integrations: ${integrationChoices.join(", ")}`,
|
|
|
196
313
|
optional: true,
|
|
197
314
|
brief: "Package manager to use in next-step commands.",
|
|
198
315
|
},
|
|
199
|
-
feature: {
|
|
200
|
-
kind: "enum",
|
|
201
|
-
values: featureChoices,
|
|
202
|
-
optional: true,
|
|
203
|
-
variadic: true,
|
|
204
|
-
brief: "Add a starter feature. Repeatable.",
|
|
205
|
-
},
|
|
206
316
|
features: {
|
|
207
317
|
kind: "enum",
|
|
208
318
|
values: featureChoices,
|
|
209
319
|
optional: true,
|
|
210
320
|
variadic: ",",
|
|
211
|
-
brief: "Add comma-separated
|
|
212
|
-
},
|
|
213
|
-
integration: {
|
|
214
|
-
kind: "enum",
|
|
215
|
-
values: integrationChoices,
|
|
216
|
-
optional: true,
|
|
217
|
-
variadic: true,
|
|
218
|
-
brief: "Add a first-party integration. Repeatable.",
|
|
321
|
+
brief: "Add starter features as a comma-separated list.",
|
|
219
322
|
},
|
|
220
323
|
integrations: {
|
|
221
324
|
kind: "enum",
|
|
222
325
|
values: integrationChoices,
|
|
223
326
|
optional: true,
|
|
224
327
|
variadic: ",",
|
|
225
|
-
brief: "Add
|
|
328
|
+
brief: "Add first-party integrations as a comma-separated list.",
|
|
329
|
+
},
|
|
330
|
+
yes: {
|
|
331
|
+
kind: "boolean",
|
|
332
|
+
optional: true,
|
|
333
|
+
withNegated: false,
|
|
334
|
+
brief: "Skip interactive prompts and use the defaults.",
|
|
226
335
|
},
|
|
227
336
|
force: forceFlag,
|
|
337
|
+
dryRun: dryRunFlag,
|
|
338
|
+
json: jsonFlag,
|
|
228
339
|
},
|
|
229
340
|
positional: {
|
|
230
341
|
kind: "tuple",
|
|
@@ -233,45 +344,92 @@ Available integrations: ${integrationChoices.join(", ")}`,
|
|
|
233
344
|
parse: parseString,
|
|
234
345
|
placeholder: "directory",
|
|
235
346
|
brief: "Project directory to create.",
|
|
347
|
+
optional: true,
|
|
236
348
|
},
|
|
237
349
|
],
|
|
238
350
|
},
|
|
239
351
|
},
|
|
240
|
-
async
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
352
|
+
loader: async () => {
|
|
353
|
+
const { createProject } = await import("./create.js");
|
|
354
|
+
const { promptCreateSelections, shouldPromptInteractively } = await import(
|
|
355
|
+
"./create-prompts.js"
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
return async function runCreate(
|
|
359
|
+
this: CliContext,
|
|
360
|
+
flags: CreateFlags,
|
|
361
|
+
directory: string | undefined,
|
|
362
|
+
) {
|
|
363
|
+
let selections: CreateSelections = {
|
|
364
|
+
directory,
|
|
365
|
+
preset: flags.preset,
|
|
366
|
+
features: flags.features,
|
|
367
|
+
integrations: flags.integrations,
|
|
368
|
+
packageManager: flags.packageManager,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const interactive = shouldPromptInteractively(
|
|
372
|
+
{ ...selections, yes: flags.yes },
|
|
373
|
+
{
|
|
374
|
+
stdin: process.stdin.isTTY === true,
|
|
375
|
+
stdout: process.stdout.isTTY === true,
|
|
376
|
+
},
|
|
377
|
+
);
|
|
378
|
+
if (interactive) {
|
|
379
|
+
const answers = await promptCreateSelections(selections);
|
|
380
|
+
if (answers === undefined) {
|
|
381
|
+
this.process.stderr.write("Cancelled.\n");
|
|
382
|
+
this.process.exitCode = 2;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
selections = answers;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (selections.directory === undefined) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
"Project directory is required. Pass beignet create <directory>, or run beignet create in an interactive terminal.",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const integrations = uniqueValues([...(selections.integrations ?? [])]);
|
|
395
|
+
const result = await createProject({
|
|
396
|
+
name: selections.directory,
|
|
397
|
+
template: flags.template,
|
|
398
|
+
preset: selections.preset ?? "standard",
|
|
399
|
+
features: uniqueValues([...(selections.features ?? [])]),
|
|
400
|
+
packageManager: selections.packageManager,
|
|
401
|
+
integrations,
|
|
402
|
+
force: Boolean(flags.force),
|
|
403
|
+
dryRun: Boolean(flags.dryRun),
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
writeOutput(
|
|
407
|
+
this,
|
|
408
|
+
flags.json
|
|
409
|
+
? JSON.stringify(result, null, 2)
|
|
410
|
+
: nextSteps(result, { integrations }),
|
|
411
|
+
);
|
|
412
|
+
};
|
|
259
413
|
},
|
|
260
414
|
});
|
|
261
415
|
|
|
262
|
-
const routesCommand = buildCommand<
|
|
416
|
+
const routesCommand = buildCommand<RoutesFlags, [], CliContext>({
|
|
263
417
|
docs: {
|
|
264
418
|
brief: "Inspect registered Beignet routes.",
|
|
265
419
|
},
|
|
266
420
|
parameters: {
|
|
267
|
-
flags:
|
|
421
|
+
flags: routesFlagParameters,
|
|
268
422
|
},
|
|
269
|
-
async
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
423
|
+
loader: async () => {
|
|
424
|
+
const { formatRoutes, inspectApp } = await import("./inspect.js");
|
|
425
|
+
|
|
426
|
+
return async function runRoutes(this: CliContext, flags: RoutesFlags) {
|
|
427
|
+
const result = await inspectApp({ cwd: flags.cwd });
|
|
428
|
+
writeOutput(
|
|
429
|
+
this,
|
|
430
|
+
flags.json ? JSON.stringify(result, null, 2) : formatRoutes(result),
|
|
431
|
+
);
|
|
432
|
+
};
|
|
275
433
|
},
|
|
276
434
|
});
|
|
277
435
|
|
|
@@ -294,252 +452,484 @@ const doctorCommand = buildCommand<DoctorFlags, [], CliContext>({
|
|
|
294
452
|
withNegated: false,
|
|
295
453
|
brief: "Apply low-risk fixes before reporting.",
|
|
296
454
|
},
|
|
455
|
+
cwd: cwdFlag,
|
|
456
|
+
format: formatFlag,
|
|
297
457
|
},
|
|
298
458
|
},
|
|
299
|
-
async
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
459
|
+
loader: async () => {
|
|
460
|
+
const { applyDoctorFixes, formatDoctor, formatDoctorGithub, inspectApp } =
|
|
461
|
+
await import("./inspect.js");
|
|
462
|
+
|
|
463
|
+
return async function runDoctor(this: CliContext, flags: DoctorFlags) {
|
|
464
|
+
const format = resolveOutputFormat(flags);
|
|
465
|
+
const fixes = flags.fix
|
|
466
|
+
? await applyDoctorFixes({
|
|
467
|
+
cwd: flags.cwd,
|
|
468
|
+
strict: Boolean(flags.strict),
|
|
469
|
+
})
|
|
470
|
+
: [];
|
|
471
|
+
const result = await inspectApp({
|
|
472
|
+
cwd: flags.cwd,
|
|
473
|
+
strict: Boolean(flags.strict),
|
|
474
|
+
});
|
|
475
|
+
result.fixes = fixes;
|
|
476
|
+
writeOutput(
|
|
477
|
+
this,
|
|
478
|
+
format === "json"
|
|
479
|
+
? JSON.stringify(result, null, 2)
|
|
480
|
+
: format === "github"
|
|
481
|
+
? formatDoctorGithub(result)
|
|
482
|
+
: formatDoctor(result, { color: useColor() }),
|
|
483
|
+
);
|
|
484
|
+
if (
|
|
485
|
+
result.diagnostics.some(
|
|
486
|
+
(diagnostic) =>
|
|
487
|
+
diagnostic.severity === "error" ||
|
|
488
|
+
(flags.strict && diagnostic.severity === "warning"),
|
|
489
|
+
)
|
|
490
|
+
) {
|
|
491
|
+
this.process.exitCode = 1;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
318
494
|
},
|
|
319
495
|
});
|
|
320
496
|
|
|
321
|
-
const lintCommand = buildCommand<
|
|
497
|
+
const lintCommand = buildCommand<LintFlags, [], CliContext>({
|
|
322
498
|
docs: {
|
|
323
499
|
brief: "Check Beignet dependency direction conventions.",
|
|
324
500
|
},
|
|
325
501
|
parameters: {
|
|
326
|
-
flags:
|
|
502
|
+
flags: lintFlagParameters,
|
|
327
503
|
},
|
|
328
|
-
async
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
504
|
+
loader: async () => {
|
|
505
|
+
const { formatLint, formatLintGithub, lintApp } = await import("./lint.js");
|
|
506
|
+
|
|
507
|
+
return async function runLint(this: CliContext, flags: LintFlags) {
|
|
508
|
+
const format = resolveOutputFormat(flags);
|
|
509
|
+
const result = await lintApp({ cwd: flags.cwd });
|
|
510
|
+
writeOutput(
|
|
511
|
+
this,
|
|
512
|
+
format === "json"
|
|
513
|
+
? JSON.stringify(result, null, 2)
|
|
514
|
+
: format === "github"
|
|
515
|
+
? formatLintGithub(result)
|
|
516
|
+
: formatLint(result, { color: useColor() }),
|
|
517
|
+
);
|
|
518
|
+
if (result.diagnostics.length > 0) {
|
|
519
|
+
this.process.exitCode = 1;
|
|
520
|
+
}
|
|
521
|
+
};
|
|
337
522
|
},
|
|
338
523
|
});
|
|
339
524
|
|
|
340
|
-
|
|
525
|
+
type DatabaseCommandName = "generate" | "migrate" | "reset" | "seed";
|
|
526
|
+
|
|
527
|
+
function databaseCommand(command: DatabaseCommandName) {
|
|
528
|
+
return buildCommand<DbFlags, [], CliContext>({
|
|
529
|
+
docs: {
|
|
530
|
+
brief: `Run the app's db:${command} script.`,
|
|
531
|
+
},
|
|
532
|
+
parameters: {
|
|
533
|
+
flags: dbFlagParameters,
|
|
534
|
+
},
|
|
535
|
+
loader: async () => {
|
|
536
|
+
const { runDatabaseCommand } = await import("./db.js");
|
|
537
|
+
|
|
538
|
+
return async function runDb(this: CliContext, flags: DbFlags) {
|
|
539
|
+
const result = await runDatabaseCommand({
|
|
540
|
+
command,
|
|
541
|
+
captureOutput: Boolean(flags.json),
|
|
542
|
+
dryRun: Boolean(flags.dryRun),
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
writeOutput(
|
|
546
|
+
this,
|
|
547
|
+
flags.json
|
|
548
|
+
? JSON.stringify(result, null, 2)
|
|
549
|
+
: databaseCommandNextSteps(result),
|
|
550
|
+
);
|
|
551
|
+
if (result.exitCode !== 0) {
|
|
552
|
+
this.process.exitCode = result.exitCode;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const dbRoutes = buildRouteMap({
|
|
341
560
|
docs: {
|
|
342
|
-
brief: "
|
|
561
|
+
brief: "Run Beignet database lifecycle commands.",
|
|
343
562
|
},
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const result = await makeResource({
|
|
350
|
-
name,
|
|
351
|
-
force: Boolean(flags.force),
|
|
352
|
-
dryRun: Boolean(flags.dryRun),
|
|
353
|
-
});
|
|
354
|
-
writeOutput(
|
|
355
|
-
this,
|
|
356
|
-
flags.json
|
|
357
|
-
? JSON.stringify(result, null, 2)
|
|
358
|
-
: makeResourceNextSteps(result),
|
|
359
|
-
);
|
|
563
|
+
routes: {
|
|
564
|
+
generate: databaseCommand("generate"),
|
|
565
|
+
migrate: databaseCommand("migrate"),
|
|
566
|
+
reset: databaseCommand("reset"),
|
|
567
|
+
seed: databaseCommand("seed"),
|
|
360
568
|
},
|
|
361
569
|
});
|
|
362
570
|
|
|
363
|
-
const
|
|
571
|
+
const taskRunCommand = buildCommand<TaskRunFlags, [string], CliContext>({
|
|
364
572
|
docs: {
|
|
365
|
-
brief: "
|
|
573
|
+
brief: "Run an app-owned operational task.",
|
|
366
574
|
},
|
|
367
575
|
parameters: {
|
|
368
|
-
flags:
|
|
576
|
+
flags: taskRunFlagParameters,
|
|
369
577
|
positional: namePositional,
|
|
370
578
|
},
|
|
371
|
-
async
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
:
|
|
382
|
-
|
|
579
|
+
loader: async () => {
|
|
580
|
+
const { runAppTask } = await import("./task.js");
|
|
581
|
+
|
|
582
|
+
return async function runTask(
|
|
583
|
+
this: CliContext,
|
|
584
|
+
flags: TaskRunFlags,
|
|
585
|
+
name: string,
|
|
586
|
+
) {
|
|
587
|
+
const result = await runAppTask({
|
|
588
|
+
name,
|
|
589
|
+
input: flags.input,
|
|
590
|
+
modulePath: flags.module,
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
writeOutput(
|
|
594
|
+
this,
|
|
595
|
+
flags.json
|
|
596
|
+
? JSON.stringify(result, null, 2)
|
|
597
|
+
: appTaskRunNextSteps(result),
|
|
598
|
+
);
|
|
599
|
+
};
|
|
383
600
|
},
|
|
384
601
|
});
|
|
385
602
|
|
|
386
|
-
const
|
|
603
|
+
const taskRoutes = buildRouteMap({
|
|
387
604
|
docs: {
|
|
388
|
-
brief: "
|
|
605
|
+
brief: "Run Beignet app operational tasks.",
|
|
389
606
|
},
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
positional: namePositional,
|
|
393
|
-
},
|
|
394
|
-
async func(flags, name) {
|
|
395
|
-
const result = await makeUseCase({
|
|
396
|
-
name,
|
|
397
|
-
force: Boolean(flags.force),
|
|
398
|
-
dryRun: Boolean(flags.dryRun),
|
|
399
|
-
});
|
|
400
|
-
writeOutput(
|
|
401
|
-
this,
|
|
402
|
-
flags.json
|
|
403
|
-
? JSON.stringify(result, null, 2)
|
|
404
|
-
: makeUseCaseNextSteps(result),
|
|
405
|
-
);
|
|
607
|
+
routes: {
|
|
608
|
+
run: taskRunCommand,
|
|
406
609
|
},
|
|
407
610
|
});
|
|
408
611
|
|
|
409
|
-
const
|
|
612
|
+
const outboxDrainCommand = buildCommand<OutboxDrainFlags, [], CliContext>({
|
|
410
613
|
docs: {
|
|
411
|
-
brief: "
|
|
614
|
+
brief: "Run one app-owned outbox drain pass.",
|
|
412
615
|
},
|
|
413
616
|
parameters: {
|
|
414
|
-
flags:
|
|
415
|
-
positional: namePositional,
|
|
617
|
+
flags: outboxDrainFlagParameters,
|
|
416
618
|
},
|
|
417
|
-
async
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
619
|
+
loader: async () => {
|
|
620
|
+
const { runOutboxDrain } = await import("./outbox.js");
|
|
621
|
+
|
|
622
|
+
return async function runDrain(this: CliContext, flags: OutboxDrainFlags) {
|
|
623
|
+
const result = await runOutboxDrain({
|
|
624
|
+
modulePath: flags.module,
|
|
625
|
+
batchSize: flags.batchSize,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
writeOutput(
|
|
629
|
+
this,
|
|
630
|
+
flags.json
|
|
631
|
+
? JSON.stringify(result, null, 2)
|
|
632
|
+
: outboxDrainNextSteps(result),
|
|
633
|
+
);
|
|
634
|
+
};
|
|
427
635
|
},
|
|
428
636
|
});
|
|
429
637
|
|
|
430
|
-
const
|
|
638
|
+
const outboxRoutes = buildRouteMap({
|
|
431
639
|
docs: {
|
|
432
|
-
brief: "
|
|
640
|
+
brief: "Run Beignet outbox operations.",
|
|
433
641
|
},
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
positional: namePositional,
|
|
642
|
+
routes: {
|
|
643
|
+
drain: outboxDrainCommand,
|
|
437
644
|
},
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
flags
|
|
447
|
-
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
const scheduleRunCommand = buildCommand<ScheduleRunFlags, [string], CliContext>(
|
|
648
|
+
{
|
|
649
|
+
docs: {
|
|
650
|
+
brief: "Run an app-owned schedule.",
|
|
651
|
+
},
|
|
652
|
+
parameters: {
|
|
653
|
+
flags: scheduleRunFlagParameters,
|
|
654
|
+
positional: namePositional,
|
|
655
|
+
},
|
|
656
|
+
loader: async () => {
|
|
657
|
+
const { runAppSchedule } = await import("./schedule.js");
|
|
658
|
+
|
|
659
|
+
return async function runSchedule(
|
|
660
|
+
this: CliContext,
|
|
661
|
+
flags: ScheduleRunFlags,
|
|
662
|
+
name: string,
|
|
663
|
+
) {
|
|
664
|
+
const result = await runAppSchedule({
|
|
665
|
+
name,
|
|
666
|
+
modulePath: flags.module,
|
|
667
|
+
payload: flags.payload,
|
|
668
|
+
id: flags.id,
|
|
669
|
+
attempt: flags.attempt,
|
|
670
|
+
scheduledAt: flags.scheduledAt,
|
|
671
|
+
triggeredAt: flags.triggeredAt,
|
|
672
|
+
source: flags.source,
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
writeOutput(
|
|
676
|
+
this,
|
|
677
|
+
flags.json
|
|
678
|
+
? JSON.stringify(result, null, 2)
|
|
679
|
+
: scheduleRunNextSteps(result),
|
|
680
|
+
);
|
|
681
|
+
};
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
const scheduleRoutes = buildRouteMap({
|
|
687
|
+
docs: {
|
|
688
|
+
brief: "Run Beignet schedule operations.",
|
|
689
|
+
},
|
|
690
|
+
routes: {
|
|
691
|
+
run: scheduleRunCommand,
|
|
448
692
|
},
|
|
449
693
|
});
|
|
450
694
|
|
|
451
|
-
const
|
|
695
|
+
const completionShellFlagParameters = {
|
|
696
|
+
shell: {
|
|
697
|
+
kind: "enum",
|
|
698
|
+
values: completionShellChoices,
|
|
699
|
+
optional: true,
|
|
700
|
+
brief: "Shell to manage completions for. Defaults to $SHELL.",
|
|
701
|
+
},
|
|
702
|
+
json: jsonFlag,
|
|
703
|
+
} satisfies FlagParametersForType<CompletionFlags, CliContext>;
|
|
704
|
+
|
|
705
|
+
const completionInstallCommand = buildCommand<CompletionFlags, [], CliContext>({
|
|
452
706
|
docs: {
|
|
453
|
-
brief: "
|
|
707
|
+
brief: "Install beignet shell completions for bash or zsh.",
|
|
454
708
|
},
|
|
455
709
|
parameters: {
|
|
456
|
-
flags:
|
|
457
|
-
positional: namePositional,
|
|
710
|
+
flags: completionShellFlagParameters,
|
|
458
711
|
},
|
|
459
|
-
async
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
712
|
+
loader: async () => {
|
|
713
|
+
const { installCompletions } = await import("./completion.js");
|
|
714
|
+
|
|
715
|
+
return async function runInstall(this: CliContext, flags: CompletionFlags) {
|
|
716
|
+
const result = await installCompletions({ shell: flags.shell });
|
|
717
|
+
writeOutput(
|
|
718
|
+
this,
|
|
719
|
+
flags.json
|
|
720
|
+
? JSON.stringify(result, null, 2)
|
|
721
|
+
: `${result.status === "updated" ? "Updated" : "Installed"} ${result.shell} completions in ${result.file}
|
|
722
|
+
|
|
723
|
+
Restart your shell or source the file to activate them.`,
|
|
724
|
+
);
|
|
725
|
+
};
|
|
471
726
|
},
|
|
472
727
|
});
|
|
473
728
|
|
|
474
|
-
const
|
|
729
|
+
const completionUninstallCommand = buildCommand<
|
|
730
|
+
CompletionFlags,
|
|
731
|
+
[],
|
|
732
|
+
CliContext
|
|
733
|
+
>({
|
|
475
734
|
docs: {
|
|
476
|
-
brief: "
|
|
735
|
+
brief: "Remove beignet shell completions for bash or zsh.",
|
|
477
736
|
},
|
|
478
737
|
parameters: {
|
|
479
|
-
flags:
|
|
480
|
-
positional: namePositional,
|
|
738
|
+
flags: completionShellFlagParameters,
|
|
481
739
|
},
|
|
482
|
-
async
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
740
|
+
loader: async () => {
|
|
741
|
+
const { uninstallCompletions } = await import("./completion.js");
|
|
742
|
+
|
|
743
|
+
return async function runUninstall(
|
|
744
|
+
this: CliContext,
|
|
745
|
+
flags: CompletionFlags,
|
|
746
|
+
) {
|
|
747
|
+
const result = await uninstallCompletions({ shell: flags.shell });
|
|
748
|
+
writeOutput(
|
|
749
|
+
this,
|
|
750
|
+
flags.json
|
|
751
|
+
? JSON.stringify(result, null, 2)
|
|
752
|
+
: result.status === "removed"
|
|
753
|
+
? `Removed ${result.shell} completions from ${result.file}`
|
|
754
|
+
: `No beignet completions found in ${result.file}`,
|
|
755
|
+
);
|
|
756
|
+
};
|
|
494
757
|
},
|
|
495
758
|
});
|
|
496
759
|
|
|
497
|
-
const
|
|
760
|
+
const completionRoutes = buildRouteMap({
|
|
498
761
|
docs: {
|
|
499
|
-
brief: "
|
|
500
|
-
},
|
|
501
|
-
parameters: {
|
|
502
|
-
flags: makeFlagParameters,
|
|
503
|
-
positional: namePositional,
|
|
762
|
+
brief: "Manage beignet shell completions.",
|
|
504
763
|
},
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
force: Boolean(flags.force),
|
|
509
|
-
dryRun: Boolean(flags.dryRun),
|
|
510
|
-
});
|
|
511
|
-
writeOutput(
|
|
512
|
-
this,
|
|
513
|
-
flags.json ? JSON.stringify(result, null, 2) : makeEventNextSteps(result),
|
|
514
|
-
);
|
|
764
|
+
routes: {
|
|
765
|
+
install: completionInstallCommand,
|
|
766
|
+
uninstall: completionUninstallCommand,
|
|
515
767
|
},
|
|
516
768
|
});
|
|
517
769
|
|
|
518
|
-
const
|
|
770
|
+
const makeResourceCommand = buildCommand<
|
|
771
|
+
MakeResourceFlags,
|
|
772
|
+
[string],
|
|
773
|
+
CliContext
|
|
774
|
+
>({
|
|
519
775
|
docs: {
|
|
520
|
-
brief: "Generate a
|
|
776
|
+
brief: "Generate a CRUD-shaped resource slice.",
|
|
521
777
|
},
|
|
522
778
|
parameters: {
|
|
523
|
-
flags:
|
|
779
|
+
flags: {
|
|
780
|
+
...makeFlagParameters,
|
|
781
|
+
auth: {
|
|
782
|
+
kind: "boolean",
|
|
783
|
+
optional: true,
|
|
784
|
+
withNegated: false,
|
|
785
|
+
brief:
|
|
786
|
+
"Generate policy metadata and use-case authorization checks for mutating routes.",
|
|
787
|
+
},
|
|
788
|
+
tenant: {
|
|
789
|
+
kind: "boolean",
|
|
790
|
+
optional: true,
|
|
791
|
+
withNegated: false,
|
|
792
|
+
brief:
|
|
793
|
+
"Generate tenant-scoped schemas, repository methods, and use-case checks.",
|
|
794
|
+
},
|
|
795
|
+
events: {
|
|
796
|
+
kind: "boolean",
|
|
797
|
+
optional: true,
|
|
798
|
+
withNegated: false,
|
|
799
|
+
brief: "Generate domain event definitions and publish resource events.",
|
|
800
|
+
},
|
|
801
|
+
softDelete: {
|
|
802
|
+
kind: "boolean",
|
|
803
|
+
optional: true,
|
|
804
|
+
withNegated: false,
|
|
805
|
+
brief:
|
|
806
|
+
"Generate soft-delete persistence that archives rows with deletedAt.",
|
|
807
|
+
},
|
|
808
|
+
} satisfies FlagParametersForType<MakeResourceFlags, CliContext>,
|
|
524
809
|
positional: namePositional,
|
|
525
810
|
},
|
|
526
|
-
async
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
811
|
+
loader: async () => {
|
|
812
|
+
const { makeResource } = await import("./make.js");
|
|
813
|
+
|
|
814
|
+
return async function runMakeResource(
|
|
815
|
+
this: CliContext,
|
|
816
|
+
flags: MakeResourceFlags,
|
|
817
|
+
name: string,
|
|
818
|
+
) {
|
|
819
|
+
const result = await makeResource({
|
|
820
|
+
name,
|
|
821
|
+
force: Boolean(flags.force),
|
|
822
|
+
dryRun: Boolean(flags.dryRun),
|
|
823
|
+
auth: Boolean(flags.auth),
|
|
824
|
+
tenant: Boolean(flags.tenant),
|
|
825
|
+
events: Boolean(flags.events),
|
|
826
|
+
softDelete: Boolean(flags.softDelete),
|
|
827
|
+
});
|
|
828
|
+
writeOutput(
|
|
829
|
+
this,
|
|
830
|
+
flags.json
|
|
831
|
+
? JSON.stringify(result, null, 2)
|
|
832
|
+
: makeResourceNextSteps(result),
|
|
833
|
+
);
|
|
834
|
+
};
|
|
536
835
|
},
|
|
537
836
|
});
|
|
538
837
|
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
838
|
+
const makeFeatureCommand = buildCommand<MakeFeatureFlags, [string], CliContext>(
|
|
839
|
+
{
|
|
840
|
+
docs: {
|
|
841
|
+
brief: "Generate a contract-first feature slice.",
|
|
842
|
+
},
|
|
843
|
+
parameters: {
|
|
844
|
+
flags: {
|
|
845
|
+
...makeFlagParameters,
|
|
846
|
+
with: {
|
|
847
|
+
kind: "enum",
|
|
848
|
+
values: makeFeatureAddonChoices,
|
|
849
|
+
optional: true,
|
|
850
|
+
variadic: ",",
|
|
851
|
+
brief:
|
|
852
|
+
"Add feature-owned artifacts. Supports policy, task/tasks, event/events, job/jobs, notification/notifications, ui, and upload/uploads.",
|
|
853
|
+
},
|
|
854
|
+
} satisfies FlagParametersForType<MakeFeatureFlags, CliContext>,
|
|
855
|
+
positional: namePositional,
|
|
856
|
+
},
|
|
857
|
+
loader: async () => {
|
|
858
|
+
const { makeFeature } = await import("./make.js");
|
|
859
|
+
|
|
860
|
+
return async function runMakeFeature(
|
|
861
|
+
this: CliContext,
|
|
862
|
+
flags: MakeFeatureFlags,
|
|
863
|
+
name: string,
|
|
864
|
+
) {
|
|
865
|
+
const result = await makeFeature({
|
|
866
|
+
name,
|
|
867
|
+
with: flags.with,
|
|
868
|
+
force: Boolean(flags.force),
|
|
869
|
+
dryRun: Boolean(flags.dryRun),
|
|
870
|
+
});
|
|
871
|
+
writeOutput(
|
|
872
|
+
this,
|
|
873
|
+
flags.json
|
|
874
|
+
? JSON.stringify(result, null, 2)
|
|
875
|
+
: makeFeatureNextSteps(result),
|
|
876
|
+
);
|
|
877
|
+
};
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
type MakeGeneratorName =
|
|
883
|
+
| "makeAdapter"
|
|
884
|
+
| "makeContract"
|
|
885
|
+
| "makeEvent"
|
|
886
|
+
| "makeFactory"
|
|
887
|
+
| "makeJob"
|
|
888
|
+
| "makeNotification"
|
|
889
|
+
| "makePolicy"
|
|
890
|
+
| "makePort"
|
|
891
|
+
| "makeSeed"
|
|
892
|
+
| "makeTask"
|
|
893
|
+
| "makeTest"
|
|
894
|
+
| "makeUpload"
|
|
895
|
+
| "makeUseCase";
|
|
896
|
+
|
|
897
|
+
function simpleMakeCommand(
|
|
898
|
+
generator: MakeGeneratorName,
|
|
899
|
+
brief: string,
|
|
900
|
+
formatNextSteps: (result: MakeNextStepsResult) => string,
|
|
901
|
+
) {
|
|
902
|
+
return buildCommand<MakeFlags, [string], CliContext>({
|
|
903
|
+
docs: {
|
|
904
|
+
brief,
|
|
905
|
+
},
|
|
906
|
+
parameters: {
|
|
907
|
+
flags: makeFlagParameters,
|
|
908
|
+
positional: namePositional,
|
|
909
|
+
},
|
|
910
|
+
loader: async () => {
|
|
911
|
+
const generators = await import("./make.js");
|
|
912
|
+
|
|
913
|
+
return async function runMake(
|
|
914
|
+
this: CliContext,
|
|
915
|
+
flags: MakeFlags,
|
|
916
|
+
name: string,
|
|
917
|
+
) {
|
|
918
|
+
const result = await generators[generator]({
|
|
919
|
+
name,
|
|
920
|
+
force: Boolean(flags.force),
|
|
921
|
+
dryRun: Boolean(flags.dryRun),
|
|
922
|
+
});
|
|
923
|
+
writeOutput(
|
|
924
|
+
this,
|
|
925
|
+
flags.json
|
|
926
|
+
? JSON.stringify(result, null, 2)
|
|
927
|
+
: formatNextSteps(result),
|
|
928
|
+
);
|
|
929
|
+
};
|
|
930
|
+
},
|
|
931
|
+
});
|
|
932
|
+
}
|
|
543
933
|
|
|
544
934
|
const makeListenerCommand = buildCommand<
|
|
545
935
|
MakeListenerFlags,
|
|
@@ -550,43 +940,44 @@ const makeListenerCommand = buildCommand<
|
|
|
550
940
|
brief: "Generate a feature event listener.",
|
|
551
941
|
},
|
|
552
942
|
parameters: {
|
|
553
|
-
flags:
|
|
943
|
+
flags: {
|
|
944
|
+
...makeFlagParameters,
|
|
945
|
+
event: parsedStringFlag(
|
|
946
|
+
"Event to listen to, for example posts/published.",
|
|
947
|
+
),
|
|
948
|
+
} satisfies FlagParametersForType<MakeListenerFlags, CliContext>,
|
|
554
949
|
positional: namePositional,
|
|
555
950
|
},
|
|
556
|
-
async
|
|
557
|
-
|
|
558
|
-
throw new Error(
|
|
559
|
-
"beignet make listener requires --event feature/name, for example --event posts/published.",
|
|
560
|
-
);
|
|
561
|
-
}
|
|
951
|
+
loader: async () => {
|
|
952
|
+
const { makeListener } = await import("./make.js");
|
|
562
953
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
954
|
+
return async function runMakeListener(
|
|
955
|
+
this: CliContext,
|
|
956
|
+
flags: MakeListenerFlags,
|
|
957
|
+
name: string,
|
|
958
|
+
) {
|
|
959
|
+
if (!flags.event) {
|
|
960
|
+
throw new Error(
|
|
961
|
+
"beignet make listener requires --event feature/name, for example --event posts/published.",
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const result = await makeListener({
|
|
966
|
+
name,
|
|
967
|
+
event: flags.event,
|
|
968
|
+
force: Boolean(flags.force),
|
|
969
|
+
dryRun: Boolean(flags.dryRun),
|
|
970
|
+
});
|
|
971
|
+
writeOutput(
|
|
972
|
+
this,
|
|
973
|
+
flags.json
|
|
974
|
+
? JSON.stringify(result, null, 2)
|
|
975
|
+
: makeListenerNextSteps(result),
|
|
976
|
+
);
|
|
977
|
+
};
|
|
575
978
|
},
|
|
576
979
|
});
|
|
577
980
|
|
|
578
|
-
const makeScheduleFlagParameters = {
|
|
579
|
-
...makeFlagParameters,
|
|
580
|
-
cron: parsedStringFlag("Cron expression. Defaults to 0 9 * * *."),
|
|
581
|
-
timezone: parsedStringFlag("IANA timezone, for example America/Chicago."),
|
|
582
|
-
route: {
|
|
583
|
-
kind: "boolean",
|
|
584
|
-
optional: true,
|
|
585
|
-
withNegated: false,
|
|
586
|
-
brief: "Generate a Next.js cron route for this schedule.",
|
|
587
|
-
},
|
|
588
|
-
} satisfies FlagParametersForType<MakeScheduleFlags, CliContext>;
|
|
589
|
-
|
|
590
981
|
const makeScheduleCommand = buildCommand<
|
|
591
982
|
MakeScheduleFlags,
|
|
592
983
|
[string],
|
|
@@ -596,24 +987,42 @@ const makeScheduleCommand = buildCommand<
|
|
|
596
987
|
brief: "Generate a feature schedule.",
|
|
597
988
|
},
|
|
598
989
|
parameters: {
|
|
599
|
-
flags:
|
|
990
|
+
flags: {
|
|
991
|
+
...makeFlagParameters,
|
|
992
|
+
cron: parsedStringFlag("Cron expression. Defaults to 0 9 * * *."),
|
|
993
|
+
timezone: parsedStringFlag("IANA timezone, for example America/Chicago."),
|
|
994
|
+
route: {
|
|
995
|
+
kind: "boolean",
|
|
996
|
+
optional: true,
|
|
997
|
+
withNegated: false,
|
|
998
|
+
brief: "Generate a Next.js cron route for this schedule.",
|
|
999
|
+
},
|
|
1000
|
+
} satisfies FlagParametersForType<MakeScheduleFlags, CliContext>,
|
|
600
1001
|
positional: namePositional,
|
|
601
1002
|
},
|
|
602
|
-
async
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
:
|
|
616
|
-
|
|
1003
|
+
loader: async () => {
|
|
1004
|
+
const { makeSchedule } = await import("./make.js");
|
|
1005
|
+
|
|
1006
|
+
return async function runMakeSchedule(
|
|
1007
|
+
this: CliContext,
|
|
1008
|
+
flags: MakeScheduleFlags,
|
|
1009
|
+
name: string,
|
|
1010
|
+
) {
|
|
1011
|
+
const result = await makeSchedule({
|
|
1012
|
+
name,
|
|
1013
|
+
cron: flags.cron,
|
|
1014
|
+
timezone: flags.timezone,
|
|
1015
|
+
route: Boolean(flags.route),
|
|
1016
|
+
force: Boolean(flags.force),
|
|
1017
|
+
dryRun: Boolean(flags.dryRun),
|
|
1018
|
+
});
|
|
1019
|
+
writeOutput(
|
|
1020
|
+
this,
|
|
1021
|
+
flags.json
|
|
1022
|
+
? JSON.stringify(result, null, 2)
|
|
1023
|
+
: makeScheduleNextSteps(result),
|
|
1024
|
+
);
|
|
1025
|
+
};
|
|
617
1026
|
},
|
|
618
1027
|
});
|
|
619
1028
|
|
|
@@ -622,17 +1031,75 @@ const makeRoutes = buildRouteMap({
|
|
|
622
1031
|
brief: "Generate Beignet app files.",
|
|
623
1032
|
},
|
|
624
1033
|
routes: {
|
|
625
|
-
adapter:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1034
|
+
adapter: simpleMakeCommand(
|
|
1035
|
+
"makeAdapter",
|
|
1036
|
+
"Generate a port adapter.",
|
|
1037
|
+
makeAdapterNextSteps,
|
|
1038
|
+
),
|
|
1039
|
+
contract: simpleMakeCommand(
|
|
1040
|
+
"makeContract",
|
|
1041
|
+
"Generate a contract group.",
|
|
1042
|
+
makeContractNextSteps,
|
|
1043
|
+
),
|
|
1044
|
+
event: simpleMakeCommand(
|
|
1045
|
+
"makeEvent",
|
|
1046
|
+
"Generate a feature event.",
|
|
1047
|
+
makeEventNextSteps,
|
|
1048
|
+
),
|
|
1049
|
+
factory: simpleMakeCommand(
|
|
1050
|
+
"makeFactory",
|
|
1051
|
+
"Generate a feature test data factory.",
|
|
1052
|
+
makeFactoryNextSteps,
|
|
1053
|
+
),
|
|
1054
|
+
feature: makeFeatureCommand,
|
|
1055
|
+
job: simpleMakeCommand(
|
|
1056
|
+
"makeJob",
|
|
1057
|
+
"Generate a feature job.",
|
|
1058
|
+
makeJobNextSteps,
|
|
1059
|
+
),
|
|
1060
|
+
notification: simpleMakeCommand(
|
|
1061
|
+
"makeNotification",
|
|
1062
|
+
"Generate a feature notification.",
|
|
1063
|
+
makeNotificationNextSteps,
|
|
1064
|
+
),
|
|
629
1065
|
listener: makeListenerCommand,
|
|
630
|
-
policy:
|
|
631
|
-
|
|
1066
|
+
policy: simpleMakeCommand(
|
|
1067
|
+
"makePolicy",
|
|
1068
|
+
"Generate an authorization policy.",
|
|
1069
|
+
makePolicyNextSteps,
|
|
1070
|
+
),
|
|
1071
|
+
port: simpleMakeCommand(
|
|
1072
|
+
"makePort",
|
|
1073
|
+
"Generate an application port.",
|
|
1074
|
+
makePortNextSteps,
|
|
1075
|
+
),
|
|
632
1076
|
resource: makeResourceCommand,
|
|
633
1077
|
schedule: makeScheduleCommand,
|
|
634
|
-
|
|
635
|
-
|
|
1078
|
+
seed: simpleMakeCommand(
|
|
1079
|
+
"makeSeed",
|
|
1080
|
+
"Generate a feature seed.",
|
|
1081
|
+
makeSeedNextSteps,
|
|
1082
|
+
),
|
|
1083
|
+
task: simpleMakeCommand(
|
|
1084
|
+
"makeTask",
|
|
1085
|
+
"Generate a feature operational task.",
|
|
1086
|
+
makeTaskNextSteps,
|
|
1087
|
+
),
|
|
1088
|
+
test: simpleMakeCommand(
|
|
1089
|
+
"makeTest",
|
|
1090
|
+
"Generate a use case test.",
|
|
1091
|
+
makeTestNextSteps,
|
|
1092
|
+
),
|
|
1093
|
+
upload: simpleMakeCommand(
|
|
1094
|
+
"makeUpload",
|
|
1095
|
+
"Generate a feature upload.",
|
|
1096
|
+
makeUploadNextSteps,
|
|
1097
|
+
),
|
|
1098
|
+
useCase: simpleMakeCommand(
|
|
1099
|
+
"makeUseCase",
|
|
1100
|
+
"Generate a use case.",
|
|
1101
|
+
makeUseCaseNextSteps,
|
|
1102
|
+
),
|
|
636
1103
|
},
|
|
637
1104
|
});
|
|
638
1105
|
|
|
@@ -641,19 +1108,27 @@ const rootRoutes = buildRouteMap({
|
|
|
641
1108
|
brief: "Beignet CLI",
|
|
642
1109
|
fullDescription: `Create apps, generate framework files, inspect routes, and check Beignet conventions.
|
|
643
1110
|
|
|
644
|
-
create
|
|
1111
|
+
Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
|
|
645
1112
|
},
|
|
646
1113
|
routes: {
|
|
1114
|
+
completion: completionRoutes,
|
|
647
1115
|
create: createCommand,
|
|
1116
|
+
db: dbRoutes,
|
|
648
1117
|
doctor: doctorCommand,
|
|
649
1118
|
lint: lintCommand,
|
|
650
1119
|
make: makeRoutes,
|
|
1120
|
+
outbox: outboxRoutes,
|
|
651
1121
|
routes: routesCommand,
|
|
1122
|
+
schedule: scheduleRoutes,
|
|
1123
|
+
task: taskRoutes,
|
|
652
1124
|
},
|
|
653
1125
|
});
|
|
654
1126
|
|
|
655
1127
|
const cli = buildApplication(rootRoutes, {
|
|
656
1128
|
name: "beignet",
|
|
1129
|
+
versionInfo: {
|
|
1130
|
+
currentVersion: getCliVersion(),
|
|
1131
|
+
},
|
|
657
1132
|
scanner: {
|
|
658
1133
|
caseStyle: "allow-kebab-for-camel",
|
|
659
1134
|
},
|
|
@@ -667,7 +1142,7 @@ const cli = buildApplication(rootRoutes, {
|
|
|
667
1142
|
commandErrorResult: (error) => error.message,
|
|
668
1143
|
},
|
|
669
1144
|
},
|
|
670
|
-
determineExitCode: () =>
|
|
1145
|
+
determineExitCode: () => 2,
|
|
671
1146
|
});
|
|
672
1147
|
|
|
673
1148
|
function uniqueValues<T>(values: readonly T[]): T[] {
|
|
@@ -682,12 +1157,108 @@ function formatCliException(error: unknown): string {
|
|
|
682
1157
|
return error instanceof Error ? error.message : String(error);
|
|
683
1158
|
}
|
|
684
1159
|
|
|
1160
|
+
type CreateNextStepsResult = {
|
|
1161
|
+
name: string;
|
|
1162
|
+
targetDir: string;
|
|
1163
|
+
dryRun: boolean;
|
|
1164
|
+
files: string[];
|
|
1165
|
+
packageManager: PackageManager;
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
type MakeNextStepsResult = {
|
|
1169
|
+
name: string;
|
|
1170
|
+
targetDir: string;
|
|
1171
|
+
dryRun: boolean;
|
|
1172
|
+
createdFiles: string[];
|
|
1173
|
+
updatedFiles: string[];
|
|
1174
|
+
skippedFiles: string[];
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
type DatabaseCommandNextStepsResult = {
|
|
1178
|
+
script: string;
|
|
1179
|
+
cwd: string;
|
|
1180
|
+
runner: string;
|
|
1181
|
+
args: string[];
|
|
1182
|
+
dryRun: boolean;
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
type AppTaskRunNextStepsResult = {
|
|
1186
|
+
name: string;
|
|
1187
|
+
durationMs: number;
|
|
1188
|
+
output: unknown;
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
type OutboxDrainNextStepsResult = {
|
|
1192
|
+
durationMs: number;
|
|
1193
|
+
result: {
|
|
1194
|
+
claimed: number;
|
|
1195
|
+
delivered: number;
|
|
1196
|
+
retried: number;
|
|
1197
|
+
deadLettered: number;
|
|
1198
|
+
};
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
type ScheduleRunNextStepsResult = {
|
|
1202
|
+
name: string;
|
|
1203
|
+
durationMs: number;
|
|
1204
|
+
run: {
|
|
1205
|
+
source: string;
|
|
1206
|
+
id?: string;
|
|
1207
|
+
attempt?: number;
|
|
1208
|
+
scheduledAt?: string;
|
|
1209
|
+
triggeredAt?: string;
|
|
1210
|
+
};
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
function appTaskRunNextSteps(result: AppTaskRunNextStepsResult): string {
|
|
1214
|
+
const output =
|
|
1215
|
+
result.output === undefined
|
|
1216
|
+
? "undefined"
|
|
1217
|
+
: JSON.stringify(result.output, null, 2);
|
|
1218
|
+
|
|
1219
|
+
return `Ran task ${result.name} in ${result.durationMs}ms
|
|
1220
|
+
|
|
1221
|
+
Output:
|
|
1222
|
+
${output}`;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function outboxDrainNextSteps(result: OutboxDrainNextStepsResult): string {
|
|
1226
|
+
return `Drained outbox in ${result.durationMs}ms
|
|
1227
|
+
|
|
1228
|
+
Result:
|
|
1229
|
+
claimed: ${result.result.claimed}
|
|
1230
|
+
delivered: ${result.result.delivered}
|
|
1231
|
+
retried: ${result.result.retried}
|
|
1232
|
+
deadLettered: ${result.result.deadLettered}`;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function scheduleRunNextSteps(result: ScheduleRunNextStepsResult): string {
|
|
1236
|
+
return `Ran schedule ${result.name} in ${result.durationMs}ms
|
|
1237
|
+
|
|
1238
|
+
Run:
|
|
1239
|
+
source: ${result.run.source}
|
|
1240
|
+
id: ${result.run.id ?? "none"}
|
|
1241
|
+
attempt: ${result.run.attempt ?? "none"}
|
|
1242
|
+
scheduledAt: ${result.run.scheduledAt ?? "none"}
|
|
1243
|
+
triggeredAt: ${result.run.triggeredAt ?? "none"}`;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
685
1246
|
function nextSteps(
|
|
686
|
-
result:
|
|
1247
|
+
result: CreateNextStepsResult,
|
|
687
1248
|
options: { integrations?: readonly IntegrationName[] } = {},
|
|
688
1249
|
): string {
|
|
1250
|
+
if (result.dryRun) {
|
|
1251
|
+
const plannedFiles = result.files.map((file) => ` ${file}`).join("\n");
|
|
1252
|
+
|
|
1253
|
+
return `Would create ${result.name} at ${result.targetDir}
|
|
1254
|
+
|
|
1255
|
+
Planned files:
|
|
1256
|
+
${plannedFiles}`;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
689
1259
|
const pm = result.packageManager;
|
|
690
1260
|
const run = pm === "npm" ? "npm run" : `${pm} run`;
|
|
1261
|
+
const cli = packageRunnerFromPackageManager(pm);
|
|
691
1262
|
const envFileStep = result.files.includes(".env.example")
|
|
692
1263
|
? " cp .env.example .env.local\n"
|
|
693
1264
|
: "";
|
|
@@ -697,14 +1268,53 @@ function nextSteps(
|
|
|
697
1268
|
envRequiredIntegrations.length > 0
|
|
698
1269
|
? " Fill .env.local for selected provider integrations before starting the app.\n"
|
|
699
1270
|
: "";
|
|
1271
|
+
const hasDatabase = result.files.some((file) => file.startsWith("infra/db/"));
|
|
1272
|
+
const databaseStep = hasDatabase
|
|
1273
|
+
? `
|
|
1274
|
+
Prepare the database:
|
|
1275
|
+
${cli} db generate
|
|
1276
|
+
${cli} db migrate
|
|
1277
|
+
`
|
|
1278
|
+
: "";
|
|
1279
|
+
const featureDatabaseSteps = hasDatabase
|
|
1280
|
+
? `
|
|
1281
|
+
${cli} db generate
|
|
1282
|
+
${cli} db migrate`
|
|
1283
|
+
: "";
|
|
700
1284
|
|
|
701
1285
|
return `Created ${result.name} at ${result.targetDir}
|
|
702
1286
|
|
|
703
1287
|
Next steps:
|
|
704
1288
|
cd ${result.targetDir}
|
|
705
1289
|
${pm} install
|
|
706
|
-
${envFileStep}${envStep}
|
|
707
|
-
|
|
1290
|
+
${envFileStep}${envStep}${databaseStep}
|
|
1291
|
+
Start the app:
|
|
1292
|
+
${run} dev
|
|
1293
|
+
|
|
1294
|
+
Inspect the app:
|
|
1295
|
+
${cli} routes
|
|
1296
|
+
${cli} lint
|
|
1297
|
+
${cli} doctor
|
|
1298
|
+
|
|
1299
|
+
Generate a feature:
|
|
1300
|
+
${cli} make feature projects${featureDatabaseSteps}
|
|
1301
|
+
${run} test
|
|
1302
|
+
${run} typecheck
|
|
1303
|
+
${cli} lint
|
|
1304
|
+
${cli} doctor`;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function packageRunnerFromPackageManager(pm: PackageManager): string {
|
|
1308
|
+
switch (pm) {
|
|
1309
|
+
case "npm":
|
|
1310
|
+
return "npm run beignet --";
|
|
1311
|
+
case "pnpm":
|
|
1312
|
+
return "pnpm beignet";
|
|
1313
|
+
case "yarn":
|
|
1314
|
+
return "yarn beignet";
|
|
1315
|
+
default:
|
|
1316
|
+
return "bun beignet";
|
|
1317
|
+
}
|
|
708
1318
|
}
|
|
709
1319
|
|
|
710
1320
|
function isEnvRequiredProviderIntegration(
|
|
@@ -717,233 +1327,168 @@ function isEnvRequiredProviderIntegration(
|
|
|
717
1327
|
);
|
|
718
1328
|
}
|
|
719
1329
|
|
|
720
|
-
function
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
.
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
.join("\n")
|
|
729
|
-
|
|
730
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} resource in ${result.targetDir}
|
|
731
|
-
|
|
732
|
-
Changed files:
|
|
733
|
-
${changedFiles || " none"}
|
|
734
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
735
|
-
|
|
736
|
-
Next steps:
|
|
737
|
-
Review the generated schemas and repository fields.
|
|
738
|
-
Run your app's typecheck and test commands.`;
|
|
1330
|
+
function changedFileLines(result: MakeNextStepsResult): {
|
|
1331
|
+
changedFiles: string;
|
|
1332
|
+
skippedFiles: string;
|
|
1333
|
+
} {
|
|
1334
|
+
return {
|
|
1335
|
+
changedFiles: [...result.createdFiles, ...result.updatedFiles]
|
|
1336
|
+
.map((file) => ` ${file}`)
|
|
1337
|
+
.join("\n"),
|
|
1338
|
+
skippedFiles: result.skippedFiles.map((file) => ` ${file}`).join("\n"),
|
|
1339
|
+
};
|
|
739
1340
|
}
|
|
740
1341
|
|
|
741
|
-
function
|
|
742
|
-
result:
|
|
1342
|
+
function makeNextSteps(
|
|
1343
|
+
result: MakeNextStepsResult,
|
|
1344
|
+
noun: string,
|
|
1345
|
+
steps: readonly string[],
|
|
743
1346
|
): string {
|
|
744
|
-
const changedFiles =
|
|
745
|
-
.map((file) => ` ${file}`)
|
|
746
|
-
.join("\n");
|
|
747
|
-
const skippedFiles = result.skippedFiles
|
|
748
|
-
.map((file) => ` ${file}`)
|
|
749
|
-
.join("\n");
|
|
1347
|
+
const { changedFiles, skippedFiles } = changedFileLines(result);
|
|
750
1348
|
|
|
751
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name}
|
|
1349
|
+
return `${result.dryRun ? "Would create" : "Created"} ${result.name} ${noun} in ${result.targetDir}
|
|
752
1350
|
|
|
753
1351
|
Changed files:
|
|
754
1352
|
${changedFiles || " none"}
|
|
755
1353
|
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
756
1354
|
|
|
757
1355
|
Next steps:
|
|
758
|
-
|
|
759
|
-
Register the exported contract list with OpenAPI if this app publishes docs.`;
|
|
1356
|
+
${steps.map((step) => ` ${step}`).join("\n")}`;
|
|
760
1357
|
}
|
|
761
1358
|
|
|
762
|
-
function
|
|
763
|
-
result
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
.
|
|
768
|
-
|
|
769
|
-
.map((file) => ` ${file}`)
|
|
770
|
-
.join("\n");
|
|
771
|
-
|
|
772
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} use case in ${result.targetDir}
|
|
773
|
-
|
|
774
|
-
Changed files:
|
|
775
|
-
${changedFiles || " none"}
|
|
776
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
777
|
-
|
|
778
|
-
Next steps:
|
|
779
|
-
Replace the starter input and output schemas with domain-specific shapes.
|
|
780
|
-
Add a focused use case test before wiring it to an HTTP route.`;
|
|
1359
|
+
function makeResourceNextSteps(result: MakeNextStepsResult): string {
|
|
1360
|
+
return makeNextSteps(result, "resource", [
|
|
1361
|
+
"Use make resource when the feature maps cleanly to a REST resource with repository-backed persistence.",
|
|
1362
|
+
"Review the generated schemas and repository fields.",
|
|
1363
|
+
"If this app uses Drizzle, run beignet db generate and beignet db migrate so the new schema is ready.",
|
|
1364
|
+
"Run your app's test and typecheck commands, then beignet lint and beignet doctor.",
|
|
1365
|
+
]);
|
|
781
1366
|
}
|
|
782
1367
|
|
|
783
|
-
function
|
|
784
|
-
result:
|
|
1368
|
+
function databaseCommandNextSteps(
|
|
1369
|
+
result: DatabaseCommandNextStepsResult,
|
|
785
1370
|
): string {
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
.join("\n");
|
|
789
|
-
const skippedFiles = result.skippedFiles
|
|
790
|
-
.map((file) => ` ${file}`)
|
|
791
|
-
.join("\n");
|
|
1371
|
+
const command = [result.runner, ...result.args].join(" ");
|
|
1372
|
+
const prefix = result.dryRun ? "Would run" : "Ran";
|
|
792
1373
|
|
|
793
|
-
return `${
|
|
1374
|
+
return `${prefix} ${result.script} in ${result.cwd}
|
|
794
1375
|
|
|
795
|
-
|
|
796
|
-
${
|
|
797
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
798
|
-
|
|
799
|
-
Next steps:
|
|
800
|
-
Replace the starter input, context, and assertion with behavior-specific coverage.
|
|
801
|
-
Run your app's test command.`;
|
|
1376
|
+
Command:
|
|
1377
|
+
${command}`;
|
|
802
1378
|
}
|
|
803
1379
|
|
|
804
|
-
function
|
|
805
|
-
result
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
.
|
|
810
|
-
|
|
811
|
-
.map((file) => ` ${file}`)
|
|
812
|
-
.join("\n");
|
|
813
|
-
|
|
814
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} port in ${result.targetDir}
|
|
815
|
-
|
|
816
|
-
Changed files:
|
|
817
|
-
${changedFiles || " none"}
|
|
818
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
819
|
-
|
|
820
|
-
Next steps:
|
|
821
|
-
Replace the starter execute method with domain-specific operations.
|
|
822
|
-
Replace the generated infrastructure stub with a real port adapter.`;
|
|
1380
|
+
function makeFeatureNextSteps(result: MakeNextStepsResult): string {
|
|
1381
|
+
return makeNextSteps(result, "feature slice", [
|
|
1382
|
+
"Use make feature for product capabilities and workflows. Use make resource when the concept is mostly CRUD-shaped.",
|
|
1383
|
+
"Treat the generated name field as a placeholder and shape the contracts, use cases, and repository around the feature's real workflow.",
|
|
1384
|
+
"If this app uses Drizzle, run beignet db generate and beignet db migrate so the new schema is ready.",
|
|
1385
|
+
"Run your app's test and typecheck commands, then beignet lint and beignet doctor.",
|
|
1386
|
+
]);
|
|
823
1387
|
}
|
|
824
1388
|
|
|
825
|
-
function
|
|
826
|
-
result
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
.join("\n");
|
|
831
|
-
const skippedFiles = result.skippedFiles
|
|
832
|
-
.map((file) => ` ${file}`)
|
|
833
|
-
.join("\n");
|
|
834
|
-
|
|
835
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} policy in ${result.targetDir}
|
|
836
|
-
|
|
837
|
-
Changed files:
|
|
838
|
-
${changedFiles || " none"}
|
|
839
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
840
|
-
|
|
841
|
-
Next steps:
|
|
842
|
-
Replace the starter abilities with domain-specific authorization rules.
|
|
843
|
-
Register the policy with createGate(...) and bind it in request context.`;
|
|
1389
|
+
function makeContractNextSteps(result: MakeNextStepsResult): string {
|
|
1390
|
+
return makeNextSteps(result, "contract", [
|
|
1391
|
+
"Add route handlers for the generated contracts.",
|
|
1392
|
+
"Register the exported contract list with OpenAPI if this app publishes docs.",
|
|
1393
|
+
]);
|
|
844
1394
|
}
|
|
845
1395
|
|
|
846
|
-
function
|
|
847
|
-
result
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
.join("\n");
|
|
852
|
-
const skippedFiles = result.skippedFiles
|
|
853
|
-
.map((file) => ` ${file}`)
|
|
854
|
-
.join("\n");
|
|
855
|
-
|
|
856
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} port adapter in ${result.targetDir}
|
|
857
|
-
|
|
858
|
-
Changed files:
|
|
859
|
-
${changedFiles || " none"}
|
|
860
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
861
|
-
|
|
862
|
-
Next steps:
|
|
863
|
-
Replace the generated throwing implementation with real infrastructure code.
|
|
864
|
-
Keep the adapter behind the port interface so use cases stay infrastructure-agnostic.`;
|
|
1396
|
+
function makeTaskNextSteps(result: MakeNextStepsResult): string {
|
|
1397
|
+
return makeNextSteps(result, "task", [
|
|
1398
|
+
"Replace the starter input schema and handler with the operational workflow.",
|
|
1399
|
+
"Keep business rules in use cases and call them from the task handler.",
|
|
1400
|
+
]);
|
|
865
1401
|
}
|
|
866
1402
|
|
|
867
|
-
function
|
|
868
|
-
result
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
.join("\n");
|
|
873
|
-
const skippedFiles = result.skippedFiles
|
|
874
|
-
.map((file) => ` ${file}`)
|
|
875
|
-
.join("\n");
|
|
876
|
-
|
|
877
|
-
return `${result.dryRun ? "Would create" : "Created"} ${result.name} event in ${result.targetDir}
|
|
878
|
-
|
|
879
|
-
Changed files:
|
|
880
|
-
${changedFiles || " none"}
|
|
881
|
-
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
882
|
-
|
|
883
|
-
Next steps:
|
|
884
|
-
Replace the starter payload schema with the domain fact shape.
|
|
885
|
-
Emit the event from a use case with .emits(...) and events.record(...).`;
|
|
1403
|
+
function makeUseCaseNextSteps(result: MakeNextStepsResult): string {
|
|
1404
|
+
return makeNextSteps(result, "use case", [
|
|
1405
|
+
"Replace the starter input and output schemas with domain-specific shapes.",
|
|
1406
|
+
"Add a focused use case test before wiring it to an HTTP route.",
|
|
1407
|
+
]);
|
|
886
1408
|
}
|
|
887
1409
|
|
|
888
|
-
function
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
.
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
.join("\n");
|
|
1410
|
+
function makeTestNextSteps(result: MakeNextStepsResult): string {
|
|
1411
|
+
return makeNextSteps(result, "test", [
|
|
1412
|
+
"Replace the starter input, context, and assertion with behavior-specific coverage.",
|
|
1413
|
+
"Run your app's test command.",
|
|
1414
|
+
]);
|
|
1415
|
+
}
|
|
895
1416
|
|
|
896
|
-
|
|
1417
|
+
function makePortNextSteps(result: MakeNextStepsResult): string {
|
|
1418
|
+
return makeNextSteps(result, "port", [
|
|
1419
|
+
"Replace the starter execute method with domain-specific operations.",
|
|
1420
|
+
"Replace the generated infrastructure stub with a real port adapter.",
|
|
1421
|
+
]);
|
|
1422
|
+
}
|
|
897
1423
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1424
|
+
function makePolicyNextSteps(result: MakeNextStepsResult): string {
|
|
1425
|
+
return makeNextSteps(result, "policy", [
|
|
1426
|
+
"Replace the starter abilities with domain-specific authorization rules.",
|
|
1427
|
+
"Register the policy with createGate(...) and bind it in request context.",
|
|
1428
|
+
]);
|
|
1429
|
+
}
|
|
901
1430
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1431
|
+
function makeAdapterNextSteps(result: MakeNextStepsResult): string {
|
|
1432
|
+
return makeNextSteps(result, "port adapter", [
|
|
1433
|
+
"Replace the generated throwing implementation with real infrastructure code.",
|
|
1434
|
+
"Keep the adapter behind the port interface so use cases stay infrastructure-agnostic.",
|
|
1435
|
+
]);
|
|
905
1436
|
}
|
|
906
1437
|
|
|
907
|
-
function
|
|
908
|
-
result
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
const skippedFiles = result.skippedFiles
|
|
914
|
-
.map((file) => ` ${file}`)
|
|
915
|
-
.join("\n");
|
|
1438
|
+
function makeEventNextSteps(result: MakeNextStepsResult): string {
|
|
1439
|
+
return makeNextSteps(result, "event", [
|
|
1440
|
+
"Replace the starter payload schema with the domain fact shape.",
|
|
1441
|
+
"Emit the event from a use case with .emits(...) and events.record(...).",
|
|
1442
|
+
]);
|
|
1443
|
+
}
|
|
916
1444
|
|
|
917
|
-
|
|
1445
|
+
function makeJobNextSteps(result: MakeNextStepsResult): string {
|
|
1446
|
+
return makeNextSteps(result, "job", [
|
|
1447
|
+
"Replace the starter payload and handler with the real background work.",
|
|
1448
|
+
"Dispatch the job through ctx.ports.jobs from a use case, listener, or schedule.",
|
|
1449
|
+
]);
|
|
1450
|
+
}
|
|
918
1451
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1452
|
+
function makeFactoryNextSteps(result: MakeNextStepsResult): string {
|
|
1453
|
+
return makeNextSteps(result, "factory", [
|
|
1454
|
+
"Replace the starter defaults with realistic test data for this feature.",
|
|
1455
|
+
"Persist through app-owned repository ports so tests avoid raw database clients.",
|
|
1456
|
+
]);
|
|
1457
|
+
}
|
|
922
1458
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1459
|
+
function makeSeedNextSteps(result: MakeNextStepsResult): string {
|
|
1460
|
+
return makeNextSteps(result, "seed", [
|
|
1461
|
+
"Replace the starter record with idempotent local or demo data.",
|
|
1462
|
+
"Import the feature seed from your app-owned database seed entrypoint and run it with runSeeds(...).",
|
|
1463
|
+
]);
|
|
926
1464
|
}
|
|
927
1465
|
|
|
928
|
-
function
|
|
929
|
-
result
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const skippedFiles = result.skippedFiles
|
|
935
|
-
.map((file) => ` ${file}`)
|
|
936
|
-
.join("\n");
|
|
1466
|
+
function makeNotificationNextSteps(result: MakeNextStepsResult): string {
|
|
1467
|
+
return makeNextSteps(result, "notification", [
|
|
1468
|
+
"Replace the starter payload and channels with the real user-facing message.",
|
|
1469
|
+
"Send the notification through ctx.ports.notifications so delivery can run inline in tests and through jobs in production.",
|
|
1470
|
+
]);
|
|
1471
|
+
}
|
|
937
1472
|
|
|
938
|
-
|
|
1473
|
+
function makeListenerNextSteps(result: MakeNextStepsResult): string {
|
|
1474
|
+
return makeNextSteps(result, "listener", [
|
|
1475
|
+
"Replace the starter handler with the event reaction.",
|
|
1476
|
+
"Register the listener collection from infrastructure startup.",
|
|
1477
|
+
]);
|
|
1478
|
+
}
|
|
939
1479
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1480
|
+
function makeScheduleNextSteps(result: MakeNextStepsResult): string {
|
|
1481
|
+
return makeNextSteps(result, "schedule", [
|
|
1482
|
+
"Replace the starter payload and handler with the scheduled workflow.",
|
|
1483
|
+
"Trigger it from a cron route, worker, or provider adapter.",
|
|
1484
|
+
]);
|
|
1485
|
+
}
|
|
943
1486
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1487
|
+
function makeUploadNextSteps(result: MakeNextStepsResult): string {
|
|
1488
|
+
return makeNextSteps(result, "upload", [
|
|
1489
|
+
"Replace the starter metadata, constraints, key, authorization, and onComplete behavior.",
|
|
1490
|
+
"Register the upload in createUploadRouter(...) and expose it with createUploadRoute(...).",
|
|
1491
|
+
]);
|
|
947
1492
|
}
|
|
948
1493
|
|
|
949
1494
|
export async function main(
|
|
@@ -952,7 +1497,15 @@ export async function main(
|
|
|
952
1497
|
): Promise<void> {
|
|
953
1498
|
if (inputs.length === 0) {
|
|
954
1499
|
await run(cli, ["--help"], context);
|
|
955
|
-
context.process.exitCode =
|
|
1500
|
+
context.process.exitCode = 2;
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Shell completion scripts call `beignet completion propose <words...>`.
|
|
1505
|
+
// Handle it before stricli parses the inputs: the trailing words are a
|
|
1506
|
+
// partial command line, not flags for this CLI.
|
|
1507
|
+
if (inputs[0] === "completion" && inputs[1] === "propose") {
|
|
1508
|
+
await writeCompletionProposals(inputs.slice(2), context);
|
|
956
1509
|
return;
|
|
957
1510
|
}
|
|
958
1511
|
|
|
@@ -960,11 +1513,42 @@ export async function main(
|
|
|
960
1513
|
normalizeExitCode(context.process);
|
|
961
1514
|
}
|
|
962
1515
|
|
|
1516
|
+
async function writeCompletionProposals(
|
|
1517
|
+
inputs: readonly string[],
|
|
1518
|
+
context: StricliDynamicCommandContext<CliContext>,
|
|
1519
|
+
): Promise<void> {
|
|
1520
|
+
try {
|
|
1521
|
+
const proposals = await proposeCompletions(
|
|
1522
|
+
cli,
|
|
1523
|
+
inputs.length === 0 ? [""] : inputs,
|
|
1524
|
+
context,
|
|
1525
|
+
);
|
|
1526
|
+
for (const proposal of proposals) {
|
|
1527
|
+
context.process.stdout.write(`${proposal.completion}\n`);
|
|
1528
|
+
}
|
|
1529
|
+
} catch {
|
|
1530
|
+
// Completion failures must never break the shell; propose nothing.
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Stricli reports usage and internal failures as negative ExitCode values
|
|
1535
|
+
// (-5..-1). Node keeps the negative value on process.exitCode while Bun wraps
|
|
1536
|
+
// it to 251..255 on assignment, so both forms map to the usage exit code.
|
|
1537
|
+
const stricliErrorExitCodes = new Set([
|
|
1538
|
+
-5, -4, -3, -2, -1, 251, 252, 253, 254, 255,
|
|
1539
|
+
]);
|
|
1540
|
+
|
|
963
1541
|
function normalizeExitCode(proc: StricliProcess): void {
|
|
964
|
-
if (
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1542
|
+
if (typeof proc.exitCode !== "number") {
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (stricliErrorExitCodes.has(proc.exitCode)) {
|
|
1547
|
+
proc.exitCode = 2;
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
if (proc.exitCode > 127) {
|
|
968
1552
|
proc.exitCode = 1;
|
|
969
1553
|
}
|
|
970
1554
|
}
|
|
@@ -987,6 +1571,6 @@ function isCliEntrypoint(): boolean {
|
|
|
987
1571
|
if (isCliEntrypoint()) {
|
|
988
1572
|
main().catch((error: unknown) => {
|
|
989
1573
|
console.error(error instanceof Error ? error.message : String(error));
|
|
990
|
-
process.exitCode =
|
|
1574
|
+
process.exitCode = 2;
|
|
991
1575
|
});
|
|
992
1576
|
}
|