@beignet/cli 0.0.48 → 0.0.50

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +117 -22
  3. package/dist/analysis/contract-factories.d.ts +17 -0
  4. package/dist/analysis/contract-factories.d.ts.map +1 -0
  5. package/dist/analysis/contract-factories.js +176 -0
  6. package/dist/analysis/contract-factories.js.map +1 -0
  7. package/dist/analysis/layers.d.ts +5 -0
  8. package/dist/analysis/layers.d.ts.map +1 -0
  9. package/dist/analysis/layers.js +172 -0
  10. package/dist/analysis/layers.js.map +1 -0
  11. package/dist/analysis/port-wiring.d.ts +23 -0
  12. package/dist/analysis/port-wiring.d.ts.map +1 -0
  13. package/dist/analysis/port-wiring.js +379 -0
  14. package/dist/analysis/port-wiring.js.map +1 -0
  15. package/dist/analysis/source-index.d.ts +5 -0
  16. package/dist/analysis/source-index.d.ts.map +1 -1
  17. package/dist/analysis/source-index.js +171 -42
  18. package/dist/analysis/source-index.js.map +1 -1
  19. package/dist/analysis/workspace.d.ts +11 -4
  20. package/dist/analysis/workspace.d.ts.map +1 -1
  21. package/dist/analysis/workspace.js +78 -8
  22. package/dist/analysis/workspace.js.map +1 -1
  23. package/dist/app-map-changes.d.ts +103 -0
  24. package/dist/app-map-changes.d.ts.map +1 -0
  25. package/dist/app-map-changes.js +949 -0
  26. package/dist/app-map-changes.js.map +1 -0
  27. package/dist/app-map.d.ts.map +1 -1
  28. package/dist/app-map.js +311 -92
  29. package/dist/app-map.js.map +1 -1
  30. package/dist/check.js +37 -3
  31. package/dist/check.js.map +1 -1
  32. package/dist/explain.js +4 -4
  33. package/dist/explain.js.map +1 -1
  34. package/dist/git-changes.d.ts +30 -0
  35. package/dist/git-changes.d.ts.map +1 -0
  36. package/dist/git-changes.js +367 -0
  37. package/dist/git-changes.js.map +1 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +26 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/inspect.d.ts.map +1 -1
  42. package/dist/inspect.js +706 -186
  43. package/dist/inspect.js.map +1 -1
  44. package/dist/lib.d.ts +3 -0
  45. package/dist/lib.d.ts.map +1 -1
  46. package/dist/lib.js +1 -0
  47. package/dist/lib.js.map +1 -1
  48. package/dist/lint.d.ts.map +1 -1
  49. package/dist/lint.js +309 -487
  50. package/dist/lint.js.map +1 -1
  51. package/dist/make/inbox.d.ts.map +1 -1
  52. package/dist/make/inbox.js +11 -4
  53. package/dist/make/inbox.js.map +1 -1
  54. package/dist/make/payments.js +2 -2
  55. package/dist/make/shared.d.ts.map +1 -1
  56. package/dist/make/shared.js +31 -27
  57. package/dist/make/shared.js.map +1 -1
  58. package/dist/make.d.ts.map +1 -1
  59. package/dist/make.js +144 -8
  60. package/dist/make.js.map +1 -1
  61. package/dist/mcp.d.ts +1 -1
  62. package/dist/mcp.d.ts.map +1 -1
  63. package/dist/mcp.js +43 -21
  64. package/dist/mcp.js.map +1 -1
  65. package/dist/provider-add.d.ts.map +1 -1
  66. package/dist/provider-add.js +142 -7
  67. package/dist/provider-add.js.map +1 -1
  68. package/dist/registry-edits.d.ts +7 -0
  69. package/dist/registry-edits.d.ts.map +1 -1
  70. package/dist/registry-edits.js +237 -53
  71. package/dist/registry-edits.js.map +1 -1
  72. package/dist/templates/agents.d.ts.map +1 -1
  73. package/dist/templates/agents.js +37 -1
  74. package/dist/templates/agents.js.map +1 -1
  75. package/dist/templates/base.d.ts.map +1 -1
  76. package/dist/templates/base.js +13 -2
  77. package/dist/templates/base.js.map +1 -1
  78. package/dist/templates/server.d.ts.map +1 -1
  79. package/dist/templates/server.js +2 -0
  80. package/dist/templates/server.js.map +1 -1
  81. package/dist/templates/shared.d.ts +1 -0
  82. package/dist/templates/shared.d.ts.map +1 -1
  83. package/dist/templates/shared.js +3 -2
  84. package/dist/templates/shared.js.map +1 -1
  85. package/package.json +7 -6
  86. package/skills/app-structure/SKILL.md +71 -7
  87. package/src/analysis/contract-factories.ts +256 -0
  88. package/src/analysis/layers.ts +266 -0
  89. package/src/analysis/port-wiring.ts +506 -0
  90. package/src/analysis/source-index.ts +225 -57
  91. package/src/analysis/workspace.ts +134 -9
  92. package/src/app-map-changes.ts +1462 -0
  93. package/src/app-map.ts +418 -109
  94. package/src/check.ts +51 -3
  95. package/src/explain.ts +4 -4
  96. package/src/git-changes.ts +511 -0
  97. package/src/index.ts +39 -1
  98. package/src/inspect.ts +1005 -238
  99. package/src/lib.ts +21 -0
  100. package/src/lint.ts +393 -643
  101. package/src/make/inbox.ts +11 -4
  102. package/src/make/payments.ts +2 -2
  103. package/src/make/shared.ts +31 -27
  104. package/src/make.ts +225 -8
  105. package/src/mcp.ts +51 -24
  106. package/src/provider-add.ts +186 -7
  107. package/src/registry-edits.ts +313 -63
  108. package/src/templates/agents.ts +37 -1
  109. package/src/templates/base.ts +13 -2
  110. package/src/templates/server.ts +2 -0
  111. package/src/templates/shared.ts +3 -2
