@beignet/cli 0.0.32 → 0.0.33
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 +49 -0
- package/README.md +26 -16
- package/dist/choices.d.ts +3 -3
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +2 -0
- package/dist/choices.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/db.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +279 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +797 -58
- package/dist/inspect.js.map +1 -1
- package/dist/make/inbox.d.ts.map +1 -1
- package/dist/make/inbox.js +5 -3
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.d.ts.map +1 -1
- package/dist/make/payments.js +58 -29
- package/dist/make/payments.js.map +1 -1
- package/dist/make/shared.d.ts +11 -2
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +44 -10
- package/dist/make/shared.js.map +1 -1
- package/dist/make/tenancy.d.ts.map +1 -1
- package/dist/make/tenancy.js +6 -4
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +129 -55
- package/dist/make.js.map +1 -1
- package/dist/outbox.d.ts +114 -1
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +190 -0
- package/dist/outbox.js.map +1 -1
- package/dist/preflight.js +1 -1
- package/dist/preflight.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +46 -0
- package/dist/provider-add.js.map +1 -1
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +2 -3
- package/dist/task.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +6 -3
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.js +3 -3
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +2 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +6 -1
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +3 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +11 -1
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.d.ts +5 -0
- package/dist/templates/testing.d.ts.map +1 -0
- package/dist/templates/testing.js +542 -0
- package/dist/templates/testing.js.map +1 -0
- package/dist/templates/todos.js +2 -2
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +12 -4
- package/src/choices.ts +3 -0
- package/src/config.ts +2 -0
- package/src/db.ts +1 -1
- package/src/index.ts +437 -1
- package/src/inspect.ts +1247 -154
- package/src/make/inbox.ts +5 -3
- package/src/make/payments.ts +58 -29
- package/src/make/shared.ts +78 -11
- package/src/make/tenancy.ts +6 -4
- package/src/make.ts +172 -54
- package/src/outbox.ts +363 -0
- package/src/preflight.ts +1 -1
- package/src/provider-add.ts +47 -0
- package/src/task.ts +2 -3
- package/src/templates/agents.ts +6 -3
- package/src/templates/base.ts +3 -3
- package/src/templates/index.ts +2 -0
- package/src/templates/server.ts +6 -1
- package/src/templates/shared.ts +13 -1
- package/src/templates/testing.ts +545 -0
- package/src/templates/todos.ts +2 -2
- package/src/test-helpers/generated-app.ts +10 -6
package/src/make/inbox.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
resolveConfig,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { addNamedValueImport, appendToNamedArray } from "../registry-edits.js";
|
|
10
|
+
import { testSupportTemplateFiles } from "../templates/testing.js";
|
|
10
11
|
import {
|
|
11
12
|
aliasModule,
|
|
12
13
|
appendSharedErrorEntries,
|
|
@@ -160,7 +161,7 @@ export async function makeInbox(
|
|
|
160
161
|
const files = generatedFiles.map((file) => file.path);
|
|
161
162
|
const dryRun = Boolean(options.dryRun);
|
|
162
163
|
const inboxSeedNames = seedNames("inbox/demo-inbox");
|
|
163
|
-
const seedScript = { "db:seed": `
|
|
164
|
+
const seedScript = { "db:seed": `tsx ${seedEntrypointPath(config)}` };
|
|
164
165
|
|
|
165
166
|
const recordSeedEntrypoint = (result: "created" | "updated" | "skipped") => {
|
|
166
167
|
const entrypoint = seedEntrypointPath(config);
|
|
@@ -399,6 +400,7 @@ export function inboxFiles(
|
|
|
399
400
|
path.join(config.paths.features, "inbox", file);
|
|
400
401
|
|
|
401
402
|
const files: GeneratedFile[] = [
|
|
403
|
+
...testSupportTemplateFiles,
|
|
402
404
|
{ path: featureDir("schemas.ts"), content: inboxSchemasFile() },
|
|
403
405
|
{ path: featureDir("ports.ts"), content: inboxPortsFile() },
|
|
404
406
|
{ path: featureDir("channel.ts"), content: inboxChannelFile(config) },
|
|
@@ -1109,7 +1111,7 @@ export function createMemoryInboxRepository(
|
|
|
1109
1111
|
}
|
|
1110
1112
|
|
|
1111
1113
|
export function inboxTestFile(config: ResolvedBeignetConfig): string {
|
|
1112
|
-
return `import { describe, expect, it } from "
|
|
1114
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
1113
1115
|
import { createUseCaseTester } from "@beignet/core/application";
|
|
1114
1116
|
import { createAnonymousActor } from "@beignet/core/ports";
|
|
1115
1117
|
import { createTestUserActor } from "@beignet/core/ports/testing";
|
|
@@ -1292,7 +1294,7 @@ export function inboxChannelTestFile(config: ResolvedBeignetConfig): string {
|
|
|
1292
1294
|
path.join(config.paths.features, "inbox/channel.ts"),
|
|
1293
1295
|
);
|
|
1294
1296
|
|
|
1295
|
-
return `import { describe, expect, it } from "
|
|
1297
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
1296
1298
|
import { createTestSystemActor } from "@beignet/core/ports/testing";
|
|
1297
1299
|
import {
|
|
1298
1300
|
\tcreateTestContextFactory,
|
package/src/make/payments.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
resolveConfig,
|
|
8
8
|
} from "../config.js";
|
|
9
9
|
import { appendToNamedArray, insertAfterImports } from "../registry-edits.js";
|
|
10
|
+
import { testSupportTemplateFiles } from "../templates/testing.js";
|
|
10
11
|
import {
|
|
11
12
|
addNamedTypeImport,
|
|
12
13
|
aliasModule,
|
|
@@ -200,9 +201,11 @@ export function makePaymentsNextStepLines(
|
|
|
200
201
|
'Gate paid actions once usage passes FREE_PLAN_LIMITS: await requireEntitlement(ctx, { entitlement: "todos.create", subject: { type: "tenant", id: tenantId } });',
|
|
201
202
|
...(hasPlanUi
|
|
202
203
|
? [
|
|
203
|
-
"Users manage their plan at /settings/plan; run
|
|
204
|
+
"Users manage their plan at /settings/plan; run your db:seed package script to create the demo billing account.",
|
|
204
205
|
]
|
|
205
|
-
: [
|
|
206
|
+
: [
|
|
207
|
+
"Run your db:seed package script to create the demo billing account.",
|
|
208
|
+
]),
|
|
206
209
|
"Use createMemoryPaymentsProvider() for local development, then swap server/providers.ts to stripePaymentsProvider when STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are configured.",
|
|
207
210
|
"Billing accounts fall back to the user id today; after beignet make tenancy they become workspace-scoped automatically.",
|
|
208
211
|
"Run your app's test, lint, and typecheck commands, then beignet lint and beignet doctor.",
|
|
@@ -223,7 +226,7 @@ export async function updatePaymentsWiring(
|
|
|
223
226
|
if (
|
|
224
227
|
await updatePackageScripts(
|
|
225
228
|
targetDir,
|
|
226
|
-
{ "db:seed": `
|
|
229
|
+
{ "db:seed": `tsx ${seedEntrypointPath(config)}` },
|
|
227
230
|
{ dryRun: options.dryRun },
|
|
228
231
|
)
|
|
229
232
|
) {
|
|
@@ -590,6 +593,7 @@ export function paymentsFiles(
|
|
|
590
593
|
: [];
|
|
591
594
|
|
|
592
595
|
return [
|
|
596
|
+
...testSupportTemplateFiles,
|
|
593
597
|
{
|
|
594
598
|
path: path.join(config.paths.features, "billing/schemas.ts"),
|
|
595
599
|
content: billingSchemasFile(),
|
|
@@ -755,6 +759,8 @@ export function billingEntitlementsFile(): string {
|
|
|
755
759
|
\tcreateEntitlements,
|
|
756
760
|
\tdenyEntitlement,
|
|
757
761
|
} from "@beignet/core/entitlements";
|
|
762
|
+
import { createTenant } from "@beignet/core/ports";
|
|
763
|
+
import { createTenantScope } from "@beignet/core/tenancy";
|
|
758
764
|
import { isBillingAccountActive, type BillingRepository } from "./ports";
|
|
759
765
|
import { billingPlanIncludesEntitlement } from "./pricing";
|
|
760
766
|
import type { BillingPlan } from "./schemas";
|
|
@@ -777,7 +783,9 @@ export function createBillingEntitlements(billing: BillingRepository) {
|
|
|
777
783
|
\t\t\t\t});
|
|
778
784
|
\t\t\t}
|
|
779
785
|
|
|
780
|
-
\t\t\tconst account = await billing.
|
|
786
|
+
\t\t\tconst account = await billing.findByTenantScope(
|
|
787
|
+
\t\t\t\tcreateTenantScope(createTenant(input.subject.id)),
|
|
788
|
+
\t\t\t);
|
|
781
789
|
\t\t\tconst plan: BillingPlan = isBillingAccountActive(account)
|
|
782
790
|
\t\t\t\t? account.plan
|
|
783
791
|
\t\t\t\t: "free";
|
|
@@ -803,7 +811,8 @@ export function createBillingEntitlements(billing: BillingRepository) {
|
|
|
803
811
|
}
|
|
804
812
|
|
|
805
813
|
export function billingPortsFile(): string {
|
|
806
|
-
return `import type {
|
|
814
|
+
return `import type { TenantScope } from "@beignet/core/tenancy";
|
|
815
|
+
import type { BillingPlan, BillingStatus } from "./schemas";
|
|
807
816
|
|
|
808
817
|
export type BillingAccount = {
|
|
809
818
|
\ttenantId: string;
|
|
@@ -822,7 +831,7 @@ export type BillingAccount = {
|
|
|
822
831
|
export type SaveBillingAccountInput = BillingAccount;
|
|
823
832
|
|
|
824
833
|
export interface BillingRepository {
|
|
825
|
-
\
|
|
834
|
+
\tfindByTenantScope(scope: TenantScope): Promise<BillingAccount | null>;
|
|
826
835
|
\tfindByCustomerId(
|
|
827
836
|
\t\tcustomerId: string,
|
|
828
837
|
\t\tprovider: string,
|
|
@@ -917,7 +926,12 @@ export function billingUseCasesFile(config: ResolvedBeignetConfig): string {
|
|
|
917
926
|
\trunIdempotently,
|
|
918
927
|
} from "@beignet/core/idempotency";
|
|
919
928
|
import type { PaymentWebhookEvent } from "@beignet/core/payments";
|
|
920
|
-
import { requireUser } from "@beignet/core/ports";
|
|
929
|
+
import { createTenant, requireUser } from "@beignet/core/ports";
|
|
930
|
+
import {
|
|
931
|
+
createTenantScope,
|
|
932
|
+
type TenantScope,
|
|
933
|
+
tenantScopeId,
|
|
934
|
+
} from "@beignet/core/tenancy";
|
|
921
935
|
import { z } from "zod";
|
|
922
936
|
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
923
937
|
import { appError } from "@/features/shared/errors";
|
|
@@ -1029,9 +1043,9 @@ function cancelAtPeriodEndFromPayload(
|
|
|
1029
1043
|
\t\t: undefined;
|
|
1030
1044
|
}
|
|
1031
1045
|
|
|
1032
|
-
function
|
|
1046
|
+
function billingTenantScope(ctx: AppContext): TenantScope {
|
|
1033
1047
|
\tconst user = requireUser(ctx);
|
|
1034
|
-
\treturn ctx.tenant?.id ?? user.id;
|
|
1048
|
+
\treturn createTenantScope(createTenant(ctx.tenant?.id ?? user.id));
|
|
1035
1049
|
}
|
|
1036
1050
|
|
|
1037
1051
|
function billingStatusOutput(tenantId: string, account: BillingAccount | null) {
|
|
@@ -1100,13 +1114,15 @@ async function saveBillingAccount(args: {
|
|
|
1100
1114
|
async function applyCheckoutCompleted(
|
|
1101
1115
|
\tevent: PaymentWebhookEvent,
|
|
1102
1116
|
\tsave: (account: BillingAccount) => Promise<BillingAccount>,
|
|
1103
|
-
\
|
|
1117
|
+
\tfindByTenantScope: (scope: TenantScope) => Promise<BillingAccount | null>,
|
|
1104
1118
|
) {
|
|
1105
1119
|
\tconst data = record(event.data);
|
|
1106
1120
|
\tconst tenantId = tenantIdFromPayload(data);
|
|
1107
1121
|
\tif (!tenantId) return null;
|
|
1108
1122
|
|
|
1109
|
-
\tconst existing = await
|
|
1123
|
+
\tconst existing = await findByTenantScope(
|
|
1124
|
+
\t\tcreateTenantScope(createTenant(tenantId)),
|
|
1125
|
+
\t);
|
|
1110
1126
|
\treturn saveBillingAccount({
|
|
1111
1127
|
\t\texisting,
|
|
1112
1128
|
\t\ttenantId,
|
|
@@ -1128,13 +1144,13 @@ async function applySubscriptionEvent(
|
|
|
1128
1144
|
\t\tcustomerId: string,
|
|
1129
1145
|
\t\tprovider: string,
|
|
1130
1146
|
\t) => Promise<BillingAccount | null>,
|
|
1131
|
-
\
|
|
1147
|
+
\tfindByTenantScope: (scope: TenantScope) => Promise<BillingAccount | null>,
|
|
1132
1148
|
) {
|
|
1133
1149
|
\tconst data = record(event.data);
|
|
1134
1150
|
\tconst customerId = providerId(data.customer);
|
|
1135
1151
|
\tconst explicitTenantId = tenantIdFromPayload(data);
|
|
1136
1152
|
\tconst existing = explicitTenantId
|
|
1137
|
-
\t\t? await
|
|
1153
|
+
\t\t? await findByTenantScope(createTenantScope(createTenant(explicitTenantId)))
|
|
1138
1154
|
\t\t: customerId
|
|
1139
1155
|
\t\t\t? await findByCustomerId(customerId, event.provider)
|
|
1140
1156
|
\t\t\t: null;
|
|
@@ -1224,8 +1240,9 @@ export const getBillingStatusUseCase = useCase
|
|
|
1224
1240
|
\t.input(z.object({}))
|
|
1225
1241
|
\t.output(billingStatusOutputSchema)
|
|
1226
1242
|
\t.run(async ({ ctx }) => {
|
|
1227
|
-
\t\tconst
|
|
1228
|
-
\t\tconst
|
|
1243
|
+
\t\tconst scope = billingTenantScope(ctx);
|
|
1244
|
+
\t\tconst tenantId = tenantScopeId(scope);
|
|
1245
|
+
\t\tconst account = await ctx.ports.billing.findByTenantScope(scope);
|
|
1229
1246
|
\t\treturn billingStatusOutput(tenantId, account);
|
|
1230
1247
|
\t});
|
|
1231
1248
|
|
|
@@ -1234,8 +1251,9 @@ export const createCheckoutSessionUseCase = useCase
|
|
|
1234
1251
|
\t.input(createCheckoutSessionInputSchema)
|
|
1235
1252
|
\t.output(createCheckoutSessionOutputSchema)
|
|
1236
1253
|
\t.run(async ({ ctx, input }) => {
|
|
1237
|
-
\t\tconst
|
|
1238
|
-
\t\tconst
|
|
1254
|
+
\t\tconst scope = billingTenantScope(ctx);
|
|
1255
|
+
\t\tconst tenantId = tenantScopeId(scope);
|
|
1256
|
+
\t\tconst account = await ctx.ports.billing.findByTenantScope(scope);
|
|
1239
1257
|
\t\tconst plan = getBillingPlan(input.plan);
|
|
1240
1258
|
\t\tconst session = await ctx.ports.payments.createCheckoutSession({
|
|
1241
1259
|
\t\t\tmode: plan.mode,
|
|
@@ -1281,8 +1299,9 @@ export const createBillingPortalSessionUseCase = useCase
|
|
|
1281
1299
|
\t.input(z.object({}))
|
|
1282
1300
|
\t.output(createBillingPortalSessionOutputSchema)
|
|
1283
1301
|
\t.run(async ({ ctx }) => {
|
|
1284
|
-
\t\tconst
|
|
1285
|
-
\t\tconst
|
|
1302
|
+
\t\tconst scope = billingTenantScope(ctx);
|
|
1303
|
+
\t\tconst tenantId = tenantScopeId(scope);
|
|
1304
|
+
\t\tconst account = await ctx.ports.billing.findByTenantScope(scope);
|
|
1286
1305
|
|
|
1287
1306
|
\t\tif (!account?.customerId) {
|
|
1288
1307
|
\t\t\tthrow appError("BillingAccountNotFound", {
|
|
@@ -1322,14 +1341,14 @@ export const handlePaymentWebhookUseCase = useCase
|
|
|
1322
1341
|
\t\t\t\t\t\taccount = await applyCheckoutCompleted(
|
|
1323
1342
|
\t\t\t\t\t\t\tinput,
|
|
1324
1343
|
\t\t\t\t\t\t\ttx.billing.save,
|
|
1325
|
-
\t\t\t\t\t\t\ttx.billing.
|
|
1344
|
+
\t\t\t\t\t\t\ttx.billing.findByTenantScope,
|
|
1326
1345
|
\t\t\t\t\t\t);
|
|
1327
1346
|
\t\t\t\t\t} else if (input.type.startsWith("customer.subscription.")) {
|
|
1328
1347
|
\t\t\t\t\t\taccount = await applySubscriptionEvent(
|
|
1329
1348
|
\t\t\t\t\t\t\tinput,
|
|
1330
1349
|
\t\t\t\t\t\t\ttx.billing.save,
|
|
1331
1350
|
\t\t\t\t\t\t\ttx.billing.findByCustomerId,
|
|
1332
|
-
\t\t\t\t\t\t\ttx.billing.
|
|
1351
|
+
\t\t\t\t\t\t\ttx.billing.findByTenantScope,
|
|
1333
1352
|
\t\t\t\t\t\t);
|
|
1334
1353
|
\t\t\t\t\t} else if (input.type === "invoice.payment_succeeded") {
|
|
1335
1354
|
\t\t\t\t\t\taccount = await applyInvoiceSucceeded(
|
|
@@ -1384,11 +1403,12 @@ export const billingRoutes = defineRouteGroup<AppContext>()({
|
|
|
1384
1403
|
}
|
|
1385
1404
|
|
|
1386
1405
|
export function billingTestFile(config: ResolvedBeignetConfig): string {
|
|
1387
|
-
return `import { describe, expect, it } from "
|
|
1406
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
1388
1407
|
import { createUseCaseTester } from "@beignet/core/application";
|
|
1389
1408
|
import { requireEntitlement } from "@beignet/core/entitlements";
|
|
1390
|
-
import { createAnonymousActor } from "@beignet/core/ports";
|
|
1409
|
+
import { createAnonymousActor, createTenant } from "@beignet/core/ports";
|
|
1391
1410
|
import { createTestUserActor } from "@beignet/core/ports/testing";
|
|
1411
|
+
import { createTenantScope, tenantScopeId } from "@beignet/core/tenancy";
|
|
1392
1412
|
import {
|
|
1393
1413
|
\tcreateTestContextFactory,
|
|
1394
1414
|
\tcreateTestPorts,
|
|
@@ -1409,7 +1429,8 @@ function createMemoryBillingRepository(): BillingRepository {
|
|
|
1409
1429
|
\tconst accounts = new Map<string, BillingAccount>();
|
|
1410
1430
|
|
|
1411
1431
|
\treturn {
|
|
1412
|
-
\t\tasync
|
|
1432
|
+
\t\tasync findByTenantScope(scope) {
|
|
1433
|
+
\t\t\tconst tenantId = tenantScopeId(scope);
|
|
1413
1434
|
\t\t\treturn accounts.get(tenantId) ?? null;
|
|
1414
1435
|
\t\t},
|
|
1415
1436
|
|
|
@@ -1498,7 +1519,9 @@ describe("billing use cases", () => {
|
|
|
1498
1519
|
\t\t\tharness.payments.checkoutSessions[0]?.input.idempotencyKey,
|
|
1499
1520
|
\t\t).toBe(undefined);
|
|
1500
1521
|
\t\tawait expect(
|
|
1501
|
-
\t\t\tharness.billing.
|
|
1522
|
+
\t\t\tharness.billing.findByTenantScope(
|
|
1523
|
+
\t\t\t\tcreateTenantScope(createTenant("tenant_example")),
|
|
1524
|
+
\t\t\t),
|
|
1502
1525
|
\t\t).resolves.toMatchObject({
|
|
1503
1526
|
\t\t\tcheckoutSessionId: checkout.sessionId,
|
|
1504
1527
|
\t\t\tplan: "pro",
|
|
@@ -1690,7 +1713,9 @@ describe("billing use cases", () => {
|
|
|
1690
1713
|
\t\t\t}),
|
|
1691
1714
|
\t\t).resolves.toEqual({ received: true, accountUpdated: true });
|
|
1692
1715
|
\t\tawait expect(
|
|
1693
|
-
\t\t\tharness.billing.
|
|
1716
|
+
\t\t\tharness.billing.findByTenantScope(
|
|
1717
|
+
\t\t\t\tcreateTenantScope(createTenant("tenant_example")),
|
|
1718
|
+
\t\t\t),
|
|
1694
1719
|
\t\t).resolves.toMatchObject({
|
|
1695
1720
|
\t\t\tstatus: "past_due",
|
|
1696
1721
|
\t\t\tlastEventId: "evt_invoice_failed",
|
|
@@ -1722,7 +1747,9 @@ describe("billing use cases", () => {
|
|
|
1722
1747
|
\t\t\t}),
|
|
1723
1748
|
\t\t).resolves.toEqual({ received: true, accountUpdated: true });
|
|
1724
1749
|
\t\tawait expect(
|
|
1725
|
-
\t\t\tharness.billing.
|
|
1750
|
+
\t\t\tharness.billing.findByTenantScope(
|
|
1751
|
+
\t\t\t\tcreateTenantScope(createTenant("tenant_example")),
|
|
1752
|
+
\t\t\t),
|
|
1726
1753
|
\t\t).resolves.toMatchObject({
|
|
1727
1754
|
\t\t\tstatus: "active",
|
|
1728
1755
|
\t\t\tcancelAtPeriodEnd: true,
|
|
@@ -1814,7 +1841,8 @@ export function billingDrizzleRepositoryFile(
|
|
|
1814
1841
|
|
|
1815
1842
|
\t\t\treturn toBillingAccount(row);`;
|
|
1816
1843
|
|
|
1817
|
-
return `import
|
|
1844
|
+
return `import { tenantScopeId } from "@beignet/core/tenancy";
|
|
1845
|
+
import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
|
|
1818
1846
|
import { and, eq } from "drizzle-orm";
|
|
1819
1847
|
import type {
|
|
1820
1848
|
\tBillingAccount,
|
|
@@ -1870,7 +1898,8 @@ export function createDrizzleBillingRepository(
|
|
|
1870
1898
|
\tdb: ${dialect.dbTypeName}<typeof schema>,
|
|
1871
1899
|
): BillingRepository {
|
|
1872
1900
|
\treturn {
|
|
1873
|
-
\t\tasync
|
|
1901
|
+
\t\tasync findByTenantScope(scope) {
|
|
1902
|
+
\t\t\tconst tenantId = tenantScopeId(scope);
|
|
1874
1903
|
\t\t\tconst [row] = await db
|
|
1875
1904
|
\t\t\t\t.select()
|
|
1876
1905
|
\t\t\t\t.from(schema.billingAccounts)
|
package/src/make/shared.ts
CHANGED
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
insertAfterImports,
|
|
16
16
|
matchingDelimiterIndex,
|
|
17
17
|
} from "../registry-edits.js";
|
|
18
|
+
import {
|
|
19
|
+
currentGeneratedPackageScripts,
|
|
20
|
+
externalVersions,
|
|
21
|
+
legacyGeneratedPackageScripts,
|
|
22
|
+
} from "../templates/shared.js";
|
|
18
23
|
|
|
19
24
|
export type MakeResult = {
|
|
20
25
|
schemaVersion: 1;
|
|
@@ -350,10 +355,43 @@ export function providersFilePath(config: ResolvedBeignetConfig): string {
|
|
|
350
355
|
return `${directoryPath(path.dirname(config.paths.server))}/providers.ts`;
|
|
351
356
|
}
|
|
352
357
|
|
|
358
|
+
export function listenersFilePath(config: ResolvedBeignetConfig): string {
|
|
359
|
+
return (
|
|
360
|
+
config.paths.listeners ??
|
|
361
|
+
`${directoryPath(path.dirname(config.paths.server))}/listeners.ts`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function createListenersRegistrySource(options: {
|
|
366
|
+
registryName: string;
|
|
367
|
+
listenerModule: string;
|
|
368
|
+
}): string {
|
|
369
|
+
return `import { ${options.registryName} } from "${options.listenerModule}";
|
|
370
|
+
|
|
371
|
+
export const listeners = [...${options.registryName}] as const;
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function updateListenersRegistrySource(
|
|
376
|
+
source: string,
|
|
377
|
+
options: {
|
|
378
|
+
registryName: string;
|
|
379
|
+
listenerModule: string;
|
|
380
|
+
},
|
|
381
|
+
): AppendResult {
|
|
382
|
+
const importLine = `import { ${options.registryName} } from "${options.listenerModule}";`;
|
|
383
|
+
return appendToNamedArray(
|
|
384
|
+
source,
|
|
385
|
+
"listeners",
|
|
386
|
+
`...${options.registryName}`,
|
|
387
|
+
importLine,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
353
391
|
/**
|
|
354
392
|
* Wire a feature listener registry into the generated providers file by
|
|
355
|
-
*
|
|
356
|
-
* `registerListeners(...)
|
|
393
|
+
* registering the central `server/listeners.ts` registry with
|
|
394
|
+
* `registerListeners(...)`.
|
|
357
395
|
*
|
|
358
396
|
* Shared by `beignet make listener` and `beignet doctor --fix` so both write
|
|
359
397
|
* paths produce byte-identical wiring. Returns `missing` when the exported
|
|
@@ -369,8 +407,8 @@ export function wireListenersProviderSource(
|
|
|
369
407
|
config: ResolvedBeignetConfig;
|
|
370
408
|
},
|
|
371
409
|
): AppendResult {
|
|
372
|
-
const {
|
|
373
|
-
const providerName =
|
|
410
|
+
const { config } = options;
|
|
411
|
+
const providerName = "listenersProvider";
|
|
374
412
|
let next = source;
|
|
375
413
|
next = addNamedImport(next, "registerListeners", "@beignet/core/events");
|
|
376
414
|
next = addNamedImport(next, "createServiceActor", "@beignet/core/ports");
|
|
@@ -387,7 +425,10 @@ export function wireListenersProviderSource(
|
|
|
387
425
|
);
|
|
388
426
|
next = addNamedTypeImport(next, "AppPorts", aliasModule(config.paths.ports));
|
|
389
427
|
|
|
390
|
-
const listenerImport = `import {
|
|
428
|
+
const listenerImport = `import { listeners } from "${relativeModule(
|
|
429
|
+
providersFilePath(config),
|
|
430
|
+
listenersFilePath(config),
|
|
431
|
+
)}";`;
|
|
391
432
|
if (!next.includes(listenerImport)) {
|
|
392
433
|
next = insertAfterImports(next, listenerImport);
|
|
393
434
|
}
|
|
@@ -398,9 +439,9 @@ export function wireListenersProviderSource(
|
|
|
398
439
|
\tAppContext,
|
|
399
440
|
\tAppServiceContextInput
|
|
400
441
|
>()({
|
|
401
|
-
\tname: "
|
|
442
|
+
\tname: "app-listeners",
|
|
402
443
|
\tsetup({ ports, createServiceContext }) {
|
|
403
|
-
\t\tconst unregister = registerListeners(ports.eventBus,
|
|
444
|
+
\t\tconst unregister = registerListeners(ports.eventBus, listeners, {
|
|
404
445
|
\t\t\tctx: () =>
|
|
405
446
|
\t\t\t\tcreateServiceContext({
|
|
406
447
|
\t\t\t\t\tactor: createServiceActor("beignet-listener"),
|
|
@@ -1027,9 +1068,22 @@ export async function updatePackageScripts(
|
|
|
1027
1068
|
const original = await readFile(filePath, "utf8");
|
|
1028
1069
|
const packageJson = JSON.parse(original) as PackageJsonLike;
|
|
1029
1070
|
packageJson.scripts = packageJson.scripts ?? {};
|
|
1071
|
+
let needsTsx = false;
|
|
1030
1072
|
|
|
1031
1073
|
for (const [name, command] of Object.entries(scripts)) {
|
|
1032
|
-
|
|
1074
|
+
if (
|
|
1075
|
+
!packageJson.scripts[name] ||
|
|
1076
|
+
packageJson.scripts[name] === legacyGeneratedPackageScripts[name]
|
|
1077
|
+
) {
|
|
1078
|
+
packageJson.scripts[name] = command;
|
|
1079
|
+
}
|
|
1080
|
+
needsTsx ||=
|
|
1081
|
+
command.startsWith("tsx ") && packageJson.scripts[name] === command;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (needsTsx) {
|
|
1085
|
+
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
1086
|
+
packageJson.devDependencies.tsx ??= externalVersions.tsx;
|
|
1033
1087
|
}
|
|
1034
1088
|
|
|
1035
1089
|
const next = `${JSON.stringify(packageJson, null, "\t")}\n`;
|
|
@@ -1064,11 +1118,11 @@ export async function updatePackageJson(
|
|
|
1064
1118
|
devDependencies?: Record<string, string>;
|
|
1065
1119
|
};
|
|
1066
1120
|
packageJson.scripts = packageJson.scripts ?? {};
|
|
1067
|
-
|
|
1068
|
-
packageJson.
|
|
1121
|
+
updateGeneratedPackageScripts(packageJson.scripts);
|
|
1122
|
+
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
1123
|
+
packageJson.devDependencies.tsx ??= externalVersions.tsx;
|
|
1069
1124
|
|
|
1070
1125
|
if (options.webTestingDependency) {
|
|
1071
|
-
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
1072
1126
|
packageJson.devDependencies["@beignet/web"] ??=
|
|
1073
1127
|
packageJson.dependencies?.["@beignet/core"] ??
|
|
1074
1128
|
packageJson.dependencies?.["@beignet/next"] ??
|
|
@@ -1080,6 +1134,19 @@ export async function updatePackageJson(
|
|
|
1080
1134
|
return next !== original;
|
|
1081
1135
|
}
|
|
1082
1136
|
|
|
1137
|
+
function updateGeneratedPackageScripts(scripts: Record<string, string>): void {
|
|
1138
|
+
for (const [name, command] of Object.entries(
|
|
1139
|
+
currentGeneratedPackageScripts,
|
|
1140
|
+
)) {
|
|
1141
|
+
if (
|
|
1142
|
+
scripts[name] === legacyGeneratedPackageScripts[name] ||
|
|
1143
|
+
(name === "test" && !scripts[name])
|
|
1144
|
+
) {
|
|
1145
|
+
scripts[name] = command;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1083
1150
|
export async function updatePortsIndex(
|
|
1084
1151
|
targetDir: string,
|
|
1085
1152
|
names: ResourceNames,
|
package/src/make/tenancy.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
arrayInitializerInfo,
|
|
12
12
|
} from "../registry-edits.js";
|
|
13
13
|
import { serverFiles } from "../templates/server.js";
|
|
14
|
+
import { testSupportTemplateFiles } from "../templates/testing.js";
|
|
14
15
|
import {
|
|
15
16
|
addNamedImport,
|
|
16
17
|
addNamedTypeImport,
|
|
@@ -428,7 +429,7 @@ async function registerTenancySeeds(
|
|
|
428
429
|
if (
|
|
429
430
|
await updatePackageScripts(
|
|
430
431
|
targetDir,
|
|
431
|
-
{ "db:seed": `
|
|
432
|
+
{ "db:seed": `tsx ${seedEntrypointPath(config)}` },
|
|
432
433
|
options,
|
|
433
434
|
)
|
|
434
435
|
) {
|
|
@@ -741,6 +742,7 @@ export function tenancyFiles(
|
|
|
741
742
|
path.join(config.paths.features, "workspaces", file);
|
|
742
743
|
|
|
743
744
|
return [
|
|
745
|
+
...testSupportTemplateFiles,
|
|
744
746
|
{ path: featureDir("schemas.ts"), content: tenancySchemasFile() },
|
|
745
747
|
{ path: featureDir("contracts.ts"), content: tenancyContractsFile() },
|
|
746
748
|
{ path: featureDir("policy.ts"), content: tenancyPolicyFile() },
|
|
@@ -2621,7 +2623,7 @@ export const workspaceInviteFactory = createFactory<
|
|
|
2621
2623
|
export function membershipTestFile(config: ResolvedBeignetConfig): string {
|
|
2622
2624
|
const tenantModule = aliasModule(tenantHelperPath(config));
|
|
2623
2625
|
|
|
2624
|
-
return `import { describe, expect, test } from "
|
|
2626
|
+
return `import { describe, expect, test } from "@/lib/beignet-test";
|
|
2625
2627
|
import type { AuthSession } from "@/ports/auth";
|
|
2626
2628
|
import {
|
|
2627
2629
|
\tcreateWorkspaceUseCase,
|
|
@@ -2920,7 +2922,7 @@ describe("member management", () => {
|
|
|
2920
2922
|
}
|
|
2921
2923
|
|
|
2922
2924
|
export function invitesTestFile(): string {
|
|
2923
|
-
return `import { describe, expect, test } from "
|
|
2925
|
+
return `import { describe, expect, test } from "@/lib/beignet-test";
|
|
2924
2926
|
import { createInvite } from "@/features/workspaces/contracts";
|
|
2925
2927
|
import {
|
|
2926
2928
|
\tacceptInviteUseCase,
|
|
@@ -3131,7 +3133,7 @@ describe("revokeInviteUseCase", () => {
|
|
|
3131
3133
|
}
|
|
3132
3134
|
|
|
3133
3135
|
export function policyTestFile(): string {
|
|
3134
|
-
return `import { describe, expect, it } from "
|
|
3136
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
3135
3137
|
import { createPolicyTester } from "@beignet/core/ports/testing";
|
|
3136
3138
|
import { workspacePolicy } from "@/features/workspaces/policy";
|
|
3137
3139
|
import { workspaceFactory } from "./factories/workspace";
|