@beignet/cli 0.0.37 → 0.0.39
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 +26 -0
- package/README.md +138 -101
- package/dist/choices.d.ts +17 -21
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +21 -67
- package/dist/choices.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/create-prompts.d.ts +5 -6
- package/dist/create-prompts.d.ts.map +1 -1
- package/dist/create-prompts.js +15 -15
- package/dist/create-prompts.js.map +1 -1
- package/dist/create.d.ts +11 -2
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +38 -28
- package/dist/create.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +149 -47
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +69 -16
- package/dist/inspect.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +27 -6
- package/dist/lint.js.map +1 -1
- package/dist/make/inbox.js +10 -10
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.d.ts +2 -1
- package/dist/make/payments.d.ts.map +1 -1
- package/dist/make/payments.js +18 -14
- package/dist/make/payments.js.map +1 -1
- package/dist/make/shared.d.ts +11 -4
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +56 -45
- package/dist/make/shared.js.map +1 -1
- package/dist/make/tenancy.js +8 -8
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts +3 -2
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +481 -200
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +15 -11
- package/dist/mcp.js.map +1 -1
- package/dist/operational-error-reporting.d.ts +15 -0
- package/dist/operational-error-reporting.d.ts.map +1 -0
- package/dist/operational-error-reporting.js +42 -0
- package/dist/operational-error-reporting.js.map +1 -0
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +73 -0
- package/dist/outbox.js.map +1 -1
- package/dist/provider-add.d.ts +21 -3
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +117 -81
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.js +3 -3
- package/dist/provider-audit.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +28 -1
- package/dist/schedule.js.map +1 -1
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +38 -5
- package/dist/task.js.map +1 -1
- package/dist/templates/base.d.ts +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +15 -15
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts +2 -2
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +7 -7
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +22 -18
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +4 -4
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +6 -6
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.js +1 -1
- package/dist/templates/todos.js +6 -6
- package/package.json +4 -4
- package/skills/app-structure/SKILL.md +13 -8
- package/src/choices.ts +38 -88
- package/src/config.ts +2 -2
- package/src/create-prompts.ts +20 -21
- package/src/create.ts +54 -36
- package/src/index.ts +193 -65
- package/src/inspect.ts +116 -18
- package/src/lib.ts +1 -1
- package/src/lint.ts +35 -4
- package/src/make/inbox.ts +10 -10
- package/src/make/payments.ts +26 -18
- package/src/make/shared.ts +72 -50
- package/src/make/tenancy.ts +8 -8
- package/src/make.ts +607 -191
- package/src/mcp.ts +20 -13
- package/src/operational-error-reporting.ts +60 -0
- package/src/outbox.ts +77 -0
- package/src/provider-add.ts +179 -95
- package/src/provider-audit.ts +3 -3
- package/src/schedule.ts +32 -1
- package/src/task.ts +42 -5
- package/src/templates/base.ts +16 -16
- package/src/templates/index.ts +8 -8
- package/src/templates/server.ts +22 -18
- package/src/templates/shared.ts +10 -10
- package/src/templates/testing.ts +1 -1
- package/src/templates/todos.ts +6 -6
- package/src/test-helpers/generated-app.ts +1 -0
package/src/inspect.ts
CHANGED
|
@@ -558,7 +558,7 @@ function inspectConvention(
|
|
|
558
558
|
]);
|
|
559
559
|
const missingResourceGenerator = missingRequirements(files, [
|
|
560
560
|
config.paths.appContext,
|
|
561
|
-
config.paths.
|
|
561
|
+
config.paths.portWiring,
|
|
562
562
|
config.paths.ports,
|
|
563
563
|
config.paths.server,
|
|
564
564
|
config.paths.useCaseBuilder,
|
|
@@ -1515,9 +1515,9 @@ async function inspectCanonicalConformance(
|
|
|
1515
1515
|
"server/providers.ts should own provider registration, even when the provider list is small.",
|
|
1516
1516
|
},
|
|
1517
1517
|
{
|
|
1518
|
-
file: config.paths.
|
|
1518
|
+
file: config.paths.portWiring,
|
|
1519
1519
|
reason:
|
|
1520
|
-
"infra/
|
|
1520
|
+
"infra/port-wiring.ts should wire concrete app ports for the selected runtime.",
|
|
1521
1521
|
},
|
|
1522
1522
|
{
|
|
1523
1523
|
file: config.paths.routesBuilder,
|
|
@@ -2067,7 +2067,7 @@ async function inspectProductionReadiness(
|
|
|
2067
2067
|
);
|
|
2068
2068
|
const configFiles = operationalConfigFiles(files, config);
|
|
2069
2069
|
const infrastructurePath = directoryPath(
|
|
2070
|
-
path.dirname(config.paths.
|
|
2070
|
+
path.dirname(config.paths.portWiring),
|
|
2071
2071
|
);
|
|
2072
2072
|
const schemaDir = `${infrastructurePath}/db/schema`;
|
|
2073
2073
|
let hasSecurityHeadersHook = false;
|
|
@@ -2115,7 +2115,13 @@ async function inspectProductionReadiness(
|
|
|
2115
2115
|
if (
|
|
2116
2116
|
isFeatureUploadDefinition(file, config) &&
|
|
2117
2117
|
containsCallExpression(source, "defineUpload") &&
|
|
2118
|
-
|
|
2118
|
+
!(await uploadHasExplicitSizeLimit(
|
|
2119
|
+
targetDir,
|
|
2120
|
+
file,
|
|
2121
|
+
source,
|
|
2122
|
+
files,
|
|
2123
|
+
sourceCache,
|
|
2124
|
+
))
|
|
2119
2125
|
) {
|
|
2120
2126
|
diagnostics.push({
|
|
2121
2127
|
severity: "warning",
|
|
@@ -2373,6 +2379,100 @@ async function inspectProductionReadiness(
|
|
|
2373
2379
|
return diagnostics;
|
|
2374
2380
|
}
|
|
2375
2381
|
|
|
2382
|
+
async function uploadHasExplicitSizeLimit(
|
|
2383
|
+
targetDir: string,
|
|
2384
|
+
file: string,
|
|
2385
|
+
source: string,
|
|
2386
|
+
files: string[],
|
|
2387
|
+
sourceCache: Map<string, string>,
|
|
2388
|
+
): Promise<boolean> {
|
|
2389
|
+
if (/\bmaxSizeBytes\s*:/.test(source)) return true;
|
|
2390
|
+
|
|
2391
|
+
const fileReference = source.match(
|
|
2392
|
+
/\bfile\s*:\s*([A-Za-z_$][A-Za-z0-9_$]*)\b/,
|
|
2393
|
+
)?.[1];
|
|
2394
|
+
if (!fileReference) return false;
|
|
2395
|
+
|
|
2396
|
+
const importPattern = /import\s*\{([\s\S]*?)\}\s*from\s*["']([^"']+)["']/g;
|
|
2397
|
+
for (const match of source.matchAll(importPattern)) {
|
|
2398
|
+
const moduleName = match[2];
|
|
2399
|
+
if (!moduleName?.startsWith(".")) continue;
|
|
2400
|
+
|
|
2401
|
+
const importedName = importedNameForLocalBinding(
|
|
2402
|
+
match[1] ?? "",
|
|
2403
|
+
fileReference,
|
|
2404
|
+
);
|
|
2405
|
+
if (!importedName) continue;
|
|
2406
|
+
|
|
2407
|
+
const importedFile = resolveLocalTypeScriptModule(file, moduleName, files);
|
|
2408
|
+
if (!importedFile) continue;
|
|
2409
|
+
|
|
2410
|
+
const importedSource = await readCachedSource(
|
|
2411
|
+
targetDir,
|
|
2412
|
+
importedFile,
|
|
2413
|
+
sourceCache,
|
|
2414
|
+
);
|
|
2415
|
+
if (
|
|
2416
|
+
exportedObjectHasProperty(importedSource, importedName, "maxSizeBytes")
|
|
2417
|
+
) {
|
|
2418
|
+
return true;
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
return false;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
function importedNameForLocalBinding(
|
|
2426
|
+
importList: string,
|
|
2427
|
+
localName: string,
|
|
2428
|
+
): string | undefined {
|
|
2429
|
+
for (const specifier of importList.split(",")) {
|
|
2430
|
+
const [importedName, aliasedLocalName] = specifier
|
|
2431
|
+
.trim()
|
|
2432
|
+
.replace(/^type\s+/, "")
|
|
2433
|
+
.split(/\s+as\s+/);
|
|
2434
|
+
if (!importedName) continue;
|
|
2435
|
+
if ((aliasedLocalName ?? importedName) === localName) return importedName;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
return undefined;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
function exportedObjectHasProperty(
|
|
2442
|
+
source: string,
|
|
2443
|
+
exportName: string,
|
|
2444
|
+
propertyName: string,
|
|
2445
|
+
): boolean {
|
|
2446
|
+
const declaration = new RegExp(
|
|
2447
|
+
`\\bexport\\s+const\\s+${escapeRegExp(exportName)}\\s*=\\s*\\{`,
|
|
2448
|
+
).exec(source);
|
|
2449
|
+
if (!declaration) return false;
|
|
2450
|
+
|
|
2451
|
+
const openIndex = source.indexOf(
|
|
2452
|
+
"{",
|
|
2453
|
+
declaration.index + declaration[0].length - 1,
|
|
2454
|
+
);
|
|
2455
|
+
const closeIndex = matchingDelimiterIndex(source, openIndex, "{", "}");
|
|
2456
|
+
if (closeIndex < 0) return false;
|
|
2457
|
+
|
|
2458
|
+
const initializer = source.slice(openIndex, closeIndex + 1);
|
|
2459
|
+
return new RegExp(`\\b${escapeRegExp(propertyName)}\\s*:`).test(initializer);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
function resolveLocalTypeScriptModule(
|
|
2463
|
+
importer: string,
|
|
2464
|
+
moduleName: string,
|
|
2465
|
+
files: string[],
|
|
2466
|
+
): string | undefined {
|
|
2467
|
+
const base = normalizePath(
|
|
2468
|
+
path.posix.normalize(
|
|
2469
|
+
path.posix.join(path.posix.dirname(importer), moduleName),
|
|
2470
|
+
),
|
|
2471
|
+
);
|
|
2472
|
+
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}/index.ts`];
|
|
2473
|
+
return candidates.find((candidate) => files.includes(candidate));
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2376
2476
|
async function hasReadinessCheck(
|
|
2377
2477
|
targetDir: string,
|
|
2378
2478
|
files: string[],
|
|
@@ -2567,7 +2667,7 @@ async function inspectPaymentsProductionReadiness(
|
|
|
2567
2667
|
diagnostics.push({
|
|
2568
2668
|
severity: "warning",
|
|
2569
2669
|
code: "BEIGNET_BILLING_ENTITLEMENTS_PROVIDER_MISSING",
|
|
2570
|
-
file: path.dirname(config.paths.
|
|
2670
|
+
file: path.dirname(config.paths.portWiring),
|
|
2571
2671
|
message:
|
|
2572
2672
|
"features/billing/entitlements.ts exists, but no infra or server provider appears to call createBillingEntitlements(...). Wire the billing-backed entitlements port into AppPorts so paid feature gates use app billing state.",
|
|
2573
2673
|
});
|
|
@@ -3034,7 +3134,7 @@ async function hasBillingEntitlementsProviderWiring(
|
|
|
3034
3134
|
config: ResolvedBeignetConfig,
|
|
3035
3135
|
sourceCache: Map<string, string>,
|
|
3036
3136
|
): Promise<boolean> {
|
|
3037
|
-
const infraPath = path.dirname(config.paths.
|
|
3137
|
+
const infraPath = path.dirname(config.paths.portWiring);
|
|
3038
3138
|
const serverPath = path.dirname(config.paths.server);
|
|
3039
3139
|
const candidateFiles = files.filter(
|
|
3040
3140
|
(file) =>
|
|
@@ -3419,13 +3519,13 @@ function isInfraOrServerFile(
|
|
|
3419
3519
|
): boolean {
|
|
3420
3520
|
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
3421
3521
|
const infrastructureDir = directoryPath(
|
|
3422
|
-
path.dirname(config.paths.
|
|
3522
|
+
path.dirname(config.paths.portWiring),
|
|
3423
3523
|
);
|
|
3424
3524
|
|
|
3425
3525
|
return (
|
|
3426
3526
|
file === config.paths.server ||
|
|
3427
3527
|
file.startsWith(`${serverDir}/`) ||
|
|
3428
|
-
file === config.paths.
|
|
3528
|
+
file === config.paths.portWiring ||
|
|
3429
3529
|
file.startsWith(`${infrastructureDir}/`)
|
|
3430
3530
|
);
|
|
3431
3531
|
}
|
|
@@ -3550,9 +3650,7 @@ async function inspectWorkflowRegistrationDrift(
|
|
|
3550
3650
|
}
|
|
3551
3651
|
}
|
|
3552
3652
|
|
|
3553
|
-
const infraDir = directoryPath(
|
|
3554
|
-
path.dirname(config.paths.infrastructurePorts),
|
|
3555
|
-
);
|
|
3653
|
+
const infraDir = directoryPath(path.dirname(config.paths.portWiring));
|
|
3556
3654
|
const listenerTarget = drift.listeners.wiringFile
|
|
3557
3655
|
? `${drift.listeners.wiringFile}, which already calls registerListeners(...)`
|
|
3558
3656
|
: drift.listeners.eventBusFile
|
|
@@ -4382,7 +4480,7 @@ async function inspectDatabaseLifecycleDrift(
|
|
|
4382
4480
|
const sourceCache = new Map<string, string>();
|
|
4383
4481
|
const resources = await collectResourceNames(targetDir, files, config);
|
|
4384
4482
|
const infrastructurePath = directoryPath(
|
|
4385
|
-
path.dirname(config.paths.
|
|
4483
|
+
path.dirname(config.paths.portWiring),
|
|
4386
4484
|
);
|
|
4387
4485
|
const featuresPath = directoryPath(config.paths.features);
|
|
4388
4486
|
const repositoriesPath = path.join(
|
|
@@ -7301,21 +7399,21 @@ async function inspectResourceSlices(
|
|
|
7301
7399
|
"errors.ts",
|
|
7302
7400
|
);
|
|
7303
7401
|
const infrastructureDir = `${directoryPath(
|
|
7304
|
-
path.dirname(config.paths.
|
|
7402
|
+
path.dirname(config.paths.portWiring),
|
|
7305
7403
|
)}/${resource}/`;
|
|
7306
7404
|
const drizzleSchemaFile = path.join(
|
|
7307
|
-
path.dirname(config.paths.
|
|
7405
|
+
path.dirname(config.paths.portWiring),
|
|
7308
7406
|
"db",
|
|
7309
7407
|
"schema",
|
|
7310
7408
|
`${resource}.ts`,
|
|
7311
7409
|
);
|
|
7312
7410
|
const drizzleRepositoryFile = path.join(
|
|
7313
|
-
path.dirname(config.paths.
|
|
7411
|
+
path.dirname(config.paths.portWiring),
|
|
7314
7412
|
resource,
|
|
7315
7413
|
`drizzle-${singular}-repository.ts`,
|
|
7316
7414
|
);
|
|
7317
7415
|
const memoryRepositoryFile = path.join(
|
|
7318
|
-
path.dirname(config.paths.
|
|
7416
|
+
path.dirname(config.paths.portWiring),
|
|
7319
7417
|
resource,
|
|
7320
7418
|
`in-memory-${singular}-repository.ts`,
|
|
7321
7419
|
);
|
|
@@ -7653,7 +7751,7 @@ async function collectResourceNames(
|
|
|
7653
7751
|
const portsPath = directoryPath(path.dirname(config.paths.ports));
|
|
7654
7752
|
const featuresPath = directoryPath(config.paths.features);
|
|
7655
7753
|
const infrastructurePath = directoryPath(
|
|
7656
|
-
path.dirname(config.paths.
|
|
7754
|
+
path.dirname(config.paths.portWiring),
|
|
7657
7755
|
);
|
|
7658
7756
|
|
|
7659
7757
|
for (const file of files) {
|
package/src/lib.ts
CHANGED
|
@@ -43,4 +43,4 @@ export {
|
|
|
43
43
|
export { runOutboxDrain } from "./outbox.js";
|
|
44
44
|
export { runAppSchedule } from "./schedule.js";
|
|
45
45
|
export { runAppTask } from "./task.js";
|
|
46
|
-
export type {
|
|
46
|
+
export type { StarterProviderName } from "./templates/index.js";
|
package/src/lint.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
3
4
|
import { createPainter } from "./ansi.js";
|
|
4
5
|
import {
|
|
5
6
|
type BeignetConfig,
|
|
@@ -89,6 +90,7 @@ export async function lintApp(
|
|
|
89
90
|
const diagnostics: LintDiagnostic[] = [];
|
|
90
91
|
const sourceFiles = lintSourceFiles(files);
|
|
91
92
|
const sourceFileSet = new Set(sourceFiles);
|
|
93
|
+
const atAliasRoot = resolveAtAliasRoot(targetDir);
|
|
92
94
|
const sourceByFile = new Map<string, string>();
|
|
93
95
|
const importGraph: ImportGraph = new Map();
|
|
94
96
|
|
|
@@ -96,7 +98,10 @@ export async function lintApp(
|
|
|
96
98
|
const sourceLayer = classifyPath(file, config);
|
|
97
99
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
98
100
|
sourceByFile.set(file, source);
|
|
99
|
-
importGraph.set(
|
|
101
|
+
importGraph.set(
|
|
102
|
+
file,
|
|
103
|
+
parseImports(source, file, sourceFileSet, atAliasRoot),
|
|
104
|
+
);
|
|
100
105
|
|
|
101
106
|
if (sourceLayer === "test" || sourceLayer === "unknown") continue;
|
|
102
107
|
|
|
@@ -281,15 +286,40 @@ function parseImports(
|
|
|
281
286
|
source: string,
|
|
282
287
|
importerFile: string,
|
|
283
288
|
files: Set<string>,
|
|
289
|
+
atAliasRoot: string,
|
|
284
290
|
): ImportReference[] {
|
|
285
291
|
const references = parseImportSpecifiers(source);
|
|
286
292
|
|
|
287
293
|
return references.map((reference) => ({
|
|
288
294
|
...reference,
|
|
289
|
-
...resolveImport(reference.importPath, importerFile, files),
|
|
295
|
+
...resolveImport(reference.importPath, importerFile, files, atAliasRoot),
|
|
290
296
|
}));
|
|
291
297
|
}
|
|
292
298
|
|
|
299
|
+
function resolveAtAliasRoot(targetDir: string): string {
|
|
300
|
+
const tsconfigPath = path.join(targetDir, "tsconfig.json");
|
|
301
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(
|
|
302
|
+
tsconfigPath,
|
|
303
|
+
{},
|
|
304
|
+
{
|
|
305
|
+
...ts.sys,
|
|
306
|
+
onUnRecoverableConfigFileDiagnostic: () => {},
|
|
307
|
+
},
|
|
308
|
+
);
|
|
309
|
+
const aliasTarget = parsed?.options.paths?.["@/*"]?.[0];
|
|
310
|
+
if (!aliasTarget) return "";
|
|
311
|
+
|
|
312
|
+
const targetWithoutWildcard = aliasTarget.replace(/[/\\]?\*$/, "");
|
|
313
|
+
const baseUrl = parsed.options.baseUrl ?? targetDir;
|
|
314
|
+
const absoluteTarget = path.resolve(baseUrl, targetWithoutWildcard);
|
|
315
|
+
const relativeTarget = normalizePath(
|
|
316
|
+
path.relative(targetDir, absoluteTarget),
|
|
317
|
+
);
|
|
318
|
+
if (relativeTarget === "" || relativeTarget === ".") return "";
|
|
319
|
+
if (relativeTarget === ".." || relativeTarget.startsWith("../")) return "";
|
|
320
|
+
return directoryPath(relativeTarget);
|
|
321
|
+
}
|
|
322
|
+
|
|
293
323
|
function parseImportSpecifiers(
|
|
294
324
|
source: string,
|
|
295
325
|
): Array<{ importPath: string; kind: ImportKind; offset: number }> {
|
|
@@ -590,11 +620,12 @@ function resolveImport(
|
|
|
590
620
|
importPath: string,
|
|
591
621
|
importerFile: string,
|
|
592
622
|
files: Set<string>,
|
|
623
|
+
atAliasRoot: string,
|
|
593
624
|
): Pick<ImportReference, "packageName" | "resolvedPath"> {
|
|
594
625
|
if (importPath.startsWith("@/")) {
|
|
595
626
|
return {
|
|
596
627
|
resolvedPath: resolveLocalImportPath(
|
|
597
|
-
stripKnownExtension(importPath.slice(2)),
|
|
628
|
+
stripKnownExtension(path.posix.join(atAliasRoot, importPath.slice(2))),
|
|
598
629
|
files,
|
|
599
630
|
),
|
|
600
631
|
};
|
|
@@ -1171,7 +1202,7 @@ function classifyPath(
|
|
|
1171
1202
|
if (
|
|
1172
1203
|
isUnder(
|
|
1173
1204
|
normalizedPath,
|
|
1174
|
-
directoryPath(path.dirname(config.paths.
|
|
1205
|
+
directoryPath(path.dirname(config.paths.portWiring)),
|
|
1175
1206
|
)
|
|
1176
1207
|
) {
|
|
1177
1208
|
return "infra";
|
package/src/make/inbox.ts
CHANGED
|
@@ -160,7 +160,7 @@ export async function makeInbox(
|
|
|
160
160
|
.map((file) => file.path);
|
|
161
161
|
const files = generatedFiles.map((file) => file.path);
|
|
162
162
|
const dryRun = Boolean(options.dryRun);
|
|
163
|
-
const inboxSeedNames = seedNames("inbox
|
|
163
|
+
const inboxSeedNames = seedNames("inbox.demo-inbox");
|
|
164
164
|
const seedScript = { "db:seed": `tsx ${seedEntrypointPath(config)}` };
|
|
165
165
|
|
|
166
166
|
const recordSeedEntrypoint = (result: "created" | "updated" | "skipped") => {
|
|
@@ -248,8 +248,8 @@ export async function updateInboxWiring(
|
|
|
248
248
|
await updatePortsIndex(targetDir, inboxNames, config, {
|
|
249
249
|
...options,
|
|
250
250
|
generationOptions: {
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
authorization: false,
|
|
252
|
+
tenantScoped: false,
|
|
253
253
|
events: false,
|
|
254
254
|
softDelete: false,
|
|
255
255
|
},
|
|
@@ -265,7 +265,7 @@ export async function updateInboxWiring(
|
|
|
265
265
|
options,
|
|
266
266
|
)
|
|
267
267
|
) {
|
|
268
|
-
updated.add(config.paths.
|
|
268
|
+
updated.add(config.paths.portWiring);
|
|
269
269
|
}
|
|
270
270
|
if (
|
|
271
271
|
await updateDrizzleSchemaIndexExports(
|
|
@@ -1119,7 +1119,7 @@ import {
|
|
|
1119
1119
|
\tcreateTestPorts,
|
|
1120
1120
|
} from "@beignet/core/testing";
|
|
1121
1121
|
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
1122
|
-
import {
|
|
1122
|
+
import { initialPorts } from "${aliasModule(config.paths.portWiring)}";
|
|
1123
1123
|
import type { AppTransactionPorts } from "${aliasModule(config.paths.ports)}";
|
|
1124
1124
|
import { getUnreadCountUseCase } from "../use-cases/get-unread-count";
|
|
1125
1125
|
import { listInboxUseCase } from "../use-cases/list-inbox";
|
|
@@ -1140,9 +1140,9 @@ function createHarness(
|
|
|
1140
1140
|
\tconst inbox = options.inbox ?? createMemoryInboxRepository();
|
|
1141
1141
|
\tconst actor = options.actor ?? createTestUserActor("user_test");
|
|
1142
1142
|
\tconst fixture = createTestPorts<AppContext["ports"], AppTransactionPorts>({
|
|
1143
|
-
\t\tbase:
|
|
1143
|
+
\t\tbase: initialPorts,
|
|
1144
1144
|
\t\toverrides: {
|
|
1145
|
-
\t\t\tgate:
|
|
1145
|
+
\t\t\tgate: initialPorts.gate,
|
|
1146
1146
|
\t\t\tinbox,
|
|
1147
1147
|
\t\t},
|
|
1148
1148
|
\t});
|
|
@@ -1305,7 +1305,7 @@ import {
|
|
|
1305
1305
|
\tdefineInboxNotificationChannel,
|
|
1306
1306
|
\ttype InboxNotificationInput,
|
|
1307
1307
|
} from "${channelModule}";
|
|
1308
|
-
import {
|
|
1308
|
+
import { initialPorts } from "${aliasModule(config.paths.portWiring)}";
|
|
1309
1309
|
import { defineNotification } from "${aliasModule(config.paths.notificationsBuilder)}";
|
|
1310
1310
|
import type { AppTransactionPorts } from "${aliasModule(config.paths.ports)}";
|
|
1311
1311
|
import { createMemoryInboxRepository } from "./factories/inbox-notification";
|
|
@@ -1340,9 +1340,9 @@ const skippingNotification = defineNotification("inbox.channel-skip-test", {
|
|
|
1340
1340
|
function createHarness() {
|
|
1341
1341
|
\tconst inbox = createMemoryInboxRepository();
|
|
1342
1342
|
\tconst fixture = createTestPorts<AppContext["ports"], AppTransactionPorts>({
|
|
1343
|
-
\t\tbase:
|
|
1343
|
+
\t\tbase: initialPorts,
|
|
1344
1344
|
\t\toverrides: {
|
|
1345
|
-
\t\t\tgate:
|
|
1345
|
+
\t\t\tgate: initialPorts.gate,
|
|
1346
1346
|
\t\t\tinbox,
|
|
1347
1347
|
\t\t},
|
|
1348
1348
|
\t});
|
package/src/make/payments.ts
CHANGED
|
@@ -82,12 +82,18 @@ export async function makePayments(
|
|
|
82
82
|
|
|
83
83
|
const database = await detectResourceDatabase(targetDir, config);
|
|
84
84
|
const ui = await detectUiShell(targetDir, config);
|
|
85
|
-
const
|
|
85
|
+
const tenantScoped = await fileExists(
|
|
86
|
+
path.join(targetDir, config.paths.features, "workspaces/contracts.ts"),
|
|
87
|
+
);
|
|
88
|
+
const generatedFiles = paymentsFiles(config, database, {
|
|
89
|
+
ui,
|
|
90
|
+
idempotencyScope: tenantScoped ? "actor-tenant" : "actor",
|
|
91
|
+
});
|
|
86
92
|
const plannedFiles = await planGeneratedFiles(targetDir, generatedFiles, {
|
|
87
93
|
force: Boolean(options.force),
|
|
88
94
|
});
|
|
89
95
|
const billingNames = resourceNames("billing");
|
|
90
|
-
const billingSeedNames = seedNames("billing
|
|
96
|
+
const billingSeedNames = seedNames("billing.demo-billing-account");
|
|
91
97
|
const plannedBillingUpdates = await updatePaymentsWiring(
|
|
92
98
|
targetDir,
|
|
93
99
|
billingNames,
|
|
@@ -235,8 +241,8 @@ export async function updatePaymentsWiring(
|
|
|
235
241
|
await updatePortsIndex(targetDir, billingNames, config, {
|
|
236
242
|
...options,
|
|
237
243
|
generationOptions: {
|
|
238
|
-
|
|
239
|
-
|
|
244
|
+
authorization: false,
|
|
245
|
+
tenantScoped: false,
|
|
240
246
|
events: false,
|
|
241
247
|
softDelete: false,
|
|
242
248
|
},
|
|
@@ -246,7 +252,7 @@ export async function updatePaymentsWiring(
|
|
|
246
252
|
}
|
|
247
253
|
if (await updatePaymentsEntitlementsPort(targetDir, config, options)) {
|
|
248
254
|
updated.add(config.paths.ports);
|
|
249
|
-
updated.add(config.paths.
|
|
255
|
+
updated.add(config.paths.portWiring);
|
|
250
256
|
}
|
|
251
257
|
if (
|
|
252
258
|
await updateInfrastructureDeferredPorts(
|
|
@@ -256,7 +262,7 @@ export async function updatePaymentsWiring(
|
|
|
256
262
|
options,
|
|
257
263
|
)
|
|
258
264
|
) {
|
|
259
|
-
updated.add(config.paths.
|
|
265
|
+
updated.add(config.paths.portWiring);
|
|
260
266
|
}
|
|
261
267
|
if (
|
|
262
268
|
await updateDrizzleSchemaIndexExports(
|
|
@@ -354,10 +360,7 @@ export async function updatePaymentsEntitlementsPort(
|
|
|
354
360
|
if (!options.dryRun) await writeFile(portsFilePath, portsNext);
|
|
355
361
|
}
|
|
356
362
|
|
|
357
|
-
const infrastructureFilePath = path.join(
|
|
358
|
-
targetDir,
|
|
359
|
-
config.paths.infrastructurePorts,
|
|
360
|
-
);
|
|
363
|
+
const infrastructureFilePath = path.join(targetDir, config.paths.portWiring);
|
|
361
364
|
const infrastructureOriginal = await readFile(infrastructureFilePath, "utf8");
|
|
362
365
|
const infrastructureNext = appendDeferredPortKey(
|
|
363
366
|
infrastructureOriginal,
|
|
@@ -368,7 +371,7 @@ export async function updatePaymentsEntitlementsPort(
|
|
|
368
371
|
!/["']entitlements["']/.test(infrastructureOriginal)
|
|
369
372
|
) {
|
|
370
373
|
throw new Error(
|
|
371
|
-
`Could not find the deferred port list in ${config.paths.
|
|
374
|
+
`Could not find the deferred port list in ${config.paths.portWiring}. Add "entitlements" manually, or restore the generated port wiring file before running make payments.`,
|
|
372
375
|
);
|
|
373
376
|
}
|
|
374
377
|
if (infrastructureNext !== infrastructureOriginal) {
|
|
@@ -558,7 +561,10 @@ export async function updateBillingSettingsNav(
|
|
|
558
561
|
export function paymentsFiles(
|
|
559
562
|
config: ResolvedBeignetConfig,
|
|
560
563
|
database: DatabaseName,
|
|
561
|
-
options: {
|
|
564
|
+
options: {
|
|
565
|
+
ui: boolean;
|
|
566
|
+
idempotencyScope?: "actor" | "actor-tenant";
|
|
567
|
+
} = { ui: false },
|
|
562
568
|
): GeneratedFile[] {
|
|
563
569
|
const uiFiles: GeneratedFile[] = options.ui
|
|
564
570
|
? [
|
|
@@ -611,7 +617,7 @@ export function paymentsFiles(
|
|
|
611
617
|
},
|
|
612
618
|
{
|
|
613
619
|
path: path.join(config.paths.features, "billing/contracts.ts"),
|
|
614
|
-
content: billingContractsFile(),
|
|
620
|
+
content: billingContractsFile(options.idempotencyScope ?? "actor"),
|
|
615
621
|
},
|
|
616
622
|
{
|
|
617
623
|
path: path.join(config.paths.features, "billing/use-cases/index.ts"),
|
|
@@ -846,7 +852,9 @@ export function isBillingAccountActive(
|
|
|
846
852
|
`;
|
|
847
853
|
}
|
|
848
854
|
|
|
849
|
-
export function billingContractsFile(
|
|
855
|
+
export function billingContractsFile(
|
|
856
|
+
idempotencyScope: "actor" | "actor-tenant" = "actor",
|
|
857
|
+
): string {
|
|
850
858
|
return `import { defineContractGroup } from "@beignet/core/contracts";
|
|
851
859
|
import { z } from "zod";
|
|
852
860
|
import {
|
|
@@ -883,7 +891,7 @@ export const createCheckoutSession = checkoutBilling
|
|
|
883
891
|
\t\tidempotency: {
|
|
884
892
|
\t\t\trequired: true,
|
|
885
893
|
\t\t\theader: "idempotency-key",
|
|
886
|
-
\t\t\tscope: "
|
|
894
|
+
\t\t\tscope: "${idempotencyScope}",
|
|
887
895
|
\t\t\tttlSec: 86_400,
|
|
888
896
|
\t\t},
|
|
889
897
|
\t})
|
|
@@ -1410,7 +1418,7 @@ import {
|
|
|
1410
1418
|
\tcreateTestPorts,
|
|
1411
1419
|
} from "@beignet/core/testing";
|
|
1412
1420
|
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
1413
|
-
import {
|
|
1421
|
+
import { initialPorts } from "@/infra/port-wiring";
|
|
1414
1422
|
import type { AppTransactionPorts } from "@/ports";
|
|
1415
1423
|
import { createBillingEntitlements } from "../entitlements";
|
|
1416
1424
|
import type { BillingAccount, BillingRepository } from "../ports";
|
|
@@ -1451,11 +1459,11 @@ function createHarness(options: { actor?: AppContext["actor"] } = {}) {
|
|
|
1451
1459
|
\tconst billing = createMemoryBillingRepository();
|
|
1452
1460
|
\tconst actor = options.actor ?? createTestUserActor("user_test");
|
|
1453
1461
|
\tconst fixture = createTestPorts<AppContext["ports"], AppTransactionPorts>({
|
|
1454
|
-
\t\tbase:
|
|
1462
|
+
\t\tbase: initialPorts,
|
|
1455
1463
|
\t\toverrides: {
|
|
1456
1464
|
\t\t\tbilling,
|
|
1457
1465
|
\t\t\tentitlements: createBillingEntitlements(billing),
|
|
1458
|
-
\t\t\tgate:
|
|
1466
|
+
\t\t\tgate: initialPorts.gate,
|
|
1459
1467
|
\t\t},
|
|
1460
1468
|
\t});
|
|
1461
1469
|
\tconst createContext = createTestContextFactory<
|