package/dist/make.js CHANGED
@@ -2,6 +2,8 @@ import { randomBytes } from "node:crypto";
2
2
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import ts from "typescript";
5
+ import { analyzePortWiringFiles } from "./analysis/port-wiring.js";
6
+ import { listProjectFiles } from "./analysis/workspace.js";
5
7
  import { clientFormsPath, clientIndexPath, loadBeignetConfig, resolveConfig, } from "./config.js";
6
8
  import { assertNextFramework } from "./framework.js";
7
9
  import { adapterFilePath, addNamedImport, addNamedTypeImport, aliasModule, appendDeferredPortKey, appendPickStringLiteralMember, applyPortProviderWiring, assertAdapterApp, assertFeatureArtifactApp, assertFeatureUiApp, assertGeneratedIdentifiers, assertPersistenceArtifactApp, assertPolicyApp, assertPortApp, assertServerRuntimeApp, assertStandardApp, assertTestApp, assertUseCaseApp, beignetDependencyVersion, constantCase, createListenersRegistrySource, defineUploadsInitializerInfo, detectResourceDatabase, detectResourcePersistence, drizzleDialects, drizzleRepositoriesPath, drizzleResourceRepositoryFilePath, drizzleSchemaIndexPath, eventBusPortWiring, eventFilePath, factoryFilePath, featureArtifactDir, featureArtifactIndexFile, featureArtifactNames, featureResourceNames, featureUiClientQueriesFilePath, featureUiComponentFilePath, featureUiIndexFilePath, featureUiNames, fileExists, hasTopLevelObjectProperty, identifierNames, infrastructureDir, insertTypeProperty, jobFilePath, listenerFilePath, listenersFilePath, mergeMakeResultInto, mergeMakeResults, missingCapabilityBuilderFiles, notificationFilePath, notificationPortWirings, planGeneratedFile, planGeneratedFiles, portFilePath, portNames, providersFilePath, readOptionalFile, relativeModule, resourceContractFilePath, resourceFeatureDir, resourceNames, resourcePortFilePath, resourceSharedErrorsPath, scheduleFilePath, seedEntrypointPath, seedFilePath, seedNames, singularize, storagePortWiring, tanstackReactQueryDependencyVersion, taskFilePath, typeBodySource, uniqueStrings, updateDrizzleRepositories, updateDrizzleSchemaIndex, updateFeatureArtifactIndex, updateInfrastructureAdapterWiring, updateInfrastructureDeferredPorts, updateInfrastructureGatePolicies, updateInfrastructurePortStub, updateInfrastructurePorts, updateListenersRegistrySource, updateOpenApiRoute, updatePackageDependencies, updatePackageJson, updatePackageScripts, updatePortIndex, updatePortsIndex, updateSeedEntrypoint, updateServerRoutes, updateServerTransactionPorts, updateSharedErrors, uploadFilePath, useCaseFeatureDir, useCaseFilePath, usesFeatureOwnedPolicies, usesFeatureOwnedTests, wireListenersProviderSource, wirePortProviders, writeGeneratedFile, writePlannedGeneratedFile, } from "./make/shared.js";
