@beignet/cli 0.0.1 → 0.0.3

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/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  } from "@stricli/core";
15
15
  import type { CreateOptions } from "./create.js";
16
16
  import { createProject } from "./create.js";
17
+ import { type DatabaseCommand, runDatabaseCommand } from "./db.js";
17
18
  import {
18
19
  applyDoctorFixes,
19
20
  formatDoctor,
@@ -25,15 +26,24 @@ import {
25
26
  makeAdapter,
26
27
  makeContract,
27
28
  makeEvent,
29
+ makeFactory,
30
+ makeFeature,
31
+ makeFeatureAddonChoices,
28
32
  makeJob,
29
33
  makeListener,
34
+ makeNotification,
30
35
  makePolicy,
31
36
  makePort,
32
37
  makeResource,
33
38
  makeSchedule,
39
+ makeSeed,
34
40
  makeTest,
41
+ makeUpload,
35
42
  makeUseCase,
36
43
  } from "./make.js";
44
+
45
+ export type { MakeFeatureAddon } from "./make.js";
46
+
37
47
  import {
38
48
  type FeatureName,
39
49
  featureChoices,
@@ -58,14 +68,21 @@ export {
58
68
  makeAdapter,
59
69
  makeContract,
60
70
  makeEvent,
71
+ makeFactory,
72
+ makeFeature,
73
+ makeFeatureAddonChoices,
61
74
  makeJob,
62
75
  makeListener,
76
+ makeNotification,
63
77
  makePolicy,
64
78
  makePort,
65
79
  makeResource,
66
80
  makeSchedule,
81
+ makeSeed,
67
82
  makeTest,
83
+ makeUpload,
68
84
  makeUseCase,
85
+ runDatabaseCommand,
69
86
  };
70
87
 
71
88
  type CliContext = CommandContext & {
@@ -89,6 +106,10 @@ type MakeFlags = {
89
106
  json?: boolean;
90
107
  };
91
108
 
109
+ type MakeFeatureFlags = MakeFlags & {
110
+ with?: readonly (typeof makeFeatureAddonChoices)[number][];
111
+ };
112
+
92
113
  type MakeListenerFlags = MakeFlags & {
93
114
  event?: string;
94
115
  };
@@ -103,6 +124,11 @@ type JsonFlags = {
103
124
  json?: boolean;
104
125
  };
105
126
 
127
+ type DbFlags = {
128
+ json?: boolean;
129
+ dryRun?: boolean;
130
+ };
131
+
106
132
  type DoctorFlags = {
107
133
  json?: boolean;
108
134
  strict?: boolean;
@@ -158,6 +184,11 @@ const jsonFlagParameters = {
158
184
  json: jsonFlag,
159
185
  } satisfies FlagParametersForType<JsonFlags, CliContext>;
160
186
 
187
+ const dbFlagParameters = {
188
+ json: jsonFlag,
189
+ dryRun: dryRunFlag,
190
+ } satisfies FlagParametersForType<DbFlags, CliContext>;
191
+
161
192
  const namePositional = {
162
193
  kind: "tuple",
163
194
  parameters: [
@@ -337,6 +368,46 @@ const lintCommand = buildCommand<JsonFlags, [], CliContext>({
337
368
  },
338
369
  });
339
370
 
371
+ function databaseCommand(command: DatabaseCommand) {
372
+ return buildCommand<DbFlags, [], CliContext>({
373
+ docs: {
374
+ brief: `Run the app's db:${command} script.`,
375
+ },
376
+ parameters: {
377
+ flags: dbFlagParameters,
378
+ },
379
+ async func(flags) {
380
+ const result = await runDatabaseCommand({
381
+ command,
382
+ captureOutput: Boolean(flags.json),
383
+ dryRun: Boolean(flags.dryRun),
384
+ });
385
+
386
+ writeOutput(
387
+ this,
388
+ flags.json
389
+ ? JSON.stringify(result, null, 2)
390
+ : databaseCommandNextSteps(result),
391
+ );
392
+ if (result.exitCode !== 0) {
393
+ this.process.exitCode = result.exitCode;
394
+ }
395
+ },
396
+ });
397
+ }
398
+
399
+ const dbRoutes = buildRouteMap({
400
+ docs: {
401
+ brief: "Run Beignet database lifecycle commands.",
402
+ },
403
+ routes: {
404
+ generate: databaseCommand("generate"),
405
+ migrate: databaseCommand("migrate"),
406
+ reset: databaseCommand("reset"),
407
+ seed: databaseCommand("seed"),
408
+ },
409
+ });
410
+
340
411
  const makeResourceCommand = buildCommand<MakeFlags, [string], CliContext>({
341
412
  docs: {
342
413
  brief: "Generate a feature resource.",
@@ -360,6 +431,42 @@ const makeResourceCommand = buildCommand<MakeFlags, [string], CliContext>({
360
431
  },
361
432
  });
362
433
 
434
+ const makeFeatureCommand = buildCommand<MakeFeatureFlags, [string], CliContext>(
435
+ {
436
+ docs: {
437
+ brief: "Generate a contract-first feature slice.",
438
+ },
439
+ parameters: {
440
+ flags: {
441
+ ...makeFlagParameters,
442
+ with: {
443
+ kind: "enum",
444
+ values: makeFeatureAddonChoices,
445
+ optional: true,
446
+ variadic: ",",
447
+ brief:
448
+ "Add feature-owned artifacts. Supports policy, event/events, job/jobs, and upload/uploads.",
449
+ },
450
+ } satisfies FlagParametersForType<MakeFeatureFlags, CliContext>,
451
+ positional: namePositional,
452
+ },
453
+ async func(flags, name) {
454
+ const result = await makeFeature({
455
+ name,
456
+ with: flags.with,
457
+ force: Boolean(flags.force),
458
+ dryRun: Boolean(flags.dryRun),
459
+ });
460
+ writeOutput(
461
+ this,
462
+ flags.json
463
+ ? JSON.stringify(result, null, 2)
464
+ : makeFeatureNextSteps(result),
465
+ );
466
+ },
467
+ },
468
+ );
469
+
363
470
  const makeContractCommand = buildCommand<MakeFlags, [string], CliContext>({
364
471
  docs: {
365
472
  brief: "Generate a contract group.",
@@ -536,6 +643,73 @@ const makeJobCommand = buildCommand<MakeFlags, [string], CliContext>({
536
643
  },
537
644
  });
538
645
 
646
+ const makeFactoryCommand = buildCommand<MakeFlags, [string], CliContext>({
647
+ docs: {
648
+ brief: "Generate a feature test data factory.",
649
+ },
650
+ parameters: {
651
+ flags: makeFlagParameters,
652
+ positional: namePositional,
653
+ },
654
+ async func(flags, name) {
655
+ const result = await makeFactory({
656
+ name,
657
+ force: Boolean(flags.force),
658
+ dryRun: Boolean(flags.dryRun),
659
+ });
660
+ writeOutput(
661
+ this,
662
+ flags.json
663
+ ? JSON.stringify(result, null, 2)
664
+ : makeFactoryNextSteps(result),
665
+ );
666
+ },
667
+ });
668
+
669
+ const makeSeedCommand = buildCommand<MakeFlags, [string], CliContext>({
670
+ docs: {
671
+ brief: "Generate a feature seed.",
672
+ },
673
+ parameters: {
674
+ flags: makeFlagParameters,
675
+ positional: namePositional,
676
+ },
677
+ async func(flags, name) {
678
+ const result = await makeSeed({
679
+ name,
680
+ force: Boolean(flags.force),
681
+ dryRun: Boolean(flags.dryRun),
682
+ });
683
+ writeOutput(
684
+ this,
685
+ flags.json ? JSON.stringify(result, null, 2) : makeSeedNextSteps(result),
686
+ );
687
+ },
688
+ });
689
+
690
+ const makeNotificationCommand = buildCommand<MakeFlags, [string], CliContext>({
691
+ docs: {
692
+ brief: "Generate a feature notification.",
693
+ },
694
+ parameters: {
695
+ flags: makeFlagParameters,
696
+ positional: namePositional,
697
+ },
698
+ async func(flags, name) {
699
+ const result = await makeNotification({
700
+ name,
701
+ force: Boolean(flags.force),
702
+ dryRun: Boolean(flags.dryRun),
703
+ });
704
+ writeOutput(
705
+ this,
706
+ flags.json
707
+ ? JSON.stringify(result, null, 2)
708
+ : makeNotificationNextSteps(result),
709
+ );
710
+ },
711
+ });
712
+
539
713
  const makeListenerFlagParameters = {
540
714
  ...makeFlagParameters,
541
715
  event: parsedStringFlag("Event to listen to, for example posts/published."),
@@ -617,6 +791,29 @@ const makeScheduleCommand = buildCommand<
617
791
  },
618
792
  });
619
793
 
794
+ const makeUploadCommand = buildCommand<MakeFlags, [string], CliContext>({
795
+ docs: {
796
+ brief: "Generate a feature upload.",
797
+ },
798
+ parameters: {
799
+ flags: makeFlagParameters,
800
+ positional: namePositional,
801
+ },
802
+ async func(flags, name) {
803
+ const result = await makeUpload({
804
+ name,
805
+ force: Boolean(flags.force),
806
+ dryRun: Boolean(flags.dryRun),
807
+ });
808
+ writeOutput(
809
+ this,
810
+ flags.json
811
+ ? JSON.stringify(result, null, 2)
812
+ : makeUploadNextSteps(result),
813
+ );
814
+ },
815
+ });
816
+
620
817
  const makeRoutes = buildRouteMap({
621
818
  docs: {
622
819
  brief: "Generate Beignet app files.",
@@ -625,13 +822,18 @@ const makeRoutes = buildRouteMap({
625
822
  adapter: makeAdapterCommand,
626
823
  contract: makeContractCommand,
627
824
  event: makeEventCommand,
825
+ factory: makeFactoryCommand,
826
+ feature: makeFeatureCommand,
628
827
  job: makeJobCommand,
828
+ notification: makeNotificationCommand,
629
829
  listener: makeListenerCommand,
630
830
  policy: makePolicyCommand,
631
831
  port: makePortCommand,
632
832
  resource: makeResourceCommand,
633
833
  schedule: makeScheduleCommand,
834
+ seed: makeSeedCommand,
634
835
  test: makeTestCommand,
836
+ upload: makeUploadCommand,
635
837
  useCase: makeUseCaseCommand,
636
838
  },
637
839
  });
@@ -645,6 +847,7 @@ create-beignet <directory> is equivalent to beignet create <directory>.`,
645
847
  },
646
848
  routes: {
647
849
  create: createCommand,
850
+ db: dbRoutes,
648
851
  doctor: doctorCommand,
649
852
  lint: lintCommand,
650
853
  make: makeRoutes,
@@ -738,6 +941,39 @@ Next steps:
738
941
  Run your app's typecheck and test commands.`;
739
942
  }
740
943
 
944
+ function databaseCommandNextSteps(
945
+ result: Awaited<ReturnType<typeof runDatabaseCommand>>,
946
+ ): string {
947
+ const command = [result.runner, ...result.args].join(" ");
948
+ const prefix = result.dryRun ? "Would run" : "Ran";
949
+
950
+ return `${prefix} ${result.script} in ${result.cwd}
951
+
952
+ Command:
953
+ ${command}`;
954
+ }
955
+
956
+ function makeFeatureNextSteps(
957
+ result: Awaited<ReturnType<typeof makeFeature>>,
958
+ ): string {
959
+ const changedFiles = [...result.createdFiles, ...result.updatedFiles]
960
+ .map((file) => ` ${file}`)
961
+ .join("\n");
962
+ const skippedFiles = result.skippedFiles
963
+ .map((file) => ` ${file}`)
964
+ .join("\n");
965
+
966
+ return `${result.dryRun ? "Would create" : "Created"} ${result.name} feature slice in ${result.targetDir}
967
+
968
+ Changed files:
969
+ ${changedFiles || " none"}
970
+ ${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
971
+
972
+ Next steps:
973
+ Treat the generated name field as a placeholder and shape the contracts, use cases, and repository around the feature's real workflow.
974
+ Run your app's typecheck, test, beignet lint, and beignet doctor commands.`;
975
+ }
976
+
741
977
  function makeContractNextSteps(
742
978
  result: Awaited<ReturnType<typeof makeContract>>,
743
979
  ): string {
@@ -904,6 +1140,69 @@ Next steps:
904
1140
  Dispatch the job through ctx.ports.jobs from a use case, listener, or schedule.`;
905
1141
  }
906
1142
 
1143
+ function makeFactoryNextSteps(
1144
+ result: Awaited<ReturnType<typeof makeFactory>>,
1145
+ ): string {
1146
+ const changedFiles = [...result.createdFiles, ...result.updatedFiles]
1147
+ .map((file) => ` ${file}`)
1148
+ .join("\n");
1149
+ const skippedFiles = result.skippedFiles
1150
+ .map((file) => ` ${file}`)
1151
+ .join("\n");
1152
+
1153
+ return `${result.dryRun ? "Would create" : "Created"} ${result.name} factory in ${result.targetDir}
1154
+
1155
+ Changed files:
1156
+ ${changedFiles || " none"}
1157
+ ${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
1158
+
1159
+ Next steps:
1160
+ Replace the starter defaults with realistic test data for this feature.
1161
+ Persist through app-owned repository ports so tests avoid raw database clients.`;
1162
+ }
1163
+
1164
+ function makeSeedNextSteps(
1165
+ result: Awaited<ReturnType<typeof makeSeed>>,
1166
+ ): string {
1167
+ const changedFiles = [...result.createdFiles, ...result.updatedFiles]
1168
+ .map((file) => ` ${file}`)
1169
+ .join("\n");
1170
+ const skippedFiles = result.skippedFiles
1171
+ .map((file) => ` ${file}`)
1172
+ .join("\n");
1173
+
1174
+ return `${result.dryRun ? "Would create" : "Created"} ${result.name} seed in ${result.targetDir}
1175
+
1176
+ Changed files:
1177
+ ${changedFiles || " none"}
1178
+ ${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
1179
+
1180
+ Next steps:
1181
+ Replace the starter record with idempotent local or demo data.
1182
+ Import the feature seed from your app-owned database seed entrypoint and run it with runSeeds(...).`;
1183
+ }
1184
+
1185
+ function makeNotificationNextSteps(
1186
+ result: Awaited<ReturnType<typeof makeNotification>>,
1187
+ ): string {
1188
+ const changedFiles = [...result.createdFiles, ...result.updatedFiles]
1189
+ .map((file) => ` ${file}`)
1190
+ .join("\n");
1191
+ const skippedFiles = result.skippedFiles
1192
+ .map((file) => ` ${file}`)
1193
+ .join("\n");
1194
+
1195
+ return `${result.dryRun ? "Would create" : "Created"} ${result.name} notification in ${result.targetDir}
1196
+
1197
+ Changed files:
1198
+ ${changedFiles || " none"}
1199
+ ${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
1200
+
1201
+ Next steps:
1202
+ Replace the starter payload and channels with the real user-facing message.
1203
+ Send the notification through ctx.ports.notifications so delivery can run inline in tests and through jobs in production.`;
1204
+ }
1205
+
907
1206
  function makeListenerNextSteps(
908
1207
  result: Awaited<ReturnType<typeof makeListener>>,
909
1208
  ): string {
@@ -946,6 +1245,27 @@ Next steps:
946
1245
  Trigger it from a cron route, worker, or provider adapter.`;
947
1246
  }
948
1247
 
1248
+ function makeUploadNextSteps(
1249
+ result: Awaited<ReturnType<typeof makeUpload>>,
1250
+ ): string {
1251
+ const changedFiles = [...result.createdFiles, ...result.updatedFiles]
1252
+ .map((file) => ` ${file}`)
1253
+ .join("\n");
1254
+ const skippedFiles = result.skippedFiles
1255
+ .map((file) => ` ${file}`)
1256
+ .join("\n");
1257
+
1258
+ return `${result.dryRun ? "Would create" : "Created"} ${result.name} upload in ${result.targetDir}
1259
+
1260
+ Changed files:
1261
+ ${changedFiles || " none"}
1262
+ ${skippedFiles ? `\nSkipped identical files:\n${skippedFiles}` : ""}
1263
+
1264
+ Next steps:
1265
+ Replace the starter metadata, constraints, key, authorization, and onComplete behavior.
1266
+ Register the upload in createUploadRouter(...) and expose it with createUploadRoute(...).`;
1267
+ }
1268
+
949
1269
  export async function main(
950
1270
  inputs = process.argv.slice(2),
951
1271
  context: StricliDynamicCommandContext<CliContext> = { process },