@beignet/cli 0.0.15 → 0.0.17
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 +20 -0
- package/README.md +21 -16
- package/dist/choices.d.ts +1 -1
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +8 -0
- package/dist/choices.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +1 -1
- package/dist/inspect.js.map +1 -1
- package/dist/make.d.ts +14 -0
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +620 -15
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +12 -4
- package/dist/mcp.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +9 -6
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/shared.js +2 -2
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/shell.d.ts.map +1 -1
- package/dist/templates/shell.js +2 -2
- package/dist/templates/shell.js.map +1 -1
- package/package.json +2 -2
- package/src/choices.ts +8 -0
- package/src/index.ts +35 -2
- package/src/inspect.ts +1 -1
- package/src/make.ts +874 -16
- package/src/mcp.ts +12 -3
- package/src/templates/agents.ts +9 -6
- package/src/templates/shared.ts +2 -2
- package/src/templates/shell.ts +2 -1
package/dist/make.js
CHANGED
|
@@ -218,6 +218,13 @@ async function makeFeatureAddon(addon, feature, options) {
|
|
|
218
218
|
if (addon === "policy") {
|
|
219
219
|
return makePolicy({ ...shared, name: feature });
|
|
220
220
|
}
|
|
221
|
+
if (addon === "factory") {
|
|
222
|
+
return makeFactory({
|
|
223
|
+
...shared,
|
|
224
|
+
name: `${feature}/${singularize(feature)}`,
|
|
225
|
+
skipPersistenceAssert: true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
221
228
|
if (addon === "task") {
|
|
222
229
|
return makeTask({ ...shared, name: `${feature}/backfill` });
|
|
223
230
|
}
|
|
@@ -227,9 +234,27 @@ async function makeFeatureAddon(addon, feature, options) {
|
|
|
227
234
|
if (addon === "job") {
|
|
228
235
|
return makeJob({ ...shared, name: `${feature}/process` });
|
|
229
236
|
}
|
|
237
|
+
if (addon === "listener") {
|
|
238
|
+
return makeListener({
|
|
239
|
+
...shared,
|
|
240
|
+
name: `${feature}/handle-created`,
|
|
241
|
+
event: `${feature}/created`,
|
|
242
|
+
skipEventAssert: true,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
230
245
|
if (addon === "notification") {
|
|
231
246
|
return makeNotification({ ...shared, name: `${feature}/created` });
|
|
232
247
|
}
|
|
248
|
+
if (addon === "schedule") {
|
|
249
|
+
return makeSchedule({ ...shared, name: `${feature}/daily-summary` });
|
|
250
|
+
}
|
|
251
|
+
if (addon === "seed") {
|
|
252
|
+
return makeSeed({
|
|
253
|
+
...shared,
|
|
254
|
+
name: `${feature}/demo-${feature}`,
|
|
255
|
+
skipPersistenceAssert: true,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
233
258
|
if (addon === "ui") {
|
|
234
259
|
return makeFeatureUi({ ...shared, name: feature });
|
|
235
260
|
}
|
|
@@ -238,16 +263,30 @@ async function makeFeatureAddon(addon, feature, options) {
|
|
|
238
263
|
function normalizeMakeFeatureAddons(addons) {
|
|
239
264
|
const normalized = new Set();
|
|
240
265
|
for (const addon of addons) {
|
|
241
|
-
if (addon === "
|
|
266
|
+
if (addon === "factories")
|
|
267
|
+
normalized.add("factory");
|
|
268
|
+
else if (addon === "events")
|
|
269
|
+
normalized.add("event");
|
|
270
|
+
else if (addon === "listeners") {
|
|
242
271
|
normalized.add("event");
|
|
272
|
+
normalized.add("listener");
|
|
273
|
+
}
|
|
243
274
|
else if (addon === "tasks")
|
|
244
275
|
normalized.add("task");
|
|
245
276
|
else if (addon === "jobs")
|
|
246
277
|
normalized.add("job");
|
|
247
278
|
else if (addon === "notifications")
|
|
248
279
|
normalized.add("notification");
|
|
280
|
+
else if (addon === "schedules")
|
|
281
|
+
normalized.add("schedule");
|
|
282
|
+
else if (addon === "seeds")
|
|
283
|
+
normalized.add("seed");
|
|
249
284
|
else if (addon === "uploads")
|
|
250
285
|
normalized.add("upload");
|
|
286
|
+
else if (addon === "listener") {
|
|
287
|
+
normalized.add("event");
|
|
288
|
+
normalized.add("listener");
|
|
289
|
+
}
|
|
251
290
|
else
|
|
252
291
|
normalized.add(addon);
|
|
253
292
|
}
|
|
@@ -255,10 +294,14 @@ function normalizeMakeFeatureAddons(addons) {
|
|
|
255
294
|
}
|
|
256
295
|
const featureAddonOrder = [
|
|
257
296
|
"policy",
|
|
297
|
+
"factory",
|
|
258
298
|
"task",
|
|
259
299
|
"event",
|
|
260
300
|
"job",
|
|
301
|
+
"listener",
|
|
261
302
|
"notification",
|
|
303
|
+
"schedule",
|
|
304
|
+
"seed",
|
|
262
305
|
"ui",
|
|
263
306
|
"upload",
|
|
264
307
|
];
|
|
@@ -271,6 +314,21 @@ function mergeMakeResults(left, right) {
|
|
|
271
314
|
skippedFiles: uniqueStrings([...left.skippedFiles, ...right.skippedFiles]),
|
|
272
315
|
};
|
|
273
316
|
}
|
|
317
|
+
function mergeMakeResultInto(target, source) {
|
|
318
|
+
target.files = uniqueStrings([...target.files, ...source.files]);
|
|
319
|
+
target.createdFiles = uniqueStrings([
|
|
320
|
+
...target.createdFiles,
|
|
321
|
+
...source.createdFiles,
|
|
322
|
+
]);
|
|
323
|
+
target.updatedFiles = uniqueStrings([
|
|
324
|
+
...target.updatedFiles,
|
|
325
|
+
...source.updatedFiles,
|
|
326
|
+
]);
|
|
327
|
+
target.skippedFiles = uniqueStrings([
|
|
328
|
+
...target.skippedFiles,
|
|
329
|
+
...source.skippedFiles,
|
|
330
|
+
]);
|
|
331
|
+
}
|
|
274
332
|
function uniqueStrings(values) {
|
|
275
333
|
return [...new Set(values)];
|
|
276
334
|
}
|
|
@@ -522,6 +580,8 @@ export async function makeEvent(options) {
|
|
|
522
580
|
? resolveConfig(options.config)
|
|
523
581
|
: await loadBeignetConfig(targetDir);
|
|
524
582
|
await assertFeatureArtifactApp(targetDir, config, "event");
|
|
583
|
+
await assertServerRuntimeApp(targetDir, config, "event");
|
|
584
|
+
await updateOutboxPortWiring(targetDir, config, { dryRun: true });
|
|
525
585
|
// Events are published through ctx.ports.eventBus, so the generator wires
|
|
526
586
|
// the port and an in-memory dev provider when the app has no bus yet. Plan
|
|
527
587
|
// the wiring before writing any files so an anchor miss aborts the
|
|
@@ -530,6 +590,9 @@ export async function makeEvent(options) {
|
|
|
530
590
|
await wirePortProviders(targetDir, config, eventBusWirings, {
|
|
531
591
|
dryRun: true,
|
|
532
592
|
});
|
|
593
|
+
const outboxDrainPlan = await planOutboxDrainWiring(targetDir, config, {
|
|
594
|
+
force: Boolean(options.force),
|
|
595
|
+
});
|
|
533
596
|
const result = await makeFeatureArtifact({
|
|
534
597
|
targetDir,
|
|
535
598
|
config,
|
|
@@ -545,6 +608,12 @@ export async function makeEvent(options) {
|
|
|
545
608
|
await applyOutboxRegistryUpdate(result, targetDir, names, "events", config, {
|
|
546
609
|
dryRun: Boolean(options.dryRun),
|
|
547
610
|
});
|
|
611
|
+
await applyOutboxDrainWiring(result, targetDir, outboxDrainPlan, {
|
|
612
|
+
dryRun: Boolean(options.dryRun),
|
|
613
|
+
});
|
|
614
|
+
await applyOutboxPortWiring(result, targetDir, config, {
|
|
615
|
+
dryRun: Boolean(options.dryRun),
|
|
616
|
+
});
|
|
548
617
|
await applyPortProviderWiring(result, targetDir, config, eventBusWirings, {
|
|
549
618
|
dryRun: Boolean(options.dryRun),
|
|
550
619
|
});
|
|
@@ -557,6 +626,11 @@ export async function makeJob(options) {
|
|
|
557
626
|
? resolveConfig(options.config)
|
|
558
627
|
: await loadBeignetConfig(targetDir);
|
|
559
628
|
await assertFeatureArtifactApp(targetDir, config, "job");
|
|
629
|
+
await assertServerRuntimeApp(targetDir, config, "job");
|
|
630
|
+
await updateOutboxPortWiring(targetDir, config, { dryRun: true });
|
|
631
|
+
const outboxDrainPlan = await planOutboxDrainWiring(targetDir, config, {
|
|
632
|
+
force: Boolean(options.force),
|
|
633
|
+
});
|
|
560
634
|
const result = await makeFeatureArtifact({
|
|
561
635
|
targetDir,
|
|
562
636
|
config,
|
|
@@ -575,6 +649,12 @@ export async function makeJob(options) {
|
|
|
575
649
|
await applyOutboxRegistryUpdate(result, targetDir, names, "jobs", config, {
|
|
576
650
|
dryRun: Boolean(options.dryRun),
|
|
577
651
|
});
|
|
652
|
+
await applyOutboxDrainWiring(result, targetDir, outboxDrainPlan, {
|
|
653
|
+
dryRun: Boolean(options.dryRun),
|
|
654
|
+
});
|
|
655
|
+
await applyOutboxPortWiring(result, targetDir, config, {
|
|
656
|
+
dryRun: Boolean(options.dryRun),
|
|
657
|
+
});
|
|
578
658
|
return result;
|
|
579
659
|
}
|
|
580
660
|
export async function makeTask(options) {
|
|
@@ -619,7 +699,9 @@ export async function makeFactory(options) {
|
|
|
619
699
|
const config = options.config
|
|
620
700
|
? resolveConfig(options.config)
|
|
621
701
|
: await loadBeignetConfig(targetDir);
|
|
622
|
-
|
|
702
|
+
if (!options.skipPersistenceAssert) {
|
|
703
|
+
await assertPersistenceArtifactApp(targetDir, names, config, "factory");
|
|
704
|
+
}
|
|
623
705
|
return makeFeatureArtifact({
|
|
624
706
|
targetDir,
|
|
625
707
|
config,
|
|
@@ -639,8 +721,10 @@ export async function makeSeed(options) {
|
|
|
639
721
|
const config = options.config
|
|
640
722
|
? resolveConfig(options.config)
|
|
641
723
|
: await loadBeignetConfig(targetDir);
|
|
642
|
-
|
|
643
|
-
|
|
724
|
+
if (!options.skipPersistenceAssert) {
|
|
725
|
+
await assertPersistenceArtifactApp(targetDir, names, config, "seed");
|
|
726
|
+
}
|
|
727
|
+
const result = await makeFeatureArtifact({
|
|
644
728
|
targetDir,
|
|
645
729
|
config,
|
|
646
730
|
force: Boolean(options.force),
|
|
@@ -652,6 +736,38 @@ export async function makeSeed(options) {
|
|
|
652
736
|
"@beignet/core": beignetDependencyVersion,
|
|
653
737
|
},
|
|
654
738
|
});
|
|
739
|
+
const factoryNamesForSeed = defaultFactoryNamesForSeed(names);
|
|
740
|
+
if (!(await fileExists(path.join(targetDir, factoryFilePath(factoryNamesForSeed, config))))) {
|
|
741
|
+
const factoryResult = await makeFeatureArtifact({
|
|
742
|
+
targetDir,
|
|
743
|
+
config,
|
|
744
|
+
force: Boolean(options.force),
|
|
745
|
+
dryRun: Boolean(options.dryRun),
|
|
746
|
+
name: `${factoryNamesForSeed.feature.kebab}/${factoryNamesForSeed.artifact.kebab}`,
|
|
747
|
+
files: factoryFiles(factoryNamesForSeed, config),
|
|
748
|
+
index: featureArtifactIndexFile("factory", factoryNamesForSeed, config),
|
|
749
|
+
dependencies: {
|
|
750
|
+
"@beignet/core": beignetDependencyVersion,
|
|
751
|
+
},
|
|
752
|
+
});
|
|
753
|
+
mergeMakeResultInto(result, factoryResult);
|
|
754
|
+
}
|
|
755
|
+
const seedEntrypointResult = await updateSeedEntrypoint(targetDir, names, config, { dryRun: Boolean(options.dryRun) });
|
|
756
|
+
if (seedEntrypointResult === "created")
|
|
757
|
+
result.createdFiles.push(seedEntrypointPath(config));
|
|
758
|
+
if (seedEntrypointResult === "updated")
|
|
759
|
+
result.updatedFiles.push(seedEntrypointPath(config));
|
|
760
|
+
if (seedEntrypointResult === "skipped")
|
|
761
|
+
result.skippedFiles.push(seedEntrypointPath(config));
|
|
762
|
+
if (!result.files.includes(seedEntrypointPath(config))) {
|
|
763
|
+
result.files.push(seedEntrypointPath(config));
|
|
764
|
+
}
|
|
765
|
+
if (await updatePackageScripts(targetDir, { "db:seed": `bun ${seedEntrypointPath(config)}` }, { dryRun: Boolean(options.dryRun) })) {
|
|
766
|
+
result.updatedFiles.push("package.json");
|
|
767
|
+
if (!result.files.includes("package.json"))
|
|
768
|
+
result.files.push("package.json");
|
|
769
|
+
}
|
|
770
|
+
return result;
|
|
655
771
|
}
|
|
656
772
|
export async function makeNotification(options) {
|
|
657
773
|
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
@@ -696,7 +812,9 @@ export async function makeListener(options) {
|
|
|
696
812
|
? resolveConfig(options.config)
|
|
697
813
|
: await loadBeignetConfig(targetDir);
|
|
698
814
|
await assertFeatureArtifactApp(targetDir, config, "listener");
|
|
699
|
-
|
|
815
|
+
if (!options.skipEventAssert) {
|
|
816
|
+
await assertListenerEventExists(targetDir, names.event, config);
|
|
817
|
+
}
|
|
700
818
|
return makeFeatureArtifact({
|
|
701
819
|
targetDir,
|
|
702
820
|
config,
|
|
@@ -850,6 +968,164 @@ async function applyCronSecretWiring(targetDir, plan, options) {
|
|
|
850
968
|
skippedFiles: plan.skippedFiles,
|
|
851
969
|
};
|
|
852
970
|
}
|
|
971
|
+
async function planOutboxDrainWiring(targetDir, config, options) {
|
|
972
|
+
if (await fileExists(path.join(targetDir, outboxDrainRoutePath(config)))) {
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
const route = await planGeneratedFile(targetDir, {
|
|
976
|
+
path: outboxDrainRoutePath(config),
|
|
977
|
+
content: outboxDrainRouteFile(config),
|
|
978
|
+
}, { force: options.force });
|
|
979
|
+
return {
|
|
980
|
+
route,
|
|
981
|
+
cronSecret: await planCronSecretWiring(targetDir),
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
async function updateOutboxPortWiring(targetDir, config, options) {
|
|
985
|
+
const updated = new Set();
|
|
986
|
+
if (await updateOutboxPorts(targetDir, config, options)) {
|
|
987
|
+
updated.add(config.paths.ports);
|
|
988
|
+
}
|
|
989
|
+
if (await updateOutboxInfrastructurePorts(targetDir, config, options)) {
|
|
990
|
+
updated.add(config.paths.infrastructurePorts);
|
|
991
|
+
}
|
|
992
|
+
if (await updateOutboxDatabaseProvider(targetDir, config, options)) {
|
|
993
|
+
updated.add(path.join(infrastructureDir(config), "db/provider.ts"));
|
|
994
|
+
}
|
|
995
|
+
if (await updateOutboxRepositoriesReturnType(targetDir, config, options)) {
|
|
996
|
+
updated.add(drizzleRepositoriesPath(config));
|
|
997
|
+
}
|
|
998
|
+
return [...updated];
|
|
999
|
+
}
|
|
1000
|
+
async function applyOutboxPortWiring(result, targetDir, config, options) {
|
|
1001
|
+
const updatedFiles = await updateOutboxPortWiring(targetDir, config, options);
|
|
1002
|
+
for (const file of updatedFiles) {
|
|
1003
|
+
if (!result.updatedFiles.includes(file))
|
|
1004
|
+
result.updatedFiles.push(file);
|
|
1005
|
+
if (!result.files.includes(file))
|
|
1006
|
+
result.files.push(file);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
async function updateOutboxPorts(targetDir, config, options) {
|
|
1010
|
+
const filePath = path.join(targetDir, config.paths.ports);
|
|
1011
|
+
const original = await readFile(filePath, "utf8");
|
|
1012
|
+
let next = addNamedTypeImport(original, "OutboxPort", "@beignet/core/outbox");
|
|
1013
|
+
if (!typeHasTopLevelProperty(next, "AppPorts", "outbox")) {
|
|
1014
|
+
next = insertTypeProperty(next, "AppPorts", "outbox: OutboxPort;", `Could not find AppPorts in ${config.paths.ports}. Add outbox: OutboxPort; manually, or restore the generated ports file before running make outbox.`);
|
|
1015
|
+
}
|
|
1016
|
+
if (!typeHasTopLevelProperty(next, "AppTransactionPorts", "outbox")) {
|
|
1017
|
+
next = insertTypeProperty(next, "AppTransactionPorts", "outbox: OutboxPort;", `Could not find AppTransactionPorts in ${config.paths.ports}. Add outbox: OutboxPort; manually, or restore the generated ports file before running make outbox.`);
|
|
1018
|
+
}
|
|
1019
|
+
if (next === original)
|
|
1020
|
+
return false;
|
|
1021
|
+
if (!options.dryRun)
|
|
1022
|
+
await writeFile(filePath, next);
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
async function updateOutboxInfrastructurePorts(targetDir, config, options) {
|
|
1026
|
+
const filePath = path.join(targetDir, config.paths.infrastructurePorts);
|
|
1027
|
+
const original = await readFile(filePath, "utf8");
|
|
1028
|
+
const next = appendDeferredPortKey(original, "outbox");
|
|
1029
|
+
if (next === original)
|
|
1030
|
+
return false;
|
|
1031
|
+
if (!options.dryRun)
|
|
1032
|
+
await writeFile(filePath, next);
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
async function updateOutboxDatabaseProvider(targetDir, config, options) {
|
|
1036
|
+
const file = path.join(infrastructureDir(config), "db/provider.ts");
|
|
1037
|
+
const filePath = path.join(targetDir, file);
|
|
1038
|
+
if (!(await fileExists(filePath)))
|
|
1039
|
+
return false;
|
|
1040
|
+
const database = await detectResourceDatabase(targetDir, config);
|
|
1041
|
+
const factoryName = drizzleOutboxPortFactoryName(database);
|
|
1042
|
+
const original = await readFile(filePath, "utf8");
|
|
1043
|
+
let next = addNamedImport(original, factoryName, `@beignet/provider-db-drizzle/${database}`);
|
|
1044
|
+
next = appendPickStringLiteralMember(next, "AppPorts", "outbox");
|
|
1045
|
+
if (!/\bconst\s+outbox\s*=/.test(next)) {
|
|
1046
|
+
const beforeOutboxConst = next;
|
|
1047
|
+
next = beforeOutboxConst.replace(/(\n\s*const idempotency = createDrizzle[A-Za-z]+IdempotencyPort\(dbPort\.db\);)/, `$1\n\t\tconst outbox = ${factoryName}(dbPort.db);`);
|
|
1048
|
+
if (next === beforeOutboxConst) {
|
|
1049
|
+
throw new Error(`Could not find the idempotency port in ${file}. Add const outbox = ${factoryName}(dbPort.db) manually, or restore the generated database provider before running make outbox.`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (!/\boutbox\s*,/.test(next)) {
|
|
1053
|
+
const withOutboxPort = next.replace(/(\n\s*idempotency,\n)/, `$1\t\t\toutbox,\n`);
|
|
1054
|
+
if (withOutboxPort === next) {
|
|
1055
|
+
throw new Error(`Could not find idempotency in providedPorts in ${file}. Add outbox to providedPorts manually, or restore the generated database provider before running make outbox.`);
|
|
1056
|
+
}
|
|
1057
|
+
next = withOutboxPort;
|
|
1058
|
+
}
|
|
1059
|
+
if (!new RegExp(`outbox:\\s*${factoryName}\\(tx\\)`).test(next)) {
|
|
1060
|
+
const withTransactionOutbox = next.replace(/(\n\s*idempotency: createDrizzle[A-Za-z]+IdempotencyPort\(tx\),\n)/, `$1\t\t\t\t\toutbox: ${factoryName}(tx),\n`);
|
|
1061
|
+
if (withTransactionOutbox === next) {
|
|
1062
|
+
throw new Error(`Could not find transaction idempotency wiring in ${file}. Add outbox: ${factoryName}(tx) manually, or restore the generated database provider before running make outbox.`);
|
|
1063
|
+
}
|
|
1064
|
+
next = withTransactionOutbox;
|
|
1065
|
+
}
|
|
1066
|
+
if (next === original)
|
|
1067
|
+
return false;
|
|
1068
|
+
if (!options.dryRun)
|
|
1069
|
+
await writeFile(filePath, next);
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
async function updateOutboxRepositoriesReturnType(targetDir, config, options) {
|
|
1073
|
+
const file = drizzleRepositoriesPath(config);
|
|
1074
|
+
const filePath = path.join(targetDir, file);
|
|
1075
|
+
if (!(await fileExists(filePath)))
|
|
1076
|
+
return false;
|
|
1077
|
+
const original = await readFile(filePath, "utf8");
|
|
1078
|
+
const next = original.replace(/Omit<AppTransactionPorts,\s*"idempotency"\s*>/g, 'Omit<AppTransactionPorts, "idempotency" | "outbox">');
|
|
1079
|
+
if (next === original)
|
|
1080
|
+
return false;
|
|
1081
|
+
if (!options.dryRun)
|
|
1082
|
+
await writeFile(filePath, next);
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
function drizzleOutboxPortFactoryName(database) {
|
|
1086
|
+
if (database === "postgres")
|
|
1087
|
+
return "createDrizzlePostgresOutboxPort";
|
|
1088
|
+
if (database === "mysql")
|
|
1089
|
+
return "createDrizzleMysqlOutboxPort";
|
|
1090
|
+
return "createDrizzleSqliteOutboxPort";
|
|
1091
|
+
}
|
|
1092
|
+
async function applyOutboxDrainWiring(result, targetDir, plan, options) {
|
|
1093
|
+
if (!plan)
|
|
1094
|
+
return;
|
|
1095
|
+
if (!options.dryRun) {
|
|
1096
|
+
await writePlannedGeneratedFile(targetDir, plan.route);
|
|
1097
|
+
}
|
|
1098
|
+
if (plan.route.result === "created") {
|
|
1099
|
+
result.createdFiles.push(plan.route.path);
|
|
1100
|
+
}
|
|
1101
|
+
if (plan.route.result === "updated") {
|
|
1102
|
+
result.updatedFiles.push(plan.route.path);
|
|
1103
|
+
}
|
|
1104
|
+
if (plan.route.result === "skipped") {
|
|
1105
|
+
result.skippedFiles.push(plan.route.path);
|
|
1106
|
+
}
|
|
1107
|
+
if (!result.files.includes(plan.route.path)) {
|
|
1108
|
+
result.files.push(plan.route.path);
|
|
1109
|
+
}
|
|
1110
|
+
const cronSecretResult = await applyCronSecretWiring(targetDir, plan.cronSecret, options);
|
|
1111
|
+
result.createdFiles = uniqueStrings([
|
|
1112
|
+
...result.createdFiles,
|
|
1113
|
+
...cronSecretResult.createdFiles,
|
|
1114
|
+
]);
|
|
1115
|
+
result.updatedFiles = uniqueStrings([
|
|
1116
|
+
...result.updatedFiles,
|
|
1117
|
+
...cronSecretResult.updatedFiles,
|
|
1118
|
+
]);
|
|
1119
|
+
result.skippedFiles = uniqueStrings([
|
|
1120
|
+
...result.skippedFiles,
|
|
1121
|
+
...cronSecretResult.skippedFiles,
|
|
1122
|
+
]);
|
|
1123
|
+
result.files = uniqueStrings([
|
|
1124
|
+
...result.files,
|
|
1125
|
+
...plan.cronSecret.files.map((file) => file.path),
|
|
1126
|
+
...plan.cronSecret.skippedFiles,
|
|
1127
|
+
]);
|
|
1128
|
+
}
|
|
853
1129
|
function eventBusPortWiring(command) {
|
|
854
1130
|
return {
|
|
855
1131
|
command,
|
|
@@ -891,6 +1167,17 @@ function paymentsPortWiring(command) {
|
|
|
891
1167
|
providerModule: "@beignet/core/payments",
|
|
892
1168
|
};
|
|
893
1169
|
}
|
|
1170
|
+
function storagePortWiring(command) {
|
|
1171
|
+
return {
|
|
1172
|
+
command,
|
|
1173
|
+
portKey: "storage",
|
|
1174
|
+
portType: "StoragePort",
|
|
1175
|
+
portTypeModule: "@beignet/core/ports",
|
|
1176
|
+
providerFactory: "createLocalStorageProvider",
|
|
1177
|
+
providerModule: "@beignet/provider-storage-local",
|
|
1178
|
+
dependency: "@beignet/provider-storage-local",
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
894
1181
|
function providersFilePath(config) {
|
|
895
1182
|
return `${directoryPath(path.dirname(config.paths.server))}/providers.ts`;
|
|
896
1183
|
}
|
|
@@ -1011,7 +1298,10 @@ export async function makeUpload(options) {
|
|
|
1011
1298
|
? resolveConfig(options.config)
|
|
1012
1299
|
: await loadBeignetConfig(targetDir);
|
|
1013
1300
|
await assertFeatureArtifactApp(targetDir, config, "upload");
|
|
1014
|
-
|
|
1301
|
+
await wirePortProviders(targetDir, config, [storagePortWiring("make upload")], {
|
|
1302
|
+
dryRun: true,
|
|
1303
|
+
});
|
|
1304
|
+
const result = await makeFeatureArtifact({
|
|
1015
1305
|
targetDir,
|
|
1016
1306
|
config,
|
|
1017
1307
|
force: Boolean(options.force),
|
|
@@ -1023,6 +1313,72 @@ export async function makeUpload(options) {
|
|
|
1023
1313
|
"@beignet/core": beignetDependencyVersion,
|
|
1024
1314
|
},
|
|
1025
1315
|
});
|
|
1316
|
+
await applyPortProviderWiring(result, targetDir, config, [storagePortWiring("make upload")], { dryRun: Boolean(options.dryRun) });
|
|
1317
|
+
const uploadRouteResult = await updateUploadRoute(targetDir, names, config, {
|
|
1318
|
+
dryRun: Boolean(options.dryRun),
|
|
1319
|
+
});
|
|
1320
|
+
if (uploadRouteResult === "created")
|
|
1321
|
+
result.createdFiles.push(uploadRoutePath(config));
|
|
1322
|
+
if (uploadRouteResult === "updated")
|
|
1323
|
+
result.updatedFiles.push(uploadRoutePath(config));
|
|
1324
|
+
if (uploadRouteResult === "skipped")
|
|
1325
|
+
result.skippedFiles.push(uploadRoutePath(config));
|
|
1326
|
+
if (!result.files.includes(uploadRoutePath(config))) {
|
|
1327
|
+
result.files.push(uploadRoutePath(config));
|
|
1328
|
+
}
|
|
1329
|
+
return result;
|
|
1330
|
+
}
|
|
1331
|
+
export async function makeOutbox(options = {}) {
|
|
1332
|
+
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
1333
|
+
const config = options.config
|
|
1334
|
+
? resolveConfig(options.config)
|
|
1335
|
+
: await loadBeignetConfig(targetDir);
|
|
1336
|
+
await assertServerRuntimeApp(targetDir, config, "outbox");
|
|
1337
|
+
await updateOutboxPortWiring(targetDir, config, { dryRun: true });
|
|
1338
|
+
const cronSecretPlan = await planCronSecretWiring(targetDir);
|
|
1339
|
+
const files = outboxFiles(config);
|
|
1340
|
+
const plannedFiles = await planGeneratedFiles(targetDir, files, {
|
|
1341
|
+
force: Boolean(options.force),
|
|
1342
|
+
});
|
|
1343
|
+
const createdFiles = plannedFiles
|
|
1344
|
+
.filter((file) => file.result === "created")
|
|
1345
|
+
.map((file) => file.path);
|
|
1346
|
+
const updatedFiles = plannedFiles
|
|
1347
|
+
.filter((file) => file.result === "updated")
|
|
1348
|
+
.map((file) => file.path);
|
|
1349
|
+
const skippedFiles = plannedFiles
|
|
1350
|
+
.filter((file) => file.result === "skipped")
|
|
1351
|
+
.map((file) => file.path);
|
|
1352
|
+
if (!options.dryRun) {
|
|
1353
|
+
for (const file of plannedFiles) {
|
|
1354
|
+
await writePlannedGeneratedFile(targetDir, file);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
const cronSecretResult = await applyCronSecretWiring(targetDir, cronSecretPlan, { dryRun: Boolean(options.dryRun) });
|
|
1358
|
+
createdFiles.push(...cronSecretResult.createdFiles);
|
|
1359
|
+
updatedFiles.push(...cronSecretResult.updatedFiles);
|
|
1360
|
+
skippedFiles.push(...cronSecretResult.skippedFiles);
|
|
1361
|
+
updatedFiles.push(...(await updateOutboxPortWiring(targetDir, config, {
|
|
1362
|
+
dryRun: Boolean(options.dryRun),
|
|
1363
|
+
})));
|
|
1364
|
+
return {
|
|
1365
|
+
schemaVersion: 1,
|
|
1366
|
+
name: "outbox",
|
|
1367
|
+
targetDir,
|
|
1368
|
+
dryRun: Boolean(options.dryRun),
|
|
1369
|
+
files: [
|
|
1370
|
+
...files.map((file) => file.path),
|
|
1371
|
+
...cronSecretPlan.files.map((file) => file.path),
|
|
1372
|
+
...cronSecretPlan.skippedFiles,
|
|
1373
|
+
config.paths.ports,
|
|
1374
|
+
config.paths.infrastructurePorts,
|
|
1375
|
+
path.join(infrastructureDir(config), "db/provider.ts"),
|
|
1376
|
+
drizzleRepositoriesPath(config),
|
|
1377
|
+
],
|
|
1378
|
+
createdFiles: uniqueStrings(createdFiles),
|
|
1379
|
+
updatedFiles: uniqueStrings(updatedFiles),
|
|
1380
|
+
skippedFiles: uniqueStrings(skippedFiles),
|
|
1381
|
+
};
|
|
1026
1382
|
}
|
|
1027
1383
|
async function makeFeatureUi(options) {
|
|
1028
1384
|
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
@@ -1180,6 +1536,21 @@ async function assertFeatureUiApp(targetDir, config) {
|
|
|
1180
1536
|
}
|
|
1181
1537
|
}
|
|
1182
1538
|
}
|
|
1539
|
+
async function assertServerRuntimeApp(targetDir, config, artifact) {
|
|
1540
|
+
const requiredFiles = [
|
|
1541
|
+
config.paths.appContext,
|
|
1542
|
+
config.paths.server,
|
|
1543
|
+
config.paths.infrastructurePorts,
|
|
1544
|
+
];
|
|
1545
|
+
for (const file of requiredFiles) {
|
|
1546
|
+
try {
|
|
1547
|
+
await stat(path.join(targetDir, file));
|
|
1548
|
+
}
|
|
1549
|
+
catch {
|
|
1550
|
+
throw new Error(`beignet make ${artifact} expects a standard Beignet app with a server runtime. Missing ${file}.`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1183
1554
|
async function assertPersistenceArtifactApp(targetDir, names, config, artifact) {
|
|
1184
1555
|
const requiredFiles = [
|
|
1185
1556
|
config.paths.appContext,
|
|
@@ -1358,6 +1729,21 @@ async function updatePackageDependencies(targetDir, dependencies, options) {
|
|
|
1358
1729
|
await writeFile(filePath, next);
|
|
1359
1730
|
return true;
|
|
1360
1731
|
}
|
|
1732
|
+
async function updatePackageScripts(targetDir, scripts, options) {
|
|
1733
|
+
const filePath = path.join(targetDir, "package.json");
|
|
1734
|
+
const original = await readFile(filePath, "utf8");
|
|
1735
|
+
const packageJson = JSON.parse(original);
|
|
1736
|
+
packageJson.scripts = packageJson.scripts ?? {};
|
|
1737
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
1738
|
+
packageJson.scripts[name] ??= command;
|
|
1739
|
+
}
|
|
1740
|
+
const next = `${JSON.stringify(packageJson, null, "\t")}\n`;
|
|
1741
|
+
if (next === original)
|
|
1742
|
+
return false;
|
|
1743
|
+
if (!options.dryRun)
|
|
1744
|
+
await writeFile(filePath, next);
|
|
1745
|
+
return true;
|
|
1746
|
+
}
|
|
1361
1747
|
function beignetDependencyVersion(packageJson) {
|
|
1362
1748
|
return (packageJson.dependencies?.["@beignet/core"] ??
|
|
1363
1749
|
packageJson.dependencies?.["@beignet/next"] ??
|
|
@@ -2239,6 +2625,10 @@ function typeBodySource(source, typeName) {
|
|
|
2239
2625
|
return undefined;
|
|
2240
2626
|
return source.slice(openBrace + 1, closeBrace);
|
|
2241
2627
|
}
|
|
2628
|
+
function typeHasTopLevelProperty(source, typeName, propertyName) {
|
|
2629
|
+
const body = typeBodySource(source, typeName);
|
|
2630
|
+
return body !== undefined && hasTopLevelObjectProperty(body, propertyName);
|
|
2631
|
+
}
|
|
2242
2632
|
function appendUnionTypeMember(source, typeName, member) {
|
|
2243
2633
|
const match = new RegExp(`(export\\s+type\\s+${typeName}\\s*=)([\\s\\S]*?);`).exec(source);
|
|
2244
2634
|
if (!match)
|
|
@@ -2429,6 +2819,30 @@ function addNamedTypeImport(source, name, moduleName) {
|
|
|
2429
2819
|
: `import type { ${names.join(", ")} } from "${moduleName}";`;
|
|
2430
2820
|
return `${source.slice(0, match.index)}${replacement}${source.slice(match.index + match[0].length)}`;
|
|
2431
2821
|
}
|
|
2822
|
+
function addNamedImport(source, name, moduleName) {
|
|
2823
|
+
const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2824
|
+
const importPattern = new RegExp(`import \\{([^}]*)\\} from "${escapedModule}";`);
|
|
2825
|
+
const match = importPattern.exec(source);
|
|
2826
|
+
if (!match) {
|
|
2827
|
+
return insertAfterImports(source, `import { ${name} } from "${moduleName}";`);
|
|
2828
|
+
}
|
|
2829
|
+
const names = match[1]
|
|
2830
|
+
.split(",")
|
|
2831
|
+
.map((entry) => entry.trim())
|
|
2832
|
+
.filter(Boolean);
|
|
2833
|
+
if (names.includes(name))
|
|
2834
|
+
return source;
|
|
2835
|
+
const insertIndex = names.findIndex((existing) => existing.localeCompare(name) > 0);
|
|
2836
|
+
if (insertIndex === -1)
|
|
2837
|
+
names.push(name);
|
|
2838
|
+
else
|
|
2839
|
+
names.splice(insertIndex, 0, name);
|
|
2840
|
+
const indent = match[1].match(/\n([\t ]+)/)?.[1] ?? "\t";
|
|
2841
|
+
const replacement = match[1].includes("\n")
|
|
2842
|
+
? `import {\n${indent}${names.join(`,\n${indent}`)},\n} from "${moduleName}";`
|
|
2843
|
+
: `import { ${names.join(", ")} } from "${moduleName}";`;
|
|
2844
|
+
return `${source.slice(0, match.index)}${replacement}${source.slice(match.index + match[0].length)}`;
|
|
2845
|
+
}
|
|
2432
2846
|
function topLevelBoundObjectRange(source, openBrace, closeBrace) {
|
|
2433
2847
|
const body = source.slice(openBrace + 1, closeBrace);
|
|
2434
2848
|
if (!hasTopLevelObjectProperty(body, "bound"))
|
|
@@ -2798,6 +3212,9 @@ function seedNames(input) {
|
|
|
2798
3212
|
seedExportName,
|
|
2799
3213
|
};
|
|
2800
3214
|
}
|
|
3215
|
+
function defaultFactoryNamesForSeed(names) {
|
|
3216
|
+
return factoryNames(`${names.feature.kebab}/${names.featureSingularKebab}`);
|
|
3217
|
+
}
|
|
2801
3218
|
function notificationNames(input) {
|
|
2802
3219
|
const names = featureArtifactNames(input, "Notification");
|
|
2803
3220
|
const notificationExportName = `${names.featureSingularPascal}${names.artifact.pascal}Notification`;
|
|
@@ -3266,6 +3683,18 @@ function uploadFiles(names, config) {
|
|
|
3266
3683
|
},
|
|
3267
3684
|
];
|
|
3268
3685
|
}
|
|
3686
|
+
function outboxFiles(config) {
|
|
3687
|
+
return [
|
|
3688
|
+
{
|
|
3689
|
+
path: config.paths.outbox,
|
|
3690
|
+
content: outboxRegistryFile(config),
|
|
3691
|
+
},
|
|
3692
|
+
{
|
|
3693
|
+
path: outboxDrainRoutePath(config),
|
|
3694
|
+
content: outboxDrainRouteFile(config),
|
|
3695
|
+
},
|
|
3696
|
+
];
|
|
3697
|
+
}
|
|
3269
3698
|
function portFilePath(names, config) {
|
|
3270
3699
|
return path.join(path.dirname(config.paths.ports), `${names.kebab}.ts`);
|
|
3271
3700
|
}
|
|
@@ -3450,6 +3879,15 @@ function scheduleFilePath(names, config) {
|
|
|
3450
3879
|
function uploadFilePath(names, config) {
|
|
3451
3880
|
return path.join(featureArtifactDir(names, "uploads", config), `${names.artifact.kebab}.ts`);
|
|
3452
3881
|
}
|
|
3882
|
+
function seedEntrypointPath(config) {
|
|
3883
|
+
return path.join(infrastructureDir(config), "db", "seed.ts");
|
|
3884
|
+
}
|
|
3885
|
+
function uploadRoutePath(config) {
|
|
3886
|
+
return path.join(config.paths.routes, "uploads", "[uploadName]", "[action]", "route.ts");
|
|
3887
|
+
}
|
|
3888
|
+
function outboxDrainRoutePath(config) {
|
|
3889
|
+
return path.join(config.paths.routes, "cron", "outbox", "drain", "route.ts");
|
|
3890
|
+
}
|
|
3453
3891
|
function scheduleRouteFilePath(names, config) {
|
|
3454
3892
|
return path.join(config.paths.routes, "cron", names.feature.kebab, names.artifact.kebab, "route.ts");
|
|
3455
3893
|
}
|
|
@@ -3755,19 +4193,87 @@ export async function stopScheduleContext(): Promise<void> {
|
|
|
3755
4193
|
await writeFile(destination, next);
|
|
3756
4194
|
return "updated";
|
|
3757
4195
|
}
|
|
4196
|
+
async function updateSeedEntrypoint(targetDir, names, config, options) {
|
|
4197
|
+
const file = seedEntrypointPath(config);
|
|
4198
|
+
const destination = path.join(targetDir, file);
|
|
4199
|
+
const existing = await readOptionalFile(destination);
|
|
4200
|
+
const registryName = `${names.featureSingularCamel}Seeds`;
|
|
4201
|
+
const importLine = `import { ${registryName} } from "${aliasModule(path.join(featureArtifactDir(names, "seeds", config), "index.ts"))}";`;
|
|
4202
|
+
const registryEntry = `...${registryName}`;
|
|
4203
|
+
if (existing === undefined) {
|
|
4204
|
+
const content = seedEntrypointFile(names, config);
|
|
4205
|
+
if (!options.dryRun) {
|
|
4206
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
4207
|
+
await writeFile(destination, content);
|
|
4208
|
+
}
|
|
4209
|
+
return "created";
|
|
4210
|
+
}
|
|
4211
|
+
let next = existing;
|
|
4212
|
+
if (!next.includes(importLine)) {
|
|
4213
|
+
next = insertAfterImports(next, importLine);
|
|
4214
|
+
}
|
|
4215
|
+
const registry = arrayInitializerInfo(next, "seeds");
|
|
4216
|
+
if (!registry) {
|
|
4217
|
+
next = `${next.trimEnd()}\n\nconst seeds = [${registryEntry}] as const;\n`;
|
|
4218
|
+
}
|
|
4219
|
+
else if (!identifiersFromArrayExpression(registry.text).has(registryName)) {
|
|
4220
|
+
const nextArray = appendToArrayExpression(registry.text, [registryEntry]);
|
|
4221
|
+
next = `${next.slice(0, registry.start)}${nextArray}${next.slice(registry.end)}`;
|
|
4222
|
+
}
|
|
4223
|
+
if (next === existing)
|
|
4224
|
+
return "skipped";
|
|
4225
|
+
if (!options.dryRun)
|
|
4226
|
+
await writeFile(destination, next);
|
|
4227
|
+
return "updated";
|
|
4228
|
+
}
|
|
4229
|
+
async function updateUploadRoute(targetDir, names, config, options) {
|
|
4230
|
+
const file = uploadRoutePath(config);
|
|
4231
|
+
const destination = path.join(targetDir, file);
|
|
4232
|
+
const existing = await readOptionalFile(destination);
|
|
4233
|
+
const registryName = `${names.featureSingularCamel}Uploads`;
|
|
4234
|
+
const registryKey = names.featureSingularCamel;
|
|
4235
|
+
const importLine = `import { ${registryName} } from "${aliasModule(path.join(featureArtifactDir(names, "uploads", config), "index.ts"))}";`;
|
|
4236
|
+
if (existing === undefined) {
|
|
4237
|
+
const content = uploadRouteFile(names, config);
|
|
4238
|
+
if (!options.dryRun) {
|
|
4239
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
4240
|
+
await writeFile(destination, content);
|
|
4241
|
+
}
|
|
4242
|
+
return "created";
|
|
4243
|
+
}
|
|
4244
|
+
let next = existing;
|
|
4245
|
+
if (!next.includes(importLine)) {
|
|
4246
|
+
next = insertAfterImports(next, importLine);
|
|
4247
|
+
}
|
|
4248
|
+
const registry = defineUploadsInitializerInfo(next, "uploadRegistry");
|
|
4249
|
+
if (!registry) {
|
|
4250
|
+
return "skipped";
|
|
4251
|
+
}
|
|
4252
|
+
if (!new RegExp(`\\b${registryKey}\\s*:`).test(registry.text)) {
|
|
4253
|
+
next = `${next.slice(0, registry.end)}\t${registryKey}: ${registryName},\n${next.slice(registry.end)}`;
|
|
4254
|
+
}
|
|
4255
|
+
if (next === existing)
|
|
4256
|
+
return "skipped";
|
|
4257
|
+
if (!options.dryRun)
|
|
4258
|
+
await writeFile(destination, next);
|
|
4259
|
+
return "updated";
|
|
4260
|
+
}
|
|
3758
4261
|
/**
|
|
3759
4262
|
* Append a feature event or job registry to `server/outbox.ts`.
|
|
3760
|
-
*
|
|
3761
|
-
*
|
|
3762
|
-
* `undefined` when the app has no outbox module. It also bails to "skipped"
|
|
3763
|
-
* instead of writing when the `defineOutboxRegistry({...})` anchor is missing
|
|
3764
|
-
* or the matching array is not editable.
|
|
4263
|
+
* Creates the registry when missing so event/job generators produce a complete
|
|
4264
|
+
* drainable path instead of leaving durable delivery as manual wiring.
|
|
3765
4265
|
*/
|
|
3766
4266
|
async function updateOutboxRegistry(targetDir, names, kind, config, options) {
|
|
3767
4267
|
const destination = path.join(targetDir, config.paths.outbox);
|
|
3768
4268
|
const existing = await readOptionalFile(destination);
|
|
3769
|
-
if (existing === undefined)
|
|
3770
|
-
|
|
4269
|
+
if (existing === undefined) {
|
|
4270
|
+
const content = outboxRegistryFile(config, { names, kind });
|
|
4271
|
+
if (!options.dryRun) {
|
|
4272
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
4273
|
+
await writeFile(destination, content);
|
|
4274
|
+
}
|
|
4275
|
+
return "created";
|
|
4276
|
+
}
|
|
3771
4277
|
const featureIndexPath = path.join(featureArtifactDir(names, kind, config), "index.ts");
|
|
3772
4278
|
const registryName = `${names.featureSingularCamel}${kind === "events" ? "Events" : "Jobs"}`;
|
|
3773
4279
|
const importLine = `import { ${registryName} } from "${aliasModule(featureIndexPath)}";`;
|
|
@@ -3786,8 +4292,8 @@ async function updateOutboxRegistry(targetDir, names, kind, config, options) {
|
|
|
3786
4292
|
*/
|
|
3787
4293
|
async function applyOutboxRegistryUpdate(result, targetDir, names, kind, config, options) {
|
|
3788
4294
|
const registryResult = await updateOutboxRegistry(targetDir, names, kind, config, options);
|
|
3789
|
-
if (registryResult ===
|
|
3790
|
-
|
|
4295
|
+
if (registryResult === "created")
|
|
4296
|
+
result.createdFiles.push(config.paths.outbox);
|
|
3791
4297
|
if (registryResult === "updated")
|
|
3792
4298
|
result.updatedFiles.push(config.paths.outbox);
|
|
3793
4299
|
if (registryResult === "skipped")
|
|
@@ -5144,6 +5650,105 @@ export const ${names.uploadExportName} = defineUpload<
|
|
|
5144
5650
|
});
|
|
5145
5651
|
`;
|
|
5146
5652
|
}
|
|
5653
|
+
function seedEntrypointFile(names, config) {
|
|
5654
|
+
const registryName = `${names.featureSingularCamel}Seeds`;
|
|
5655
|
+
return `import { createServiceActor } from "@beignet/core/ports";
|
|
5656
|
+
import { runSeeds } from "@beignet/core/testing";
|
|
5657
|
+
import { ${registryName} } from "${aliasModule(path.join(featureArtifactDir(names, "seeds", config), "index.ts"))}";
|
|
5658
|
+
import { server } from "${aliasModule(config.paths.server)}";
|
|
5659
|
+
|
|
5660
|
+
const seeds = [...${registryName}] as const;
|
|
5661
|
+
|
|
5662
|
+
const ctx = await server.createServiceContext({
|
|
5663
|
+
\tactor: createServiceActor("beignet-seed"),
|
|
5664
|
+
});
|
|
5665
|
+
|
|
5666
|
+
try {
|
|
5667
|
+
\tawait runSeeds({ ctx, seeds });
|
|
5668
|
+
} finally {
|
|
5669
|
+
\tawait server.stop();
|
|
5670
|
+
}
|
|
5671
|
+
`;
|
|
5672
|
+
}
|
|
5673
|
+
function uploadRouteFile(names, config) {
|
|
5674
|
+
const registryName = `${names.featureSingularCamel}Uploads`;
|
|
5675
|
+
return `import {
|
|
5676
|
+
\tcreateUploadRouter,
|
|
5677
|
+
\tdefineUploads,
|
|
5678
|
+
\tuploadsFromRegistry,
|
|
5679
|
+
} from "@beignet/core/uploads";
|
|
5680
|
+
import { createUploadRoute } from "@beignet/next";
|
|
5681
|
+
import { ${registryName} } from "${aliasModule(path.join(featureArtifactDir(names, "uploads", config), "index.ts"))}";
|
|
5682
|
+
import { server } from "${aliasModule(config.paths.server)}";
|
|
5683
|
+
|
|
5684
|
+
const uploadRegistry = defineUploads({
|
|
5685
|
+
\t${names.featureSingularCamel}: ${registryName},
|
|
5686
|
+
});
|
|
5687
|
+
|
|
5688
|
+
const uploadRouter = createUploadRouter({
|
|
5689
|
+
\tuploads: uploadsFromRegistry(uploadRegistry),
|
|
5690
|
+
\tctx: () => server.createContextFromNext(),
|
|
5691
|
+
\tstorage: server.ports.storage,
|
|
5692
|
+
});
|
|
5693
|
+
|
|
5694
|
+
export const { POST } = createUploadRoute(uploadRouter);
|
|
5695
|
+
`;
|
|
5696
|
+
}
|
|
5697
|
+
function outboxRegistryFile(config, registration) {
|
|
5698
|
+
const featureRegistry = registration
|
|
5699
|
+
? outboxFeatureRegistry(registration.names, registration.kind, config)
|
|
5700
|
+
: undefined;
|
|
5701
|
+
const eventEntry = featureRegistry?.kind === "events" ? `...${featureRegistry.name}` : "";
|
|
5702
|
+
const jobEntry = featureRegistry?.kind === "jobs" ? `...${featureRegistry.name}` : "";
|
|
5703
|
+
const featureImport = featureRegistry
|
|
5704
|
+
? `${featureRegistry.importLine}\n`
|
|
5705
|
+
: "";
|
|
5706
|
+
return `import { defineOutboxRegistry } from "@beignet/core/outbox";
|
|
5707
|
+
import { createServiceActor } from "@beignet/core/ports";
|
|
5708
|
+
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
5709
|
+
${featureImport}import { server } from "${relativeModule(config.paths.outbox, config.paths.server)}";
|
|
5710
|
+
|
|
5711
|
+
export const outboxRegistry = defineOutboxRegistry({
|
|
5712
|
+
\tevents: [${eventEntry}],
|
|
5713
|
+
\tjobs: [${jobEntry}],
|
|
5714
|
+
});
|
|
5715
|
+
|
|
5716
|
+
export async function createOutboxDrainContext(): Promise<AppContext> {
|
|
5717
|
+
\treturn server.createServiceContext({
|
|
5718
|
+
\t\tactor: createServiceActor("beignet-outbox"),
|
|
5719
|
+
\t});
|
|
5720
|
+
}
|
|
5721
|
+
|
|
5722
|
+
export async function stopOutboxDrainContext(): Promise<void> {
|
|
5723
|
+
\tawait server.stop();
|
|
5724
|
+
}
|
|
5725
|
+
`;
|
|
5726
|
+
}
|
|
5727
|
+
function outboxDrainRouteFile(config) {
|
|
5728
|
+
return `import { createOutboxDrainRoute } from "@beignet/next";
|
|
5729
|
+
import { env } from "@/lib/env";
|
|
5730
|
+
import { server } from "${aliasModule(config.paths.server)}";
|
|
5731
|
+
import { outboxRegistry } from "${aliasModule(config.paths.outbox)}";
|
|
5732
|
+
|
|
5733
|
+
export const runtime = "nodejs";
|
|
5734
|
+
|
|
5735
|
+
export const { GET, POST } = createOutboxDrainRoute({
|
|
5736
|
+
\tserver,
|
|
5737
|
+
\tregistry: outboxRegistry,
|
|
5738
|
+
\tsecret: env.CRON_SECRET,
|
|
5739
|
+
\tbatchSize: 100,
|
|
5740
|
+
});
|
|
5741
|
+
`;
|
|
5742
|
+
}
|
|
5743
|
+
function outboxFeatureRegistry(names, kind, config) {
|
|
5744
|
+
const featureIndexPath = path.join(featureArtifactDir(names, kind, config), "index.ts");
|
|
5745
|
+
const name = `${names.featureSingularCamel}${kind === "events" ? "Events" : "Jobs"}`;
|
|
5746
|
+
return {
|
|
5747
|
+
kind,
|
|
5748
|
+
name,
|
|
5749
|
+
importLine: `import { ${name} } from "${aliasModule(featureIndexPath)}";`,
|
|
5750
|
+
};
|
|
5751
|
+
}
|
|
5147
5752
|
function billingSchemasFile() {
|
|
5148
5753
|
return `import { z } from "zod";
|
|
5149
5754
|
|