@beignet/cli 0.0.26 → 0.0.28
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 +59 -15
- package/dist/check.d.ts +61 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +164 -0
- package/dist/check.js.map +1 -0
- package/dist/choices.d.ts +8 -0
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +14 -0
- package/dist/choices.js.map +1 -1
- package/dist/db.d.ts +8 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +4 -4
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +156 -21
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +121 -3
- package/dist/inspect.js.map +1 -1
- package/dist/make/inbox.d.ts +63 -0
- package/dist/make/inbox.d.ts.map +1 -0
- package/dist/make/inbox.js +1747 -0
- package/dist/make/inbox.js.map +1 -0
- package/dist/make/payments.d.ts +61 -0
- package/dist/make/payments.d.ts.map +1 -0
- package/dist/make/payments.js +1900 -0
- package/dist/make/payments.js.map +1 -0
- package/dist/make/shared.d.ts +592 -0
- package/dist/make/shared.d.ts.map +1 -0
- package/dist/make/shared.js +2233 -0
- package/dist/make/shared.js.map +1 -0
- package/dist/make/tenancy.d.ts +101 -0
- package/dist/make/tenancy.d.ts.map +1 -0
- package/dist/make/tenancy.js +4409 -0
- package/dist/make/tenancy.js.map +1 -0
- package/dist/make.d.ts +8 -44
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +57 -3689
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +45 -4
- package/dist/mcp.js.map +1 -1
- package/dist/provider-add.d.ts +19 -0
- package/dist/provider-add.d.ts.map +1 -0
- package/dist/provider-add.js +691 -0
- package/dist/provider-add.js.map +1 -0
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +77 -1
- package/dist/provider-audit.js.map +1 -1
- package/dist/registry-edits.d.ts +9 -0
- package/dist/registry-edits.d.ts.map +1 -1
- package/dist/registry-edits.js +32 -0
- package/dist/registry-edits.js.map +1 -1
- package/dist/task.d.ts +2 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +2 -0
- package/dist/task.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +29 -11
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +42 -12
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/sqlite.d.ts.map +1 -1
- package/dist/templates/db/sqlite.js +7 -1
- package/dist/templates/db/sqlite.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +44 -37
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +5 -0
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +5 -0
- package/dist/templates/shared.js.map +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +1 -1
- package/src/check.ts +246 -0
- package/src/choices.ts +28 -0
- package/src/db.ts +4 -4
- package/src/index.ts +232 -21
- package/src/inspect.ts +168 -2
- package/src/make/inbox.ts +2015 -0
- package/src/make/payments.ts +2182 -0
- package/src/make/shared.ts +3638 -0
- package/src/make/tenancy.ts +4809 -0
- package/src/make.ts +388 -5355
- package/src/mcp.ts +66 -3
- package/src/provider-add.ts +926 -0
- package/src/provider-audit.ts +95 -3
- package/src/registry-edits.ts +47 -0
- package/src/task.ts +8 -10
- package/src/templates/agents.ts +29 -11
- package/src/templates/base.ts +43 -12
- package/src/templates/db/sqlite.ts +7 -1
- package/src/templates/server.ts +44 -37
- package/src/templates/shared.ts +5 -0
- package/src/test-helpers/generated-app.ts +168 -0
package/dist/inspect.js
CHANGED
|
@@ -4,6 +4,7 @@ import { parseProviderPackageMetadata } from "@beignet/core/providers";
|
|
|
4
4
|
import { createPainter } from "./ansi.js";
|
|
5
5
|
import { clientDirPath, clientFormsPath, clientIndexPath, defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
|
|
6
6
|
import { formatGithubAnnotation, } from "./github-annotations.js";
|
|
7
|
+
import { providersFilePath, wireListenersProviderSource, } from "./make/shared.js";
|
|
7
8
|
import { detectedProviderVariants, diagnosticPackageJsonFile, formatProviderMetadataIssues, installedPackageNames, providerDoctorRulesForInstalledPackages, providerOperationalConfigFiles, providerRequiredEnvExists, providerRequiredTableExists, providerVariantRegistrationSeverity, readProviderListEntries, readProviderPackageMetadataSource, registrationTokenHint, serverProviderFiles, } from "./provider-audit.js";
|
|
8
9
|
import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryValueInfo, } from "./registry-edits.js";
|
|
9
10
|
import { getCliVersion } from "./version.js";
|
|
@@ -61,6 +62,9 @@ export async function applyDoctorFixes(options = {}) {
|
|
|
61
62
|
if (taskFix)
|
|
62
63
|
fixes.push(taskFix);
|
|
63
64
|
fixes.push(...(await fixUnregisteredOutboxEntries(targetDir, files, drift, config)));
|
|
65
|
+
const listenerFix = await fixUnregisteredListeners(targetDir, files, drift, config);
|
|
66
|
+
if (listenerFix)
|
|
67
|
+
fixes.push(listenerFix);
|
|
64
68
|
const openApiFix = await fixDirectOpenApiArrayDrift(targetDir, files, config, convention, contracts, matchedRoutes);
|
|
65
69
|
if (openApiFix)
|
|
66
70
|
fixes.push(openApiFix);
|
|
@@ -595,6 +599,22 @@ function parseRouteExports(source, handlerFile, routePath, config) {
|
|
|
595
599
|
const exports = [];
|
|
596
600
|
const imports = parseNamedImports(source, config);
|
|
597
601
|
const exportRegex = /export const\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*=\s*([^;\n]+)/g;
|
|
602
|
+
const apiRouteExportRegex = /export const\s*\{([^}]+)\}\s*=\s*createApiRoute\s*\(/g;
|
|
603
|
+
for (const match of source.matchAll(apiRouteExportRegex)) {
|
|
604
|
+
for (const member of match[1].split(",")) {
|
|
605
|
+
const parts = member.split(":");
|
|
606
|
+
const method = (parts[1] ?? parts[0])?.trim();
|
|
607
|
+
if (!method || !isHttpMethod(method))
|
|
608
|
+
continue;
|
|
609
|
+
exports.push({
|
|
610
|
+
method,
|
|
611
|
+
handlerFile,
|
|
612
|
+
contractRef: routePath,
|
|
613
|
+
catchAllPrefix: catchAllRoutePrefix(routePath),
|
|
614
|
+
source: "next-route",
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
598
618
|
for (const match of source.matchAll(exportRegex)) {
|
|
599
619
|
const method = match[1];
|
|
600
620
|
const expression = match[2];
|
|
@@ -822,7 +842,7 @@ async function inspectDiagnostics(targetDir, files, config, convention, contract
|
|
|
822
842
|
for (const routeHandler of matchedRoutes.unmatchedRouteHandlers) {
|
|
823
843
|
diagnostics.push(unmatchedRouteDiagnostic(routeHandler));
|
|
824
844
|
}
|
|
825
|
-
diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
|
|
845
|
+
diagnostics.push(...(await inspectPackageScripts(targetDir, files, convention)), ...(await inspectOpenApiDrift(targetDir, files, config, convention, contracts, matchedRoutes)), ...(await inspectFeatureRouteRegistration(targetDir, files, config, convention, contracts)), ...(await inspectRouteErrorCatalogDrift(targetDir, files, config, convention, contracts, strict)), ...(await inspectCanonicalConformance(targetDir, files, config, convention, strict)), ...(await inspectVersionSkew(targetDir, files)), ...(await inspectProviderMetadataDrift(targetDir, files, convention)), ...(await inspectPortProviderDrift(targetDir, files, config, convention)), ...(await inspectProviderRegistrationDrift(targetDir, files, config, convention)), ...(await inspectDatabaseLifecycleDrift(targetDir, files, config, convention, strict)), ...(await inspectAuthzAuditDrift(targetDir, files, config, convention, contracts)), ...(await inspectServerlessFootguns(targetDir, files, config, convention)), ...(await inspectProductionReadiness(targetDir, files, config, convention)), ...(await inspectFeatureArtifactDrift(targetDir, files, config, convention)), ...(await inspectWorkflowRegistrationDrift(targetDir, files, config, convention)), ...(await inspectResourceSlices(targetDir, files, config, convention, strict)));
|
|
826
846
|
return dedupeDiagnostics(diagnostics);
|
|
827
847
|
}
|
|
828
848
|
async function inspectCanonicalConformance(targetDir, files, config, convention, strict) {
|
|
@@ -2314,7 +2334,7 @@ function isWorkflowTestFile(file) {
|
|
|
2314
2334
|
file.startsWith("tests/") ||
|
|
2315
2335
|
file.includes("/tests/"));
|
|
2316
2336
|
}
|
|
2317
|
-
async function inspectServerlessFootguns(targetDir, files) {
|
|
2337
|
+
async function inspectServerlessFootguns(targetDir, files, config, convention) {
|
|
2318
2338
|
const diagnostics = [];
|
|
2319
2339
|
for (const file of files) {
|
|
2320
2340
|
if (!file.endsWith(".ts") ||
|
|
@@ -2326,6 +2346,16 @@ async function inspectServerlessFootguns(targetDir, files) {
|
|
|
2326
2346
|
const isProviderFile = /(^|\/)provider(s)?\.ts$/.test(file) ||
|
|
2327
2347
|
/(^|\/)[^/]*provider[^/]*\.ts$/.test(file);
|
|
2328
2348
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
2349
|
+
if (convention.resourceGenerator &&
|
|
2350
|
+
isNextRouteFile(file, config) &&
|
|
2351
|
+
importsEagerServer(source, file, files, config)) {
|
|
2352
|
+
diagnostics.push({
|
|
2353
|
+
severity: "warning",
|
|
2354
|
+
code: "BEIGNET_NEXT_ROUTE_EAGER_SERVER_IMPORT",
|
|
2355
|
+
file,
|
|
2356
|
+
message: `${file} imports the eager server singleton. Next.js imports route modules during production builds, so use createNextServerLoader(...) in ${config.paths.server} and pass getServer to createApiRoute(...), createHealthRoute(...), or the focused route helper instead.`,
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2329
2359
|
if (isInfraOrServerFile &&
|
|
2330
2360
|
isProviderFile &&
|
|
2331
2361
|
/\bset(?:Interval|Timeout)\s*\(/.test(source)) {
|
|
@@ -2370,6 +2400,26 @@ async function inspectServerlessFootguns(targetDir, files) {
|
|
|
2370
2400
|
}
|
|
2371
2401
|
return diagnostics;
|
|
2372
2402
|
}
|
|
2403
|
+
function isNextRouteFile(file, config) {
|
|
2404
|
+
const routesPath = directoryPath(config.paths.routes);
|
|
2405
|
+
return (file.startsWith(`${routesPath}/`) &&
|
|
2406
|
+
file.endsWith("/route.ts") &&
|
|
2407
|
+
!file.endsWith(".test.ts"));
|
|
2408
|
+
}
|
|
2409
|
+
function importsEagerServer(source, importerFile, files, config) {
|
|
2410
|
+
const importRegex = /import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
|
|
2411
|
+
for (const match of source.matchAll(importRegex)) {
|
|
2412
|
+
const members = match[1]
|
|
2413
|
+
.split(",")
|
|
2414
|
+
.map((member) => parseImportMember(member));
|
|
2415
|
+
if (!members.some((member) => member?.importedName === "server"))
|
|
2416
|
+
continue;
|
|
2417
|
+
const sourceFile = sourceFileFromImport(match[2], importerFile, files);
|
|
2418
|
+
if (sourceFile === config.paths.server)
|
|
2419
|
+
return true;
|
|
2420
|
+
}
|
|
2421
|
+
return false;
|
|
2422
|
+
}
|
|
2373
2423
|
function hasOutboxRegistry(files) {
|
|
2374
2424
|
return files.some(isOutboxRegistryFile);
|
|
2375
2425
|
}
|
|
@@ -2533,7 +2583,9 @@ async function inspectDatabaseLifecycleDrift(targetDir, files, config, conventio
|
|
|
2533
2583
|
{
|
|
2534
2584
|
script: "db:seed",
|
|
2535
2585
|
command: "seed",
|
|
2536
|
-
|
|
2586
|
+
// The seed entrypoint lives beside the server runtime because it boots
|
|
2587
|
+
// the app through getServer(), which infra/ must not import.
|
|
2588
|
+
file: `${directoryPath(path.dirname(config.paths.server))}/seed.ts`,
|
|
2537
2589
|
code: "BEIGNET_DB_SEED_ENTRYPOINT_MISSING",
|
|
2538
2590
|
},
|
|
2539
2591
|
{
|
|
@@ -3341,6 +3393,72 @@ async function fixUnregisteredTasks(targetDir, files, drift, config) {
|
|
|
3341
3393
|
append: (source, entry, importLine) => appendToNamedArray(source, "tasks", entry, importLine),
|
|
3342
3394
|
});
|
|
3343
3395
|
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Wire fully unregistered feature listener registries into the providers
|
|
3398
|
+
* file with the same `<registry>Provider` block `beignet make listener`
|
|
3399
|
+
* generates, so both write paths stay byte-compatible.
|
|
3400
|
+
*
|
|
3401
|
+
* Bails without writing when the providers file or its exported array is
|
|
3402
|
+
* missing or customized, when the app has no eventBus port to register
|
|
3403
|
+
* against, or when the registry name is already imported from another file.
|
|
3404
|
+
* The diagnostic stays in every bail case.
|
|
3405
|
+
*/
|
|
3406
|
+
async function fixUnregisteredListeners(targetDir, files, drift, config) {
|
|
3407
|
+
const candidates = drift.listeners.unregistered.filter((entry) => entry.fullyUnregistered);
|
|
3408
|
+
if (candidates.length === 0)
|
|
3409
|
+
return undefined;
|
|
3410
|
+
const providersFile = providersFilePath(config);
|
|
3411
|
+
const contextFile = normalizePath(path.join(path.dirname(config.paths.server), "context.ts"));
|
|
3412
|
+
const wiringDependencies = [
|
|
3413
|
+
providersFile,
|
|
3414
|
+
contextFile,
|
|
3415
|
+
config.paths.appContext,
|
|
3416
|
+
config.paths.ports,
|
|
3417
|
+
];
|
|
3418
|
+
if (wiringDependencies.some((file) => !files.includes(file))) {
|
|
3419
|
+
return undefined;
|
|
3420
|
+
}
|
|
3421
|
+
// The generated wiring registers listeners on ports.eventBus; without the
|
|
3422
|
+
// port the fix would produce code that cannot typecheck.
|
|
3423
|
+
const portsSource = await readFile(path.join(targetDir, config.paths.ports), "utf8");
|
|
3424
|
+
if (!/\beventBus\s*[:?]/.test(portsSource))
|
|
3425
|
+
return undefined;
|
|
3426
|
+
const providersPath = path.join(targetDir, providersFile);
|
|
3427
|
+
const original = await readFile(providersPath, "utf8");
|
|
3428
|
+
let next = original;
|
|
3429
|
+
const registeredNames = [];
|
|
3430
|
+
for (const { registry } of candidates) {
|
|
3431
|
+
const imported = parseNamedImportSources(next).get(registry.registryName);
|
|
3432
|
+
if (imported) {
|
|
3433
|
+
const importedFile = sourceFileFromImport(imported.sourcePath, providersFile, files);
|
|
3434
|
+
if (importedFile !== registry.indexFile)
|
|
3435
|
+
return undefined;
|
|
3436
|
+
}
|
|
3437
|
+
const result = wireListenersProviderSource(next, {
|
|
3438
|
+
registryName: registry.registryName,
|
|
3439
|
+
featureKebab: listenerFeatureKebab(registry.indexFile),
|
|
3440
|
+
listenerModule: `@/${modulePath(registry.indexFile)}`,
|
|
3441
|
+
config,
|
|
3442
|
+
});
|
|
3443
|
+
if (result.kind === "missing")
|
|
3444
|
+
return undefined;
|
|
3445
|
+
if (result.kind === "updated") {
|
|
3446
|
+
next = result.source;
|
|
3447
|
+
registeredNames.push(registry.registryName);
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
if (next === original || registeredNames.length === 0)
|
|
3451
|
+
return undefined;
|
|
3452
|
+
await writeFile(providersPath, next);
|
|
3453
|
+
return {
|
|
3454
|
+
code: "BEIGNET_LISTENER_UNREGISTERED",
|
|
3455
|
+
file: providersFile,
|
|
3456
|
+
message: `Registered ${registeredNames.join(", ")} with registerListeners(...) in ${providersFile}.`,
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
function listenerFeatureKebab(indexFile) {
|
|
3460
|
+
return path.posix.basename(path.posix.dirname(path.posix.dirname(normalizePath(indexFile))));
|
|
3461
|
+
}
|
|
3344
3462
|
async function fixUnregisteredOutboxEntries(targetDir, files, drift, config) {
|
|
3345
3463
|
if (!drift.outbox.exists)
|
|
3346
3464
|
return [];
|