@@ -552,6 +554,7 @@ export async function makeJob(options) {
552
554
  await assertFeatureArtifactApp(targetDir, config, "job");
553
555
  await assertServerRuntimeApp(targetDir, config, "job");
554
556
  await updateOutboxPortWiring(targetDir, config, { dryRun: true });
557
+ const jobDispatcherPlan = await planInlineJobDispatcherWiring(targetDir, config);
555
558
  const outboxDrainPlan = await planOutboxDrainWiring(targetDir, config, {
556
559
  force: Boolean(options.force),
557
560
  });
@@ -580,6 +583,9 @@ export async function makeJob(options) {
580
583
  await applyOutboxDrainWiring(result, targetDir, outboxDrainPlan, {
581
584
  dryRun: Boolean(options.dryRun),
582
585
  });
586
+ await applyInlineJobDispatcherWiring(result, targetDir, jobDispatcherPlan, {
587
+ dryRun: Boolean(options.dryRun),
588
+ });
583
589
  await applyOutboxPortWiring(result, targetDir, config, {
584
590
  dryRun: Boolean(options.dryRun),
585
591
  });
@@ -588,6 +594,123 @@ export async function makeJob(options) {
588
594
  });
589
595
  return result;
590
596
  }
597
+ async function planInlineJobDispatcherWiring(targetDir, config) {
598
+ const portsFile = config.paths.ports;
599
+ const portWiringFile = config.paths.portWiring;
600
+ const providersFile = providersFilePath(config);
601
+ const ports = await readFile(path.join(targetDir, portsFile), "utf8");
602
+ const portWiring = await readFile(path.join(targetDir, portWiringFile), "utf8");
603
+ const analysis = await analyzePortWiringFiles({
604
+ targetDir,
605
+ files: await listProjectFiles(targetDir),
606
+ config,
607
+ });
608
+ const declaresJobs = analysis.declared.has("jobs");
609
+ const wiresJobs = analysis.bound.has("jobs") || analysis.deferred.has("jobs");
610
+ if (declaresJobs && wiresJobs) {
611
+ return { changes: [] };
612
+ }
613
+ if (!declaresJobs && analysis.declarationIndeterminate) {
614
+ throw new Error(`Could not prove whether AppPorts in ${portsFile} declares the jobs port. Preserve the app's custom port type and add jobs: JobDispatcherPort manually before running make job.`);
615
+ }
616
+ if (!wiresJobs && analysis.indeterminate) {
617
+ throw new Error(`Could not prove how the jobs port is wired in ${portWiringFile}. Preserve the app's custom dispatcher by binding or deferring "jobs" in a statically inspectable definePorts(...) object before running make job.`);
618
+ }
619
+ let nextPorts = ports;
620
+ if (!declaresJobs && !analysis.appPortsInferredFromWiring) {
621
+ nextPorts = addNamedTypeImport(nextPorts, "JobDispatcherPort", "@beignet/core/ports");
622
+ nextPorts = insertTypeProperty(nextPorts, "AppPorts", "jobs: JobDispatcherPort;", `Could not find AppPorts in ${portsFile}. Add jobs: JobDispatcherPort manually and wire a dispatcher, or restore the generated ports file before running make job.`);
623
+ }
624
+ if (wiresJobs) {
625
+ return {
626
+ changes: ports === nextPorts
627
+ ? []
628
+ : [{ path: portsFile, before: ports, after: nextPorts }],
629
+ };
630
+ }
631
+ const nextPortWiring = appendDeferredPortKey(portWiring, "jobs");
632
+ if (nextPortWiring === portWiring) {
633
+ throw new Error(`Could not find the deferred port list in ${portWiringFile}. Add "jobs" manually and wire a dispatcher, or restore the generated port wiring file before running make job.`);
634
+ }
635
+ const providersPath = path.join(targetDir, providersFile);
636
+ const providers = await readOptionalFile(providersPath);
637
+ if (providers === undefined) {
638
+ throw new Error(`Could not find ${providersFile}. Register an inline or provider-backed jobs dispatcher manually, or restore the generated providers file before running make job.`);
639
+ }
640
+ let nextProviders = addNamedImport(providers, "createInlineJobDispatcher", "@beignet/core/jobs");
641
+ nextProviders = addNamedImport(nextProviders, "createProvider", "@beignet/core/providers");
642
+ nextProviders = addNamedTypeImport(nextProviders, "AppContext", aliasModule(config.paths.appContext));
643
+ nextProviders = addNamedTypeImport(nextProviders, "AppPorts", aliasModule(config.paths.ports));
644
+ nextProviders = addNamedTypeImport(nextProviders, "AppServiceContextInput", aliasModule(path.join(path.dirname(config.paths.server), "context.ts")));
645
+ const providerName = "inlineJobsProvider";
646
+ const generatedProviderMarker = "// beignet:generated-inline-jobs-provider";
647
+ const providersSourceFile = ts.createSourceFile(providersFile, nextProviders, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
648
+ const providerDeclaration = providersSourceFile.statements
649
+ .filter(ts.isVariableStatement)
650
+ .flatMap((statement) => [...statement.declarationList.declarations])
651
+ .find((declaration) => ts.isIdentifier(declaration.name) &&
652
+ declaration.name.text === providerName);
653
+ const providerDeclarationExists = providerDeclaration !== undefined;
654
+ const providerIsGenerated = providerDeclaration
655
+ ? nextProviders
656
+ .slice(0, providerDeclaration.getStart(providersSourceFile))
657
+ .trimEnd()
658
+ .endsWith(generatedProviderMarker)
659
+ : false;
660
+ if (providerDeclarationExists && !providerIsGenerated) {
661
+ throw new Error(`Could not install the generated inline jobs dispatcher because ${providerName} already exists in ${providersFile}. Rename the app-owned declaration or wire its jobs port explicitly before running make job.`);
662
+ }
663
+ if (!providerDeclarationExists) {
664
+ const definition = `// beignet:generated-inline-jobs-provider
665
+ const ${providerName} = createProvider<
666
+ {},
667
+ AppContext,
668
+ AppServiceContextInput
669
+ >()({
670
+ name: "inline-jobs",
671
+ setup({ createServiceContext }): { ports: Pick<AppPorts, "jobs"> } {
672
+ return {
673
+ ports: {
674
+ jobs: createInlineJobDispatcher<AppContext>({
675
+ ctx: () => createServiceContext(undefined),
676
+ }),
677
+ },
678
+ };
679
+ },
680
+ });
681
+ `;
682
+ const inserted = nextProviders.replace(/\nexport const providers = \[/, `\n${definition}\nexport const providers = [`);
683
+ if (inserted === nextProviders) {
684
+ throw new Error(`Could not find the exported providers array in ${providersFile}. Register an inline or provider-backed jobs dispatcher manually, or restore the generated providers file before running make job.`);
685
+ }
686
+ nextProviders = inserted;
687
+ }
688
+ const appended = appendToNamedArray(nextProviders, "providers", providerName);
689
+ if (appended.kind === "missing") {
690
+ throw new Error(`Could not find the exported providers array in ${providersFile}. Register ${providerName} manually, or restore the generated providers file before running make job.`);
691
+ }
692
+ if (appended.kind === "updated")
693
+ nextProviders = appended.source;
694
+ return {
695
+ changes: [
696
+ { path: portsFile, before: ports, after: nextPorts },
697
+ { path: portWiringFile, before: portWiring, after: nextPortWiring },
698
+ { path: providersFile, before: providers, after: nextProviders },
699
+ ].filter((change) => change.before !== change.after),
700
+ };
701
+ }
702
+ async function applyInlineJobDispatcherWiring(result, targetDir, plan, options) {
703
+ for (const change of plan.changes) {
704
+ if (!options.dryRun) {
705
+ await writeFile(path.join(targetDir, change.path), change.after);
706
+ }
707
+ if (!result.updatedFiles.includes(change.path)) {
708
+ result.updatedFiles.push(change.path);
709
+ }
710
+ if (!result.files.includes(change.path))
711
+ result.files.push(change.path);
712
+ }
713
+ }
591
714
  export async function makeTask(options) {
592
715
  const targetDir = path.resolve(options.cwd ?? process.cwd());
593
716
  const names = taskNames(options.name);
@@ -2552,8 +2675,8 @@ export function decode${names.singularPascal}Cursor(cursor: string): ${names.sin
2552
2675
 
2553
2676
  export const List${names.pluralPascal}InputSchema = z
2554
2677
  .object({
2555
- limit: z.coerce.number().int().min(1).max(100).default(20),
2556
- cursor: z.string().nullable().default(null),
2678
+ limit: z.number().int().min(1).max(100).default(20),
2679
+ cursor: z.string().optional().transform((value) => value ?? null),
2557
2680
  name: z.string().trim().min(1).max(120).optional(),
2558
2681
  sortBy: ${names.singularPascal}SortBySchema.default("createdAt"),
2559
2682
  sortDirection: ${names.singularPascal}SortDirectionSchema.default("desc"),
@@ -3316,7 +3439,7 @@ ${createMethod}${resourceMethods}
3316
3439
  `;
3317
3440
  }
3318
3441
  function contractFile(names, config, mode, options) {
3319
- return `import { defineContractGroup } from "@beignet/core/contracts";
3442
+ return `import { defineContractGroup, defineQueryTransport, query } from "@beignet/core/contracts";
3320
3443
  import { z } from "zod";
3321
3444
  ${mode === "resource" ? `import { errors } from "${aliasModule(resourceSharedErrorsPath(config))}";\n` : ""}import {
3322
3445
  Create${names.singularPascal}InputSchema,
@@ -3339,7 +3462,16 @@ const ${names.pluralCamel} = defineContractGroup()
3339
3462
 
3340
3463
  export const list${names.pluralPascal} = ${names.pluralCamel}
3341
3464
  .get("/api/${names.pluralKebab}")
3342
- .query(List${names.pluralPascal}InputSchema)
3465
+ .query(
3466
+ List${names.pluralPascal}InputSchema,
3467
+ defineQueryTransport({
3468
+ limit: query.integer(),
3469
+ cursor: query.string(),
3470
+ name: query.string(),
3471
+ sortBy: query.string(),
3472
+ sortDirection: query.string(),
3473
+ }),
3474
+ )
3343
3475
  .responses({
3344
3476
  200: List${names.pluralPascal}OutputSchema,
3345
3477
  });
@@ -3381,7 +3513,7 @@ ${options.authorization ? `\t.meta({\n\t\tauth: "required",\n\t\tauthorization:
3381
3513
  `;
3382
3514
  }
3383
3515
  function standaloneContractFile(names) {
3384
- return `import { defineContractGroup } from "@beignet/core/contracts";
3516
+ return `import { defineContractGroup, defineQueryTransport, query } from "@beignet/core/contracts";
3385
3517
  import { z } from "zod";
3386
3518
 
3387
3519
  const ErrorResponseSchema = z.object({
@@ -3405,8 +3537,12 @@ export const list${names.pluralPascal} = ${names.pluralCamel}
3405
3537
  .get("/api/${names.pluralKebab}")
3406
3538
  .query(
3407
3539
  z.object({
3408
- limit: z.coerce.number().int().min(1).max(100).default(20),
3409
- offset: z.coerce.number().int().min(0).default(0),
3540
+ limit: z.number().int().min(1).max(100).default(20),
3541
+ offset: z.number().int().min(0).default(0),
3542
+ }),
3543
+ defineQueryTransport({
3544
+ limit: query.integer(),
3545
+ offset: query.integer(),
3410
3546
  }),
3411
3547
  )
3412
3548
  .responses({
@@ -4372,7 +4508,7 @@ ${options.authorization ? `\t\t\t\tgate: initialPorts.gate,\n` : ""}
4372
4508
  );
4373
4509
  const secondPage = await tester.run(
4374
4510
  list${names.pluralPascal}UseCase,
4375
- { limit: 2, cursor: firstPage.page.nextCursor },
4511
+ { limit: 2, cursor: firstPage.page.nextCursor! },
4376
4512
  { ctx },
4377
4513
  );
4378
4514
  const nameAscendingPage = await tester.run(