@beignet/cli 0.0.1
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 +5 -0
- package/README.md +409 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +131 -0
- package/dist/config.js.map +1 -0
- package/dist/create-bin.d.ts +3 -0
- package/dist/create-bin.d.ts.map +1 -0
- package/dist/create-bin.js +9 -0
- package/dist/create-bin.js.map +1 -0
- package/dist/create.d.ts +20 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +99 -0
- package/dist/create.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +735 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +54 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +1240 -0
- package/dist/inspect.js.map +1 -0
- package/dist/lint.d.ts +21 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +576 -0
- package/dist/lint.js.map +1 -0
- package/dist/make.d.ts +115 -0
- package/dist/make.d.ts.map +1 -0
- package/dist/make.js +2719 -0
- package/dist/make.js.map +1 -0
- package/dist/templates.d.ts +22 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +2236 -0
- package/dist/templates.js.map +1 -0
- package/package.json +73 -0
- package/src/config.ts +214 -0
- package/src/create-bin.ts +11 -0
- package/src/create.ts +164 -0
- package/src/index.ts +992 -0
- package/src/inspect.ts +1951 -0
- package/src/lint.ts +785 -0
- package/src/make.ts +3931 -0
- package/src/templates.ts +2460 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,992 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import {
|
|
5
|
+
buildApplication,
|
|
6
|
+
buildCommand,
|
|
7
|
+
buildRouteMap,
|
|
8
|
+
type CommandContext,
|
|
9
|
+
type FlagParametersForType,
|
|
10
|
+
run,
|
|
11
|
+
type StricliDynamicCommandContext,
|
|
12
|
+
type StricliProcess,
|
|
13
|
+
text_en,
|
|
14
|
+
} 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
|
+
import {
|
|
38
|
+
type FeatureName,
|
|
39
|
+
featureChoices,
|
|
40
|
+
type IntegrationName,
|
|
41
|
+
integrationChoices,
|
|
42
|
+
type PackageManager,
|
|
43
|
+
type PresetName,
|
|
44
|
+
presetChoices,
|
|
45
|
+
type TemplateName,
|
|
46
|
+
} from "./templates.js";
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
applyDoctorFixes,
|
|
50
|
+
type CreateOptions,
|
|
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
|
+
};
|
|
70
|
+
|
|
71
|
+
type CliContext = CommandContext & {
|
|
72
|
+
readonly process: StricliProcess;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type CreateFlags = {
|
|
76
|
+
template: TemplateName;
|
|
77
|
+
preset: PresetName;
|
|
78
|
+
packageManager?: PackageManager;
|
|
79
|
+
feature?: readonly FeatureName[];
|
|
80
|
+
features?: readonly FeatureName[];
|
|
81
|
+
integration?: readonly IntegrationName[];
|
|
82
|
+
integrations?: readonly IntegrationName[];
|
|
83
|
+
force?: boolean;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type MakeFlags = {
|
|
87
|
+
force?: boolean;
|
|
88
|
+
dryRun?: boolean;
|
|
89
|
+
json?: boolean;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type MakeListenerFlags = MakeFlags & {
|
|
93
|
+
event?: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
type MakeScheduleFlags = MakeFlags & {
|
|
97
|
+
cron?: string;
|
|
98
|
+
timezone?: string;
|
|
99
|
+
route?: boolean;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type JsonFlags = {
|
|
103
|
+
json?: boolean;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type DoctorFlags = {
|
|
107
|
+
json?: boolean;
|
|
108
|
+
strict?: boolean;
|
|
109
|
+
fix?: boolean;
|
|
110
|
+
};
|
|
111
|
+
|
|
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
|
+
const parseString = (input: string): string => input;
|
|
121
|
+
|
|
122
|
+
const forceFlag = {
|
|
123
|
+
kind: "boolean",
|
|
124
|
+
optional: true,
|
|
125
|
+
withNegated: false,
|
|
126
|
+
brief: "Overwrite conflicting files or write into a non-empty directory.",
|
|
127
|
+
} as const;
|
|
128
|
+
|
|
129
|
+
const dryRunFlag = {
|
|
130
|
+
kind: "boolean",
|
|
131
|
+
optional: true,
|
|
132
|
+
withNegated: false,
|
|
133
|
+
brief: "Preview generated changes without writing files.",
|
|
134
|
+
} as const;
|
|
135
|
+
|
|
136
|
+
const jsonFlag = {
|
|
137
|
+
kind: "boolean",
|
|
138
|
+
optional: true,
|
|
139
|
+
withNegated: false,
|
|
140
|
+
brief: "Print machine-readable JSON.",
|
|
141
|
+
} as const;
|
|
142
|
+
|
|
143
|
+
const parsedStringFlag = (brief: string) =>
|
|
144
|
+
({
|
|
145
|
+
kind: "parsed",
|
|
146
|
+
parse: parseString,
|
|
147
|
+
optional: true,
|
|
148
|
+
brief,
|
|
149
|
+
}) as const;
|
|
150
|
+
|
|
151
|
+
const makeFlagParameters = {
|
|
152
|
+
force: forceFlag,
|
|
153
|
+
dryRun: dryRunFlag,
|
|
154
|
+
json: jsonFlag,
|
|
155
|
+
} satisfies FlagParametersForType<MakeFlags, CliContext>;
|
|
156
|
+
|
|
157
|
+
const jsonFlagParameters = {
|
|
158
|
+
json: jsonFlag,
|
|
159
|
+
} satisfies FlagParametersForType<JsonFlags, CliContext>;
|
|
160
|
+
|
|
161
|
+
const namePositional = {
|
|
162
|
+
kind: "tuple",
|
|
163
|
+
parameters: [
|
|
164
|
+
{
|
|
165
|
+
parse: parseString,
|
|
166
|
+
placeholder: "name",
|
|
167
|
+
brief: "Name for the generated artifact.",
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
} as const;
|
|
171
|
+
|
|
172
|
+
const createCommand = buildCommand<CreateFlags, [string], CliContext>({
|
|
173
|
+
docs: {
|
|
174
|
+
brief: "Create a new Beignet app.",
|
|
175
|
+
fullDescription: `Available features: ${featureChoices.join(", ")}
|
|
176
|
+
|
|
177
|
+
Available integrations: ${integrationChoices.join(", ")}`,
|
|
178
|
+
},
|
|
179
|
+
parameters: {
|
|
180
|
+
flags: {
|
|
181
|
+
template: {
|
|
182
|
+
kind: "enum",
|
|
183
|
+
values: templateChoices,
|
|
184
|
+
default: "next",
|
|
185
|
+
brief: "Template to use.",
|
|
186
|
+
},
|
|
187
|
+
preset: {
|
|
188
|
+
kind: "enum",
|
|
189
|
+
values: presetChoices,
|
|
190
|
+
default: "standard",
|
|
191
|
+
brief: "Starter preset to generate.",
|
|
192
|
+
},
|
|
193
|
+
packageManager: {
|
|
194
|
+
kind: "enum",
|
|
195
|
+
values: packageManagerChoices,
|
|
196
|
+
optional: true,
|
|
197
|
+
brief: "Package manager to use in next-step commands.",
|
|
198
|
+
},
|
|
199
|
+
feature: {
|
|
200
|
+
kind: "enum",
|
|
201
|
+
values: featureChoices,
|
|
202
|
+
optional: true,
|
|
203
|
+
variadic: true,
|
|
204
|
+
brief: "Add a starter feature. Repeatable.",
|
|
205
|
+
},
|
|
206
|
+
features: {
|
|
207
|
+
kind: "enum",
|
|
208
|
+
values: featureChoices,
|
|
209
|
+
optional: true,
|
|
210
|
+
variadic: ",",
|
|
211
|
+
brief: "Add comma-separated starter features.",
|
|
212
|
+
},
|
|
213
|
+
integration: {
|
|
214
|
+
kind: "enum",
|
|
215
|
+
values: integrationChoices,
|
|
216
|
+
optional: true,
|
|
217
|
+
variadic: true,
|
|
218
|
+
brief: "Add a first-party integration. Repeatable.",
|
|
219
|
+
},
|
|
220
|
+
integrations: {
|
|
221
|
+
kind: "enum",
|
|
222
|
+
values: integrationChoices,
|
|
223
|
+
optional: true,
|
|
224
|
+
variadic: ",",
|
|
225
|
+
brief: "Add comma-separated first-party integrations.",
|
|
226
|
+
},
|
|
227
|
+
force: forceFlag,
|
|
228
|
+
},
|
|
229
|
+
positional: {
|
|
230
|
+
kind: "tuple",
|
|
231
|
+
parameters: [
|
|
232
|
+
{
|
|
233
|
+
parse: parseString,
|
|
234
|
+
placeholder: "directory",
|
|
235
|
+
brief: "Project directory to create.",
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
async func(flags, directory) {
|
|
241
|
+
const integrations = uniqueValues([
|
|
242
|
+
...(flags.integration ?? []),
|
|
243
|
+
...(flags.integrations ?? []),
|
|
244
|
+
]);
|
|
245
|
+
const result = await createProject({
|
|
246
|
+
name: directory,
|
|
247
|
+
template: flags.template,
|
|
248
|
+
preset: flags.preset,
|
|
249
|
+
features: uniqueValues([
|
|
250
|
+
...(flags.feature ?? []),
|
|
251
|
+
...(flags.features ?? []),
|
|
252
|
+
]),
|
|
253
|
+
packageManager: flags.packageManager,
|
|
254
|
+
integrations,
|
|
255
|
+
force: Boolean(flags.force),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
writeOutput(this, nextSteps(result, { integrations }));
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const routesCommand = buildCommand<JsonFlags, [], CliContext>({
|
|
263
|
+
docs: {
|
|
264
|
+
brief: "Inspect registered Beignet routes.",
|
|
265
|
+
},
|
|
266
|
+
parameters: {
|
|
267
|
+
flags: jsonFlagParameters,
|
|
268
|
+
},
|
|
269
|
+
async func(flags) {
|
|
270
|
+
const result = await inspectApp();
|
|
271
|
+
writeOutput(
|
|
272
|
+
this,
|
|
273
|
+
flags.json ? JSON.stringify(result, null, 2) : formatRoutes(result),
|
|
274
|
+
);
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const doctorCommand = buildCommand<DoctorFlags, [], CliContext>({
|
|
279
|
+
docs: {
|
|
280
|
+
brief: "Inspect app wiring and framework conventions.",
|
|
281
|
+
},
|
|
282
|
+
parameters: {
|
|
283
|
+
flags: {
|
|
284
|
+
json: jsonFlag,
|
|
285
|
+
strict: {
|
|
286
|
+
kind: "boolean",
|
|
287
|
+
optional: true,
|
|
288
|
+
withNegated: false,
|
|
289
|
+
brief: "Include CI-oriented warnings and fail on warnings.",
|
|
290
|
+
},
|
|
291
|
+
fix: {
|
|
292
|
+
kind: "boolean",
|
|
293
|
+
optional: true,
|
|
294
|
+
withNegated: false,
|
|
295
|
+
brief: "Apply low-risk fixes before reporting.",
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
async func(flags) {
|
|
300
|
+
const fixes = flags.fix
|
|
301
|
+
? await applyDoctorFixes({ strict: Boolean(flags.strict) })
|
|
302
|
+
: [];
|
|
303
|
+
const result = await inspectApp({ strict: Boolean(flags.strict) });
|
|
304
|
+
result.fixes = fixes;
|
|
305
|
+
writeOutput(
|
|
306
|
+
this,
|
|
307
|
+
flags.json ? JSON.stringify(result, null, 2) : formatDoctor(result),
|
|
308
|
+
);
|
|
309
|
+
if (
|
|
310
|
+
result.diagnostics.some(
|
|
311
|
+
(diagnostic) =>
|
|
312
|
+
diagnostic.severity === "error" ||
|
|
313
|
+
(flags.strict && diagnostic.severity === "warning"),
|
|
314
|
+
)
|
|
315
|
+
) {
|
|
316
|
+
this.process.exitCode = 1;
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const lintCommand = buildCommand<JsonFlags, [], CliContext>({
|
|
322
|
+
docs: {
|
|
323
|
+
brief: "Check Beignet dependency direction conventions.",
|
|
324
|
+
},
|
|
325
|
+
parameters: {
|
|
326
|
+
flags: jsonFlagParameters,
|
|
327
|
+
},
|
|
328
|
+
async func(flags) {
|
|
329
|
+
const result = await lintApp();
|
|
330
|
+
writeOutput(
|
|
331
|
+
this,
|
|
332
|
+
flags.json ? JSON.stringify(result, null, 2) : formatLint(result),
|
|
333
|
+
);
|
|
334
|
+
if (result.diagnostics.length > 0) {
|
|
335
|
+
this.process.exitCode = 1;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const makeResourceCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
341
|
+
docs: {
|
|
342
|
+
brief: "Generate a feature resource.",
|
|
343
|
+
},
|
|
344
|
+
parameters: {
|
|
345
|
+
flags: makeFlagParameters,
|
|
346
|
+
positional: namePositional,
|
|
347
|
+
},
|
|
348
|
+
async func(flags, name) {
|
|
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
|
+
);
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const makeContractCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
364
|
+
docs: {
|
|
365
|
+
brief: "Generate a contract group.",
|
|
366
|
+
},
|
|
367
|
+
parameters: {
|
|
368
|
+
flags: makeFlagParameters,
|
|
369
|
+
positional: namePositional,
|
|
370
|
+
},
|
|
371
|
+
async func(flags, name) {
|
|
372
|
+
const result = await makeContract({
|
|
373
|
+
name,
|
|
374
|
+
force: Boolean(flags.force),
|
|
375
|
+
dryRun: Boolean(flags.dryRun),
|
|
376
|
+
});
|
|
377
|
+
writeOutput(
|
|
378
|
+
this,
|
|
379
|
+
flags.json
|
|
380
|
+
? JSON.stringify(result, null, 2)
|
|
381
|
+
: makeContractNextSteps(result),
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const makeUseCaseCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
387
|
+
docs: {
|
|
388
|
+
brief: "Generate a use case.",
|
|
389
|
+
},
|
|
390
|
+
parameters: {
|
|
391
|
+
flags: makeFlagParameters,
|
|
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
|
+
);
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const makeTestCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
410
|
+
docs: {
|
|
411
|
+
brief: "Generate a use case test.",
|
|
412
|
+
},
|
|
413
|
+
parameters: {
|
|
414
|
+
flags: makeFlagParameters,
|
|
415
|
+
positional: namePositional,
|
|
416
|
+
},
|
|
417
|
+
async func(flags, name) {
|
|
418
|
+
const result = await makeTest({
|
|
419
|
+
name,
|
|
420
|
+
force: Boolean(flags.force),
|
|
421
|
+
dryRun: Boolean(flags.dryRun),
|
|
422
|
+
});
|
|
423
|
+
writeOutput(
|
|
424
|
+
this,
|
|
425
|
+
flags.json ? JSON.stringify(result, null, 2) : makeTestNextSteps(result),
|
|
426
|
+
);
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const makePortCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
431
|
+
docs: {
|
|
432
|
+
brief: "Generate an application port.",
|
|
433
|
+
},
|
|
434
|
+
parameters: {
|
|
435
|
+
flags: makeFlagParameters,
|
|
436
|
+
positional: namePositional,
|
|
437
|
+
},
|
|
438
|
+
async func(flags, name) {
|
|
439
|
+
const result = await makePort({
|
|
440
|
+
name,
|
|
441
|
+
force: Boolean(flags.force),
|
|
442
|
+
dryRun: Boolean(flags.dryRun),
|
|
443
|
+
});
|
|
444
|
+
writeOutput(
|
|
445
|
+
this,
|
|
446
|
+
flags.json ? JSON.stringify(result, null, 2) : makePortNextSteps(result),
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const makeAdapterCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
452
|
+
docs: {
|
|
453
|
+
brief: "Generate a port adapter.",
|
|
454
|
+
},
|
|
455
|
+
parameters: {
|
|
456
|
+
flags: makeFlagParameters,
|
|
457
|
+
positional: namePositional,
|
|
458
|
+
},
|
|
459
|
+
async func(flags, name) {
|
|
460
|
+
const result = await makeAdapter({
|
|
461
|
+
name,
|
|
462
|
+
force: Boolean(flags.force),
|
|
463
|
+
dryRun: Boolean(flags.dryRun),
|
|
464
|
+
});
|
|
465
|
+
writeOutput(
|
|
466
|
+
this,
|
|
467
|
+
flags.json
|
|
468
|
+
? JSON.stringify(result, null, 2)
|
|
469
|
+
: makeAdapterNextSteps(result),
|
|
470
|
+
);
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const makePolicyCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
475
|
+
docs: {
|
|
476
|
+
brief: "Generate an authorization policy.",
|
|
477
|
+
},
|
|
478
|
+
parameters: {
|
|
479
|
+
flags: makeFlagParameters,
|
|
480
|
+
positional: namePositional,
|
|
481
|
+
},
|
|
482
|
+
async func(flags, name) {
|
|
483
|
+
const result = await makePolicy({
|
|
484
|
+
name,
|
|
485
|
+
force: Boolean(flags.force),
|
|
486
|
+
dryRun: Boolean(flags.dryRun),
|
|
487
|
+
});
|
|
488
|
+
writeOutput(
|
|
489
|
+
this,
|
|
490
|
+
flags.json
|
|
491
|
+
? JSON.stringify(result, null, 2)
|
|
492
|
+
: makePolicyNextSteps(result),
|
|
493
|
+
);
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const makeEventCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
498
|
+
docs: {
|
|
499
|
+
brief: "Generate a feature event.",
|
|
500
|
+
},
|
|
501
|
+
parameters: {
|
|
502
|
+
flags: makeFlagParameters,
|
|
503
|
+
positional: namePositional,
|
|
504
|
+
},
|
|
505
|
+
async func(flags, name) {
|
|
506
|
+
const result = await makeEvent({
|
|
507
|
+
name,
|
|
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
|
+
);
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const makeJobCommand = buildCommand<MakeFlags, [string], CliContext>({
|
|
519
|
+
docs: {
|
|
520
|
+
brief: "Generate a feature job.",
|
|
521
|
+
},
|
|
522
|
+
parameters: {
|
|
523
|
+
flags: makeFlagParameters,
|
|
524
|
+
positional: namePositional,
|
|
525
|
+
},
|
|
526
|
+
async func(flags, name) {
|
|
527
|
+
const result = await makeJob({
|
|
528
|
+
name,
|
|
529
|
+
force: Boolean(flags.force),
|
|
530
|
+
dryRun: Boolean(flags.dryRun),
|
|
531
|
+
});
|
|
532
|
+
writeOutput(
|
|
533
|
+
this,
|
|
534
|
+
flags.json ? JSON.stringify(result, null, 2) : makeJobNextSteps(result),
|
|
535
|
+
);
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const makeListenerFlagParameters = {
|
|
540
|
+
...makeFlagParameters,
|
|
541
|
+
event: parsedStringFlag("Event to listen to, for example posts/published."),
|
|
542
|
+
} satisfies FlagParametersForType<MakeListenerFlags, CliContext>;
|
|
543
|
+
|
|
544
|
+
const makeListenerCommand = buildCommand<
|
|
545
|
+
MakeListenerFlags,
|
|
546
|
+
[string],
|
|
547
|
+
CliContext
|
|
548
|
+
>({
|
|
549
|
+
docs: {
|
|
550
|
+
brief: "Generate a feature event listener.",
|
|
551
|
+
},
|
|
552
|
+
parameters: {
|
|
553
|
+
flags: makeListenerFlagParameters,
|
|
554
|
+
positional: namePositional,
|
|
555
|
+
},
|
|
556
|
+
async func(flags, name) {
|
|
557
|
+
if (!flags.event) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
"beignet make listener requires --event feature/name, for example --event posts/published.",
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const result = await makeListener({
|
|
564
|
+
name,
|
|
565
|
+
event: flags.event,
|
|
566
|
+
force: Boolean(flags.force),
|
|
567
|
+
dryRun: Boolean(flags.dryRun),
|
|
568
|
+
});
|
|
569
|
+
writeOutput(
|
|
570
|
+
this,
|
|
571
|
+
flags.json
|
|
572
|
+
? JSON.stringify(result, null, 2)
|
|
573
|
+
: makeListenerNextSteps(result),
|
|
574
|
+
);
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
|
|
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
|
+
const makeScheduleCommand = buildCommand<
|
|
591
|
+
MakeScheduleFlags,
|
|
592
|
+
[string],
|
|
593
|
+
CliContext
|
|
594
|
+
>({
|
|
595
|
+
docs: {
|
|
596
|
+
brief: "Generate a feature schedule.",
|
|
597
|
+
},
|
|
598
|
+
parameters: {
|
|
599
|
+
flags: makeScheduleFlagParameters,
|
|
600
|
+
positional: namePositional,
|
|
601
|
+
},
|
|
602
|
+
async func(flags, name) {
|
|
603
|
+
const result = await makeSchedule({
|
|
604
|
+
name,
|
|
605
|
+
cron: flags.cron,
|
|
606
|
+
timezone: flags.timezone,
|
|
607
|
+
route: Boolean(flags.route),
|
|
608
|
+
force: Boolean(flags.force),
|
|
609
|
+
dryRun: Boolean(flags.dryRun),
|
|
610
|
+
});
|
|
611
|
+
writeOutput(
|
|
612
|
+
this,
|
|
613
|
+
flags.json
|
|
614
|
+
? JSON.stringify(result, null, 2)
|
|
615
|
+
: makeScheduleNextSteps(result),
|
|
616
|
+
);
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const makeRoutes = buildRouteMap({
|
|
621
|
+
docs: {
|
|
622
|
+
brief: "Generate Beignet app files.",
|
|
623
|
+
},
|
|
624
|
+
routes: {
|
|
625
|
+
adapter: makeAdapterCommand,
|
|
626
|
+
contract: makeContractCommand,
|
|
627
|
+
event: makeEventCommand,
|
|
628
|
+
job: makeJobCommand,
|
|
629
|
+
listener: makeListenerCommand,
|
|
630
|
+
policy: makePolicyCommand,
|
|
631
|
+
port: makePortCommand,
|
|
632
|
+
resource: makeResourceCommand,
|
|
633
|
+
schedule: makeScheduleCommand,
|
|
634
|
+
test: makeTestCommand,
|
|
635
|
+
useCase: makeUseCaseCommand,
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
const rootRoutes = buildRouteMap({
|
|
640
|
+
docs: {
|
|
641
|
+
brief: "Beignet CLI",
|
|
642
|
+
fullDescription: `Create apps, generate framework files, inspect routes, and check Beignet conventions.
|
|
643
|
+
|
|
644
|
+
create-beignet <directory> is equivalent to beignet create <directory>.`,
|
|
645
|
+
},
|
|
646
|
+
routes: {
|
|
647
|
+
create: createCommand,
|
|
648
|
+
doctor: doctorCommand,
|
|
649
|
+
lint: lintCommand,
|
|
650
|
+
make: makeRoutes,
|
|
651
|
+
routes: routesCommand,
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const cli = buildApplication(rootRoutes, {
|
|
656
|
+
name: "beignet",
|
|
657
|
+
scanner: {
|
|
658
|
+
caseStyle: "allow-kebab-for-camel",
|
|
659
|
+
},
|
|
660
|
+
documentation: {
|
|
661
|
+
disableAnsiColor: true,
|
|
662
|
+
},
|
|
663
|
+
localization: {
|
|
664
|
+
text: {
|
|
665
|
+
...text_en,
|
|
666
|
+
exceptionWhileRunningCommand: formatCliException,
|
|
667
|
+
commandErrorResult: (error) => error.message,
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
determineExitCode: () => 1,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
function uniqueValues<T>(values: readonly T[]): T[] {
|
|
674
|
+
return [...new Set(values)];
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function writeOutput(context: CliContext, output: string): void {
|
|
678
|
+
context.process.stdout.write(`${output}\n`);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function formatCliException(error: unknown): string {
|
|
682
|
+
return error instanceof Error ? error.message : String(error);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function nextSteps(
|
|
686
|
+
result: Awaited<ReturnType<typeof createProject>>,
|
|
687
|
+
options: { integrations?: readonly IntegrationName[] } = {},
|
|
688
|
+
): string {
|
|
689
|
+
const pm = result.packageManager;
|
|
690
|
+
const run = pm === "npm" ? "npm run" : `${pm} run`;
|
|
691
|
+
const envFileStep = result.files.includes(".env.example")
|
|
692
|
+
? " cp .env.example .env.local\n"
|
|
693
|
+
: "";
|
|
694
|
+
const envRequiredIntegrations =
|
|
695
|
+
options.integrations?.filter(isEnvRequiredProviderIntegration) ?? [];
|
|
696
|
+
const envStep =
|
|
697
|
+
envRequiredIntegrations.length > 0
|
|
698
|
+
? " Fill .env.local for selected provider integrations before starting the app.\n"
|
|
699
|
+
: "";
|
|
700
|
+
|
|
701
|
+
return `Created ${result.name} at ${result.targetDir}
|
|
702
|
+
|
|
703
|
+
Next steps:
|
|
704
|
+
cd ${result.targetDir}
|
|
705
|
+
${pm} install
|
|
706
|
+
${envFileStep}${envStep}\
|
|
707
|
+
${run} dev`;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function isEnvRequiredProviderIntegration(
|
|
711
|
+
integration: IntegrationName,
|
|
712
|
+
): boolean {
|
|
713
|
+
return (
|
|
714
|
+
integration === "inngest" ||
|
|
715
|
+
integration === "resend" ||
|
|
716
|
+
integration === "upstash-rate-limit"
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function makeResourceNextSteps(
|
|
721
|
+
result: Awaited<ReturnType<typeof makeResource>>,
|
|
722
|
+
): string {
|
|
723
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
724
|
+
.map((file) => ` ${file}`)
|
|
725
|
+
.join("\n");
|
|
726
|
+
const skippedFiles = result.skippedFiles
|
|
727
|
+
.map((file) => ` ${file}`)
|
|
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.`;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function makeContractNextSteps(
|
|
742
|
+
result: Awaited<ReturnType<typeof makeContract>>,
|
|
743
|
+
): string {
|
|
744
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
745
|
+
.map((file) => ` ${file}`)
|
|
746
|
+
.join("\n");
|
|
747
|
+
const skippedFiles = result.skippedFiles
|
|
748
|
+
.map((file) => ` ${file}`)
|
|
749
|
+
.join("\n");
|
|
750
|
+
|
|
751
|
+
return `${result.dryRun ? "Would create" : "Created"} ${result.name} contract in ${result.targetDir}
|
|
752
|
+
|
|
753
|
+
Changed files:
|
|
754
|
+
${changedFiles || " none"}
|
|
755
|
+
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
756
|
+
|
|
757
|
+
Next steps:
|
|
758
|
+
Add route handlers for the generated contracts.
|
|
759
|
+
Register the exported contract list with OpenAPI if this app publishes docs.`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function makeUseCaseNextSteps(
|
|
763
|
+
result: Awaited<ReturnType<typeof makeUseCase>>,
|
|
764
|
+
): string {
|
|
765
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
766
|
+
.map((file) => ` ${file}`)
|
|
767
|
+
.join("\n");
|
|
768
|
+
const skippedFiles = result.skippedFiles
|
|
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.`;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function makeTestNextSteps(
|
|
784
|
+
result: Awaited<ReturnType<typeof makeTest>>,
|
|
785
|
+
): string {
|
|
786
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
787
|
+
.map((file) => ` ${file}`)
|
|
788
|
+
.join("\n");
|
|
789
|
+
const skippedFiles = result.skippedFiles
|
|
790
|
+
.map((file) => ` ${file}`)
|
|
791
|
+
.join("\n");
|
|
792
|
+
|
|
793
|
+
return `${result.dryRun ? "Would create" : "Created"} ${result.name} test in ${result.targetDir}
|
|
794
|
+
|
|
795
|
+
Changed files:
|
|
796
|
+
${changedFiles || " none"}
|
|
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.`;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function makePortNextSteps(
|
|
805
|
+
result: Awaited<ReturnType<typeof makePort>>,
|
|
806
|
+
): string {
|
|
807
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
808
|
+
.map((file) => ` ${file}`)
|
|
809
|
+
.join("\n");
|
|
810
|
+
const skippedFiles = result.skippedFiles
|
|
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.`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function makePolicyNextSteps(
|
|
826
|
+
result: Awaited<ReturnType<typeof makePolicy>>,
|
|
827
|
+
): string {
|
|
828
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
829
|
+
.map((file) => ` ${file}`)
|
|
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.`;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function makeAdapterNextSteps(
|
|
847
|
+
result: Awaited<ReturnType<typeof makeAdapter>>,
|
|
848
|
+
): string {
|
|
849
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
850
|
+
.map((file) => ` ${file}`)
|
|
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.`;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function makeEventNextSteps(
|
|
868
|
+
result: Awaited<ReturnType<typeof makeEvent>>,
|
|
869
|
+
): string {
|
|
870
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
871
|
+
.map((file) => ` ${file}`)
|
|
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(...).`;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function makeJobNextSteps(result: Awaited<ReturnType<typeof makeJob>>): string {
|
|
889
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
890
|
+
.map((file) => ` ${file}`)
|
|
891
|
+
.join("\n");
|
|
892
|
+
const skippedFiles = result.skippedFiles
|
|
893
|
+
.map((file) => ` ${file}`)
|
|
894
|
+
.join("\n");
|
|
895
|
+
|
|
896
|
+
return `${result.dryRun ? "Would create" : "Created"} ${result.name} job in ${result.targetDir}
|
|
897
|
+
|
|
898
|
+
Changed files:
|
|
899
|
+
${changedFiles || " none"}
|
|
900
|
+
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
901
|
+
|
|
902
|
+
Next steps:
|
|
903
|
+
Replace the starter payload and handler with the real background work.
|
|
904
|
+
Dispatch the job through ctx.ports.jobs from a use case, listener, or schedule.`;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function makeListenerNextSteps(
|
|
908
|
+
result: Awaited<ReturnType<typeof makeListener>>,
|
|
909
|
+
): string {
|
|
910
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
911
|
+
.map((file) => ` ${file}`)
|
|
912
|
+
.join("\n");
|
|
913
|
+
const skippedFiles = result.skippedFiles
|
|
914
|
+
.map((file) => ` ${file}`)
|
|
915
|
+
.join("\n");
|
|
916
|
+
|
|
917
|
+
return `${result.dryRun ? "Would create" : "Created"} ${result.name} listener in ${result.targetDir}
|
|
918
|
+
|
|
919
|
+
Changed files:
|
|
920
|
+
${changedFiles || " none"}
|
|
921
|
+
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
922
|
+
|
|
923
|
+
Next steps:
|
|
924
|
+
Replace the starter handler with the event reaction.
|
|
925
|
+
Register the listener collection from infrastructure startup.`;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function makeScheduleNextSteps(
|
|
929
|
+
result: Awaited<ReturnType<typeof makeSchedule>>,
|
|
930
|
+
): string {
|
|
931
|
+
const changedFiles = [...result.createdFiles, ...result.updatedFiles]
|
|
932
|
+
.map((file) => ` ${file}`)
|
|
933
|
+
.join("\n");
|
|
934
|
+
const skippedFiles = result.skippedFiles
|
|
935
|
+
.map((file) => ` ${file}`)
|
|
936
|
+
.join("\n");
|
|
937
|
+
|
|
938
|
+
return `${result.dryRun ? "Would create" : "Created"} ${result.name} schedule in ${result.targetDir}
|
|
939
|
+
|
|
940
|
+
Changed files:
|
|
941
|
+
${changedFiles || " none"}
|
|
942
|
+
${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
|
|
943
|
+
|
|
944
|
+
Next steps:
|
|
945
|
+
Replace the starter payload and handler with the scheduled workflow.
|
|
946
|
+
Trigger it from a cron route, worker, or provider adapter.`;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
export async function main(
|
|
950
|
+
inputs = process.argv.slice(2),
|
|
951
|
+
context: StricliDynamicCommandContext<CliContext> = { process },
|
|
952
|
+
): Promise<void> {
|
|
953
|
+
if (inputs.length === 0) {
|
|
954
|
+
await run(cli, ["--help"], context);
|
|
955
|
+
context.process.exitCode = 1;
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
await run(cli, inputs, context);
|
|
960
|
+
normalizeExitCode(context.process);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function normalizeExitCode(proc: StricliProcess): void {
|
|
964
|
+
if (
|
|
965
|
+
typeof proc.exitCode === "number" &&
|
|
966
|
+
(proc.exitCode < 0 || proc.exitCode > 127)
|
|
967
|
+
) {
|
|
968
|
+
proc.exitCode = 1;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function isCliEntrypoint(): boolean {
|
|
973
|
+
const argvPath = process.argv[1];
|
|
974
|
+
if (!argvPath) {
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
try {
|
|
979
|
+
return (
|
|
980
|
+
realpathSync(fileURLToPath(import.meta.url)) === realpathSync(argvPath)
|
|
981
|
+
);
|
|
982
|
+
} catch {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (isCliEntrypoint()) {
|
|
988
|
+
main().catch((error: unknown) => {
|
|
989
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
990
|
+
process.exitCode = 1;
|
|
991
|
+
});
|
|
992
|
+
}
|