@beignet/cli 0.0.21 → 0.0.23
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 +19 -0
- package/README.md +25 -3
- package/dist/choices.d.ts +8 -0
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +4 -0
- package/dist/choices.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +152 -0
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/make.d.ts +4 -3
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +333 -30
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +6 -2
- package/dist/mcp.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +9 -5
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +1 -0
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +1 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +1 -0
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +30 -0
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shell.d.ts.map +1 -1
- package/dist/templates/shell.js +63 -29
- package/dist/templates/shell.js.map +1 -1
- package/package.json +2 -2
- package/src/choices.ts +10 -0
- package/src/index.ts +11 -0
- package/src/inspect.ts +266 -0
- package/src/lib.ts +2 -1
- package/src/make.ts +433 -38
- package/src/mcp.ts +7 -1
- package/src/templates/agents.ts +9 -5
- package/src/templates/base.ts +1 -0
- package/src/templates/index.ts +1 -0
- package/src/templates/server.ts +30 -0
- package/src/templates/shell.ts +64 -29
package/src/make.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type { MakeFeatureAddon } from "./choices.js";
|
|
4
|
+
import type { MakeFeatureAddon, MakeFeatureRecipe } from "./choices.js";
|
|
5
5
|
import {
|
|
6
6
|
type BeignetConfig,
|
|
7
7
|
clientIndexPath,
|
|
@@ -33,8 +33,11 @@ type MakeResourceOptions = {
|
|
|
33
33
|
softDelete?: boolean;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
export type { MakeFeatureAddon } from "./choices.js";
|
|
37
|
-
export {
|
|
36
|
+
export type { MakeFeatureAddon, MakeFeatureRecipe } from "./choices.js";
|
|
37
|
+
export {
|
|
38
|
+
makeFeatureAddonChoices,
|
|
39
|
+
makeFeatureRecipeChoices,
|
|
40
|
+
} from "./choices.js";
|
|
38
41
|
|
|
39
42
|
type NormalizedMakeFeatureAddon =
|
|
40
43
|
| "policy"
|
|
@@ -51,6 +54,7 @@ type NormalizedMakeFeatureAddon =
|
|
|
51
54
|
|
|
52
55
|
type MakeFeatureOptions = MakeResourceOptions & {
|
|
53
56
|
with?: readonly MakeFeatureAddon[];
|
|
57
|
+
recipe?: MakeFeatureRecipe;
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
type MakeContractOptions = {
|
|
@@ -490,17 +494,26 @@ export async function makeResource(
|
|
|
490
494
|
export async function makeFeature(
|
|
491
495
|
options: MakeFeatureOptions,
|
|
492
496
|
): Promise<MakeFeatureResult> {
|
|
493
|
-
const addons = normalizeMakeFeatureAddons(
|
|
497
|
+
const addons = normalizeMakeFeatureAddons([
|
|
498
|
+
...makeFeatureRecipeAddons(options.recipe),
|
|
499
|
+
...(options.with ?? []),
|
|
500
|
+
...(options.events ? (["events"] as const) : []),
|
|
501
|
+
]);
|
|
502
|
+
const featureOptions = {
|
|
503
|
+
...options,
|
|
504
|
+
events: addons.includes("event"),
|
|
505
|
+
};
|
|
506
|
+
|
|
494
507
|
if (addons.length === 0) {
|
|
495
|
-
return makeFeatureResource(
|
|
508
|
+
return makeFeatureResource(featureOptions);
|
|
496
509
|
}
|
|
497
510
|
|
|
498
511
|
if (options.dryRun) {
|
|
499
|
-
return makeFeatureSlice(
|
|
512
|
+
return makeFeatureSlice(featureOptions, addons);
|
|
500
513
|
}
|
|
501
514
|
|
|
502
|
-
await makeFeatureSlice({ ...
|
|
503
|
-
return makeFeatureSlice(
|
|
515
|
+
await makeFeatureSlice({ ...featureOptions, dryRun: true }, addons);
|
|
516
|
+
return makeFeatureSlice(featureOptions, addons);
|
|
504
517
|
}
|
|
505
518
|
|
|
506
519
|
export async function makePayments(
|
|
@@ -718,6 +731,15 @@ function resourceGenerationOptions(
|
|
|
718
731
|
options: MakeResourceOptions,
|
|
719
732
|
mode: ResourceGenerationMode,
|
|
720
733
|
): ResourceGenerationOptions {
|
|
734
|
+
if (mode === "feature") {
|
|
735
|
+
return {
|
|
736
|
+
auth: false,
|
|
737
|
+
tenant: false,
|
|
738
|
+
events: Boolean(options.events),
|
|
739
|
+
softDelete: false,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
721
743
|
if (mode !== "resource") {
|
|
722
744
|
return { auth: false, tenant: false, events: false, softDelete: false };
|
|
723
745
|
}
|
|
@@ -831,6 +853,28 @@ function normalizeMakeFeatureAddons(
|
|
|
831
853
|
return featureAddonOrder.filter((addon) => normalized.has(addon));
|
|
832
854
|
}
|
|
833
855
|
|
|
856
|
+
function makeFeatureRecipeAddons(
|
|
857
|
+
recipe: MakeFeatureRecipe | undefined,
|
|
858
|
+
): MakeFeatureAddon[] {
|
|
859
|
+
if (recipe === "full-slice") {
|
|
860
|
+
return [
|
|
861
|
+
"policy",
|
|
862
|
+
"factories",
|
|
863
|
+
"seeds",
|
|
864
|
+
"tasks",
|
|
865
|
+
"events",
|
|
866
|
+
"listeners",
|
|
867
|
+
"jobs",
|
|
868
|
+
"notifications",
|
|
869
|
+
"schedules",
|
|
870
|
+
"ui",
|
|
871
|
+
"uploads",
|
|
872
|
+
];
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return [];
|
|
876
|
+
}
|
|
877
|
+
|
|
834
878
|
const featureAddonOrder: readonly NormalizedMakeFeatureAddon[] = [
|
|
835
879
|
"policy",
|
|
836
880
|
"factory",
|
|
@@ -1462,8 +1506,11 @@ export async function makeListener(
|
|
|
1462
1506
|
if (!options.skipEventAssert) {
|
|
1463
1507
|
await assertListenerEventExists(targetDir, names.event, config);
|
|
1464
1508
|
}
|
|
1509
|
+
await updateListenerProviderWiring(targetDir, names, config, {
|
|
1510
|
+
dryRun: true,
|
|
1511
|
+
});
|
|
1465
1512
|
|
|
1466
|
-
|
|
1513
|
+
const result = await makeFeatureArtifact({
|
|
1467
1514
|
targetDir,
|
|
1468
1515
|
config,
|
|
1469
1516
|
force: Boolean(options.force),
|
|
@@ -1478,6 +1525,26 @@ export async function makeListener(
|
|
|
1478
1525
|
"@beignet/core": beignetDependencyVersion,
|
|
1479
1526
|
},
|
|
1480
1527
|
});
|
|
1528
|
+
|
|
1529
|
+
const providerResult = await updateListenerProviderWiring(
|
|
1530
|
+
targetDir,
|
|
1531
|
+
names,
|
|
1532
|
+
config,
|
|
1533
|
+
{
|
|
1534
|
+
dryRun: Boolean(options.dryRun),
|
|
1535
|
+
},
|
|
1536
|
+
);
|
|
1537
|
+
if (providerResult === "created")
|
|
1538
|
+
result.createdFiles.push(providersFilePath(config));
|
|
1539
|
+
if (providerResult === "updated")
|
|
1540
|
+
result.updatedFiles.push(providersFilePath(config));
|
|
1541
|
+
if (providerResult === "skipped")
|
|
1542
|
+
result.skippedFiles.push(providersFilePath(config));
|
|
1543
|
+
if (!result.files.includes(providersFilePath(config))) {
|
|
1544
|
+
result.files.push(providersFilePath(config));
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
return result;
|
|
1481
1548
|
}
|
|
1482
1549
|
|
|
1483
1550
|
async function assertListenerEventExists(
|
|
@@ -1496,6 +1563,98 @@ async function assertListenerEventExists(
|
|
|
1496
1563
|
}
|
|
1497
1564
|
}
|
|
1498
1565
|
|
|
1566
|
+
async function updateListenerProviderWiring(
|
|
1567
|
+
targetDir: string,
|
|
1568
|
+
names: ListenerNames,
|
|
1569
|
+
config: ResolvedBeignetConfig,
|
|
1570
|
+
options: { dryRun: boolean },
|
|
1571
|
+
): Promise<WriteGeneratedFileResult> {
|
|
1572
|
+
const providersFile = providersFilePath(config);
|
|
1573
|
+
const filePath = path.join(targetDir, providersFile);
|
|
1574
|
+
const original = await readOptionalFile(filePath);
|
|
1575
|
+
if (original === undefined) {
|
|
1576
|
+
throw new Error(
|
|
1577
|
+
`Could not find the generated providers file ${providersFile}. Register listeners manually with registerListeners(...), or restore the generated providers file before running make listener.`,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
const listenerIndex = featureArtifactIndexFile("listener", names, config);
|
|
1582
|
+
const providerName = `${names.featureSingularCamel}ListenersProvider`;
|
|
1583
|
+
let next = original;
|
|
1584
|
+
next = addNamedImport(next, "registerListeners", "@beignet/core/events");
|
|
1585
|
+
next = addNamedImport(next, "createServiceActor", "@beignet/core/ports");
|
|
1586
|
+
next = addNamedImport(next, "createProvider", "@beignet/core/providers");
|
|
1587
|
+
next = addNamedTypeImport(
|
|
1588
|
+
next,
|
|
1589
|
+
"AppContext",
|
|
1590
|
+
aliasModule(config.paths.appContext),
|
|
1591
|
+
);
|
|
1592
|
+
next = addNamedTypeImport(
|
|
1593
|
+
next,
|
|
1594
|
+
"AppServiceContextInput",
|
|
1595
|
+
aliasModule(path.join(path.dirname(config.paths.server), "context.ts")),
|
|
1596
|
+
);
|
|
1597
|
+
next = addNamedTypeImport(next, "AppPorts", aliasModule(config.paths.ports));
|
|
1598
|
+
|
|
1599
|
+
const listenerImport = `import { ${listenerIndex.registryName} } from "${aliasModule(listenerIndex.path)}";`;
|
|
1600
|
+
if (!next.includes(listenerImport)) {
|
|
1601
|
+
next = insertAfterImports(next, listenerImport);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
if (!new RegExp(`\\bconst\\s+${providerName}\\b`).test(next)) {
|
|
1605
|
+
const providerDefinition = `const ${providerName} = createProvider<
|
|
1606
|
+
\tPick<AppPorts, "eventBus" | "logger">,
|
|
1607
|
+
\tAppContext,
|
|
1608
|
+
\tAppServiceContextInput
|
|
1609
|
+
>()({
|
|
1610
|
+
\tname: "${names.feature.kebab}-listeners",
|
|
1611
|
+
\tsetup({ ports, createServiceContext }) {
|
|
1612
|
+
\t\tconst unregister = registerListeners(ports.eventBus, ${listenerIndex.registryName}, {
|
|
1613
|
+
\t\t\tctx: () =>
|
|
1614
|
+
\t\t\t\tcreateServiceContext({
|
|
1615
|
+
\t\t\t\t\tactor: createServiceActor("beignet-listener"),
|
|
1616
|
+
\t\t\t\t}),
|
|
1617
|
+
\t\t\tonError(error, listener) {
|
|
1618
|
+
\t\t\t\tports.logger.error("Event listener failed", {
|
|
1619
|
+
\t\t\t\t\terror,
|
|
1620
|
+
\t\t\t\t\tlistenerName: listener.name,
|
|
1621
|
+
\t\t\t\t});
|
|
1622
|
+
\t\t\t},
|
|
1623
|
+
\t\t});
|
|
1624
|
+
|
|
1625
|
+
\t\treturn {
|
|
1626
|
+
\t\t\tstop() {
|
|
1627
|
+
\t\t\t\tunregister();
|
|
1628
|
+
\t\t\t},
|
|
1629
|
+
\t\t};
|
|
1630
|
+
\t},
|
|
1631
|
+
});
|
|
1632
|
+
`;
|
|
1633
|
+
const withProvider = next.replace(
|
|
1634
|
+
/\nexport const providers = \[/,
|
|
1635
|
+
`\n${providerDefinition}\nexport const providers = [`,
|
|
1636
|
+
);
|
|
1637
|
+
if (withProvider === next) {
|
|
1638
|
+
throw new Error(
|
|
1639
|
+
`Could not find the exported providers array in ${providersFile}. Register ${listenerIndex.registryName} manually with registerListeners(...), or restore the generated providers file before running make listener.`,
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
next = withProvider;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
const appended = appendToNamedArray(next, "providers", providerName);
|
|
1646
|
+
if (appended.kind === "missing") {
|
|
1647
|
+
throw new Error(
|
|
1648
|
+
`Could not find the exported providers array in ${providersFile}. Register ${listenerIndex.registryName} manually with registerListeners(...), or restore the generated providers file before running make listener.`,
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
if (appended.kind === "updated") next = appended.source;
|
|
1652
|
+
|
|
1653
|
+
if (next === original) return "skipped";
|
|
1654
|
+
if (!options.dryRun) await writeFile(filePath, next);
|
|
1655
|
+
return "updated";
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1499
1658
|
export async function makeSchedule(
|
|
1500
1659
|
options: MakeScheduleOptions,
|
|
1501
1660
|
): Promise<MakeScheduleResult> {
|
|
@@ -1570,6 +1729,12 @@ const cronSecretEnvEntry = `\t\tCRON_SECRET: z.string().min(1).optional(),`;
|
|
|
1570
1729
|
// slots in directly below it, matching the documented env shape.
|
|
1571
1730
|
const cronSecretEnvAnchor = `\t\tAPP_URL: z.string().url().default("http://localhost:3000"),`;
|
|
1572
1731
|
|
|
1732
|
+
type FileChangeSet = {
|
|
1733
|
+
createdFiles: string[];
|
|
1734
|
+
updatedFiles: string[];
|
|
1735
|
+
skippedFiles: string[];
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1573
1738
|
type CronSecretWiringPlan = {
|
|
1574
1739
|
files: Array<GeneratedFile & { result: "created" | "updated" }>;
|
|
1575
1740
|
skippedFiles: string[];
|
|
@@ -1703,23 +1868,58 @@ async function updateOutboxPortWiring(
|
|
|
1703
1868
|
targetDir: string,
|
|
1704
1869
|
config: ResolvedBeignetConfig,
|
|
1705
1870
|
options: { dryRun: boolean },
|
|
1706
|
-
): Promise<
|
|
1707
|
-
const
|
|
1871
|
+
): Promise<FileChangeSet> {
|
|
1872
|
+
const changes: FileChangeSet = {
|
|
1873
|
+
createdFiles: [],
|
|
1874
|
+
updatedFiles: [],
|
|
1875
|
+
skippedFiles: [],
|
|
1876
|
+
};
|
|
1708
1877
|
|
|
1709
1878
|
if (await updateOutboxPorts(targetDir, config, options)) {
|
|
1710
|
-
|
|
1879
|
+
changes.updatedFiles.push(config.paths.ports);
|
|
1711
1880
|
}
|
|
1712
1881
|
if (await updateOutboxInfrastructurePorts(targetDir, config, options)) {
|
|
1713
|
-
|
|
1882
|
+
changes.updatedFiles.push(config.paths.infrastructurePorts);
|
|
1714
1883
|
}
|
|
1715
1884
|
if (await updateOutboxDatabaseProvider(targetDir, config, options)) {
|
|
1716
|
-
|
|
1885
|
+
changes.updatedFiles.push(
|
|
1886
|
+
path.join(infrastructureDir(config), "db/provider.ts"),
|
|
1887
|
+
);
|
|
1717
1888
|
}
|
|
1718
1889
|
if (await updateOutboxRepositoriesReturnType(targetDir, config, options)) {
|
|
1719
|
-
|
|
1890
|
+
changes.updatedFiles.push(drizzleRepositoriesPath(config));
|
|
1720
1891
|
}
|
|
1721
1892
|
|
|
1722
|
-
|
|
1893
|
+
const schemaResult = await updateOutboxDrizzleSchema(
|
|
1894
|
+
targetDir,
|
|
1895
|
+
config,
|
|
1896
|
+
options,
|
|
1897
|
+
);
|
|
1898
|
+
if (schemaResult.result === "created") {
|
|
1899
|
+
changes.createdFiles.push(schemaResult.path);
|
|
1900
|
+
}
|
|
1901
|
+
if (schemaResult.result === "updated") {
|
|
1902
|
+
changes.updatedFiles.push(schemaResult.path);
|
|
1903
|
+
}
|
|
1904
|
+
if (schemaResult.result === "skipped") {
|
|
1905
|
+
changes.skippedFiles.push(schemaResult.path);
|
|
1906
|
+
}
|
|
1907
|
+
if (schemaResult.canExport) {
|
|
1908
|
+
const schemaIndexUpdated = await updateOutboxDrizzleSchemaIndex(
|
|
1909
|
+
targetDir,
|
|
1910
|
+
config,
|
|
1911
|
+
options,
|
|
1912
|
+
);
|
|
1913
|
+
if (schemaIndexUpdated) {
|
|
1914
|
+
changes.updatedFiles.push(drizzleSchemaIndexPath(config));
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
return {
|
|
1919
|
+
createdFiles: uniqueStrings(changes.createdFiles),
|
|
1920
|
+
updatedFiles: uniqueStrings(changes.updatedFiles),
|
|
1921
|
+
skippedFiles: uniqueStrings(changes.skippedFiles),
|
|
1922
|
+
};
|
|
1723
1923
|
}
|
|
1724
1924
|
|
|
1725
1925
|
async function applyOutboxPortWiring(
|
|
@@ -1728,11 +1928,19 @@ async function applyOutboxPortWiring(
|
|
|
1728
1928
|
config: ResolvedBeignetConfig,
|
|
1729
1929
|
options: { dryRun: boolean },
|
|
1730
1930
|
): Promise<void> {
|
|
1731
|
-
const
|
|
1732
|
-
for (const file of
|
|
1931
|
+
const changes = await updateOutboxPortWiring(targetDir, config, options);
|
|
1932
|
+
for (const file of changes.createdFiles) {
|
|
1933
|
+
if (!result.createdFiles.includes(file)) result.createdFiles.push(file);
|
|
1934
|
+
if (!result.files.includes(file)) result.files.push(file);
|
|
1935
|
+
}
|
|
1936
|
+
for (const file of changes.updatedFiles) {
|
|
1733
1937
|
if (!result.updatedFiles.includes(file)) result.updatedFiles.push(file);
|
|
1734
1938
|
if (!result.files.includes(file)) result.files.push(file);
|
|
1735
1939
|
}
|
|
1940
|
+
for (const file of changes.skippedFiles) {
|
|
1941
|
+
if (!result.skippedFiles.includes(file)) result.skippedFiles.push(file);
|
|
1942
|
+
if (!result.files.includes(file)) result.files.push(file);
|
|
1943
|
+
}
|
|
1736
1944
|
}
|
|
1737
1945
|
|
|
1738
1946
|
async function updateOutboxPorts(
|
|
@@ -1862,6 +2070,61 @@ async function updateOutboxRepositoriesReturnType(
|
|
|
1862
2070
|
return true;
|
|
1863
2071
|
}
|
|
1864
2072
|
|
|
2073
|
+
async function updateOutboxDrizzleSchema(
|
|
2074
|
+
targetDir: string,
|
|
2075
|
+
config: ResolvedBeignetConfig,
|
|
2076
|
+
options: { dryRun: boolean },
|
|
2077
|
+
): Promise<{
|
|
2078
|
+
path: string;
|
|
2079
|
+
result?: WriteGeneratedFileResult;
|
|
2080
|
+
canExport: boolean;
|
|
2081
|
+
}> {
|
|
2082
|
+
const indexPath = path.join(targetDir, drizzleSchemaIndexPath(config));
|
|
2083
|
+
const schemaPath = outboxDrizzleSchemaFilePath(config);
|
|
2084
|
+
const destination = path.join(targetDir, schemaPath);
|
|
2085
|
+
if (!(await fileExists(indexPath))) {
|
|
2086
|
+
return { path: schemaPath, canExport: false };
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
const database = await detectResourceDatabase(targetDir, config);
|
|
2090
|
+
const content = outboxDrizzleSchemaFile(drizzleDialects[database]);
|
|
2091
|
+
const existing = await readOptionalFile(destination);
|
|
2092
|
+
|
|
2093
|
+
if (existing === undefined) {
|
|
2094
|
+
if (!options.dryRun) {
|
|
2095
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
2096
|
+
await writeFile(destination, content);
|
|
2097
|
+
}
|
|
2098
|
+
return { path: schemaPath, result: "created", canExport: true };
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
if (
|
|
2102
|
+
existing === content ||
|
|
2103
|
+
/\bexport\s+const\s+outboxMessages\b/.test(existing)
|
|
2104
|
+
) {
|
|
2105
|
+
return { path: schemaPath, result: "skipped", canExport: true };
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
return { path: schemaPath, result: "skipped", canExport: false };
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
async function updateOutboxDrizzleSchemaIndex(
|
|
2112
|
+
targetDir: string,
|
|
2113
|
+
config: ResolvedBeignetConfig,
|
|
2114
|
+
options: { dryRun: boolean },
|
|
2115
|
+
): Promise<boolean> {
|
|
2116
|
+
const filePath = path.join(targetDir, drizzleSchemaIndexPath(config));
|
|
2117
|
+
if (!(await fileExists(filePath))) return false;
|
|
2118
|
+
|
|
2119
|
+
const original = await readFile(filePath, "utf8");
|
|
2120
|
+
const exportLine = 'export { outboxMessages } from "./outbox-messages";';
|
|
2121
|
+
if (original.includes(exportLine)) return false;
|
|
2122
|
+
|
|
2123
|
+
const next = `${original.trimEnd()}\n${exportLine}\n`;
|
|
2124
|
+
if (!options.dryRun) await writeFile(filePath, next);
|
|
2125
|
+
return true;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
1865
2128
|
function drizzleOutboxPortFactoryName(database: DatabaseName): string {
|
|
1866
2129
|
if (database === "postgres") return "createDrizzlePostgresOutboxPort";
|
|
1867
2130
|
if (database === "mysql") return "createDrizzleMysqlOutboxPort";
|
|
@@ -2267,11 +2530,12 @@ export async function makeOutbox(
|
|
|
2267
2530
|
createdFiles.push(...cronSecretResult.createdFiles);
|
|
2268
2531
|
updatedFiles.push(...cronSecretResult.updatedFiles);
|
|
2269
2532
|
skippedFiles.push(...cronSecretResult.skippedFiles);
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
);
|
|
2533
|
+
const outboxWiringResult = await updateOutboxPortWiring(targetDir, config, {
|
|
2534
|
+
dryRun: Boolean(options.dryRun),
|
|
2535
|
+
});
|
|
2536
|
+
createdFiles.push(...outboxWiringResult.createdFiles);
|
|
2537
|
+
updatedFiles.push(...outboxWiringResult.updatedFiles);
|
|
2538
|
+
skippedFiles.push(...outboxWiringResult.skippedFiles);
|
|
2275
2539
|
|
|
2276
2540
|
return {
|
|
2277
2541
|
schemaVersion: 1,
|
|
@@ -2286,6 +2550,8 @@ export async function makeOutbox(
|
|
|
2286
2550
|
config.paths.infrastructurePorts,
|
|
2287
2551
|
path.join(infrastructureDir(config), "db/provider.ts"),
|
|
2288
2552
|
drizzleRepositoriesPath(config),
|
|
2553
|
+
outboxDrizzleSchemaFilePath(config),
|
|
2554
|
+
drizzleSchemaIndexPath(config),
|
|
2289
2555
|
],
|
|
2290
2556
|
createdFiles: uniqueStrings(createdFiles),
|
|
2291
2557
|
updatedFiles: uniqueStrings(updatedFiles),
|
|
@@ -5456,9 +5722,14 @@ function featureUiComponentFiles(
|
|
|
5456
5722
|
names: FeatureUiNames,
|
|
5457
5723
|
config: ResolvedBeignetConfig,
|
|
5458
5724
|
): GeneratedFile[] {
|
|
5725
|
+
const clientQueriesPath = featureUiClientQueriesFilePath(names, config);
|
|
5459
5726
|
const componentPath = featureUiComponentFilePath(names, config);
|
|
5460
5727
|
|
|
5461
5728
|
return [
|
|
5729
|
+
{
|
|
5730
|
+
path: clientQueriesPath,
|
|
5731
|
+
content: featureUiClientQueriesFile(names, config),
|
|
5732
|
+
},
|
|
5462
5733
|
{
|
|
5463
5734
|
path: componentPath,
|
|
5464
5735
|
content: featureUiComponentFile(names, config),
|
|
@@ -5721,6 +5992,15 @@ function drizzleResourceSchemaFilePath(
|
|
|
5721
5992
|
);
|
|
5722
5993
|
}
|
|
5723
5994
|
|
|
5995
|
+
function outboxDrizzleSchemaFilePath(config: ResolvedBeignetConfig): string {
|
|
5996
|
+
return path.join(
|
|
5997
|
+
infrastructureDir(config),
|
|
5998
|
+
"db",
|
|
5999
|
+
"schema",
|
|
6000
|
+
"outbox-messages.ts",
|
|
6001
|
+
);
|
|
6002
|
+
}
|
|
6003
|
+
|
|
5724
6004
|
function drizzleResourceRepositoryFilePath(
|
|
5725
6005
|
names: ResourceNames,
|
|
5726
6006
|
config: ResolvedBeignetConfig,
|
|
@@ -5826,6 +6106,20 @@ function featureUiDir(
|
|
|
5826
6106
|
return path.join(resourceFeatureDir(names, config), "components");
|
|
5827
6107
|
}
|
|
5828
6108
|
|
|
6109
|
+
function featureUiClientDir(
|
|
6110
|
+
names: FeatureUiNames,
|
|
6111
|
+
config: ResolvedBeignetConfig,
|
|
6112
|
+
): string {
|
|
6113
|
+
return path.join(resourceFeatureDir(names, config), "client");
|
|
6114
|
+
}
|
|
6115
|
+
|
|
6116
|
+
function featureUiClientQueriesFilePath(
|
|
6117
|
+
names: FeatureUiNames,
|
|
6118
|
+
config: ResolvedBeignetConfig,
|
|
6119
|
+
): string {
|
|
6120
|
+
return path.join(featureUiClientDir(names, config), "queries.ts");
|
|
6121
|
+
}
|
|
6122
|
+
|
|
5829
6123
|
function featureUiComponentFilePath(
|
|
5830
6124
|
names: FeatureUiNames,
|
|
5831
6125
|
config: ResolvedBeignetConfig,
|
|
@@ -7285,6 +7579,78 @@ ${options.softDelete ? `\tdeletedAt: ${dialect.timestampColumn("deleted_at")},\n
|
|
|
7285
7579
|
`;
|
|
7286
7580
|
}
|
|
7287
7581
|
|
|
7582
|
+
function outboxDrizzleSchemaFile(dialect: DrizzleDialect): string {
|
|
7583
|
+
if (dialect.subpath === "mysql") {
|
|
7584
|
+
return `import { index, int, longtext, mysqlTable, varchar } from "drizzle-orm/mysql-core";
|
|
7585
|
+
|
|
7586
|
+
export const outboxMessages = mysqlTable(
|
|
7587
|
+
\t"outbox_messages",
|
|
7588
|
+
\t{
|
|
7589
|
+
\t\tid: varchar("id", { length: 255 }).primaryKey(),
|
|
7590
|
+
\t\tkind: varchar("kind", { length: 32 }).notNull(),
|
|
7591
|
+
\t\tname: varchar("name", { length: 255 }).notNull(),
|
|
7592
|
+
\t\tpayloadJson: longtext("payload_json").notNull(),
|
|
7593
|
+
\t\tstatus: varchar("status", { length: 32 }).notNull(),
|
|
7594
|
+
\t\tattempts: int("attempts").notNull().default(0),
|
|
7595
|
+
\t\tmaxAttempts: int("max_attempts").notNull().default(3),
|
|
7596
|
+
\t\tavailableAt: varchar("available_at", { length: 32 }).notNull(),
|
|
7597
|
+
\t\tclaimedAt: varchar("claimed_at", { length: 32 }),
|
|
7598
|
+
\t\tlockedUntil: varchar("locked_until", { length: 32 }),
|
|
7599
|
+
\t\tclaimToken: varchar("claim_token", { length: 64 }),
|
|
7600
|
+
\t\tdeliveredAt: varchar("delivered_at", { length: 32 }),
|
|
7601
|
+
\t\tlastErrorJson: longtext("last_error_json"),
|
|
7602
|
+
\t\tcreatedAt: varchar("created_at", { length: 32 }).notNull(),
|
|
7603
|
+
\t\tupdatedAt: varchar("updated_at", { length: 32 }).notNull(),
|
|
7604
|
+
\t},
|
|
7605
|
+
\t(table) => ({
|
|
7606
|
+
\t\tavailableIdx: index("outbox_messages_available_idx").on(
|
|
7607
|
+
\t\t\ttable.status,
|
|
7608
|
+
\t\t\ttable.availableAt,
|
|
7609
|
+
\t\t),
|
|
7610
|
+
\t\tlockedIdx: index("outbox_messages_locked_idx").on(
|
|
7611
|
+
\t\t\ttable.status,
|
|
7612
|
+
\t\t\ttable.lockedUntil,
|
|
7613
|
+
\t\t),
|
|
7614
|
+
\t}),
|
|
7615
|
+
);
|
|
7616
|
+
`;
|
|
7617
|
+
}
|
|
7618
|
+
|
|
7619
|
+
return `import { index, integer, ${dialect.tableFunction}, text } from "${dialect.columnModule}";
|
|
7620
|
+
|
|
7621
|
+
export const outboxMessages = ${dialect.tableFunction}(
|
|
7622
|
+
\t"outbox_messages",
|
|
7623
|
+
\t{
|
|
7624
|
+
\t\tid: text("id").primaryKey(),
|
|
7625
|
+
\t\tkind: text("kind").notNull(),
|
|
7626
|
+
\t\tname: text("name").notNull(),
|
|
7627
|
+
\t\tpayloadJson: text("payload_json").notNull(),
|
|
7628
|
+
\t\tstatus: text("status").notNull(),
|
|
7629
|
+
\t\tattempts: integer("attempts").notNull().default(0),
|
|
7630
|
+
\t\tmaxAttempts: integer("max_attempts").notNull().default(3),
|
|
7631
|
+
\t\tavailableAt: text("available_at").notNull(),
|
|
7632
|
+
\t\tclaimedAt: text("claimed_at"),
|
|
7633
|
+
\t\tlockedUntil: text("locked_until"),
|
|
7634
|
+
\t\tclaimToken: text("claim_token"),
|
|
7635
|
+
\t\tdeliveredAt: text("delivered_at"),
|
|
7636
|
+
\t\tlastErrorJson: text("last_error_json"),
|
|
7637
|
+
\t\tcreatedAt: text("created_at").notNull(),
|
|
7638
|
+
\t\tupdatedAt: text("updated_at").notNull(),
|
|
7639
|
+
\t},
|
|
7640
|
+
\t(table) => ({
|
|
7641
|
+
\t\tavailableIdx: index("outbox_messages_available_idx").on(
|
|
7642
|
+
\t\t\ttable.status,
|
|
7643
|
+
\t\t\ttable.availableAt,
|
|
7644
|
+
\t\t),
|
|
7645
|
+
\t\tlockedIdx: index("outbox_messages_locked_idx").on(
|
|
7646
|
+
\t\t\ttable.status,
|
|
7647
|
+
\t\t\ttable.lockedUntil,
|
|
7648
|
+
\t\t),
|
|
7649
|
+
\t}),
|
|
7650
|
+
);
|
|
7651
|
+
`;
|
|
7652
|
+
}
|
|
7653
|
+
|
|
7288
7654
|
function drizzleResourceWhere(
|
|
7289
7655
|
names: ResourceNames,
|
|
7290
7656
|
options: ResourceGenerationOptions,
|
|
@@ -9569,30 +9935,31 @@ function featureUiComponentFile(
|
|
|
9569
9935
|
names: FeatureUiNames,
|
|
9570
9936
|
config: ResolvedBeignetConfig,
|
|
9571
9937
|
): string {
|
|
9938
|
+
const componentPath = featureUiComponentFilePath(names, config);
|
|
9939
|
+
const clientQueriesPath = featureUiClientQueriesFilePath(names, config);
|
|
9940
|
+
|
|
9572
9941
|
return `"use client";
|
|
9573
9942
|
|
|
9574
9943
|
import { contractErrorMessage } from "@beignet/core/client";
|
|
9575
9944
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
9576
9945
|
import { useState } from "react";
|
|
9577
|
-
import {
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9946
|
+
import {
|
|
9947
|
+
\tcreate${names.singularPascal}MutationOptions,
|
|
9948
|
+
\tinvalidate${names.pluralPascal},
|
|
9949
|
+
\tlist${names.pluralPascal}QueryOptions,
|
|
9950
|
+
} from "${relativeModule(componentPath, clientQueriesPath)}";
|
|
9581
9951
|
|
|
9582
9952
|
export function ${names.componentExportName}() {
|
|
9583
9953
|
\tconst [name, setName] = useState("");
|
|
9584
9954
|
\tconst queryClient = useQueryClient();
|
|
9585
|
-
\tconst ${names.pluralCamel}Query = useQuery(
|
|
9586
|
-
\
|
|
9587
|
-
\t)
|
|
9588
|
-
\
|
|
9589
|
-
\t\
|
|
9590
|
-
\t\t\
|
|
9591
|
-
\t\t
|
|
9592
|
-
\t
|
|
9593
|
-
\t\t\t},
|
|
9594
|
-
\t\t}),
|
|
9595
|
-
\t);
|
|
9955
|
+
\tconst ${names.pluralCamel}Query = useQuery(list${names.pluralPascal}QueryOptions());
|
|
9956
|
+
\tconst create${names.singularPascal}Mutation = useMutation({
|
|
9957
|
+
\t\t...create${names.singularPascal}MutationOptions(),
|
|
9958
|
+
\t\tonSuccess: async () => {
|
|
9959
|
+
\t\t\tsetName("");
|
|
9960
|
+
\t\t\tawait invalidate${names.pluralPascal}(queryClient);
|
|
9961
|
+
\t\t},
|
|
9962
|
+
\t});
|
|
9596
9963
|
|
|
9597
9964
|
\treturn (
|
|
9598
9965
|
\t\t<section className="${names.pluralKebab}-panel">
|
|
@@ -9660,6 +10027,34 @@ export function ${names.componentExportName}() {
|
|
|
9660
10027
|
`;
|
|
9661
10028
|
}
|
|
9662
10029
|
|
|
10030
|
+
function featureUiClientQueriesFile(
|
|
10031
|
+
names: FeatureUiNames,
|
|
10032
|
+
config: ResolvedBeignetConfig,
|
|
10033
|
+
): string {
|
|
10034
|
+
const clientQueriesPath = featureUiClientQueriesFilePath(names, config);
|
|
10035
|
+
const contractsPath = resourceContractFilePath(names, config);
|
|
10036
|
+
|
|
10037
|
+
return `import type { QueryClient } from "@tanstack/react-query";
|
|
10038
|
+
import { rq } from "${aliasModule(clientIndexPath(config))}";
|
|
10039
|
+
import {
|
|
10040
|
+
\tcreate${names.singularPascal},
|
|
10041
|
+
\tlist${names.pluralPascal},
|
|
10042
|
+
} from "${relativeModule(clientQueriesPath, contractsPath)}";
|
|
10043
|
+
|
|
10044
|
+
export function list${names.pluralPascal}QueryOptions() {
|
|
10045
|
+
\treturn rq(list${names.pluralPascal}).queryOptions({ query: {} });
|
|
10046
|
+
}
|
|
10047
|
+
|
|
10048
|
+
export function create${names.singularPascal}MutationOptions() {
|
|
10049
|
+
\treturn rq(create${names.singularPascal}).mutationOptions();
|
|
10050
|
+
}
|
|
10051
|
+
|
|
10052
|
+
export function invalidate${names.pluralPascal}(queryClient: QueryClient) {
|
|
10053
|
+
\treturn rq(list${names.pluralPascal}).invalidate(queryClient);
|
|
10054
|
+
}
|
|
10055
|
+
`;
|
|
10056
|
+
}
|
|
10057
|
+
|
|
9663
10058
|
function scheduleRouteFile(
|
|
9664
10059
|
names: ScheduleNames,
|
|
9665
10060
|
config: ResolvedBeignetConfig,
|
package/src/mcp.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
makeFactory,
|
|
13
13
|
makeFeature,
|
|
14
14
|
makeFeatureAddonChoices,
|
|
15
|
+
makeFeatureRecipeChoices,
|
|
15
16
|
makeJob,
|
|
16
17
|
makeListener,
|
|
17
18
|
makeNotification,
|
|
@@ -76,6 +77,10 @@ const makeInputSchema = {
|
|
|
76
77
|
.describe(
|
|
77
78
|
"feature only: add feature-owned artifacts such as policy, factories, seeds, events, listeners, jobs, notifications, schedules, tasks, ui, and uploads.",
|
|
78
79
|
),
|
|
80
|
+
recipe: z
|
|
81
|
+
.enum(makeFeatureRecipeChoices)
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("feature only: generate a named recipe such as full-slice."),
|
|
79
84
|
event: z
|
|
80
85
|
.string()
|
|
81
86
|
.optional()
|
|
@@ -132,6 +137,7 @@ type MakeToolInput = {
|
|
|
132
137
|
artifact: MakeArtifact;
|
|
133
138
|
name?: string;
|
|
134
139
|
with?: Array<(typeof makeFeatureAddonChoices)[number]>;
|
|
140
|
+
recipe?: (typeof makeFeatureRecipeChoices)[number];
|
|
135
141
|
event?: string;
|
|
136
142
|
cron?: string;
|
|
137
143
|
timezone?: string;
|
|
@@ -216,7 +222,7 @@ async function runMakeTool(
|
|
|
216
222
|
case "factory":
|
|
217
223
|
return makeFactory(base);
|
|
218
224
|
case "feature":
|
|
219
|
-
return makeFeature({ ...base, with: input.with });
|
|
225
|
+
return makeFeature({ ...base, with: input.with, recipe: input.recipe });
|
|
220
226
|
case "job":
|
|
221
227
|
return makeJob(base);
|
|
222
228
|
case "listener":
|