@pattern-stack/codegen 0.17.1 → 0.18.0

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 +36 -0
  2. package/README.md +106 -2
  3. package/dist/{chunk-SFQRETXJ.js → chunk-2VGVSL2D.js} +6 -6
  4. package/dist/{chunk-VNBC3VXM.js → chunk-3A34R6CI.js} +7 -7
  5. package/dist/{chunk-FVNAU7VO.js → chunk-7MMS36AN.js} +6 -6
  6. package/dist/{chunk-FWRL7BZ5.js → chunk-C5E7H553.js} +25 -15
  7. package/dist/chunk-C5E7H553.js.map +1 -0
  8. package/dist/{chunk-IOQMMH6C.js → chunk-CFFTPWHM.js} +79 -4
  9. package/dist/chunk-CFFTPWHM.js.map +1 -0
  10. package/dist/{chunk-HOIRY5XP.js → chunk-EWYI5GGJ.js} +10 -10
  11. package/dist/{chunk-BHZP6LOV.js → chunk-IN3EWFB4.js} +4 -4
  12. package/dist/{chunk-CZQUOIDY.js → chunk-J7JMVS2B.js} +4 -4
  13. package/dist/{chunk-KSTZIULO.js → chunk-K2I6XIK5.js} +4 -4
  14. package/dist/{chunk-T6SCOJF4.js → chunk-NXHL5YII.js} +4 -4
  15. package/dist/{chunk-JA7GJDNI.js → chunk-PKDS6QIJ.js} +4 -4
  16. package/dist/{chunk-MYQIQ27N.js → chunk-Q6LRJ4VI.js} +51 -2
  17. package/dist/chunk-Q6LRJ4VI.js.map +1 -0
  18. package/dist/{chunk-EJBK7I4F.js → chunk-R4BPUUB5.js} +3 -3
  19. package/dist/{chunk-4PFF3ED4.js → chunk-RKNW56RU.js} +5 -5
  20. package/dist/{chunk-SGSWVNNB.js → chunk-TBGTMALE.js} +4 -4
  21. package/dist/{chunk-GM3RMJIJ.js → chunk-VHAR2BGH.js} +4 -4
  22. package/dist/{chunk-DUMI2J5M.js → chunk-VQOAATIG.js} +4 -4
  23. package/dist/{chunk-HPS554L4.js → chunk-X6BP6LI5.js} +6 -6
  24. package/dist/{chunk-PSDVGPQR.js → chunk-YZLBU6O2.js} +9 -9
  25. package/dist/runtime/shared/openapi/index.js +3 -3
  26. package/dist/runtime/subsystems/analytics/analytics.module.js +2 -2
  27. package/dist/runtime/subsystems/analytics/index.js +4 -4
  28. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  29. package/dist/runtime/subsystems/auth/index.js +10 -10
  30. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +2 -2
  31. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +2 -2
  32. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +6 -6
  33. package/dist/runtime/subsystems/bridge/bridge.module.js +17 -17
  34. package/dist/runtime/subsystems/bridge/index.js +24 -24
  35. package/dist/runtime/subsystems/cache/cache.module.js +1 -1
  36. package/dist/runtime/subsystems/cache/index.js +3 -3
  37. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +3 -3
  38. package/dist/runtime/subsystems/events/events.module.js +6 -6
  39. package/dist/runtime/subsystems/events/generated/bus.js +2 -2
  40. package/dist/runtime/subsystems/events/generated/index.js +2 -2
  41. package/dist/runtime/subsystems/events/index.js +10 -10
  42. package/dist/runtime/subsystems/index.js +64 -64
  43. package/dist/runtime/subsystems/integration/index.js +10 -10
  44. package/dist/runtime/subsystems/integration/integration.module.js +2 -2
  45. package/dist/runtime/subsystems/jobs/index.js +21 -21
  46. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +5 -5
  47. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +3 -3
  48. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +2 -2
  49. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +3 -3
  50. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +2 -2
  51. package/dist/runtime/subsystems/jobs/job-worker.d.ts +8 -0
  52. package/dist/runtime/subsystems/jobs/job-worker.js +3 -3
  53. package/dist/runtime/subsystems/jobs/job-worker.module.js +11 -11
  54. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +9 -9
  55. package/dist/runtime/subsystems/jobs/pg-notify.d.ts +25 -1
  56. package/dist/runtime/subsystems/jobs/pg-notify.js +1 -1
  57. package/dist/src/cli/index.js +1408 -245
  58. package/dist/src/cli/index.js.map +1 -1
  59. package/dist/src/index.d.ts +18 -0
  60. package/dist/src/index.js +5 -5
  61. package/package.json +1 -1
  62. package/runtime/subsystems/jobs/job-worker.ts +29 -11
  63. package/runtime/subsystems/jobs/pg-notify.ts +63 -3
  64. package/src/config/locations.mjs +0 -6
  65. package/src/config/paths.mjs +0 -13
  66. package/templates/entity/new/prompt.js +12 -88
  67. package/dist/chunk-FWRL7BZ5.js.map +0 -1
  68. package/dist/chunk-IOQMMH6C.js.map +0 -1
  69. package/dist/chunk-MYQIQ27N.js.map +0 -1
  70. package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +0 -7
  71. package/templates/entity/new/frontend/_inject-entities-import.ejs.t +0 -7
  72. package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +0 -10
  73. package/templates/entity/new/frontend/collections/_inject-index.ejs.t +0 -9
  74. package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +0 -9
  75. package/templates/entity/new/frontend/collections/collection.ejs.t +0 -86
  76. package/templates/entity/new/frontend/collections/collections-base.ejs.t +0 -35
  77. package/templates/entity/new/frontend/entity/collection.ejs.t +0 -173
  78. package/templates/entity/new/frontend/entity/combined.ejs.t +0 -505
  79. package/templates/entity/new/frontend/entity/fields.ejs.t +0 -105
  80. package/templates/entity/new/frontend/entity/hooks.ejs.t +0 -74
  81. package/templates/entity/new/frontend/entity/index.ejs.t +0 -22
  82. package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +0 -85
  83. package/templates/entity/new/frontend/entity/mutations.ejs.t +0 -39
  84. package/templates/entity/new/frontend/entity/types.ejs.t +0 -60
  85. package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +0 -7
  86. package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +0 -7
  87. package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +0 -7
  88. package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +0 -9
  89. package/templates/entity/new/frontend/store/_inject-collections.ejs.t +0 -9
  90. package/templates/entity/new/frontend/store/_inject-entity.ejs.t +0 -9
  91. package/templates/entity/new/frontend/store/_inject-import.ejs.t +0 -9
  92. package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +0 -9
  93. package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +0 -10
  94. package/templates/entity/new/frontend/store/hooks.ejs.t +0 -73
  95. package/templates/entity/new/frontend/unified-entity.ejs.t +0 -29
  96. /package/dist/{chunk-SFQRETXJ.js.map → chunk-2VGVSL2D.js.map} +0 -0
  97. /package/dist/{chunk-VNBC3VXM.js.map → chunk-3A34R6CI.js.map} +0 -0
  98. /package/dist/{chunk-FVNAU7VO.js.map → chunk-7MMS36AN.js.map} +0 -0
  99. /package/dist/{chunk-HOIRY5XP.js.map → chunk-EWYI5GGJ.js.map} +0 -0
  100. /package/dist/{chunk-BHZP6LOV.js.map → chunk-IN3EWFB4.js.map} +0 -0
  101. /package/dist/{chunk-CZQUOIDY.js.map → chunk-J7JMVS2B.js.map} +0 -0
  102. /package/dist/{chunk-KSTZIULO.js.map → chunk-K2I6XIK5.js.map} +0 -0
  103. /package/dist/{chunk-T6SCOJF4.js.map → chunk-NXHL5YII.js.map} +0 -0
  104. /package/dist/{chunk-JA7GJDNI.js.map → chunk-PKDS6QIJ.js.map} +0 -0
  105. /package/dist/{chunk-EJBK7I4F.js.map → chunk-R4BPUUB5.js.map} +0 -0
  106. /package/dist/{chunk-4PFF3ED4.js.map → chunk-RKNW56RU.js.map} +0 -0
  107. /package/dist/{chunk-SGSWVNNB.js.map → chunk-TBGTMALE.js.map} +0 -0
  108. /package/dist/{chunk-GM3RMJIJ.js.map → chunk-VHAR2BGH.js.map} +0 -0
  109. /package/dist/{chunk-DUMI2J5M.js.map → chunk-VQOAATIG.js.map} +0 -0
  110. /package/dist/{chunk-HPS554L4.js.map → chunk-X6BP6LI5.js.map} +0 -0
  111. /package/dist/{chunk-PSDVGPQR.js.map → chunk-YZLBU6O2.js.map} +0 -0
@@ -22,6 +22,7 @@ import {
22
22
  loadEntities,
23
23
  loadEntitiesFromYaml,
24
24
  loadEntityFromYaml,
25
+ loadEntityRegistry,
25
26
  loadEventFromYaml,
26
27
  loadJunctionFromYaml,
27
28
  loadProvidersFromYaml,
@@ -36,22 +37,20 @@ import {
36
37
  validateOrchestrationProject,
37
38
  validateProviders,
38
39
  writeManifest
39
- } from "../../chunk-IOQMMH6C.js";
40
+ } from "../../chunk-CFFTPWHM.js";
40
41
  import "../../chunk-KVOWSC5S.js";
41
- import "../../chunk-JA7GJDNI.js";
42
+ import "../../chunk-PKDS6QIJ.js";
42
43
  import "../../chunk-PRWIX6UW.js";
43
- import "../../chunk-AHV4GDYM.js";
44
44
  import "../../chunk-YK5JEVLX.js";
45
45
  import "../../chunk-EO2QPOKH.js";
46
46
  import "../../chunk-SQDOBLBP.js";
47
- import "../../chunk-4KNXX6TI.js";
48
- import "../../chunk-3CJFPU6Q.js";
49
47
  import "../../chunk-TDEHU73T.js";
50
48
  import "../../chunk-LG57S2SC.js";
51
49
  import "../../chunk-XWBK3XJK.js";
52
50
  import "../../chunk-S7C6TIIF.js";
53
51
  import "../../chunk-MZ6GV4YF.js";
54
52
  import "../../chunk-HNWZFNKP.js";
53
+ import "../../chunk-AHV4GDYM.js";
55
54
  import "../../chunk-43SBT72G.js";
56
55
  import "../../chunk-4MF3HKJA.js";
57
56
  import "../../chunk-TIZXQU26.js";
@@ -59,12 +58,14 @@ import "../../chunk-JEINYUJH.js";
59
58
  import {
60
59
  isDivisibleCursor
61
60
  } from "../../chunk-5TK7MEN4.js";
61
+ import "../../chunk-4KNXX6TI.js";
62
+ import "../../chunk-3CJFPU6Q.js";
62
63
  import "../../chunk-U64T4YZE.js";
63
64
  import "../../chunk-2E224ZSN.js";
64
65
 
65
66
  // src/cli/index.ts
66
67
  import { readFileSync as readFileSync6 } from "fs";
67
- import { join as join10 } from "path";
68
+ import { join as join17 } from "path";
68
69
  import { Builtins, Cli, Command as Command13 } from "clipanion";
69
70
 
70
71
  // src/cli/noun-module.ts
@@ -1099,7 +1100,7 @@ var icons = {
1099
1100
 
1100
1101
  // src/cli/commands/entity.ts
1101
1102
  import fs10 from "fs";
1102
- import path13 from "path";
1103
+ import path14 from "path";
1103
1104
  import { Command as Command2, Option as Option2 } from "clipanion";
1104
1105
 
1105
1106
  // src/cli/shared/hygen.ts
@@ -3847,8 +3848,8 @@ function generateIntegrationAggregator(surface, entries) {
3847
3848
  );
3848
3849
  const importLines = sorted.map((e) => {
3849
3850
  const cls = assemblyModuleClass(e.entityName, e.provider);
3850
- const path35 = `./modules/${e.provider}/${e.entityName}-integration.module`;
3851
- return `import { ${cls} } from '${path35}';`;
3851
+ const path36 = `./modules/${e.provider}/${e.entityName}-integration.module`;
3852
+ return `import { ${cls} } from '${path36}';`;
3852
3853
  }).join("\n");
3853
3854
  const membersInline = moduleClasses.join(", ");
3854
3855
  return `${generatedBanner(`surface: ${surface}`)}
@@ -4472,9 +4473,9 @@ function emitAdapters(opts) {
4472
4473
  [aggregatorPath, generateSurfaceAggregator(surface, slugs, mode)],
4473
4474
  [typedViewPath, generateTypedView(surface, slugs, entitiesBySurface.get(surface) ?? [])]
4474
4475
  ];
4475
- for (const [path35, content] of files) {
4476
- if (!opts.dryRun) writeIfChanged(path35, content);
4477
- result.written.push(path35);
4476
+ for (const [path36, content] of files) {
4477
+ if (!opts.dryRun) writeIfChanged(path36, content);
4478
+ result.written.push(path36);
4478
4479
  }
4479
4480
  if (opts.backendSrcAbs) {
4480
4481
  const aliases = opts.aliases ?? {};
@@ -4819,15 +4820,1067 @@ function relativeSource(filePath) {
4819
4820
  return slash === -1 ? filePath : `definitions/providers/${filePath.slice(slash + 1)}`;
4820
4821
  }
4821
4822
 
4822
- // src/cli/shared/events-path.ts
4823
+ // src/emitters/frontend/emit-base.ts
4824
+ import { join as join10 } from "path";
4825
+
4826
+ // src/emitters/frontend/types.ts
4827
+ function sortEntities(entities) {
4828
+ return [...entities].sort((a, b) => a.name.localeCompare(b.name));
4829
+ }
4830
+ function resolveSyncMode(entity, config) {
4831
+ return entity.sync ?? config.globalSyncMode;
4832
+ }
4833
+
4834
+ // src/emitters/frontend/emit-utils.ts
4835
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
4836
+ import { dirname as dirname3 } from "path";
4837
+ function generatedBanner3(sourceDesc) {
4838
+ return `// @generated by @pattern-stack/codegen from ${sourceDesc} \u2014 DO NOT EDIT.
4839
+ // Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`.`;
4840
+ }
4841
+ function withBanner(sourceDesc, body) {
4842
+ return `${generatedBanner3(sourceDesc)}
4843
+
4844
+ ${body}`;
4845
+ }
4846
+ function writeFile2(outPath, content) {
4847
+ mkdirSync3(dirname3(outPath), { recursive: true });
4848
+ writeFileSync3(outPath, content);
4849
+ }
4850
+
4851
+ // src/emitters/frontend/emit-base.ts
4852
+ var SOURCE_DESC = "the entity set";
4853
+ function buildQueryClientFile() {
4854
+ const body = `import { QueryClient } from '@tanstack/react-query';
4855
+
4856
+ /**
4857
+ * Shared QueryClient for REST-backed (\`api\` sync mode) collections.
4858
+ *
4859
+ * Replaceable: swap this for your app's own QueryClient if you already create
4860
+ * one \u2014 every generated collection imports \`queryClient\` from this module.
4861
+ */
4862
+ export const queryClient = new QueryClient({
4863
+ defaultOptions: {
4864
+ queries: {
4865
+ staleTime: 60 * 1000, // 60s
4866
+ gcTime: 5 * 60 * 1000, // 5m
4867
+ },
4868
+ },
4869
+ });
4870
+ `;
4871
+ return withBanner(SOURCE_DESC, body);
4872
+ }
4873
+ function buildConfigFile(ctx) {
4874
+ const entities = sortEntities(ctx.entities);
4875
+ const entityNameUnion = entities.length > 0 ? entities.map((e) => `'${e.name}'`).join(" | ") : "string";
4876
+ const defaultEntries = entities.map((e) => ` ${e.name}: { mode: '${resolveSyncMode(e, ctx.config)}' },`).join("\n");
4877
+ const body = `/**
4878
+ * Per-entity sync configuration + runtime overrides.
4879
+ *
4880
+ * \`mode\` selects the collection backing for an entity:
4881
+ * - 'electric' \u2192 real-time shape sync (electricCollectionOptions)
4882
+ * - 'api' \u2192 REST via TanStack Query (queryCollectionOptions)
4883
+ *
4884
+ * The offline mode (Electric + Dexie) is deferred \u2014 see
4885
+ * docs/specs/2026-06-04-frontend-pipeline-rebuild.md OQ-6.
4886
+ */
4887
+
4888
+ export type SyncMode = 'api' | 'electric';
4889
+
4890
+ export type EntityName = ${entityNameUnion};
4891
+
4892
+ export interface EntitySyncConfig {
4893
+ mode: SyncMode;
4894
+ }
4895
+
4896
+ /** Resolved per-entity sync modes (per-entity \`sync:\` over global default). */
4897
+ export const defaultConfig: Record<EntityName, EntitySyncConfig> = {
4898
+ ${defaultEntries}
4899
+ };
4900
+
4901
+ /** Runtime overrides, layered over \`defaultConfig\`. */
4902
+ const overrides: Partial<Record<EntityName, EntitySyncConfig>> = {};
4903
+
4904
+ /** Resolve an entity's effective sync mode (override wins over default). */
4905
+ export function getSyncMode(entity: EntityName): SyncMode {
4906
+ return (overrides[entity] ?? defaultConfig[entity]).mode;
4907
+ }
4908
+
4909
+ /** Override an entity's sync config at runtime. */
4910
+ export function setEntityConfig(entity: EntityName, config: EntitySyncConfig): void {
4911
+ overrides[entity] = config;
4912
+ }
4913
+ `;
4914
+ return withBanner(SOURCE_DESC, body);
4915
+ }
4916
+ function emitBase(ctx, outDir) {
4917
+ const written = [];
4918
+ const queryClientPath = join10(outDir, "query-client.ts");
4919
+ writeFile2(queryClientPath, buildQueryClientFile());
4920
+ written.push(queryClientPath);
4921
+ const configPath = join10(outDir, "config.ts");
4922
+ writeFile2(configPath, buildConfigFile(ctx));
4923
+ written.push(configPath);
4924
+ return written;
4925
+ }
4926
+
4927
+ // src/emitters/frontend/emit-api.ts
4928
+ import { join as join11 } from "path";
4929
+ var SOURCE_DESC_SET = "the entity set";
4930
+ function updateVerb(architecture) {
4931
+ return architecture === "clean-lite-ps" ? "PATCH" : "PUT";
4932
+ }
4933
+ function buildClientFile(ctx) {
4934
+ const { config } = ctx;
4935
+ const importLines = [];
4936
+ if (config.apiBaseUrlImport) {
4937
+ importLines.push(`import { API_BASE_URL } from '${config.apiBaseUrlImport}';`);
4938
+ }
4939
+ if (config.authFunction) {
4940
+ importLines.push(`import { ${config.authFunction} } from '${config.authImport}';`);
4941
+ }
4942
+ const baseUrlConst = config.apiBaseUrlImport ? "const BASE_URL = API_BASE_URL;" : `const BASE_URL = '${config.apiUrl}';`;
4943
+ const authHeaderBlock = config.authFunction ? ` const headers: Record<string, string> = {
4944
+ 'Content-Type': 'application/json',
4945
+ Authorization: ${config.authFunction}(),
4946
+ };` : ` const headers: Record<string, string> = {
4947
+ 'Content-Type': 'application/json',
4948
+ };`;
4949
+ const imports = importLines.length > 0 ? `${importLines.join("\n")}
4950
+
4951
+ ` : "";
4952
+ const body = `${imports}${baseUrlConst}
4953
+
4954
+ /**
4955
+ * Base REST transport for \`api\` sync-mode collections and entity api clients.
4956
+ * Throws on non-2xx; returns parsed JSON, or \`undefined\` for 204 No Content.
4957
+ */
4958
+ export async function request<T>(
4959
+ method: string,
4960
+ path: string,
4961
+ body?: unknown,
4962
+ ): Promise<T> {
4963
+ ${authHeaderBlock}
4964
+
4965
+ const res = await fetch(\`\${BASE_URL}\${path}\`, {
4966
+ method,
4967
+ headers,
4968
+ body: body === undefined ? undefined : JSON.stringify(body),
4969
+ });
4970
+
4971
+ if (!res.ok) {
4972
+ throw new Error(\`\${method} \${path} \u2192 \${res.status} \${res.statusText}\`);
4973
+ }
4974
+
4975
+ if (res.status === 204) {
4976
+ return undefined as T;
4977
+ }
4978
+
4979
+ return res.json() as Promise<T>;
4980
+ }
4981
+ `;
4982
+ return withBanner(SOURCE_DESC_SET, body);
4983
+ }
4984
+ function buildEntityApiFile(entity, ctx) {
4985
+ const { config } = ctx;
4986
+ const { camelName, plural, className, name } = entity;
4987
+ const verb = updateVerb(config.architecture);
4988
+ const body = `import { request } from './client';
4989
+ import type { ${className} } from '${config.dbEntitiesImport}/${name}';
4990
+
4991
+ export const ${camelName}Api = {
4992
+ list: (): Promise<${className}[]> => request<${className}[]>('GET', '/${plural}'),
4993
+
4994
+ get: (id: string): Promise<${className}> =>
4995
+ request<${className}>('GET', \`/${plural}/\${id}\`),
4996
+
4997
+ create: (data: Partial<${className}>): Promise<${className}> =>
4998
+ request<${className}>('POST', '/${plural}', data),
4999
+
5000
+ update: (id: string, data: Partial<${className}>): Promise<${className}> =>
5001
+ request<${className}>('${verb}', \`/${plural}/\${id}\`, data),
5002
+
5003
+ delete: (id: string): Promise<void> =>
5004
+ request<void>('DELETE', \`/${plural}/\${id}\`),
5005
+ };
5006
+ `;
5007
+ return withBanner(`entities/${name}.yaml`, body);
5008
+ }
5009
+ function buildApiIndexFile(ctx) {
5010
+ const entities = sortEntities(ctx.entities);
5011
+ const lines = [
5012
+ "export * from './client';",
5013
+ ...entities.map((e) => `export * from './${e.name}';`)
5014
+ ];
5015
+ return withBanner(SOURCE_DESC_SET, `${lines.join("\n")}
5016
+ `);
5017
+ }
5018
+ function emitApi(ctx, outDir) {
5019
+ const apiDir = join11(outDir, "api");
5020
+ const entities = sortEntities(ctx.entities);
5021
+ const written = [];
5022
+ const clientPath = join11(apiDir, "client.ts");
5023
+ writeFile2(clientPath, buildClientFile(ctx));
5024
+ written.push(clientPath);
5025
+ for (const entity of entities) {
5026
+ const entityPath = join11(apiDir, `${entity.name}.ts`);
5027
+ writeFile2(entityPath, buildEntityApiFile(entity, ctx));
5028
+ written.push(entityPath);
5029
+ }
5030
+ const indexPath = join11(apiDir, "index.ts");
5031
+ writeFile2(indexPath, buildApiIndexFile(ctx));
5032
+ written.push(indexPath);
5033
+ return written;
5034
+ }
5035
+
5036
+ // src/emitters/frontend/emit-collections.ts
5037
+ import { join as join12 } from "path";
5038
+ var SOURCE_DESC_SET2 = "the entity set";
5039
+ function baseUrlExpr(base, plural, apiBaseUrlImport) {
5040
+ return apiBaseUrlImport ? `\`\${API_BASE_URL}/${plural}\`` : `\`${base}/${plural}\``;
5041
+ }
5042
+ var SSR_ORIGIN_EXPR = "typeof window !== 'undefined' ? window.location.origin : ''";
5043
+ function buildElectricCollection(entity, ctx) {
5044
+ const { config } = ctx;
5045
+ const { camelName, plural, name } = entity;
5046
+ const imports = [
5047
+ "import { electricCollectionOptions } from '@tanstack/electric-db-collection';",
5048
+ "import { createCollection } from '@tanstack/react-db';"
5049
+ ];
5050
+ if (config.columnMapper) {
5051
+ imports.push(`import { ${config.columnMapper} } from '@electric-sql/client';`);
5052
+ }
5053
+ if (config.authFunction) {
5054
+ imports.push(`import { ${config.authFunction} } from '${config.authImport}';`);
5055
+ }
5056
+ if (config.apiBaseUrlImport) {
5057
+ imports.push(`import { API_BASE_URL } from '${config.apiBaseUrlImport}';`);
5058
+ }
5059
+ imports.push(`import { ${camelName}Schema } from '${config.dbEntitiesImport}/${name}';`);
5060
+ let urlBlock;
5061
+ if (config.useTableParam) {
5062
+ urlBlock = ` url: new URL(
5063
+ '${config.shapeUrl}',
5064
+ ${SSR_ORIGIN_EXPR},
5065
+ ).toString(),
5066
+ params: {
5067
+ table: '${plural}',
5068
+ },`;
5069
+ } else {
5070
+ const shapeUrl = baseUrlExpr(config.shapeUrl, plural, config.apiBaseUrlImport);
5071
+ urlBlock = ` url: new URL(
5072
+ ${shapeUrl},
5073
+ ${SSR_ORIGIN_EXPR},
5074
+ ).toString(),`;
5075
+ }
5076
+ const headersBlock = config.authFunction ? `
5077
+ headers: {
5078
+ Authorization: ${config.authFunction}(),
5079
+ },` : "";
5080
+ const parserEntries = Object.entries(config.parsers).map(([type, fn]) => ` ${type}: ${fn},`).join("\n");
5081
+ const parserBlock = parserEntries ? `
5082
+ parser: {
5083
+ ${parserEntries}
5084
+ },` : `
5085
+ parser: {},`;
5086
+ let columnMapperBlock = "";
5087
+ if (config.columnMapper) {
5088
+ const mapperExpr = config.columnMapperNeedsCall ? `${config.columnMapper}()` : config.columnMapper;
5089
+ columnMapperBlock = `
5090
+ columnMapper: ${mapperExpr},`;
5091
+ }
5092
+ const body = `${imports.join("\n")}
5093
+
5094
+ export const ${camelName}Collection = createCollection(
5095
+ electricCollectionOptions({
5096
+ id: '${plural}',
5097
+ shapeOptions: {
5098
+ ${urlBlock}${headersBlock}${parserBlock}${columnMapperBlock}
5099
+ },
5100
+ schema: ${camelName}Schema,
5101
+ getKey: (item) => item.id,
5102
+ }),
5103
+ );
5104
+ `;
5105
+ return withBanner(`entities/${name}.yaml`, body);
5106
+ }
5107
+ function buildApiCollection(entity, ctx) {
5108
+ const { config } = ctx;
5109
+ const { camelName, plural, name } = entity;
5110
+ const imports = [
5111
+ "import { queryCollectionOptions } from '@tanstack/query-db-collection';",
5112
+ "import { createCollection } from '@tanstack/react-db';",
5113
+ "import { queryClient } from '../query-client';",
5114
+ `import { ${camelName}Api } from '../api/${name}';`,
5115
+ `import { ${camelName}Schema } from '${config.dbEntitiesImport}/${name}';`
5116
+ ];
5117
+ const body = `${imports.join("\n")}
5118
+
5119
+ export const ${camelName}Collection = createCollection(
5120
+ queryCollectionOptions({
5121
+ id: '${plural}',
5122
+ queryKey: ['${plural}'],
5123
+ queryClient,
5124
+ queryFn: () => ${camelName}Api.list(),
5125
+ getKey: (item) => item.id,
5126
+ schema: ${camelName}Schema,
5127
+ }),
5128
+ );
5129
+ `;
5130
+ return withBanner(`entities/${name}.yaml`, body);
5131
+ }
5132
+ function buildCollectionFile(entity, ctx) {
5133
+ const mode = resolveSyncMode(entity, ctx.config);
5134
+ return mode === "api" ? buildApiCollection(entity, ctx) : buildElectricCollection(entity, ctx);
5135
+ }
5136
+ function buildCollectionsIndexFile(ctx) {
5137
+ const entities = sortEntities(ctx.entities);
5138
+ const lines = entities.map((e) => `export * from './${e.name}';`);
5139
+ return withBanner(SOURCE_DESC_SET2, `${lines.join("\n")}
5140
+ `);
5141
+ }
5142
+ function emitCollections(ctx, outDir) {
5143
+ const collectionsDir = join12(outDir, "collections");
5144
+ const entities = sortEntities(ctx.entities);
5145
+ const written = [];
5146
+ for (const entity of entities) {
5147
+ const filePath = join12(collectionsDir, `${entity.name}.ts`);
5148
+ writeFile2(filePath, buildCollectionFile(entity, ctx));
5149
+ written.push(filePath);
5150
+ }
5151
+ const indexPath = join12(collectionsDir, "index.ts");
5152
+ writeFile2(indexPath, buildCollectionsIndexFile(ctx));
5153
+ written.push(indexPath);
5154
+ return written;
5155
+ }
5156
+
5157
+ // src/emitters/frontend/emit-entities.ts
5158
+ import { join as join13 } from "path";
5159
+ var SOURCE_DESC_SET3 = "the entity set";
5160
+ function buildEntityHooksFile(entity, ctx) {
5161
+ const { camelName, className, name } = entity;
5162
+ const body = `import { createEntityHooks } from '@pattern-stack/frontend-patterns';
5163
+ import { ${camelName}Collection } from '../collections/${name}';
5164
+ import { ${camelName}Api } from '../api/${name}';
5165
+ import { getSyncMode } from '../config';
5166
+ import type { ${className} } from '${ctx.config.dbEntitiesImport}/${name}';
5167
+
5168
+ /**
5169
+ * Typed hooks for ${className}, wired via the framework factory.
5170
+ *
5171
+ * \`localFirst\` is resolved at call time from the entity's runtime sync mode
5172
+ * (\`getSyncMode('${name}')\`) \u2014 \`api\` mode is confirmed-write, everything else
5173
+ * is local-first (optimistic).
5174
+ */
5175
+ export const ${camelName}Hooks = createEntityHooks<${className}>({
5176
+ name: '${name}',
5177
+ collection: ${camelName}Collection,
5178
+ api: ${camelName}Api,
5179
+ localFirst: () => getSyncMode('${name}') !== 'api',
5180
+ });
5181
+
5182
+ // Per-entity hook re-exports for direct imports.
5183
+ export const {
5184
+ useList: use${className}List,
5185
+ useGet: use${className},
5186
+ useCreate: useCreate${className},
5187
+ useUpdate: useUpdate${className},
5188
+ useDelete: useDelete${className},
5189
+ keys: ${camelName}Keys,
5190
+ } = ${camelName}Hooks;
5191
+ `;
5192
+ return withBanner(`entities/${name}.yaml`, body);
5193
+ }
5194
+ function buildEntitiesIndexFile(ctx) {
5195
+ const entities = sortEntities(ctx.entities);
5196
+ const blocks = entities.map((e) => {
5197
+ const { camelName, className, name } = e;
5198
+ return `export {
5199
+ ${camelName}Hooks,
5200
+ use${className}List,
5201
+ use${className},
5202
+ useCreate${className},
5203
+ useUpdate${className},
5204
+ useDelete${className},
5205
+ ${camelName}Keys,
5206
+ } from './${name}';`;
5207
+ });
5208
+ return withBanner(SOURCE_DESC_SET3, `${blocks.join("\n\n")}
5209
+ `);
5210
+ }
5211
+ function emitEntities(ctx, outDir) {
5212
+ const entitiesDir = join13(outDir, "entities");
5213
+ const entities = sortEntities(ctx.entities);
5214
+ const written = [];
5215
+ for (const entity of entities) {
5216
+ const filePath = join13(entitiesDir, `${entity.name}.ts`);
5217
+ writeFile2(filePath, buildEntityHooksFile(entity, ctx));
5218
+ written.push(filePath);
5219
+ }
5220
+ const indexPath = join13(entitiesDir, "index.ts");
5221
+ writeFile2(indexPath, buildEntitiesIndexFile(ctx));
5222
+ written.push(indexPath);
5223
+ return written;
5224
+ }
5225
+
5226
+ // src/emitters/frontend/emit-store.ts
5227
+ import { join as join14 } from "path";
5228
+ var SOURCE_DESC_SET4 = "the entity set";
5229
+ var CAMEL = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
5230
+ function resolvableRels(entity, ctx) {
5231
+ const parsed = ctx.parsed.get(entity.name);
5232
+ if (!parsed) return [];
5233
+ const registryByName = new Map(ctx.entities.map((e) => [e.name, e]));
5234
+ const out = [];
5235
+ for (const rel2 of parsed.relationships.values()) {
5236
+ if (rel2.type !== "belongs_to") continue;
5237
+ const target = registryByName.get(rel2.target);
5238
+ if (!target) continue;
5239
+ out.push({
5240
+ propertyName: CAMEL(rel2.name),
5241
+ fieldNameCamel: CAMEL(fkField(rel2)),
5242
+ target
5243
+ });
5244
+ }
5245
+ out.sort((a, b) => a.propertyName.localeCompare(b.propertyName));
5246
+ return out;
5247
+ }
5248
+ function fkField(rel2) {
5249
+ return rel2.foreignKey && rel2.foreignKey.length > 0 ? rel2.foreignKey : `${rel2.target}_id`;
5250
+ }
5251
+ function buildStoreIndexFile(ctx) {
5252
+ const entities = sortEntities(ctx.entities);
5253
+ const hookImports = entities.map((e) => `import { ${e.camelName}Hooks } from '../entities/${e.name}';`).join("\n");
5254
+ const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
5255
+ const entityEntries = entities.map((e) => ` ${e.plural}: ${e.camelName}Hooks,`).join("\n");
5256
+ const collectionEntries = entities.map((e) => ` ${e.plural}: ${e.camelName}Collection,`).join("\n");
5257
+ const body = `import { createStore } from '@pattern-stack/frontend-patterns';
5258
+
5259
+ ${hookImports}
5260
+
5261
+ ${collectionImports}
5262
+
5263
+ /**
5264
+ * The application store \u2014 unified access to every entity.
5265
+ *
5266
+ * Entities and collections are keyed by their plural name:
5267
+ * store.${entities[0]?.plural ?? "things"}.useList()
5268
+ * store.resolve.<entity>(id)
5269
+ * store.lookups.build()
5270
+ */
5271
+ export const store = createStore({
5272
+ entities: {
5273
+ ${entityEntries}
5274
+ },
5275
+ collections: {
5276
+ ${collectionEntries}
5277
+ },
5278
+ });
5279
+
5280
+ /** Store type for the \`useStore\` hook. */
5281
+ export type AppStore = typeof store;
5282
+ `;
5283
+ return withBanner(SOURCE_DESC_SET4, body);
5284
+ }
5285
+ function buildResolversFile(ctx) {
5286
+ const entities = sortEntities(ctx.entities);
5287
+ const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
5288
+ const typeImports = entities.map((e) => `import type { ${e.className} } from '${ctx.config.dbEntitiesImport}/${e.name}';`).join("\n");
5289
+ const resolverIface = entities.map(
5290
+ (e) => ` ${e.camelName}: (id: string | null | undefined) => ${e.className} | undefined;`
5291
+ ).join("\n");
5292
+ const resolverImpls = entities.map(
5293
+ (e) => ` ${e.camelName}: (id) => {
5294
+ if (!id) return undefined;
5295
+ return ${e.camelName}Collection.state.get(id) as ${e.className} | undefined;
5296
+ },`
5297
+ ).join("\n");
5298
+ const refBlocks = [];
5299
+ for (const e of entities) {
5300
+ const rels = resolvableRels(e, ctx);
5301
+ if (rels.length === 0) continue;
5302
+ const refFields = rels.map(
5303
+ (r) => ` ${r.propertyName}: ${r.target.className} | undefined;`
5304
+ ).join("\n");
5305
+ const hydrateFields = rels.map(
5306
+ (r) => ` ${r.propertyName}: resolvers.${r.target.camelName}(entity.${r.fieldNameCamel}),`
5307
+ ).join("\n");
5308
+ refBlocks.push(`/** Resolved FK references for ${e.className}. */
5309
+ export interface ${e.className}Refs {
5310
+ ${refFields}
5311
+ }
5312
+
5313
+ /** Hydrate a ${e.className} with its resolved FK references. */
5314
+ export function resolve${e.className}Refs(
5315
+ entity: ${e.className},
5316
+ resolvers: Resolvers,
5317
+ ): ${e.className} & ${e.className}Refs {
5318
+ return {
5319
+ ...entity,
5320
+ ${hydrateFields}
5321
+ };
5322
+ }`);
5323
+ }
5324
+ const refsSection = refBlocks.length > 0 ? `
5325
+ // ${"=".repeat(73)}
5326
+ // WithResolved helpers \u2014 hydrate entities with resolved FKs
5327
+ // ${"=".repeat(73)}
5328
+
5329
+ ${refBlocks.join("\n\n")}
5330
+ ` : "";
5331
+ const body = `${collectionImports}
5332
+ ${typeImports}
5333
+
5334
+ /**
5335
+ * FK resolvers \u2014 resolve a foreign-key id to the full entity object via the
5336
+ * backing collection's local state (\`O(1)\` \`Map.get\`).
5337
+ *
5338
+ * Usage:
5339
+ * const ${entities[0]?.camelName ?? "thing"} = resolvers.${entities[0]?.camelName ?? "thing"}(other.${entities[0]?.camelName ?? "thing"}Id);
5340
+ */
5341
+ export interface Resolvers {
5342
+ ${resolverIface}
5343
+ }
5344
+
5345
+ /** Build the resolver table over the generated collections. */
5346
+ export function createResolvers(): Resolvers {
5347
+ return {
5348
+ ${resolverImpls}
5349
+ };
5350
+ }
5351
+ ${refsSection}`;
5352
+ return withBanner(SOURCE_DESC_SET4, body);
5353
+ }
5354
+ function buildLookupsFile(ctx) {
5355
+ const entities = sortEntities(ctx.entities);
5356
+ const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
5357
+ const typeImports = entities.map((e) => `import type { ${e.className} } from '${ctx.config.dbEntitiesImport}/${e.name}';`).join("\n");
5358
+ const lookupIface = entities.map((e) => ` ${e.plural}: Map<string, ${e.className}>;`).join("\n");
5359
+ const lookupBuild = entities.map(
5360
+ (e) => ` ${e.plural}: new Map(
5361
+ Array.from(${e.camelName}Collection.state.values()).map((item) => [
5362
+ (item as ${e.className}).id as string,
5363
+ item as ${e.className},
5364
+ ]),
5365
+ ),`
5366
+ ).join("\n");
5367
+ const body = `${collectionImports}
5368
+ ${typeImports}
5369
+
5370
+ /** All entity lookup maps, keyed by plural entity name (id \u2192 entity). */
5371
+ export interface EntityLookups {
5372
+ ${lookupIface}
5373
+ }
5374
+
5375
+ /** Build fresh lookup maps from current collection state. */
5376
+ export function buildLookups(): EntityLookups {
5377
+ return {
5378
+ ${lookupBuild}
5379
+ };
5380
+ }
5381
+
5382
+ /** Caching lookup factory: \`build()\` (re)computes, \`current\` reads, \`clear()\` resets. */
5383
+ export function createLookups() {
5384
+ let cache: EntityLookups | null = null;
5385
+ return {
5386
+ build: (): EntityLookups => {
5387
+ cache = buildLookups();
5388
+ return cache;
5389
+ },
5390
+ get current(): EntityLookups | null {
5391
+ return cache;
5392
+ },
5393
+ clear: (): void => {
5394
+ cache = null;
5395
+ },
5396
+ };
5397
+ }
5398
+ `;
5399
+ return withBanner(SOURCE_DESC_SET4, body);
5400
+ }
5401
+ function buildStoreModuleIndexFile(ctx) {
5402
+ const entities = sortEntities(ctx.entities);
5403
+ const lines = [
5404
+ "export { store, type AppStore } from './index';",
5405
+ "export { createResolvers, type Resolvers } from './resolvers';",
5406
+ "export { buildLookups, createLookups, type EntityLookups } from './lookups';"
5407
+ ];
5408
+ const refExports = entities.filter((e) => resolvableRels(e, ctx).length > 0).map(
5409
+ (e) => `export { resolve${e.className}Refs, type ${e.className}Refs } from './resolvers';`
5410
+ );
5411
+ if (refExports.length > 0) {
5412
+ lines.push("", ...refExports);
5413
+ }
5414
+ return withBanner(SOURCE_DESC_SET4, `${lines.join("\n")}
5415
+ `);
5416
+ }
5417
+ function emitStore(ctx, outDir) {
5418
+ const storeDir = join14(outDir, "store");
5419
+ const written = [];
5420
+ const files = [
5421
+ ["index.ts", buildStoreIndexFile(ctx)],
5422
+ ["resolvers.ts", buildResolversFile(ctx)],
5423
+ ["lookups.ts", buildLookupsFile(ctx)],
5424
+ ["module-index.ts", buildStoreModuleIndexFile(ctx)]
5425
+ ];
5426
+ for (const [fileName, content] of files) {
5427
+ const filePath = join14(storeDir, fileName);
5428
+ writeFile2(filePath, content);
5429
+ written.push(filePath);
5430
+ }
5431
+ return written;
5432
+ }
5433
+
5434
+ // src/emitters/frontend/emit-fields.ts
5435
+ import { join as join15 } from "path";
5436
+
5437
+ // src/emitters/frontend/field-meta.ts
5438
+ var CAMEL2 = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
5439
+ function formatLabel(fieldName) {
5440
+ return fieldName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
5441
+ }
5442
+ function inferUiType(field) {
5443
+ if (field.ui.type) return field.ui.type;
5444
+ if (Array.isArray(field.choices) && field.choices.length > 0) return "enum";
5445
+ if (field.foreignKey) return "reference";
5446
+ const nameLower = field.name.toLowerCase();
5447
+ if (nameLower.includes("email")) return "email";
5448
+ if (nameLower.includes("url") || nameLower.includes("website")) return "url";
5449
+ if (nameLower.includes("password")) return "password";
5450
+ if (nameLower.includes("price") || nameLower.includes("amount") || nameLower.includes("cost") || nameLower.includes("value") || nameLower.includes("revenue")) {
5451
+ return "money";
5452
+ }
5453
+ if (nameLower.includes("percent") || nameLower.includes("rate")) {
5454
+ return "percentage";
5455
+ }
5456
+ switch (field.type) {
5457
+ case "string":
5458
+ return field.constraints.maxLength && field.constraints.maxLength > 500 ? "textarea" : "text";
5459
+ case "integer":
5460
+ case "decimal":
5461
+ return "number";
5462
+ case "boolean":
5463
+ return "boolean";
5464
+ case "uuid":
5465
+ return "text";
5466
+ case "date":
5467
+ return "date";
5468
+ case "datetime":
5469
+ return "datetime";
5470
+ case "json":
5471
+ return "json";
5472
+ default:
5473
+ return "text";
5474
+ }
5475
+ }
5476
+ function inferUiImportance(field) {
5477
+ if (field.ui.importance) return field.ui.importance;
5478
+ const nameLower = field.name.toLowerCase();
5479
+ if (["id", "created_at", "updated_at", "deleted_at"].includes(nameLower)) {
5480
+ return "tertiary";
5481
+ }
5482
+ if (field.foreignKey && nameLower.endsWith("_id")) return "secondary";
5483
+ if (field.required) return "primary";
5484
+ if (nameLower.includes("name") || nameLower.includes("title")) return "primary";
5485
+ return "secondary";
5486
+ }
5487
+ function isEntityRefField(field) {
5488
+ if (field.type === "entity_ref") return true;
5489
+ return field.name.endsWith("_entity_type") || field.name.endsWith("_entity_id");
5490
+ }
5491
+ function deriveFieldMeta(field) {
5492
+ const hasChoices = Array.isArray(field.choices) && field.choices.length > 0;
5493
+ const meta = {
5494
+ field: CAMEL2(field.name),
5495
+ label: field.ui.label ?? formatLabel(field.name),
5496
+ type: inferUiType(field),
5497
+ importance: inferUiImportance(field),
5498
+ sortable: field.ui.sortable ?? false,
5499
+ filterable: field.ui.filterable ?? false
5500
+ };
5501
+ if (hasChoices) meta.choices = field.choices;
5502
+ if (field.foreignKey) meta.reference = field.foreignKey.table;
5503
+ return meta;
5504
+ }
5505
+
5506
+ // src/emitters/frontend/emit-fields.ts
5507
+ var SOURCE_DESC_SET5 = "the entity set";
5508
+ function buildFieldMetaTypeFile() {
5509
+ const body = `/**
5510
+ * Field metadata types for DataGrid, forms, and admin surfaces.
5511
+ */
5512
+
5513
+ export type FieldType =
5514
+ | 'text'
5515
+ | 'textarea'
5516
+ | 'number'
5517
+ | 'boolean'
5518
+ | 'date'
5519
+ | 'datetime'
5520
+ | 'email'
5521
+ | 'url'
5522
+ | 'password'
5523
+ | 'money'
5524
+ | 'percentage'
5525
+ | 'json'
5526
+ | 'enum'
5527
+ | 'reference'
5528
+ | 'entity';
5529
+
5530
+ export type FieldImportance = 'primary' | 'secondary' | 'tertiary';
5531
+
5532
+ export interface FieldMeta<T = unknown> {
5533
+ /** Property key on the entity (\`keyof T\` for typed access). */
5534
+ field: keyof T & string;
5535
+ label: string;
5536
+ type: FieldType;
5537
+ importance: FieldImportance;
5538
+ sortable?: boolean;
5539
+ filterable?: boolean;
5540
+ format?: Record<string, unknown>;
5541
+ choices?: string[];
5542
+ reference?: string;
5543
+ }
5544
+ `;
5545
+ return withBanner(SOURCE_DESC_SET5, body);
5546
+ }
5547
+ function hasTimestamps(parsed) {
5548
+ return parsed?.behaviors.includes("timestamps") ?? false;
5549
+ }
5550
+ function displayFields(parsed) {
5551
+ if (!parsed) return [];
5552
+ const out = [];
5553
+ for (const field of parsed.fields.values()) {
5554
+ if (field.name === "id") continue;
5555
+ if (isEntityRefField(field)) continue;
5556
+ out.push(deriveFieldMeta(field));
5557
+ }
5558
+ return out;
5559
+ }
5560
+ function renderFieldMeta(meta) {
5561
+ const lines = [
5562
+ ` field: '${meta.field}',`,
5563
+ ` label: '${meta.label}',`,
5564
+ ` type: '${meta.type}' as FieldType,`,
5565
+ ` importance: '${meta.importance}' as FieldImportance,`
5566
+ ];
5567
+ if (meta.sortable) lines.push(" sortable: true,");
5568
+ if (meta.filterable) lines.push(" filterable: true,");
5569
+ if (meta.choices) lines.push(` choices: ${JSON.stringify(meta.choices)},`);
5570
+ if (meta.reference) lines.push(` reference: '${meta.reference}',`);
5571
+ return ` ${meta.field}: {
5572
+ ${lines.join("\n")}
5573
+ },`;
5574
+ }
5575
+ function humanizeClass(className) {
5576
+ return className.replace(/([A-Z])/g, " $1").trim();
5577
+ }
5578
+ function buildEntityFieldsFile(entity, ctx) {
5579
+ const parsed = ctx.parsed.get(entity.name);
5580
+ const { camelName, className, classNamePlural, name, plural } = entity;
5581
+ const fields = displayFields(parsed);
5582
+ const rels = resolvableRels(entity, ctx);
5583
+ const ts3 = hasTimestamps(parsed);
5584
+ const fieldEntries = fields.map(renderFieldMeta);
5585
+ const relEntries = rels.map(
5586
+ (r) => ` ${r.propertyName}: {
5587
+ field: '${r.propertyName}',
5588
+ label: '${humanizeClass(r.target.className)}',
5589
+ type: 'entity' as FieldType,
5590
+ importance: 'secondary' as FieldImportance,
5591
+ reference: '${r.target.plural}',
5592
+ },`
5593
+ );
5594
+ const tsEntries = ts3 ? [
5595
+ ` createdAt: {
5596
+ field: 'createdAt',
5597
+ label: 'Created',
5598
+ type: 'datetime' as FieldType,
5599
+ importance: 'tertiary' as FieldImportance,
5600
+ format: { dateFormat: 'relative' },
5601
+ },`,
5602
+ ` updatedAt: {
5603
+ field: 'updatedAt',
5604
+ label: 'Updated',
5605
+ type: 'datetime' as FieldType,
5606
+ importance: 'tertiary' as FieldImportance,
5607
+ format: { dateFormat: 'relative' },
5608
+ },`
5609
+ ] : [];
5610
+ const allEntries = [...fieldEntries, ...relEntries, ...tsEntries].join("\n");
5611
+ const primaryFields = fields.filter((f) => f.importance === "primary").map((f) => ` '${f.field}',`).join("\n");
5612
+ const searchFields = fields.filter((f) => f.filterable).map((f) => ` '${f.field}',`).join("\n");
5613
+ const defaultSortField = ts3 ? "createdAt" : "id";
5614
+ const expose = parsed?.expose ?? ["repository", "rest", "trpc"];
5615
+ const canWrite = expose.includes("repository") || expose.includes("trpc");
5616
+ const body = `import type { FieldMeta, FieldType, FieldImportance } from './field-meta';
5617
+ import type { ${className} } from '${ctx.config.dbEntitiesImport}/${name}';
5618
+
5619
+ export const ${camelName}Fields: Record<string, FieldMeta<${className}>> = {
5620
+ ${allEntries}
5621
+ };
5622
+
5623
+ export const ${camelName}Metadata = {
5624
+ name: '${name}',
5625
+ plural: '${plural}',
5626
+ displayName: '${humanizeClass(className)}',
5627
+ displayNamePlural: '${humanizeClass(classNamePlural)}',
5628
+
5629
+ fields: ${camelName}Fields,
5630
+
5631
+ primaryFields: [
5632
+ ${primaryFields}
5633
+ ],
5634
+ searchFields: [
5635
+ ${searchFields}
5636
+ ],
5637
+ defaultSort: { field: '${defaultSortField}', direction: 'desc' as const },
5638
+
5639
+ capabilities: {
5640
+ create: ${canWrite},
5641
+ update: ${canWrite},
5642
+ delete: ${canWrite},
5643
+ list: true,
5644
+ get: true,
5645
+ },
5646
+ } as const;
5647
+ `;
5648
+ return withBanner(`entities/${name}.yaml`, body);
5649
+ }
5650
+ function buildFieldsIndexFile(ctx) {
5651
+ const entities = sortEntities(ctx.entities);
5652
+ const lines = entities.map((e) => `export * from './${e.name}';`);
5653
+ return withBanner(SOURCE_DESC_SET5, `${lines.join("\n")}
5654
+ `);
5655
+ }
5656
+ function emitFields(ctx, outDir) {
5657
+ const fieldsDir = join15(outDir, "fields");
5658
+ const entities = sortEntities(ctx.entities);
5659
+ const written = [];
5660
+ const typePath = join15(fieldsDir, "field-meta.ts");
5661
+ writeFile2(typePath, buildFieldMetaTypeFile());
5662
+ written.push(typePath);
5663
+ for (const entity of entities) {
5664
+ const filePath = join15(fieldsDir, `${entity.name}.ts`);
5665
+ writeFile2(filePath, buildEntityFieldsFile(entity, ctx));
5666
+ written.push(filePath);
5667
+ }
5668
+ const indexPath = join15(fieldsDir, "index.ts");
5669
+ writeFile2(indexPath, buildFieldsIndexFile(ctx));
5670
+ written.push(indexPath);
5671
+ return written;
5672
+ }
5673
+
5674
+ // src/emitters/frontend/emit-index.ts
5675
+ import { join as join16 } from "path";
5676
+
5677
+ // src/emitters/frontend/deps.ts
5678
+ var FRONTEND_EMITTED_DEPS = {
5679
+ "@pattern-stack/frontend-patterns": "^0.2.0-alpha.18",
5680
+ "@tanstack/react-db": "^0.1.55",
5681
+ "@tanstack/electric-db-collection": "^0.2.11",
5682
+ "@tanstack/query-db-collection": "^1.0.6",
5683
+ "@tanstack/react-query": "^5.0.0"
5684
+ };
5685
+
5686
+ // src/emitters/frontend/emit-index.ts
5687
+ var SOURCE_DESC_SET6 = "the entity set";
5688
+ function buildVersionPairingComment() {
5689
+ const entries = Object.entries(FRONTEND_EMITTED_DEPS);
5690
+ const nameWidth = Math.max(...entries.map(([name]) => name.length));
5691
+ const rows = entries.map(([name, range]) => ` * ${name.padEnd(nameWidth)} ${range}`).join("\n");
5692
+ return ` * Version pairing \u2014 the emitted imports require these package ranges in the
5693
+ * consumer's frontend package.json:
5694
+ *
5695
+ ${rows}`;
5696
+ }
5697
+ function buildRootIndexFile(ctx) {
5698
+ const entities = sortEntities(ctx.entities);
5699
+ const entityList = entities.map((e) => ` * - ${e.className}`).join("\n");
5700
+ const body = `/**
5701
+ * Generated frontend data layer.
5702
+ *
5703
+ * Entities:
5704
+ ${entityList || " * (none)"}
5705
+ *
5706
+ ${buildVersionPairingComment()}
5707
+ */
5708
+
5709
+ // Per-entity sync configuration + runtime overrides
5710
+ export * from './config';
5711
+
5712
+ // Shared TanStack QueryClient
5713
+ export * from './query-client';
5714
+
5715
+ // REST api client
5716
+ export * from './api/index';
5717
+
5718
+ // TanStack DB collections (per-entity sync mode)
5719
+ export * from './collections/index';
5720
+
5721
+ // Entity hooks (createEntityHooks wiring)
5722
+ export * from './entities/index';
5723
+
5724
+ // Field metadata (DataGrid / forms / admin)
5725
+ export * from './fields/index';
5726
+
5727
+ // Unified store (entities + collections + resolvers + lookups)
5728
+ export * from './store/module-index';
5729
+ `;
5730
+ return withBanner(SOURCE_DESC_SET6, body);
5731
+ }
5732
+ function emitIndex(ctx, outDir) {
5733
+ const indexPath = join16(outDir, "index.ts");
5734
+ writeFile2(indexPath, buildRootIndexFile(ctx));
5735
+ return [indexPath];
5736
+ }
5737
+
5738
+ // src/emitters/frontend/load-context.ts
4823
5739
  import path12 from "path";
5740
+
5741
+ // src/schema/codegen-config.schema.ts
5742
+ import { z } from "zod";
5743
+ var GenerateConfigSchema = z.object({
5744
+ /**
5745
+ * Backend architecture to generate. One of:
5746
+ * - 'clean' — Full Clean Architecture (domain + application + infrastructure + presentation)
5747
+ * - 'clean-lite-ps' — Clean-Lite-PS modules/{plural}/ layout
5748
+ *
5749
+ * Default: 'clean'.
5750
+ */
5751
+ architecture: z.enum(["clean", "clean-lite-ps"]).default("clean"),
5752
+ /**
5753
+ * Whether to emit the frontend pipeline (collections, hooks, entity metadata).
5754
+ * Default: false — backend-only projects opt out by default.
5755
+ */
5756
+ frontend: z.boolean().default(false),
5757
+ /**
5758
+ * Analytics backend to generate.
5759
+ * - 'none': no analytics layer (default)
5760
+ * - 'cube': generate cube.js semantic layer and analytics providers
5761
+ */
5762
+ analytics: z.enum(["none", "cube"]).default("none")
5763
+ }).passthrough();
5764
+ var PathsConfigSchema = z.object({
5765
+ events_dir: z.string().optional(),
5766
+ generated: z.string().default("src/generated")
5767
+ }).passthrough();
5768
+ var PatternsConfigSchema = z.array(z.string()).optional().default(["src/patterns/*.pattern.ts"]);
5769
+ var RuntimeModeSchema = z.enum(["package", "vendored"]).default("package");
5770
+ var FrontendAuthConfigSchema = z.object({
5771
+ function: z.string().nullable().default("getAuthorizationHeader")
5772
+ }).default({ function: "getAuthorizationHeader" });
5773
+ var FrontendSyncConfigSchema = z.object({
5774
+ mode: z.enum(["api", "electric"]).default("electric"),
5775
+ shapeUrl: z.string().default("/v1/shape"),
5776
+ useTableParam: z.boolean().default(true),
5777
+ columnMapper: z.string().nullable().default("snakeCamelMapper"),
5778
+ columnMapperNeedsCall: z.boolean().default(true),
5779
+ apiBaseUrlImport: z.string().nullable().default(null),
5780
+ apiUrl: z.string().default("/api")
5781
+ }).default({});
5782
+ var FrontendConfigSchema = z.object({
5783
+ auth: FrontendAuthConfigSchema,
5784
+ parsers: z.record(z.string()).default({ timestamptz: "(date: string) => new Date(date)" }),
5785
+ sync: FrontendSyncConfigSchema
5786
+ }).strict().default({});
5787
+
5788
+ // src/emitters/frontend/load-context.ts
5789
+ var DEFAULT_DB_ENTITIES = {
5790
+ path: "packages/db/src/entities",
5791
+ import: "@repo/db/entities"
5792
+ };
5793
+ var DEFAULT_FRONTEND_GENERATED = {
5794
+ path: "apps/frontend/src/generated",
5795
+ import: "@/generated"
5796
+ };
5797
+ var DEFAULT_FRONTEND_COLLECTIONS_AUTH = {
5798
+ path: "apps/frontend/src/lib/collections/auth",
5799
+ import: "@/lib/collections/auth"
5800
+ };
5801
+ function resolveLocation(config, key, fallback) {
5802
+ const override = config.locations?.[key];
5803
+ return {
5804
+ path: override?.path ?? fallback.path,
5805
+ import: override?.import ?? fallback.import
5806
+ };
5807
+ }
5808
+ function mapFrontendEmitConfig(config) {
5809
+ const parsed = FrontendConfigSchema.safeParse(config.frontend ?? {});
5810
+ const fe = parsed.success ? parsed.data : FrontendConfigSchema.parse({});
5811
+ const dbEntities = resolveLocation(config, "dbEntities", DEFAULT_DB_ENTITIES);
5812
+ const collectionsAuth = resolveLocation(
5813
+ config,
5814
+ "frontendCollectionsAuth",
5815
+ DEFAULT_FRONTEND_COLLECTIONS_AUTH
5816
+ );
5817
+ const architecture = config.generate?.architecture === "clean-lite-ps" ? "clean-lite-ps" : "clean";
5818
+ return {
5819
+ globalSyncMode: fe.sync.mode,
5820
+ // auth.function: absent → 'getAuthorizationHeader' (schema default), explicit
5821
+ // null → disabled (no header lines emitted).
5822
+ authFunction: fe.auth.function,
5823
+ authImport: collectionsAuth.import,
5824
+ shapeUrl: fe.sync.shapeUrl,
5825
+ useTableParam: fe.sync.useTableParam,
5826
+ columnMapper: fe.sync.columnMapper,
5827
+ columnMapperNeedsCall: fe.sync.columnMapperNeedsCall,
5828
+ apiUrl: fe.sync.apiUrl,
5829
+ apiBaseUrlImport: fe.sync.apiBaseUrlImport,
5830
+ parsers: fe.parsers,
5831
+ architecture,
5832
+ dbEntitiesImport: dbEntities.import
5833
+ };
5834
+ }
5835
+ function loadFrontendEmitContext(cwd, config, opts = {}) {
5836
+ const entitiesDir = opts.entitiesDir ?? path12.resolve(cwd, config.paths?.entities_dir ?? "entities");
5837
+ const { registry } = loadEntityRegistry(entitiesDir);
5838
+ const entities = sortEntities([...registry.values()]);
5839
+ if (entities.length === 0) {
5840
+ return {
5841
+ skip: `no entities found in ${path12.relative(cwd, entitiesDir) || entitiesDir}`
5842
+ };
5843
+ }
5844
+ const parsedList = loadEntities(entitiesDir).entities;
5845
+ const parsed = new Map(
5846
+ parsedList.map((p) => [p.name, p])
5847
+ );
5848
+ const emitConfig = mapFrontendEmitConfig(config);
5849
+ const generated = resolveLocation(
5850
+ config,
5851
+ "frontendGenerated",
5852
+ DEFAULT_FRONTEND_GENERATED
5853
+ );
5854
+ const outDir = path12.resolve(cwd, generated.path);
5855
+ return {
5856
+ skip: void 0,
5857
+ ctx: { entities, parsed, config: emitConfig },
5858
+ outDir
5859
+ };
5860
+ }
5861
+
5862
+ // src/emitters/frontend/index.ts
5863
+ function emitFrontendSet(ctx, outDir) {
5864
+ return [
5865
+ ...emitBase(ctx, outDir),
5866
+ ...emitApi(ctx, outDir),
5867
+ ...emitCollections(ctx, outDir),
5868
+ ...emitEntities(ctx, outDir),
5869
+ ...emitStore(ctx, outDir),
5870
+ ...emitFields(ctx, outDir),
5871
+ ...emitIndex(ctx, outDir)
5872
+ ];
5873
+ }
5874
+
5875
+ // src/cli/shared/events-path.ts
5876
+ import path13 from "path";
4824
5877
  var FALLBACK = "events";
4825
5878
  function resolveEventsDirFromConfig(cwd, config) {
4826
5879
  const configured = config?.paths?.events_dir;
4827
5880
  if (typeof configured === "string" && configured.length > 0) {
4828
- return path12.resolve(cwd, configured);
5881
+ return path13.resolve(cwd, configured);
4829
5882
  }
4830
- return path12.resolve(cwd, FALLBACK);
5883
+ return path13.resolve(cwd, FALLBACK);
4831
5884
  }
4832
5885
  function resolveEventsDir(ctx) {
4833
5886
  return resolveEventsDirFromConfig(ctx.cwd, ctx.config);
@@ -4856,7 +5909,7 @@ function printInfo(msg) {
4856
5909
  // src/cli/commands/entity.ts
4857
5910
  function resolveProvidersDir(ctx) {
4858
5911
  const fromConfig = ctx.config?.paths?.providers;
4859
- return fromConfig != null ? path13.resolve(ctx.cwd, fromConfig) : path13.resolve(ctx.cwd, "definitions/providers");
5912
+ return fromConfig != null ? path14.resolve(ctx.cwd, fromConfig) : path14.resolve(ctx.cwd, "definitions/providers");
4860
5913
  }
4861
5914
  function listEntityYamls2(dir, providersDir) {
4862
5915
  if (!fs10.existsSync(dir)) return [];
@@ -4938,10 +5991,10 @@ async function hints(ctx) {
4938
5991
  { command: "codegen entity validate", description: "Validate YAML definitions" },
4939
5992
  { command: "codegen entity list", description: "List entities as a table" }
4940
5993
  ];
4941
- const providersDir = ctx.config?.paths?.providers != null ? path13.resolve(
5994
+ const providersDir = ctx.config?.paths?.providers != null ? path14.resolve(
4942
5995
  ctx.cwd,
4943
5996
  ctx.config.paths.providers
4944
- ) : path13.resolve(ctx.cwd, "definitions/providers");
5997
+ ) : path14.resolve(ctx.cwd, "definitions/providers");
4945
5998
  if (fs10.existsSync(providersDir)) {
4946
5999
  baseHints.push({
4947
6000
  command: "codegen entity new --all",
@@ -4997,14 +6050,14 @@ var EntityNewCommand = class extends Command2 {
4997
6050
  }
4998
6051
  let targets = [];
4999
6052
  if (this.all) {
5000
- const dir = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
6053
+ const dir = ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
5001
6054
  targets = listEntityYamls2(dir, resolveProvidersDir(ctx));
5002
6055
  if (targets.length === 0) {
5003
6056
  printError(`No entity YAML files found in ${dir}`);
5004
6057
  return 1;
5005
6058
  }
5006
6059
  } else if (this.yaml) {
5007
- targets = [path13.resolve(ctx.cwd, this.yaml)];
6060
+ targets = [path14.resolve(ctx.cwd, this.yaml)];
5008
6061
  } else {
5009
6062
  printError("Missing YAML path. Pass a file or --all.");
5010
6063
  return 2;
@@ -5021,7 +6074,7 @@ var EntityNewCommand = class extends Command2 {
5021
6074
  }
5022
6075
  if (invalid.length > 0 && !this.continueOnError) {
5023
6076
  for (const i of invalid) {
5024
- printError(`${path13.basename(i.file)} \u2014 ${i.message}`);
6077
+ printError(`${path14.basename(i.file)} \u2014 ${i.message}`);
5025
6078
  for (const detail of i.details ?? []) {
5026
6079
  printError(` \u2022 ${detail}`);
5027
6080
  }
@@ -5030,7 +6083,7 @@ var EntityNewCommand = class extends Command2 {
5030
6083
  return 1;
5031
6084
  }
5032
6085
  }
5033
- const entitiesDirForEmits = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
6086
+ const entitiesDirForEmits = ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
5034
6087
  const eventsDirForEmits = resolveEventsDir(ctx);
5035
6088
  const allEntitiesForEmits = loadEntities(entitiesDirForEmits, {
5036
6089
  excludeDirs: [resolveProvidersDir(ctx)]
@@ -5079,29 +6132,29 @@ var EntityNewCommand = class extends Command2 {
5079
6132
  if (!isJsonMode()) return 1;
5080
6133
  }
5081
6134
  }
5082
- const entitiesDir = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
5083
- const relationshipsDir = path13.resolve(ctx.cwd, "relationships");
6135
+ const entitiesDir = ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
6136
+ const relationshipsDir = path14.resolve(ctx.cwd, "relationships");
5084
6137
  const generatedDir = resolveGeneratedDir(ctx);
5085
6138
  const architecture = resolveArchitecture(ctx);
5086
6139
  const subsystemsRoot = resolveSubsystemsRoot(ctx);
5087
6140
  const runtimeMode = resolveRuntimeMode(ctx.config);
5088
- const scopeEntityTypePath = runtimeMode === "package" ? path13.resolve(generatedDir, "scope-entity-type.ts") : path13.resolve(subsystemsRoot, "jobs/generated/scope-entity-type.ts");
6141
+ const scopeEntityTypePath = runtimeMode === "package" ? path14.resolve(generatedDir, "scope-entity-type.ts") : path14.resolve(subsystemsRoot, "jobs/generated/scope-entity-type.ts");
5089
6142
  const eventsDir = resolveEventsDir(ctx);
5090
- const eventCodegenOutputDir = runtimeMode === "package" ? path13.resolve(generatedDir, "events") : path13.resolve(subsystemsRoot, "events/generated");
6143
+ const eventCodegenOutputDir = runtimeMode === "package" ? path14.resolve(generatedDir, "events") : path14.resolve(subsystemsRoot, "events/generated");
5091
6144
  const bridgeInstalledForRegistry = configuredSubsystemNames(
5092
6145
  ctx.config
5093
6146
  ).includes("bridge");
5094
- const bridgeRegistryOutputDir = runtimeMode === "package" ? generatedDir : path13.resolve(subsystemsRoot, "bridge/generated");
6147
+ const bridgeRegistryOutputDir = runtimeMode === "package" ? generatedDir : path14.resolve(subsystemsRoot, "bridge/generated");
5095
6148
  const backendSrcForHandlers = ctx.config?.paths?.backend_src ?? "src";
5096
- const bridgeHandlersDir = path13.resolve(
6149
+ const bridgeHandlersDir = path14.resolve(
5097
6150
  ctx.cwd,
5098
6151
  backendSrcForHandlers,
5099
6152
  "jobs"
5100
6153
  );
5101
6154
  const orchestrationConfigured = ctx.config?.paths?.orchestration_src;
5102
- const orchestrationOutputRoot = path13.resolve(
6155
+ const orchestrationOutputRoot = path14.resolve(
5103
6156
  ctx.cwd,
5104
- typeof orchestrationConfigured === "string" && orchestrationConfigured.length > 0 ? orchestrationConfigured : path13.join(backendSrcForHandlers, "orchestration")
6157
+ typeof orchestrationConfigured === "string" && orchestrationConfigured.length > 0 ? orchestrationConfigured : path14.join(backendSrcForHandlers, "orchestration")
5105
6158
  );
5106
6159
  const orchestrationGlobs = (() => {
5107
6160
  const fromCfg = ctx.config?.patterns;
@@ -5232,7 +6285,7 @@ var EntityNewCommand = class extends Command2 {
5232
6285
  }
5233
6286
  if (invalid.length > 0) {
5234
6287
  for (const i of invalid) {
5235
- printWarning(`${path13.basename(i.file)} \u2014 ${i.message}`);
6288
+ printWarning(`${path14.basename(i.file)} \u2014 ${i.message}`);
5236
6289
  }
5237
6290
  }
5238
6291
  console.log("");
@@ -5250,7 +6303,7 @@ var EntityNewCommand = class extends Command2 {
5250
6303
  }
5251
6304
  const succeeded = [];
5252
6305
  const failed = [
5253
- ...invalid.map((i) => ({ name: path13.basename(i.file), file: i.file, message: i.message }))
6306
+ ...invalid.map((i) => ({ name: path14.basename(i.file), file: i.file, message: i.message }))
5254
6307
  ];
5255
6308
  for (const v of validated) {
5256
6309
  if (!isJsonMode()) {
@@ -5373,10 +6426,40 @@ var EntityNewCommand = class extends Command2 {
5373
6426
  }
5374
6427
  }
5375
6428
  }
6429
+ let frontendResult = null;
6430
+ const frontendEnabled = ctx.config?.generate?.frontend === true;
6431
+ if (frontendEnabled) {
6432
+ try {
6433
+ const loaded = loadFrontendEmitContext(
6434
+ ctx.cwd,
6435
+ ctx.config,
6436
+ { entitiesDir }
6437
+ );
6438
+ if (loaded.skip !== void 0) {
6439
+ if (!isJsonMode()) {
6440
+ printInfo(`frontend emission skipped \u2014 ${loaded.skip}`);
6441
+ }
6442
+ } else {
6443
+ const { ctx: frontendCtx, outDir: frontendOutDir } = loaded;
6444
+ const written = emitFrontendSet(frontendCtx, frontendOutDir);
6445
+ frontendResult = { written, outDir: frontendOutDir };
6446
+ if (!isJsonMode()) {
6447
+ printInfo(
6448
+ `frontend emitted (${written.length} files) \u2192 ${path14.relative(ctx.cwd, frontendOutDir)}`
6449
+ );
6450
+ }
6451
+ }
6452
+ } catch (err) {
6453
+ const msg = err instanceof Error ? err.message : String(err);
6454
+ if (!isJsonMode()) {
6455
+ printWarning(`frontend emission failed \u2014 ${msg}`);
6456
+ }
6457
+ }
6458
+ }
5376
6459
  let providerResult = null;
5377
6460
  try {
5378
6461
  const providersDir = resolveProvidersDir(ctx);
5379
- const providerOutputRoot = path13.resolve(
6462
+ const providerOutputRoot = path14.resolve(
5380
6463
  ctx.cwd,
5381
6464
  backendSrcForHandlers,
5382
6465
  "integrations/providers"
@@ -5418,7 +6501,7 @@ var EntityNewCommand = class extends Command2 {
5418
6501
  try {
5419
6502
  if (providerResult && !providerResult.skipped && providerResult.issues.length === 0) {
5420
6503
  const providersDir = providerResult.providersDir;
5421
- const adapterOutputRoot = path13.resolve(
6504
+ const adapterOutputRoot = path14.resolve(
5422
6505
  ctx.cwd,
5423
6506
  backendSrcForHandlers,
5424
6507
  "integrations"
@@ -5437,7 +6520,7 @@ var EntityNewCommand = class extends Command2 {
5437
6520
  providers: loadedProviders,
5438
6521
  entities: entityDefs,
5439
6522
  outputRoot: adapterOutputRoot,
5440
- backendSrcAbs: path13.resolve(ctx.cwd, backendSrcForHandlers),
6523
+ backendSrcAbs: path14.resolve(ctx.cwd, backendSrcForHandlers),
5441
6524
  aliases: assemblyTsAliases?.aliases ?? {},
5442
6525
  mode: runtimeMode
5443
6526
  });
@@ -5514,6 +6597,11 @@ var EntityNewCommand = class extends Command2 {
5514
6597
  relativePath: f.relativePath
5515
6598
  }))
5516
6599
  } : null,
6600
+ frontend: frontendResult ? {
6601
+ outDir: frontendResult.outDir,
6602
+ written: frontendResult.written,
6603
+ fileCount: frontendResult.written.length
6604
+ } : null,
5517
6605
  emits: {
5518
6606
  warnings: emitsWarnings.map((w) => ({
5519
6607
  entity: w.entity ?? null,
@@ -5539,22 +6627,27 @@ var EntityNewCommand = class extends Command2 {
5539
6627
  }
5540
6628
  if (barrelResult) {
5541
6629
  printInfo(
5542
- `barrels regenerated (${barrelResult.entityCount} entities) \u2192 ${path13.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path13.relative(ctx.cwd, barrelResult.schemaBarrel)}`
6630
+ `barrels regenerated (${barrelResult.entityCount} entities) \u2192 ${path14.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path14.relative(ctx.cwd, barrelResult.schemaBarrel)}`
5543
6631
  );
5544
6632
  }
5545
6633
  if (scopeResult) {
5546
6634
  printInfo(
5547
- `scope-entity-type regenerated (${scopeResult.scopeableNames.length} scopeable) \u2192 ${path13.relative(ctx.cwd, scopeResult.outputPath)}`
6635
+ `scope-entity-type regenerated (${scopeResult.scopeableNames.length} scopeable) \u2192 ${path14.relative(ctx.cwd, scopeResult.outputPath)}`
5548
6636
  );
5549
6637
  }
5550
6638
  if (eventCodegenResult) {
5551
6639
  printInfo(
5552
- `event codegen regenerated (${eventCodegenResult.eventCount} events) \u2192 ${path13.relative(ctx.cwd, eventCodegenResult.outputDir)}`
6640
+ `event codegen regenerated (${eventCodegenResult.eventCount} events) \u2192 ${path14.relative(ctx.cwd, eventCodegenResult.outputDir)}`
5553
6641
  );
5554
6642
  }
5555
6643
  if (orchestrationResult && orchestrationResult.patterns.length > 0) {
5556
6644
  printInfo(
5557
- `orchestration regenerated (${orchestrationResult.patterns.length} patterns, ${orchestrationResult.files.length} files) \u2192 ${path13.relative(ctx.cwd, orchestrationResult.outputRoot)}`
6645
+ `orchestration regenerated (${orchestrationResult.patterns.length} patterns, ${orchestrationResult.files.length} files) \u2192 ${path14.relative(ctx.cwd, orchestrationResult.outputRoot)}`
6646
+ );
6647
+ }
6648
+ if (frontendResult) {
6649
+ printInfo(
6650
+ `frontend regenerated (${frontendResult.written.length} files) \u2192 ${path14.relative(ctx.cwd, frontendResult.outDir)}`
5558
6651
  );
5559
6652
  }
5560
6653
  }
@@ -5640,7 +6733,7 @@ var EntityValidateCommand = class extends Command2 {
5640
6733
  json: this.json,
5641
6734
  skipDetection: true
5642
6735
  });
5643
- const targetDir = this.dir ? path13.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
6736
+ const targetDir = this.dir ? path14.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
5644
6737
  if (!fs10.existsSync(targetDir)) {
5645
6738
  printError(`Directory not found: ${targetDir}`);
5646
6739
  return 1;
@@ -5693,7 +6786,7 @@ var entity_default = entityNoun;
5693
6786
 
5694
6787
  // src/cli/commands/subsystem.ts
5695
6788
  import fs13 from "fs";
5696
- import path23 from "path";
6789
+ import path24 from "path";
5697
6790
  import { Command as Command3, Option as Option3 } from "clipanion";
5698
6791
 
5699
6792
  // src/cli/shared/config-block-detect.ts
@@ -5726,26 +6819,26 @@ function stripConfigBlock(yamlSource, subsystem) {
5726
6819
  }
5727
6820
 
5728
6821
  // src/cli/shared/events-scaffold-locals.ts
5729
- import path14 from "path";
6822
+ import path15 from "path";
5730
6823
  function resolveEventsScaffoldLocals(input) {
5731
6824
  const { cwd, config } = input;
5732
6825
  void input.fileExists;
5733
6826
  const eventsBlock = config?.events ?? {};
5734
6827
  const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
5735
- const configPath = path14.resolve(cwd, "codegen.config.yaml");
5736
- const schemaPath = path14.resolve(
6828
+ const configPath = path15.resolve(cwd, "codegen.config.yaml");
6829
+ const schemaPath = path15.resolve(
5737
6830
  subsystemsRoot,
5738
6831
  "events",
5739
6832
  "domain-events.schema.ts"
5740
6833
  );
5741
- const generatedKeepPath = path14.resolve(
6834
+ const generatedKeepPath = path15.resolve(
5742
6835
  subsystemsRoot,
5743
6836
  "events",
5744
6837
  "generated",
5745
6838
  ".gitkeep"
5746
6839
  );
5747
6840
  return {
5748
- appName: path14.basename(cwd),
6841
+ appName: path15.basename(cwd),
5749
6842
  multiTenant: normaliseMultiTenant(eventsBlock.multi_tenant),
5750
6843
  configPath,
5751
6844
  schemaPath,
@@ -5771,7 +6864,7 @@ function localsToHygenArgs(locals) {
5771
6864
  }
5772
6865
 
5773
6866
  // src/cli/shared/jobs-scaffold-locals.ts
5774
- import path15 from "path";
6867
+ import path16 from "path";
5775
6868
  var MAIN_HOOK_SENTINEL = "JOBS \u2014 Embedded worker mode (optional)";
5776
6869
  function workerSkipValue(exists) {
5777
6870
  return exists ? "true" : "";
@@ -5780,10 +6873,10 @@ function resolveJobsScaffoldLocals(input) {
5780
6873
  const { cwd, config, fileExists, readFile } = input;
5781
6874
  const jobsBlock = config?.jobs ?? {};
5782
6875
  const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
5783
- const workerPath = path15.resolve(cwd, "worker.ts");
5784
- const mainTsPath = path15.resolve(cwd, "src/main.ts");
5785
- const configPath = path15.resolve(cwd, "codegen.config.yaml");
5786
- const schemaPath = path15.resolve(
6876
+ const workerPath = path16.resolve(cwd, "worker.ts");
6877
+ const mainTsPath = path16.resolve(cwd, "src/main.ts");
6878
+ const configPath = path16.resolve(cwd, "codegen.config.yaml");
6879
+ const schemaPath = path16.resolve(
5787
6880
  subsystemsRoot,
5788
6881
  "jobs",
5789
6882
  "job-orchestration.schema.ts"
@@ -5791,7 +6884,7 @@ function resolveJobsScaffoldLocals(input) {
5791
6884
  const mainContent = readFile(mainTsPath);
5792
6885
  const mainHookInjected = mainContent !== null && mainContent.includes(MAIN_HOOK_SENTINEL);
5793
6886
  return {
5794
- appName: path15.basename(cwd),
6887
+ appName: path16.basename(cwd),
5795
6888
  workerMode: normaliseWorkerMode(jobsBlock.worker_mode),
5796
6889
  multiTenant: normaliseMultiTenant2(jobsBlock.multi_tenant),
5797
6890
  mainTsPath,
@@ -5833,20 +6926,20 @@ function localsToHygenArgs2(locals) {
5833
6926
  }
5834
6927
 
5835
6928
  // src/cli/shared/integration-scaffold-locals.ts
5836
- import path16 from "path";
6929
+ import path17 from "path";
5837
6930
  function resolveIntegrationScaffoldLocals(input) {
5838
6931
  const { cwd, config } = input;
5839
6932
  void input.fileExists;
5840
6933
  const integrationBlock = config?.integration ?? {};
5841
6934
  const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
5842
- const configPath = path16.resolve(cwd, "codegen.config.yaml");
5843
- const schemaPath = path16.resolve(
6935
+ const configPath = path17.resolve(cwd, "codegen.config.yaml");
6936
+ const schemaPath = path17.resolve(
5844
6937
  subsystemsRoot,
5845
6938
  "integration",
5846
6939
  "integration-audit.schema.ts"
5847
6940
  );
5848
6941
  return {
5849
- appName: path16.basename(cwd),
6942
+ appName: path17.basename(cwd),
5850
6943
  multiTenant: normaliseMultiTenant3(integrationBlock.multi_tenant),
5851
6944
  configPath,
5852
6945
  schemaPath
@@ -5869,21 +6962,21 @@ function localsToHygenArgs3(locals) {
5869
6962
  }
5870
6963
 
5871
6964
  // src/cli/shared/bridge-scaffold-locals.ts
5872
- import path17 from "path";
6965
+ import path18 from "path";
5873
6966
  function resolveBridgeScaffoldLocals(input) {
5874
6967
  const { cwd, config } = input;
5875
6968
  void input.fileExists;
5876
6969
  const bridgeBlock = config?.bridge ?? {};
5877
6970
  const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
5878
- const configPath = path17.resolve(cwd, "codegen.config.yaml");
5879
- const generatedKeepPath = path17.resolve(
6971
+ const configPath = path18.resolve(cwd, "codegen.config.yaml");
6972
+ const generatedKeepPath = path18.resolve(
5880
6973
  subsystemsRoot,
5881
6974
  "bridge",
5882
6975
  "generated",
5883
6976
  ".gitkeep"
5884
6977
  );
5885
6978
  return {
5886
- appName: path17.basename(cwd),
6979
+ appName: path18.basename(cwd),
5887
6980
  multiTenant: normaliseMultiTenant4(bridgeBlock.multi_tenant),
5888
6981
  configPath,
5889
6982
  generatedKeepPath
@@ -5906,19 +6999,19 @@ function localsToHygenArgs4(locals) {
5906
6999
  }
5907
7000
 
5908
7001
  // src/cli/shared/observability-scaffold-locals.ts
5909
- import path18 from "path";
7002
+ import path19 from "path";
5910
7003
  var FALLBACK_BACKEND_SRC2 = "src";
5911
7004
  function resolveObservabilityScaffoldLocals(input) {
5912
7005
  const { cwd, config } = input;
5913
7006
  void input.fileExists;
5914
7007
  const backendSrc = typeof config?.paths?.backend_src === "string" && config.paths.backend_src.length > 0 ? config.paths.backend_src : FALLBACK_BACKEND_SRC2;
5915
- const appModulePath = path18.resolve(cwd, backendSrc, "app.module.ts");
5916
- const configPath = path18.resolve(cwd, "codegen.config.yaml");
7008
+ const appModulePath = path19.resolve(cwd, backendSrc, "app.module.ts");
7009
+ const configPath = path19.resolve(cwd, "codegen.config.yaml");
5917
7010
  const obsBlock = config?.observability ?? {};
5918
7011
  const reporters = obsBlock.reporters ?? {};
5919
7012
  const bridgeMetrics = reporters.bridgeMetrics ?? {};
5920
7013
  return {
5921
- appName: path18.basename(cwd),
7014
+ appName: path19.basename(cwd),
5922
7015
  appModulePath,
5923
7016
  configPath,
5924
7017
  bridgeMetricsEnabled: bridgeMetrics.enabled === true
@@ -5939,7 +7032,7 @@ function localsToHygenArgs5(locals) {
5939
7032
 
5940
7033
  // src/cli/shared/auth-scaffold-locals.ts
5941
7034
  import crypto from "crypto";
5942
- import path19 from "path";
7035
+ import path20 from "path";
5943
7036
  var FALLBACK_BACKEND_SRC3 = "src";
5944
7037
  var DEFAULT_REDIRECT_URI_BASE = "http://localhost:3000";
5945
7038
  function resolveAuthScaffoldLocals(input) {
@@ -5951,15 +7044,15 @@ function resolveAuthScaffoldLocals(input) {
5951
7044
  const redirectUriBase = typeof redirectRaw === "string" && redirectRaw.length > 0 ? redirectRaw : DEFAULT_REDIRECT_URI_BASE;
5952
7045
  const tokenEncryptionKey = crypto.randomBytes(32).toString("base64");
5953
7046
  return {
5954
- appName: path19.basename(cwd),
5955
- configPath: path19.resolve(cwd, "codegen.config.yaml"),
5956
- schemaPath: path19.resolve(
7047
+ appName: path20.basename(cwd),
7048
+ configPath: path20.resolve(cwd, "codegen.config.yaml"),
7049
+ schemaPath: path20.resolve(
5957
7050
  subsystemsRoot,
5958
7051
  "auth",
5959
7052
  "auth-oauth-state.schema.ts"
5960
7053
  ),
5961
- appModulePath: path19.resolve(cwd, backendSrc, "app.module.ts"),
5962
- envConfigPath: path19.resolve(cwd, ".env.config"),
7054
+ appModulePath: path20.resolve(cwd, backendSrc, "app.module.ts"),
7055
+ envConfigPath: path20.resolve(cwd, ".env.config"),
5963
7056
  redirectUriBase,
5964
7057
  tokenEncryptionKey
5965
7058
  };
@@ -5984,7 +7077,7 @@ function localsToHygenArgs6(locals) {
5984
7077
  }
5985
7078
 
5986
7079
  // src/cli/shared/auth-integrations-scaffold-locals.ts
5987
- import path20 from "path";
7080
+ import path21 from "path";
5988
7081
  var FALLBACK_BACKEND_SRC4 = "src";
5989
7082
  var DEFAULT_MODULES_DIR = "modules";
5990
7083
  var DEFAULT_DEFINITIONS_DIR = "definitions/entities";
@@ -5993,17 +7086,17 @@ function resolveAuthIntegrationsScaffoldLocals(input) {
5993
7086
  const backendSrc = typeof config?.paths?.backend_src === "string" && config.paths.backend_src.length > 0 ? config.paths.backend_src : FALLBACK_BACKEND_SRC4;
5994
7087
  const pathsAny = config?.paths;
5995
7088
  const modulesConfigured = pathsAny?.modules_dir;
5996
- const vendorRoot = typeof modulesConfigured === "string" && modulesConfigured.length > 0 ? path20.resolve(cwd, modulesConfigured) : path20.resolve(cwd, backendSrc, DEFAULT_MODULES_DIR);
7089
+ const vendorRoot = typeof modulesConfigured === "string" && modulesConfigured.length > 0 ? path21.resolve(cwd, modulesConfigured) : path21.resolve(cwd, backendSrc, DEFAULT_MODULES_DIR);
5997
7090
  const entitiesConfigured = typeof pathsAny?.entities === "string" && pathsAny.entities.length > 0 ? pathsAny.entities : typeof pathsAny?.entities_dir === "string" && pathsAny.entities_dir.length > 0 ? pathsAny.entities_dir : null;
5998
- const definitionsPath = entitiesConfigured !== null ? path20.resolve(cwd, entitiesConfigured, "connection.yaml") : path20.resolve(cwd, DEFAULT_DEFINITIONS_DIR, "connection.yaml");
5999
- const appModulePath = path20.resolve(cwd, backendSrc, "app.module.ts");
7091
+ const definitionsPath = entitiesConfigured !== null ? path21.resolve(cwd, entitiesConfigured, "connection.yaml") : path21.resolve(cwd, DEFAULT_DEFINITIONS_DIR, "connection.yaml");
7092
+ const appModulePath = path21.resolve(cwd, backendSrc, "app.module.ts");
6000
7093
  let authModuleRegistered = false;
6001
7094
  const appModuleSource = input.readFile(appModulePath);
6002
7095
  if (appModuleSource && appModuleSource.includes("AuthModule.forRoot")) {
6003
7096
  authModuleRegistered = true;
6004
7097
  }
6005
7098
  return {
6006
- appName: path20.basename(cwd),
7099
+ appName: path21.basename(cwd),
6007
7100
  appModulePath,
6008
7101
  vendorRoot,
6009
7102
  definitionsPath,
@@ -6021,7 +7114,7 @@ function localsToHygenArgs7(locals) {
6021
7114
 
6022
7115
  // src/cli/shared/runtime-copier.ts
6023
7116
  import fs11 from "fs";
6024
- import path21 from "path";
7117
+ import path22 from "path";
6025
7118
  function readIfExists(p) {
6026
7119
  try {
6027
7120
  return fs11.readFileSync(p, "utf-8");
@@ -6039,8 +7132,8 @@ function extractRelativeImports(source) {
6039
7132
  return out;
6040
7133
  }
6041
7134
  function resolveSourceImport(sourceFile, specifier) {
6042
- const base = path21.resolve(path21.dirname(sourceFile), specifier);
6043
- const candidates = [base + ".ts", base + ".tsx", path21.join(base, "index.ts")];
7135
+ const base = path22.resolve(path22.dirname(sourceFile), specifier);
7136
+ const candidates = [base + ".ts", base + ".tsx", path22.join(base, "index.ts")];
6044
7137
  for (const c of candidates) {
6045
7138
  if (fs11.existsSync(c)) return c;
6046
7139
  }
@@ -6051,8 +7144,8 @@ async function copyRuntime(opts) {
6051
7144
  if (!fs11.existsSync(sourceDir) || !fs11.statSync(sourceDir).isDirectory()) {
6052
7145
  throw new Error(`runtime source directory not found: ${sourceDir}`);
6053
7146
  }
6054
- const runtimeRoot4 = opts.runtimeRoot ? path21.resolve(opts.runtimeRoot) : path21.resolve(sourceDir, "..", "..");
6055
- const depsTargetRoot = opts.depsTargetRoot ?? path21.resolve(targetDir, "..");
7147
+ const runtimeRoot4 = opts.runtimeRoot ? path22.resolve(opts.runtimeRoot) : path22.resolve(sourceDir, "..", "..");
7148
+ const depsTargetRoot = opts.depsTargetRoot ?? path22.resolve(targetDir, "..");
6056
7149
  const result = {
6057
7150
  written: [],
6058
7151
  updated: [],
@@ -6063,7 +7156,7 @@ async function copyRuntime(opts) {
6063
7156
  const queue = [];
6064
7157
  function walk(dir) {
6065
7158
  for (const entry of fs11.readdirSync(dir)) {
6066
- const src = path21.join(dir, entry);
7159
+ const src = path22.join(dir, entry);
6067
7160
  const stat = fs11.statSync(src);
6068
7161
  if (stat.isDirectory()) {
6069
7162
  if (entry === "generated") continue;
@@ -6072,9 +7165,9 @@ async function copyRuntime(opts) {
6072
7165
  }
6073
7166
  if (!stat.isFile()) continue;
6074
7167
  if (!entry.endsWith(".ts") && !entry.endsWith(".tsx")) continue;
6075
- const rel2 = path21.relative(sourceDir, src);
7168
+ const rel2 = path22.relative(sourceDir, src);
6076
7169
  if (filter && !filter(rel2) && !filter(entry)) continue;
6077
- queue.push({ src, dest: path21.join(targetDir, rel2), isDep: false });
7170
+ queue.push({ src, dest: path22.join(targetDir, rel2), isDep: false });
6078
7171
  }
6079
7172
  }
6080
7173
  walk(sourceDir);
@@ -6095,18 +7188,18 @@ async function copyRuntime(opts) {
6095
7188
  else result.unchanged.push(next.dest);
6096
7189
  if (next.isDep) result.dependenciesCopied.push(next.dest);
6097
7190
  if (!dryRun && status !== "unchanged") {
6098
- fs11.mkdirSync(path21.dirname(next.dest), { recursive: true });
7191
+ fs11.mkdirSync(path22.dirname(next.dest), { recursive: true });
6099
7192
  fs11.writeFileSync(next.dest, content);
6100
7193
  }
6101
7194
  if (resolveDeps) {
6102
7195
  for (const spec of extractRelativeImports(content)) {
6103
7196
  const resolvedSrc = resolveSourceImport(next.src, spec);
6104
7197
  if (!resolvedSrc) continue;
6105
- const relToRuntime = path21.relative(runtimeRoot4, resolvedSrc);
6106
- if (relToRuntime.startsWith("..") || path21.isAbsolute(relToRuntime)) continue;
6107
- const relToSource = path21.relative(sourceDir, resolvedSrc);
6108
- if (!relToSource.startsWith("..") && !path21.isAbsolute(relToSource)) continue;
6109
- const depDest = path21.join(depsTargetRoot, relToRuntime);
7198
+ const relToRuntime = path22.relative(runtimeRoot4, resolvedSrc);
7199
+ if (relToRuntime.startsWith("..") || path22.isAbsolute(relToRuntime)) continue;
7200
+ const relToSource = path22.relative(sourceDir, resolvedSrc);
7201
+ if (!relToSource.startsWith("..") && !path22.isAbsolute(relToSource)) continue;
7202
+ const depDest = path22.join(depsTargetRoot, relToRuntime);
6110
7203
  queue.push({ src: resolvedSrc, dest: depDest, isDep: true });
6111
7204
  }
6112
7205
  }
@@ -6116,7 +7209,7 @@ async function copyRuntime(opts) {
6116
7209
 
6117
7210
  // src/cli/shared/subsystems-install-config.ts
6118
7211
  import fs12 from "fs";
6119
- import path22 from "path";
7212
+ import path23 from "path";
6120
7213
  import yaml2 from "yaml";
6121
7214
  function readInstallList(config) {
6122
7215
  const raw = config?.subsystems?.install;
@@ -6125,7 +7218,7 @@ function readInstallList(config) {
6125
7218
  }
6126
7219
  function ensureSubsystemInstalled(configPath, name) {
6127
7220
  if (!fs12.existsSync(configPath)) {
6128
- fs12.mkdirSync(path22.dirname(configPath), { recursive: true });
7221
+ fs12.mkdirSync(path23.dirname(configPath), { recursive: true });
6129
7222
  fs12.writeFileSync(
6130
7223
  configPath,
6131
7224
  `subsystems:
@@ -6176,13 +7269,13 @@ ${indent}- ${name}${after}`;
6176
7269
 
6177
7270
  // src/cli/commands/subsystem.ts
6178
7271
  function runtimeRoot() {
6179
- const pkgRoot = path23.resolve(import.meta.dirname, "..", "..", "..");
6180
- const topLevel = path23.join(pkgRoot, "runtime");
7272
+ const pkgRoot = path24.resolve(import.meta.dirname, "..", "..", "..");
7273
+ const topLevel = path24.join(pkgRoot, "runtime");
6181
7274
  if (fs13.existsSync(topLevel)) return topLevel;
6182
- return path23.join(pkgRoot, "dist", "runtime");
7275
+ return path24.join(pkgRoot, "dist", "runtime");
6183
7276
  }
6184
7277
  function subsystemSource(name) {
6185
- return path23.join(runtimeRoot(), "subsystems", name);
7278
+ return path24.join(runtimeRoot(), "subsystems", name);
6186
7279
  }
6187
7280
  function describeSubsystem(name) {
6188
7281
  return SUBSYSTEMS.find((s) => s.name === name) ?? null;
@@ -6217,7 +7310,7 @@ async function summary2(ctx) {
6217
7310
  }
6218
7311
  body.push(theme.muted("Installed:"));
6219
7312
  for (const i of installed) {
6220
- const rel2 = path23.relative(ctx.cwd, i.path) || i.path;
7313
+ const rel2 = path24.relative(ctx.cwd, i.path) || i.path;
6221
7314
  body.push(
6222
7315
  ` ${theme.success(icons.check)} ${i.name.padEnd(10)} ${theme.muted(
6223
7316
  `${i.backend} backend`
@@ -6360,14 +7453,14 @@ var SubsystemInstallCommand = class extends Command3 {
6360
7453
  return 0;
6361
7454
  }
6362
7455
  const targetRoot = resolveSubsystemsRoot(ctx, this.target);
6363
- const subsystemTarget = path23.join(targetRoot, desc.name);
7456
+ const subsystemTarget = path24.join(targetRoot, desc.name);
6364
7457
  const source = subsystemSource(desc.name);
6365
7458
  if (!fs13.existsSync(source)) {
6366
7459
  printError(`Runtime subsystem source missing: ${source}`);
6367
7460
  return 1;
6368
7461
  }
6369
7462
  if (!this.force) {
6370
- const gitCheck = checkGitSafety([path23.relative(ctx.cwd, subsystemTarget) || subsystemTarget], ctx.cwd);
7463
+ const gitCheck = checkGitSafety([path24.relative(ctx.cwd, subsystemTarget) || subsystemTarget], ctx.cwd);
6371
7464
  if (gitCheck.inRepo && !gitCheck.clean) {
6372
7465
  printWarning(
6373
7466
  `Uncommitted changes under ${subsystemTarget}. Pass --force to overwrite.`
@@ -6376,7 +7469,7 @@ var SubsystemInstallCommand = class extends Command3 {
6376
7469
  }
6377
7470
  }
6378
7471
  if (!isJsonMode()) {
6379
- printInfo(`target = ${path23.relative(ctx.cwd, subsystemTarget) || subsystemTarget}`);
7472
+ printInfo(`target = ${path24.relative(ctx.cwd, subsystemTarget) || subsystemTarget}`);
6380
7473
  printInfo(`backend = ${backend}`);
6381
7474
  }
6382
7475
  const result = await copyRuntime({
@@ -6385,7 +7478,7 @@ var SubsystemInstallCommand = class extends Command3 {
6385
7478
  filter: backendFileFilter(backend, desc.name),
6386
7479
  resolveDeps: true,
6387
7480
  runtimeRoot: runtimeRoot(),
6388
- depsTargetRoot: path23.resolve(targetRoot, ".."),
7481
+ depsTargetRoot: path24.resolve(targetRoot, ".."),
6389
7482
  dryRun: this.dryRun
6390
7483
  });
6391
7484
  const jobsScaffold = desc.name === "jobs" ? runJobsScaffold(ctx.cwd, ctx.config, {
@@ -6480,14 +7573,14 @@ var SubsystemInstallCommand = class extends Command3 {
6480
7573
  if (this.dryRun) {
6481
7574
  printInfo(`Dry run \u2014 ${result.planned.length} files would be written`);
6482
7575
  for (const p of result.planned) {
6483
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7576
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6484
7577
  }
6485
7578
  if (jobsScaffold?.planned?.length) {
6486
7579
  printInfo(
6487
7580
  `Jobs scaffold \u2014 ${jobsScaffold.planned.length} template targets`
6488
7581
  );
6489
7582
  for (const p of jobsScaffold.planned) {
6490
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7583
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6491
7584
  }
6492
7585
  }
6493
7586
  if (eventsScaffold?.planned?.length) {
@@ -6495,7 +7588,7 @@ var SubsystemInstallCommand = class extends Command3 {
6495
7588
  `Events scaffold \u2014 ${eventsScaffold.planned.length} template targets`
6496
7589
  );
6497
7590
  for (const p of eventsScaffold.planned) {
6498
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7591
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6499
7592
  }
6500
7593
  }
6501
7594
  if (integrationScaffold?.planned?.length) {
@@ -6503,7 +7596,7 @@ var SubsystemInstallCommand = class extends Command3 {
6503
7596
  `Integration scaffold \u2014 ${integrationScaffold.planned.length} template targets`
6504
7597
  );
6505
7598
  for (const p of integrationScaffold.planned) {
6506
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7599
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6507
7600
  }
6508
7601
  }
6509
7602
  if (bridgeScaffold?.planned?.length) {
@@ -6511,7 +7604,7 @@ var SubsystemInstallCommand = class extends Command3 {
6511
7604
  `Bridge scaffold \u2014 ${bridgeScaffold.planned.length} template targets`
6512
7605
  );
6513
7606
  for (const p of bridgeScaffold.planned) {
6514
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7607
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6515
7608
  }
6516
7609
  }
6517
7610
  if (observabilityScaffold?.planned?.length) {
@@ -6519,7 +7612,7 @@ var SubsystemInstallCommand = class extends Command3 {
6519
7612
  `Observability scaffold \u2014 ${observabilityScaffold.planned.length} template targets`
6520
7613
  );
6521
7614
  for (const p of observabilityScaffold.planned) {
6522
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7615
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6523
7616
  }
6524
7617
  }
6525
7618
  if (authScaffold?.planned?.length) {
@@ -6527,7 +7620,7 @@ var SubsystemInstallCommand = class extends Command3 {
6527
7620
  `Auth scaffold \u2014 ${authScaffold.planned.length} template targets`
6528
7621
  );
6529
7622
  for (const p of authScaffold.planned) {
6530
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7623
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6531
7624
  }
6532
7625
  }
6533
7626
  return 0;
@@ -6657,7 +7750,7 @@ var SubsystemInstallCommand = class extends Command3 {
6657
7750
  * `--force-config` and always regenerates the barrels).
6658
7751
  */
6659
7752
  async executePackageMode(ctx, desc, backend) {
6660
- const configPath = path23.join(ctx.cwd, "codegen.config.yaml");
7753
+ const configPath = path24.join(ctx.cwd, "codegen.config.yaml");
6661
7754
  const installed = configuredSubsystemNames(
6662
7755
  ctx.config
6663
7756
  );
@@ -6784,7 +7877,7 @@ var SubsystemInstallCommand = class extends Command3 {
6784
7877
  * semantics as jobs/events/integration/bridge.
6785
7878
  */
6786
7879
  async executeOpenApiConfig(ctx) {
6787
- const configPath = path23.join(ctx.cwd, "codegen.config.yaml");
7880
+ const configPath = path24.join(ctx.cwd, "codegen.config.yaml");
6788
7881
  const outcome = planConfigBlockAction(configPath, "openapi", this.forceConfig);
6789
7882
  if (outcome === "parse-error") {
6790
7883
  printError(
@@ -6803,7 +7896,7 @@ var SubsystemInstallCommand = class extends Command3 {
6803
7896
  });
6804
7897
  } else {
6805
7898
  printInfo(`Dry run \u2014 openapi config block would be ${outcome}`);
6806
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, configPath) || configPath}`);
7899
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, configPath) || configPath}`);
6807
7900
  }
6808
7901
  return 0;
6809
7902
  }
@@ -6898,7 +7991,7 @@ var SubsystemInstallCommand = class extends Command3 {
6898
7991
  );
6899
7992
  for (const p of scaffold.planned) {
6900
7993
  console.log(
6901
- ` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`
7994
+ ` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`
6902
7995
  );
6903
7996
  }
6904
7997
  return 0;
@@ -7281,7 +8374,7 @@ function runAuthScaffold(cwd, config, opts) {
7281
8374
  return { ok: true, planned, configBlockOutcome };
7282
8375
  }
7283
8376
  if (!fs13.existsSync(locals.envConfigPath)) {
7284
- fs13.mkdirSync(path23.dirname(locals.envConfigPath), { recursive: true });
8377
+ fs13.mkdirSync(path24.dirname(locals.envConfigPath), { recursive: true });
7285
8378
  fs13.writeFileSync(locals.envConfigPath, "", "utf-8");
7286
8379
  }
7287
8380
  const result = invokeHygen({
@@ -7318,10 +8411,10 @@ function runAuthScaffold(cwd, config, opts) {
7318
8411
  return { ok: true, planned, configBlockOutcome };
7319
8412
  }
7320
8413
  function authIntegrationsExamplesRoot() {
7321
- const pkgRoot = path23.resolve(import.meta.dirname, "..", "..", "..");
7322
- const topLevel = path23.join(pkgRoot, "examples", "auth-integrations");
8414
+ const pkgRoot = path24.resolve(import.meta.dirname, "..", "..", "..");
8415
+ const topLevel = path24.join(pkgRoot, "examples", "auth-integrations");
7323
8416
  if (fs13.existsSync(topLevel)) return topLevel;
7324
- return path23.join(pkgRoot, "dist", "examples", "auth-integrations");
8417
+ return path24.join(pkgRoot, "dist", "examples", "auth-integrations");
7325
8418
  }
7326
8419
  function copyTreeIdempotent(srcDir, destDir, force, transform) {
7327
8420
  const written = [];
@@ -7329,8 +8422,8 @@ function copyTreeIdempotent(srcDir, destDir, force, transform) {
7329
8422
  const walk = (src, dest) => {
7330
8423
  const entries = fs13.readdirSync(src, { withFileTypes: true });
7331
8424
  for (const entry of entries) {
7332
- const srcPath = path23.join(src, entry.name);
7333
- const destPath = path23.join(dest, entry.name);
8425
+ const srcPath = path24.join(src, entry.name);
8426
+ const destPath = path24.join(dest, entry.name);
7334
8427
  if (entry.isDirectory()) {
7335
8428
  fs13.mkdirSync(destPath, { recursive: true });
7336
8429
  walk(srcPath, destPath);
@@ -7341,7 +8434,7 @@ function copyTreeIdempotent(srcDir, destDir, force, transform) {
7341
8434
  skipped.push(destPath);
7342
8435
  continue;
7343
8436
  }
7344
- fs13.mkdirSync(path23.dirname(destPath), { recursive: true });
8437
+ fs13.mkdirSync(path24.dirname(destPath), { recursive: true });
7345
8438
  const isTextSource = transform && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"));
7346
8439
  if (isTextSource && transform) {
7347
8440
  const raw = fs13.readFileSync(srcPath, "utf-8");
@@ -7359,16 +8452,16 @@ function copyTreeIdempotent(srcDir, destDir, force, transform) {
7359
8452
  }
7360
8453
  var AUTH_BARE_IMPORT_RE = /(['"])@pattern-stack\/codegen\/runtime\/subsystems\/auth\1/g;
7361
8454
  function buildAuthImportRewriter(subsystemsRoot) {
7362
- const authRoot = path23.join(subsystemsRoot, "auth");
8455
+ const authRoot = path24.join(subsystemsRoot, "auth");
7363
8456
  return (content, destPath) => {
7364
8457
  if (!AUTH_BARE_IMPORT_RE.test(content)) {
7365
8458
  AUTH_BARE_IMPORT_RE.lastIndex = 0;
7366
8459
  return content;
7367
8460
  }
7368
8461
  AUTH_BARE_IMPORT_RE.lastIndex = 0;
7369
- let rel2 = path23.relative(path23.dirname(destPath), authRoot);
8462
+ let rel2 = path24.relative(path24.dirname(destPath), authRoot);
7370
8463
  if (!rel2.startsWith(".")) rel2 = `./${rel2}`;
7371
- const relPosix = rel2.split(path23.sep).join("/");
8464
+ const relPosix = rel2.split(path24.sep).join("/");
7372
8465
  return content.replace(
7373
8466
  AUTH_BARE_IMPORT_RE,
7374
8467
  (_match, quote) => `${quote}${relPosix}${quote}`
@@ -7390,9 +8483,9 @@ function runAuthIntegrationsScaffold(cwd, config, opts) {
7390
8483
  error: `auth-integrations starter source missing: ${examplesRoot}`
7391
8484
  };
7392
8485
  }
7393
- const adaptersSrc = path23.join(examplesRoot, "runtime", "connections");
7394
- const adaptersDest = path23.join(locals.vendorRoot, "connections");
7395
- const connectionYamlSrc = path23.join(
8486
+ const adaptersSrc = path24.join(examplesRoot, "runtime", "connections");
8487
+ const adaptersDest = path24.join(locals.vendorRoot, "connections");
8488
+ const connectionYamlSrc = path24.join(
7396
8489
  examplesRoot,
7397
8490
  "definitions",
7398
8491
  "entities",
@@ -7424,7 +8517,7 @@ function runAuthIntegrationsScaffold(cwd, config, opts) {
7424
8517
  if (fs13.existsSync(connectionYamlDest) && !opts.force) {
7425
8518
  yamlSkipped = true;
7426
8519
  } else if (fs13.existsSync(connectionYamlSrc)) {
7427
- fs13.mkdirSync(path23.dirname(connectionYamlDest), { recursive: true });
8520
+ fs13.mkdirSync(path24.dirname(connectionYamlDest), { recursive: true });
7428
8521
  fs13.copyFileSync(connectionYamlSrc, connectionYamlDest);
7429
8522
  yamlWritten = true;
7430
8523
  }
@@ -7490,7 +8583,7 @@ var SubsystemListCommand = class extends Command3 {
7490
8583
  name: s.name,
7491
8584
  status: inst ? inst.status : "available",
7492
8585
  backend: inst ? inst.backend : null,
7493
- path: inst ? path23.relative(ctx.cwd, inst.path) || inst.path : null
8586
+ path: inst ? path24.relative(ctx.cwd, inst.path) || inst.path : null
7494
8587
  };
7495
8588
  });
7496
8589
  if (isJsonMode()) {
@@ -7586,7 +8679,7 @@ var SubsystemRemoveCommand = class extends Command3 {
7586
8679
  return 1;
7587
8680
  }
7588
8681
  if (!this.force) {
7589
- const rel2 = path23.relative(ctx.cwd, subsystemDir) || subsystemDir;
8682
+ const rel2 = path24.relative(ctx.cwd, subsystemDir) || subsystemDir;
7590
8683
  const gitCheck = checkGitSafety([rel2], ctx.cwd);
7591
8684
  if (gitCheck.inRepo && !gitCheck.clean) {
7592
8685
  printWarning(
@@ -7622,7 +8715,7 @@ var SubsystemRemoveCommand = class extends Command3 {
7622
8715
  return 0;
7623
8716
  }
7624
8717
  printSuccess(
7625
- `${desc.name} subsystem removed (${path23.relative(ctx.cwd, subsystemDir) || subsystemDir}).`
8718
+ `${desc.name} subsystem removed (${path24.relative(ctx.cwd, subsystemDir) || subsystemDir}).`
7626
8719
  );
7627
8720
  if (barrelRegenerated) {
7628
8721
  printInfo("Regenerated <generated>/subsystems.ts barrel.");
@@ -7651,27 +8744,27 @@ var subsystem_default = subsystemNoun;
7651
8744
 
7652
8745
  // src/cli/commands/project.ts
7653
8746
  import fs19 from "fs";
7654
- import path29 from "path";
8747
+ import path30 from "path";
7655
8748
  import readline from "readline";
7656
8749
  import { Command as Command7, Option as Option7 } from "clipanion";
7657
8750
  import { stringify as stringifyYaml2 } from "yaml";
7658
8751
 
7659
8752
  // src/cli/shared/init-scaffold.ts
7660
8753
  import fs14 from "fs";
7661
- import path24 from "path";
8754
+ import path25 from "path";
7662
8755
  import { stringify as stringifyYaml } from "yaml";
7663
8756
  function runtimeRoot2() {
7664
- const pkgRoot = path24.resolve(import.meta.dirname, "..", "..", "..");
7665
- const topLevel = path24.join(pkgRoot, "runtime");
8757
+ const pkgRoot = path25.resolve(import.meta.dirname, "..", "..", "..");
8758
+ const topLevel = path25.join(pkgRoot, "runtime");
7666
8759
  if (fs14.existsSync(topLevel)) return topLevel;
7667
- return path24.join(pkgRoot, "dist", "runtime");
8760
+ return path25.join(pkgRoot, "dist", "runtime");
7668
8761
  }
7669
8762
  function resolveRuntimePath(cwd) {
7670
- const shimDir = path24.join(cwd, "src", "shared", "base-classes");
7671
- return path24.relative(shimDir, runtimeRoot2());
8763
+ const shimDir = path25.join(cwd, "src", "shared", "base-classes");
8764
+ return path25.relative(shimDir, runtimeRoot2());
7672
8765
  }
7673
8766
  function loadRuntimeFile(relPath) {
7674
- return fs14.readFileSync(path24.join(runtimeRoot2(), relPath), "utf-8");
8767
+ return fs14.readFileSync(path25.join(runtimeRoot2(), relPath), "utf-8");
7675
8768
  }
7676
8769
  var VENDORED_RUNTIME_FILES = [
7677
8770
  // base-classes — consumer-facing inheritance targets
@@ -8087,8 +9180,38 @@ function mergeTsconfig(raw) {
8087
9180
  unchanged: false
8088
9181
  };
8089
9182
  }
9183
+ function mergeFrontendDeps(raw) {
9184
+ let parsed;
9185
+ try {
9186
+ parsed = JSON.parse(raw);
9187
+ } catch (err) {
9188
+ return {
9189
+ content: raw,
9190
+ added: [],
9191
+ unchanged: true,
9192
+ parseError: err instanceof Error ? err.message : String(err)
9193
+ };
9194
+ }
9195
+ const deps = parsed.dependencies ?? {};
9196
+ const added = [];
9197
+ for (const [pkg, range] of Object.entries(FRONTEND_EMITTED_DEPS)) {
9198
+ if (!(pkg in deps)) {
9199
+ deps[pkg] = range;
9200
+ added.push(pkg);
9201
+ }
9202
+ }
9203
+ if (added.length === 0) {
9204
+ return { content: raw, added: [], unchanged: true };
9205
+ }
9206
+ parsed.dependencies = deps;
9207
+ return {
9208
+ content: JSON.stringify(parsed, null, 2) + "\n",
9209
+ added,
9210
+ unchanged: false
9211
+ };
9212
+ }
8090
9213
  function relOf(cwd, abs) {
8091
- return path24.relative(cwd, abs) || abs;
9214
+ return path25.relative(cwd, abs) || abs;
8092
9215
  }
8093
9216
  function fileEntry(cwd, absPath, content, opts) {
8094
9217
  const exists = fs14.existsSync(absPath);
@@ -8139,7 +9262,7 @@ async function buildInitPlan(ctx, options) {
8139
9262
  const runtimePath = options.runtimePath ?? resolveRuntimePath(cwd);
8140
9263
  const entries = [];
8141
9264
  {
8142
- const configPath = path24.join(cwd, "codegen.config.yaml");
9265
+ const configPath = path25.join(cwd, "codegen.config.yaml");
8143
9266
  const config = {
8144
9267
  // Runtime mode (ADR-037). `package` (default) imports the runtime from
8145
9268
  // `@pattern-stack/codegen/*`; `vendored` imports it via `@shared/*` and
@@ -8174,7 +9297,7 @@ async function buildInitPlan(ctx, options) {
8174
9297
  entries.push(fileEntry(cwd, configPath, content, { force }));
8175
9298
  }
8176
9299
  {
8177
- const tsconfigPath = path24.join(cwd, "tsconfig.json");
9300
+ const tsconfigPath = path25.join(cwd, "tsconfig.json");
8178
9301
  if (fs14.existsSync(tsconfigPath)) {
8179
9302
  const raw = fs14.readFileSync(tsconfigPath, "utf-8");
8180
9303
  const merged = mergeTsconfig(raw);
@@ -8221,7 +9344,7 @@ async function buildInitPlan(ctx, options) {
8221
9344
  entries.push(
8222
9345
  fileEntry(
8223
9346
  cwd,
8224
- path24.join(cwd, "src", "shared", "database", "database.module.ts"),
9347
+ path25.join(cwd, "src", "shared", "database", "database.module.ts"),
8225
9348
  databaseModuleContent(runtimeMode),
8226
9349
  { force }
8227
9350
  )
@@ -8229,14 +9352,14 @@ async function buildInitPlan(ctx, options) {
8229
9352
  if (runtimeMode === "vendored") {
8230
9353
  for (const v of VENDORED_RUNTIME_FILES) {
8231
9354
  entries.push(
8232
- fileEntry(cwd, path24.join(cwd, v.target), loadRuntimeFile(v.runtime), { force })
9355
+ fileEntry(cwd, path25.join(cwd, v.target), loadRuntimeFile(v.runtime), { force })
8233
9356
  );
8234
9357
  }
8235
9358
  }
8236
9359
  entries.push(
8237
9360
  fileEntry(
8238
9361
  cwd,
8239
- path24.join(cwd, "src", "generated", "modules.ts"),
9362
+ path25.join(cwd, "src", "generated", "modules.ts"),
8240
9363
  emptyModulesBarrel(),
8241
9364
  { force }
8242
9365
  )
@@ -8244,13 +9367,13 @@ async function buildInitPlan(ctx, options) {
8244
9367
  entries.push(
8245
9368
  fileEntry(
8246
9369
  cwd,
8247
- path24.join(cwd, "src", "generated", "schema.ts"),
9370
+ path25.join(cwd, "src", "generated", "schema.ts"),
8248
9371
  emptySchemaBarrel(),
8249
9372
  { force }
8250
9373
  )
8251
9374
  );
8252
9375
  {
8253
- const appModulePath = path24.join(cwd, "src", "app.module.ts");
9376
+ const appModulePath = path25.join(cwd, "src", "app.module.ts");
8254
9377
  if (!fs14.existsSync(appModulePath)) {
8255
9378
  entries.push({
8256
9379
  path: appModulePath,
@@ -8268,7 +9391,7 @@ async function buildInitPlan(ctx, options) {
8268
9391
  }
8269
9392
  }
8270
9393
  {
8271
- const mainPath = path24.join(cwd, "src", "main.ts");
9394
+ const mainPath = path25.join(cwd, "src", "main.ts");
8272
9395
  if (!fs14.existsSync(mainPath)) {
8273
9396
  entries.push({
8274
9397
  path: mainPath,
@@ -8286,7 +9409,7 @@ async function buildInitPlan(ctx, options) {
8286
9409
  }
8287
9410
  }
8288
9411
  {
8289
- const schemaPath = path24.join(cwd, "src", "schema.ts");
9412
+ const schemaPath = path25.join(cwd, "src", "schema.ts");
8290
9413
  if (!fs14.existsSync(schemaPath)) {
8291
9414
  entries.push({
8292
9415
  path: schemaPath,
@@ -8303,12 +9426,12 @@ async function buildInitPlan(ctx, options) {
8303
9426
  });
8304
9427
  }
8305
9428
  }
8306
- entries.push(dirEntry(cwd, path24.join(cwd, "entities")));
9429
+ entries.push(dirEntry(cwd, path25.join(cwd, "entities")));
8307
9430
  {
8308
- const entitiesDir = path24.join(cwd, "entities");
8309
- const examplePath = path24.join(entitiesDir, "example.yaml");
9431
+ const entitiesDir = path25.join(cwd, "entities");
9432
+ const examplePath = path25.join(entitiesDir, "example.yaml");
8310
9433
  const hasOtherYamls = fs14.existsSync(entitiesDir) && findYamlFiles(entitiesDir).some(
8311
- (f) => path24.basename(f) !== "example.yaml"
9434
+ (f) => path25.basename(f) !== "example.yaml"
8312
9435
  );
8313
9436
  if (fs14.existsSync(examplePath)) {
8314
9437
  entries.push({
@@ -8333,6 +9456,46 @@ async function buildInitPlan(ctx, options) {
8333
9456
  });
8334
9457
  }
8335
9458
  }
9459
+ if (frontend) {
9460
+ const frontendSrc = ctx.config?.paths?.frontend_src ?? "apps/frontend/src";
9461
+ const frontendRoot = path25.dirname(path25.join(cwd, frontendSrc));
9462
+ const pkgPath = path25.join(frontendRoot, "package.json");
9463
+ const depsList = Object.entries(FRONTEND_EMITTED_DEPS).map(([p, r]) => `${p}@${r}`).join(", ");
9464
+ if (fs14.existsSync(pkgPath)) {
9465
+ const raw = fs14.readFileSync(pkgPath, "utf-8");
9466
+ const merged = mergeFrontendDeps(raw);
9467
+ if (merged.parseError) {
9468
+ entries.push({
9469
+ path: pkgPath,
9470
+ relPath: relOf(cwd, pkgPath),
9471
+ action: "skip",
9472
+ reason: `unable to parse (${merged.parseError}); add frontend deps manually: ${depsList}`
9473
+ });
9474
+ } else if (merged.unchanged) {
9475
+ entries.push({
9476
+ path: pkgPath,
9477
+ relPath: relOf(cwd, pkgPath),
9478
+ action: "skip",
9479
+ reason: "frontend deps already present"
9480
+ });
9481
+ } else {
9482
+ entries.push({
9483
+ path: pkgPath,
9484
+ relPath: relOf(cwd, pkgPath),
9485
+ action: "merge",
9486
+ content: merged.content,
9487
+ reason: `add frontend deps: ${merged.added.join(", ")}`
9488
+ });
9489
+ }
9490
+ } else {
9491
+ entries.push({
9492
+ path: pkgPath,
9493
+ relPath: relOf(cwd, pkgPath),
9494
+ action: "skip",
9495
+ reason: `frontend enabled but ${relOf(cwd, pkgPath)} not found \u2014 install: ${depsList}`
9496
+ });
9497
+ }
9498
+ }
8336
9499
  return {
8337
9500
  entries,
8338
9501
  summary: {
@@ -8364,7 +9527,7 @@ function writePlan(plan) {
8364
9527
  skipped.push(e);
8365
9528
  continue;
8366
9529
  }
8367
- fs14.mkdirSync(path24.dirname(e.path), { recursive: true });
9530
+ fs14.mkdirSync(path25.dirname(e.path), { recursive: true });
8368
9531
  fs14.writeFileSync(e.path, e.content, "utf-8");
8369
9532
  if (e.action === "create") created.push(e);
8370
9533
  else if (e.action === "merge") merged.push(e);
@@ -8375,7 +9538,7 @@ function writePlan(plan) {
8375
9538
 
8376
9539
  // src/cli/commands/project-upgrade-openapi.ts
8377
9540
  import fs15 from "fs";
8378
- import path25 from "path";
9541
+ import path26 from "path";
8379
9542
  import { Command as Command4, Option as Option4 } from "clipanion";
8380
9543
  import { Project, IndentationText, QuoteKind, NewLineKind } from "ts-morph";
8381
9544
 
@@ -8614,31 +9777,31 @@ var MAIN_SWAGGER_IMPORTS = [
8614
9777
  "import { OPENAPI_REGISTRY, OpenApiRegistry } from './shared/openapi';"
8615
9778
  ];
8616
9779
  function runtimeRoot3() {
8617
- const pkgRoot = path25.resolve(import.meta.dirname, "..", "..", "..");
8618
- const topLevel = path25.join(pkgRoot, "runtime");
9780
+ const pkgRoot = path26.resolve(import.meta.dirname, "..", "..", "..");
9781
+ const topLevel = path26.join(pkgRoot, "runtime");
8619
9782
  if (fs15.existsSync(topLevel)) return topLevel;
8620
- return path25.join(pkgRoot, "dist", "runtime");
9783
+ return path26.join(pkgRoot, "dist", "runtime");
8621
9784
  }
8622
9785
  function loadRuntimeFile2(rel2) {
8623
- return fs15.readFileSync(path25.join(runtimeRoot3(), rel2), "utf-8");
9786
+ return fs15.readFileSync(path26.join(runtimeRoot3(), rel2), "utf-8");
8624
9787
  }
8625
9788
  function resolveProjectRoot(startDir) {
8626
- let dir = path25.resolve(startDir);
9789
+ let dir = path26.resolve(startDir);
8627
9790
  for (let i = 0; i < 16; i++) {
8628
- if (fs15.existsSync(path25.join(dir, "codegen.config.yaml")) || fs15.existsSync(path25.join(dir, "package.json"))) {
9791
+ if (fs15.existsSync(path26.join(dir, "codegen.config.yaml")) || fs15.existsSync(path26.join(dir, "package.json"))) {
8629
9792
  return dir;
8630
9793
  }
8631
- const parent = path25.dirname(dir);
9794
+ const parent = path26.dirname(dir);
8632
9795
  if (parent === dir) break;
8633
9796
  dir = parent;
8634
9797
  }
8635
- return path25.resolve(startDir);
9798
+ return path26.resolve(startDir);
8636
9799
  }
8637
9800
  async function runUpgradeOpenapi(opts) {
8638
9801
  const { projectRoot, dryRun, force } = opts;
8639
9802
  const changes = [];
8640
9803
  for (const v of OPENAPI_VENDORED_FILES) {
8641
- const target = path25.join(projectRoot, v.target);
9804
+ const target = path26.join(projectRoot, v.target);
8642
9805
  const exists = fs15.existsSync(target);
8643
9806
  const newContent = loadRuntimeFile2(v.runtime);
8644
9807
  if (exists && !force) {
@@ -8654,7 +9817,7 @@ async function runUpgradeOpenapi(opts) {
8654
9817
  }
8655
9818
  } else {
8656
9819
  if (!dryRun) {
8657
- fs15.mkdirSync(path25.dirname(target), { recursive: true });
9820
+ fs15.mkdirSync(path26.dirname(target), { recursive: true });
8658
9821
  fs15.writeFileSync(target, newContent);
8659
9822
  }
8660
9823
  changes.push({
@@ -8663,7 +9826,7 @@ async function runUpgradeOpenapi(opts) {
8663
9826
  });
8664
9827
  }
8665
9828
  }
8666
- const appModulePath = path25.join(projectRoot, "src", "app.module.ts");
9829
+ const appModulePath = path26.join(projectRoot, "src", "app.module.ts");
8667
9830
  if (!fs15.existsSync(appModulePath)) {
8668
9831
  return {
8669
9832
  projectRoot,
@@ -8747,7 +9910,7 @@ async function runUpgradeOpenapi(opts) {
8747
9910
  } else {
8748
9911
  changes.push({ path: "src/app.module.ts", action: "unchanged" });
8749
9912
  }
8750
- const mainPath = path25.join(projectRoot, "src", "main.ts");
9913
+ const mainPath = path26.join(projectRoot, "src", "main.ts");
8751
9914
  if (fs15.existsSync(mainPath)) {
8752
9915
  const mainSource = project.addSourceFileAtPath(mainPath);
8753
9916
  const mainBefore = mainSource.getFullText();
@@ -8828,7 +9991,7 @@ var ProjectUpgradeOpenapiCommand = class extends Command4 {
8828
9991
  json = Option4.Boolean("--json", false);
8829
9992
  async execute() {
8830
9993
  if (this.json) setJsonMode(true);
8831
- const startDir = this.pathOpt ? path25.resolve(this.pathOpt) : process.cwd();
9994
+ const startDir = this.pathOpt ? path26.resolve(this.pathOpt) : process.cwd();
8832
9995
  if (!fs15.existsSync(startDir)) {
8833
9996
  printError(`Directory not found: ${startDir}`);
8834
9997
  return 1;
@@ -8905,17 +10068,17 @@ ${CONSUMER_SETUP_POINTER}
8905
10068
 
8906
10069
  // src/cli/commands/project-update.ts
8907
10070
  import fs18 from "fs";
8908
- import path28 from "path";
10071
+ import path29 from "path";
8909
10072
  import { Command as Command6, Option as Option6 } from "clipanion";
8910
10073
 
8911
10074
  // src/cli/commands/skills.ts
8912
10075
  import fs17 from "fs";
8913
- import path27 from "path";
10076
+ import path28 from "path";
8914
10077
  import { Command as Command5, Option as Option5 } from "clipanion";
8915
10078
 
8916
10079
  // src/cli/shared/tree-copier.ts
8917
10080
  import fs16 from "fs";
8918
- import path26 from "path";
10081
+ import path27 from "path";
8919
10082
  var TEXT_EXTENSIONS = [".ts", ".tsx", ".md", ".mdx", ".yaml", ".yml", ".json"];
8920
10083
  function isTextFile(name) {
8921
10084
  return TEXT_EXTENSIONS.some((ext) => name.endsWith(ext));
@@ -8932,17 +10095,17 @@ function copyTreeWithReport(opts) {
8932
10095
  throw new Error(`tree-copier source directory not found: ${srcDir}`);
8933
10096
  }
8934
10097
  const walk = (relDir) => {
8935
- const absSrcDir = path26.join(srcDir, relDir);
10098
+ const absSrcDir = path27.join(srcDir, relDir);
8936
10099
  for (const entry of fs16.readdirSync(absSrcDir, { withFileTypes: true })) {
8937
- const relPath = relDir ? path26.posix.join(relDir, entry.name) : entry.name;
8938
- const absSrc = path26.join(srcDir, relPath);
10100
+ const relPath = relDir ? path27.posix.join(relDir, entry.name) : entry.name;
10101
+ const absSrc = path27.join(srcDir, relPath);
8939
10102
  if (entry.isDirectory()) {
8940
10103
  walk(relPath);
8941
10104
  continue;
8942
10105
  }
8943
10106
  if (!entry.isFile()) continue;
8944
10107
  if (include && !include(relPath)) continue;
8945
- const dest = path26.join(destDir, relPath);
10108
+ const dest = path27.join(destDir, relPath);
8946
10109
  let content = fs16.readFileSync(absSrc, "utf-8");
8947
10110
  if (transform && isTextFile(entry.name)) {
8948
10111
  content = transform(content, dest);
@@ -8957,7 +10120,7 @@ function copyTreeWithReport(opts) {
8957
10120
  action = "updated";
8958
10121
  }
8959
10122
  if (!dryRun && action !== "unchanged") {
8960
- fs16.mkdirSync(path26.dirname(dest), { recursive: true });
10123
+ fs16.mkdirSync(path27.dirname(dest), { recursive: true });
8961
10124
  fs16.writeFileSync(dest, content, "utf-8");
8962
10125
  }
8963
10126
  const record = { relPath, dest, action };
@@ -8971,13 +10134,13 @@ function copyTreeWithReport(opts) {
8971
10134
 
8972
10135
  // src/cli/commands/skills.ts
8973
10136
  function consumerSkillsRoot() {
8974
- const pkgRoot = path27.resolve(import.meta.dirname, "..", "..", "..");
8975
- const topLevel = path27.join(pkgRoot, "consumer-skills");
10137
+ const pkgRoot = path28.resolve(import.meta.dirname, "..", "..", "..");
10138
+ const topLevel = path28.join(pkgRoot, "consumer-skills");
8976
10139
  if (fs17.existsSync(topLevel)) return topLevel;
8977
- return path27.join(pkgRoot, "dist", "consumer-skills");
10140
+ return path28.join(pkgRoot, "dist", "consumer-skills");
8978
10141
  }
8979
10142
  function skillsTargetDir(cwd) {
8980
- return path27.join(cwd, ".claude", "skills");
10143
+ return path28.join(cwd, ".claude", "skills");
8981
10144
  }
8982
10145
  function availableSkills() {
8983
10146
  const root = consumerSkillsRoot();
@@ -9024,13 +10187,13 @@ async function summary3(ctx) {
9024
10187
  return {
9025
10188
  title: "skills",
9026
10189
  body,
9027
- footer: `${installedCount} of ${skills.length} skills installed \u2192 ${path27.relative(ctx.cwd, targetDir) || targetDir}`
10190
+ footer: `${installedCount} of ${skills.length} skills installed \u2192 ${path28.relative(ctx.cwd, targetDir) || targetDir}`
9028
10191
  };
9029
10192
  }
9030
10193
  async function hints3(ctx) {
9031
10194
  const skills = availableSkills();
9032
10195
  const targetDir = skillsTargetDir(ctx.cwd);
9033
- const allPresent = skills.length > 0 && fs17.existsSync(targetDir) && skills.every((s) => fs17.existsSync(path27.join(targetDir, s)));
10196
+ const allPresent = skills.length > 0 && fs17.existsSync(targetDir) && skills.every((s) => fs17.existsSync(path28.join(targetDir, s)));
9034
10197
  if (allPresent) {
9035
10198
  return [
9036
10199
  { command: "codegen update", description: "Re-sync skills + runtime after a package bump" }
@@ -9105,7 +10268,7 @@ var SkillsInstallCommand = class extends Command5 {
9105
10268
  });
9106
10269
  return 0;
9107
10270
  }
9108
- printInfo(`target = ${path27.relative(ctx.cwd, result.targetDir) || result.targetDir}`);
10271
+ printInfo(`target = ${path28.relative(ctx.cwd, result.targetDir) || result.targetDir}`);
9109
10272
  console.log("");
9110
10273
  renderTreeReport(report);
9111
10274
  console.log("");
@@ -9133,7 +10296,7 @@ var SkillsListCommand = class extends Command5 {
9133
10296
  const skills = availableSkills();
9134
10297
  const targetDir = skillsTargetDir(ctx.cwd);
9135
10298
  const rows = skills.map((name) => {
9136
- const dir = path27.join(targetDir, name);
10299
+ const dir = path28.join(targetDir, name);
9137
10300
  return { name, status: fs17.existsSync(dir) ? "installed" : "available" };
9138
10301
  });
9139
10302
  if (isJsonMode()) {
@@ -9163,7 +10326,7 @@ var NON_RUNTIME_SUBSYSTEMS = /* @__PURE__ */ new Set(["openapi-config", "auth-in
9163
10326
  function syncVendoredRuntime(cwd, write) {
9164
10327
  const changes = [];
9165
10328
  for (const v of VENDORED_RUNTIME_FILES) {
9166
- const dest = path28.join(cwd, v.target);
10329
+ const dest = path29.join(cwd, v.target);
9167
10330
  const content = loadRuntimeFile(v.runtime);
9168
10331
  const existing = fs18.existsSync(dest) ? fs18.readFileSync(dest, "utf-8") : null;
9169
10332
  let action;
@@ -9171,7 +10334,7 @@ function syncVendoredRuntime(cwd, write) {
9171
10334
  else if (existing === content) action = "unchanged";
9172
10335
  else action = "updated";
9173
10336
  if (write && action !== "unchanged") {
9174
- fs18.mkdirSync(path28.dirname(dest), { recursive: true });
10337
+ fs18.mkdirSync(path29.dirname(dest), { recursive: true });
9175
10338
  fs18.writeFileSync(dest, content, "utf-8");
9176
10339
  }
9177
10340
  changes.push({ path: v.target, action });
@@ -9186,14 +10349,14 @@ async function syncSubsystemRuntime(cwd, inst, write) {
9186
10349
  if (!fs18.existsSync(source)) {
9187
10350
  return { name: inst.name, changes: [], skippedReason: "no runtime source in package" };
9188
10351
  }
9189
- const subsystemsRoot = path28.dirname(inst.path);
10352
+ const subsystemsRoot = path29.dirname(inst.path);
9190
10353
  const result = await copyRuntime({
9191
10354
  sourceDir: source,
9192
10355
  targetDir: inst.path,
9193
10356
  filter: backendFileFilter(inst.backend, inst.name),
9194
10357
  resolveDeps: true,
9195
10358
  runtimeRoot: runtimeRoot2(),
9196
- depsTargetRoot: path28.resolve(subsystemsRoot, ".."),
10359
+ depsTargetRoot: path29.resolve(subsystemsRoot, ".."),
9197
10360
  dryRun: !write,
9198
10361
  // Refresh files already vendored for this subsystem; never install new
9199
10362
  // ones (that's `subsystem install`). copyRuntime classifies accurately
@@ -9207,7 +10370,7 @@ async function syncSubsystemRuntime(cwd, inst, write) {
9207
10370
  return { name: inst.name, changes };
9208
10371
  }
9209
10372
  function rel(cwd, abs) {
9210
- return path28.relative(cwd, abs) || abs;
10373
+ return path29.relative(cwd, abs) || abs;
9211
10374
  }
9212
10375
  var ProjectUpdateCommand = class extends Command6 {
9213
10376
  static paths = [["project", "update"]];
@@ -9246,7 +10409,7 @@ var ProjectUpdateCommand = class extends Command6 {
9246
10409
  const installed = this.skipSubsystems ? [] : await detectInstalledSubsystems(ctx);
9247
10410
  if (!this.dryRun && !this.force) {
9248
10411
  const vendoredDry = syncVendoredRuntime(ctx.cwd, false);
9249
- const vendoredDirtyCandidates = vendoredDry.filter((c) => c.action === "updated").map((c) => path28.join(ctx.cwd, c.path));
10412
+ const vendoredDirtyCandidates = vendoredDry.filter((c) => c.action === "updated").map((c) => path29.join(ctx.cwd, c.path));
9250
10413
  const skillDirtyCandidates = this.skipSkills ? [] : runSkillsInstall({ cwd: ctx.cwd, dryRun: true }).report?.updated.map((e) => e.dest) ?? [];
9251
10414
  const subsystemDirs = installed.filter((i) => !NON_RUNTIME_SUBSYSTEMS.has(i.name)).map((i) => i.path);
9252
10415
  const gate = checkGitSafety(
@@ -9597,8 +10760,8 @@ var ProjectScanCommand = class extends Command7 {
9597
10760
  cwd = Option7.String("--cwd", { required: false });
9598
10761
  async execute() {
9599
10762
  if (this.json) setJsonMode(true);
9600
- const baseCwd = this.cwd ? path29.resolve(this.cwd) : process.cwd();
9601
- const target = this.directory ? path29.resolve(baseCwd, this.directory) : baseCwd;
10763
+ const baseCwd = this.cwd ? path30.resolve(this.cwd) : process.cwd();
10764
+ const target = this.directory ? path30.resolve(baseCwd, this.directory) : baseCwd;
9602
10765
  if (!fs19.existsSync(target)) {
9603
10766
  printError(`Directory not found: ${target}`);
9604
10767
  return 1;
@@ -9649,7 +10812,7 @@ var ProjectScanCommand = class extends Command7 {
9649
10812
  `architecture: ${profile.architecture.evidence.join(", ") || "\u2014"}`
9650
10813
  ]);
9651
10814
  }
9652
- const outPath = path29.join(target, "codegen.config.yaml");
10815
+ const outPath = path30.join(target, "codegen.config.yaml");
9653
10816
  const existsNow = fs19.existsSync(outPath);
9654
10817
  if (this.dryRun) {
9655
10818
  console.log("");
@@ -9770,8 +10933,8 @@ var ProjectInspectCommand = class extends Command7 {
9770
10933
  return 2;
9771
10934
  }
9772
10935
  resolveEntitiesDir(ctx) {
9773
- if (this.dir) return path29.resolve(ctx.cwd, this.dir);
9774
- return ctx.entitiesDir ?? path29.resolve(ctx.cwd, "entities");
10936
+ if (this.dir) return path30.resolve(ctx.cwd, this.dir);
10937
+ return ctx.entitiesDir ?? path30.resolve(ctx.cwd, "entities");
9775
10938
  }
9776
10939
  async runAnalysis(ctx, kind) {
9777
10940
  const entitiesDir = this.resolveEntitiesDir(ctx);
@@ -9974,15 +11137,15 @@ var ProjectGraphCommand = class extends Command7 {
9974
11137
  json: this.json,
9975
11138
  skipDetection: true
9976
11139
  });
9977
- const entitiesDir = this.dir ? path29.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path29.resolve(ctx.cwd, "entities");
11140
+ const entitiesDir = this.dir ? path30.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path30.resolve(ctx.cwd, "entities");
9978
11141
  if (!fs19.existsSync(entitiesDir)) {
9979
11142
  printError(`Entity directory not found: ${entitiesDir}`);
9980
11143
  return 1;
9981
11144
  }
9982
11145
  const relCandidates = [
9983
- path29.resolve(path29.dirname(entitiesDir), "relationships"),
9984
- path29.resolve(entitiesDir, "relationships"),
9985
- path29.resolve(ctx.cwd, "relationships")
11146
+ path30.resolve(path30.dirname(entitiesDir), "relationships"),
11147
+ path30.resolve(entitiesDir, "relationships"),
11148
+ path30.resolve(ctx.cwd, "relationships")
9986
11149
  ];
9987
11150
  const relationshipsDir = relCandidates.find((d) => fs19.existsSync(d));
9988
11151
  const result = await analyzeDomain(entitiesDir, relationshipsDir);
@@ -9998,20 +11161,20 @@ var ProjectGraphCommand = class extends Command7 {
9998
11161
  return 0;
9999
11162
  }
10000
11163
  if (this.output) {
10001
- const outPath = path29.resolve(ctx.cwd, this.output);
11164
+ const outPath = path30.resolve(ctx.cwd, this.output);
10002
11165
  fs19.writeFileSync(outPath, JSON.stringify(serialized, null, 2));
10003
11166
  printSuccess(`Graph written to ${outPath}`);
10004
11167
  printInfo(`${result.entities.length} entities, ${result.relationshipDefinitions.length} relationships, ${result.graph.edges.length} edges`);
10005
11168
  return 0;
10006
11169
  }
10007
11170
  const os = await import("os");
10008
- const tmpDir = fs19.mkdtempSync(path29.join(os.default.tmpdir(), "codegen-graph-"));
10009
- const graphPath = path29.join(tmpDir, "graph.json");
11171
+ const tmpDir = fs19.mkdtempSync(path30.join(os.default.tmpdir(), "codegen-graph-"));
11172
+ const graphPath = path30.join(tmpDir, "graph.json");
10010
11173
  fs19.writeFileSync(graphPath, JSON.stringify(serialized, null, 2));
10011
- const viewerDir = path29.resolve(import.meta.dirname, "..", "..", "..", "tools", "schema-graph-viewer");
10012
- const viewerDist = path29.join(viewerDir, "dist", "index.html");
11174
+ const viewerDir = path30.resolve(import.meta.dirname, "..", "..", "..", "tools", "schema-graph-viewer");
11175
+ const viewerDist = path30.join(viewerDir, "dist", "index.html");
10013
11176
  if (fs19.existsSync(viewerDist)) {
10014
- fs19.copyFileSync(graphPath, path29.join(viewerDir, "dist", "graph.json"));
11177
+ fs19.copyFileSync(graphPath, path30.join(viewerDir, "dist", "graph.json"));
10015
11178
  printSuccess("Graph exported");
10016
11179
  printInfo(`${result.entities.length} entities, ${result.relationshipDefinitions.length} relationships, ${result.graph.edges.length} edges`);
10017
11180
  printInfo(`Graph JSON: ${graphPath}`);
@@ -10043,7 +11206,7 @@ var project_default = projectNoun;
10043
11206
 
10044
11207
  // src/cli/commands/dev.ts
10045
11208
  import fs20 from "fs";
10046
- import path30 from "path";
11209
+ import path31 from "path";
10047
11210
  import { execSync as execSync3, spawn, spawnSync } from "child_process";
10048
11211
  import { Command as Command8, Option as Option8 } from "clipanion";
10049
11212
  var DEFAULT_APP_PORT = 3e3;
@@ -10076,14 +11239,14 @@ function getRedisPort(_ctx) {
10076
11239
  return Number(process.env.DEV_REDIS_PORT ?? DEFAULT_REDIS_PORT);
10077
11240
  }
10078
11241
  function composeFilePath(cwd) {
10079
- const devPath = path30.join(cwd, COMPOSE_FILE);
11242
+ const devPath = path31.join(cwd, COMPOSE_FILE);
10080
11243
  if (fs20.existsSync(devPath)) return devPath;
10081
- const rootPath = path30.join(cwd, "docker-compose.yml");
11244
+ const rootPath = path31.join(cwd, "docker-compose.yml");
10082
11245
  if (fs20.existsSync(rootPath)) return rootPath;
10083
11246
  return devPath;
10084
11247
  }
10085
11248
  function pidFilePath(cwd) {
10086
- return path30.join(cwd, PID_FILE);
11249
+ return path31.join(cwd, PID_FILE);
10087
11250
  }
10088
11251
  function readAppPid(cwd) {
10089
11252
  const p = pidFilePath(cwd);
@@ -10144,7 +11307,7 @@ function checkApp(cwd, port) {
10144
11307
  function listEntityNames(ctx) {
10145
11308
  if (!ctx.entitiesDir || !fs20.existsSync(ctx.entitiesDir)) return [];
10146
11309
  return findYamlFiles(ctx.entitiesDir).map(
10147
- (f) => path30.basename(f).replace(/\.ya?ml$/, "")
11310
+ (f) => path31.basename(f).replace(/\.ya?ml$/, "")
10148
11311
  );
10149
11312
  }
10150
11313
  function formatServiceLine(svc) {
@@ -10154,7 +11317,7 @@ function formatServiceLine(svc) {
10154
11317
  return `${icon} ${svc.name.padEnd(12)} ${theme.muted(`${svc.host}:${svc.port}`)} ${status}${pidStr}`;
10155
11318
  }
10156
11319
  function ensureComposeFile(cwd, pgPort, redisPort) {
10157
- const composePath = path30.join(cwd, COMPOSE_FILE);
11320
+ const composePath = path31.join(cwd, COMPOSE_FILE);
10158
11321
  if (fs20.existsSync(composePath)) return composePath;
10159
11322
  const content = `# Auto-generated by codegen dev
10160
11323
  # Ports offset from defaults to avoid conflicts with other local services.
@@ -10239,7 +11402,7 @@ var DevUpCommand = class extends Command8 {
10239
11402
  if (!pgReady) printWarning("postgres did not become healthy in time");
10240
11403
  if (!redisReady) printWarning("redis did not become healthy in time");
10241
11404
  const drizzleConfig = ["drizzle.config.ts", "drizzle.config.js"].find(
10242
- (f) => fs20.existsSync(path30.join(ctx.cwd, f))
11405
+ (f) => fs20.existsSync(path31.join(ctx.cwd, f))
10243
11406
  );
10244
11407
  if (drizzleConfig) {
10245
11408
  if (!isJsonMode()) printInfo("pushing database schema...");
@@ -10259,7 +11422,7 @@ var DevUpCommand = class extends Command8 {
10259
11422
  if (!isJsonMode()) printInfo("starting NestJS app...");
10260
11423
  const dbUrl = `postgres://postgres:postgres@localhost:${pgPort}/codegen_dev`;
10261
11424
  const redisUrl = `redis://localhost:${redisPort}`;
10262
- const logFile = path30.join(ctx.cwd, ".dev-app.log");
11425
+ const logFile = path31.join(ctx.cwd, ".dev-app.log");
10263
11426
  const logFd = fs20.openSync(logFile, "a");
10264
11427
  const child = spawn("bun", ["src/main.ts"], {
10265
11428
  cwd: ctx.cwd,
@@ -10417,7 +11580,7 @@ var DevLogsCommand = class extends Command8 {
10417
11580
  }
10418
11581
  return 0;
10419
11582
  }
10420
- const logFile = path30.join(ctx.cwd, ".dev-app.log");
11583
+ const logFile = path31.join(ctx.cwd, ".dev-app.log");
10421
11584
  if (!fs20.existsSync(logFile)) {
10422
11585
  printInfo("no app logs found \u2014 is the app running?");
10423
11586
  return 0;
@@ -10461,7 +11624,7 @@ var DevRestartCommand = class extends Command8 {
10461
11624
  spawnSync("sleep", ["1"]);
10462
11625
  const dbUrl = `postgres://postgres:postgres@localhost:${pgPort}/codegen_dev`;
10463
11626
  const redisUrl = `redis://localhost:${redisPort}`;
10464
- const logFile = path30.join(ctx.cwd, ".dev-app.log");
11627
+ const logFile = path31.join(ctx.cwd, ".dev-app.log");
10465
11628
  const logFd = fs20.openSync(logFile, "a");
10466
11629
  const child = spawn("bun", ["src/main.ts"], {
10467
11630
  cwd: ctx.cwd,
@@ -10591,7 +11754,7 @@ var dev_default = devNoun;
10591
11754
 
10592
11755
  // src/cli/commands/relationship.ts
10593
11756
  import fs21 from "fs";
10594
- import path31 from "path";
11757
+ import path32 from "path";
10595
11758
  import { Command as Command9, Option as Option9 } from "clipanion";
10596
11759
  function listRelationshipYamls2(dir) {
10597
11760
  if (!fs21.existsSync(dir)) return [];
@@ -10618,7 +11781,7 @@ function padRight2(s, n) {
10618
11781
  return s.length >= n ? s : s + " ".repeat(n - s.length);
10619
11782
  }
10620
11783
  async function summary6(ctx) {
10621
- const relDir = path31.resolve(ctx.cwd, "relationships");
11784
+ const relDir = path32.resolve(ctx.cwd, "relationships");
10622
11785
  const files = listRelationshipYamls2(relDir);
10623
11786
  if (files.length === 0) {
10624
11787
  return {
@@ -10688,14 +11851,14 @@ var RelationshipNewCommand = class extends Command9 {
10688
11851
  }
10689
11852
  let targets = [];
10690
11853
  if (this.all) {
10691
- const dir = path31.resolve(ctx.cwd, "relationships");
11854
+ const dir = path32.resolve(ctx.cwd, "relationships");
10692
11855
  targets = listRelationshipYamls2(dir);
10693
11856
  if (targets.length === 0) {
10694
11857
  printError(`No relationship YAML files found in ${dir}`);
10695
11858
  return 1;
10696
11859
  }
10697
11860
  } else if (this.yaml) {
10698
- targets = [path31.resolve(ctx.cwd, this.yaml)];
11861
+ targets = [path32.resolve(ctx.cwd, this.yaml)];
10699
11862
  } else {
10700
11863
  printError("Missing YAML path. Pass a file or --all.");
10701
11864
  return 2;
@@ -10712,7 +11875,7 @@ var RelationshipNewCommand = class extends Command9 {
10712
11875
  }
10713
11876
  if (invalid.length > 0) {
10714
11877
  for (const i of invalid) {
10715
- printError(`${path31.basename(i.file)} \u2014 ${i.message}`);
11878
+ printError(`${path32.basename(i.file)} \u2014 ${i.message}`);
10716
11879
  }
10717
11880
  if (!isJsonMode()) return 1;
10718
11881
  }
@@ -10743,7 +11906,7 @@ var RelationshipNewCommand = class extends Command9 {
10743
11906
  }
10744
11907
  const succeeded = [];
10745
11908
  const failed = [
10746
- ...invalid.map((i) => ({ name: path31.basename(i.file), file: i.file, message: i.message }))
11909
+ ...invalid.map((i) => ({ name: path32.basename(i.file), file: i.file, message: i.message }))
10747
11910
  ];
10748
11911
  for (const v of validated) {
10749
11912
  if (!isJsonMode()) {
@@ -10762,8 +11925,8 @@ var RelationshipNewCommand = class extends Command9 {
10762
11925
  if (!isJsonMode()) printError(`${v.name} \u2014 ${res.stderr ?? "failed"}`);
10763
11926
  }
10764
11927
  }
10765
- const entitiesDir = ctx.entitiesDir ?? path31.resolve(ctx.cwd, "entities");
10766
- const relationshipsDir = path31.resolve(ctx.cwd, "relationships");
11928
+ const entitiesDir = ctx.entitiesDir ?? path32.resolve(ctx.cwd, "entities");
11929
+ const relationshipsDir = path32.resolve(ctx.cwd, "relationships");
10767
11930
  const generatedDir = resolveGeneratedDir(ctx);
10768
11931
  const architecture = resolveArchitecture(ctx);
10769
11932
  let barrelResult = null;
@@ -10808,7 +11971,7 @@ var RelationshipNewCommand = class extends Command9 {
10808
11971
  }
10809
11972
  if (barrelResult) {
10810
11973
  printInfo(
10811
- `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path31.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path31.relative(ctx.cwd, barrelResult.schemaBarrel)}`
11974
+ `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path32.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path32.relative(ctx.cwd, barrelResult.schemaBarrel)}`
10812
11975
  );
10813
11976
  }
10814
11977
  }
@@ -10831,7 +11994,7 @@ var RelationshipListCommand = class extends Command9 {
10831
11994
  json: this.json,
10832
11995
  skipDetection: true
10833
11996
  });
10834
- const relDir = path31.resolve(ctx.cwd, "relationships");
11997
+ const relDir = path32.resolve(ctx.cwd, "relationships");
10835
11998
  const files = listRelationshipYamls2(relDir);
10836
11999
  if (files.length === 0) {
10837
12000
  printInfo("No relationship definitions found.");
@@ -10871,7 +12034,7 @@ var relationshipNoun = {
10871
12034
  var relationship_default = relationshipNoun;
10872
12035
 
10873
12036
  // src/cli/commands/junction.ts
10874
- import path32 from "path";
12037
+ import path33 from "path";
10875
12038
  import { Command as Command10, Option as Option10 } from "clipanion";
10876
12039
  function summarizeJunctionFile(filePath) {
10877
12040
  const result = loadJunctionFromYaml(filePath);
@@ -10894,7 +12057,7 @@ function padRight3(s, n) {
10894
12057
  return s.length >= n ? s : s + " ".repeat(n - s.length);
10895
12058
  }
10896
12059
  async function summary7(ctx) {
10897
- const junctionDir = path32.resolve(ctx.cwd, "junctions");
12060
+ const junctionDir = path33.resolve(ctx.cwd, "junctions");
10898
12061
  const files = listJunctionYamls(junctionDir);
10899
12062
  if (files.length === 0) {
10900
12063
  return {
@@ -10964,14 +12127,14 @@ var JunctionNewCommand = class extends Command10 {
10964
12127
  }
10965
12128
  let targets = [];
10966
12129
  if (this.all) {
10967
- const dir = path32.resolve(ctx.cwd, "junctions");
12130
+ const dir = path33.resolve(ctx.cwd, "junctions");
10968
12131
  targets = listJunctionYamls(dir);
10969
12132
  if (targets.length === 0) {
10970
12133
  printError(`No junction YAML files found in ${dir}`);
10971
12134
  return 1;
10972
12135
  }
10973
12136
  } else if (this.yaml) {
10974
- targets = [path32.resolve(ctx.cwd, this.yaml)];
12137
+ targets = [path33.resolve(ctx.cwd, this.yaml)];
10975
12138
  } else {
10976
12139
  printError("Missing YAML path. Pass a file or --all.");
10977
12140
  return 2;
@@ -10990,7 +12153,7 @@ var JunctionNewCommand = class extends Command10 {
10990
12153
  }
10991
12154
  if (invalid.length > 0) {
10992
12155
  for (const i of invalid) {
10993
- printError(`${path32.basename(i.file)} \u2014 ${i.message}`);
12156
+ printError(`${path33.basename(i.file)} \u2014 ${i.message}`);
10994
12157
  }
10995
12158
  if (!isJsonMode()) return 1;
10996
12159
  }
@@ -11021,7 +12184,7 @@ var JunctionNewCommand = class extends Command10 {
11021
12184
  }
11022
12185
  const succeeded = [];
11023
12186
  const failed = [
11024
- ...invalid.map((i) => ({ name: path32.basename(i.file), file: i.file, message: i.message }))
12187
+ ...invalid.map((i) => ({ name: path33.basename(i.file), file: i.file, message: i.message }))
11025
12188
  ];
11026
12189
  for (const v of validated) {
11027
12190
  if (!isJsonMode()) {
@@ -11040,9 +12203,9 @@ var JunctionNewCommand = class extends Command10 {
11040
12203
  if (!isJsonMode()) printError(`${v.name} \u2014 ${res.stderr ?? "failed"}`);
11041
12204
  }
11042
12205
  }
11043
- const entitiesDir = ctx.entitiesDir ?? path32.resolve(ctx.cwd, "entities");
11044
- const relationshipsDir = path32.resolve(ctx.cwd, "relationships");
11045
- const junctionsDir = path32.resolve(ctx.cwd, "junctions");
12206
+ const entitiesDir = ctx.entitiesDir ?? path33.resolve(ctx.cwd, "entities");
12207
+ const relationshipsDir = path33.resolve(ctx.cwd, "relationships");
12208
+ const junctionsDir = path33.resolve(ctx.cwd, "junctions");
11046
12209
  const generatedDir = resolveGeneratedDir(ctx);
11047
12210
  const architecture = resolveArchitecture(ctx);
11048
12211
  let barrelResult = null;
@@ -11088,7 +12251,7 @@ var JunctionNewCommand = class extends Command10 {
11088
12251
  }
11089
12252
  if (barrelResult) {
11090
12253
  printInfo(
11091
- `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path32.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path32.relative(ctx.cwd, barrelResult.schemaBarrel)}`
12254
+ `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path33.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path33.relative(ctx.cwd, barrelResult.schemaBarrel)}`
11092
12255
  );
11093
12256
  }
11094
12257
  }
@@ -11111,7 +12274,7 @@ var JunctionListCommand = class extends Command10 {
11111
12274
  json: this.json,
11112
12275
  skipDetection: true
11113
12276
  });
11114
- const junctionDir = path32.resolve(ctx.cwd, "junctions");
12277
+ const junctionDir = path33.resolve(ctx.cwd, "junctions");
11115
12278
  const files = listJunctionYamls(junctionDir);
11116
12279
  if (files.length === 0) {
11117
12280
  printInfo("No junction definitions found.");
@@ -11152,7 +12315,7 @@ var junction_default = junctionNoun;
11152
12315
 
11153
12316
  // src/cli/commands/events.ts
11154
12317
  import fs22 from "fs";
11155
- import path33 from "path";
12318
+ import path34 from "path";
11156
12319
  import ts2 from "typescript";
11157
12320
  import { Command as Command11, Option as Option11 } from "clipanion";
11158
12321
  function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
@@ -11288,7 +12451,7 @@ function suggestEventTypes(target, known, limit = 3) {
11288
12451
  return known.map((t) => ({ t, d: levenshtein(target, t) })).sort((a, b) => a.d - b.d).slice(0, limit).map((x) => x.t);
11289
12452
  }
11290
12453
  function renderConsumerReport(result, cwd) {
11291
- const rel2 = (p) => path33.relative(cwd, p) || p;
12454
+ const rel2 = (p) => path34.relative(cwd, p) || p;
11292
12455
  const lines = [];
11293
12456
  const total = result.tier3.length + result.tier2.length + result.tier1.length;
11294
12457
  lines.push(`Event: ${result.eventType}`);
@@ -11324,7 +12487,7 @@ function renderConsumerReport(result, cwd) {
11324
12487
  return lines;
11325
12488
  }
11326
12489
  function runConsumersScan(opts) {
11327
- const scanRoot = opts.scanRoot ?? path33.join(opts.cwd, "src");
12490
+ const scanRoot = opts.scanRoot ?? path34.join(opts.cwd, "src");
11328
12491
  const handlersDir = opts.handlersDir ?? scanRoot;
11329
12492
  const allTriggers = scanHandlerFiles(handlersDir);
11330
12493
  const tier3 = allTriggers.filter((t) => t.event === opts.eventType).map((t) => ({
@@ -11334,7 +12497,7 @@ function runConsumersScan(opts) {
11334
12497
  sourceLine: t.sourceLine
11335
12498
  }));
11336
12499
  const tier21 = fs22.existsSync(scanRoot) ? scanDirectoryForConsumers(scanRoot, opts.eventType) : { tier2: [], tier1: [], hasEventFlowImport: false };
11337
- const eventsGeneratedDir = opts.eventsGeneratedDir ?? path33.join(
12500
+ const eventsGeneratedDir = opts.eventsGeneratedDir ?? path34.join(
11338
12501
  resolveSubsystemsRootFromContext(opts.cwd, opts.config),
11339
12502
  "events",
11340
12503
  "generated"
@@ -11355,11 +12518,11 @@ function runConsumersScan(opts) {
11355
12518
  function resolveSubsystemsRootFromContext(cwd, config) {
11356
12519
  const configured = config?.paths?.subsystems;
11357
12520
  if (typeof configured === "string" && configured.length > 0) {
11358
- return path33.resolve(cwd, configured);
12521
+ return path34.resolve(cwd, configured);
11359
12522
  }
11360
12523
  const backendSrc = config?.paths?.backend_src;
11361
12524
  const base = typeof backendSrc === "string" && backendSrc.length > 0 ? backendSrc : "src";
11362
- return path33.resolve(cwd, base, "shared", "subsystems");
12525
+ return path34.resolve(cwd, base, "shared", "subsystems");
11363
12526
  }
11364
12527
  var EventsConsumersCommand = class extends Command11 {
11365
12528
  static paths = [["events", "consumers"]];
@@ -11448,7 +12611,7 @@ var eventsNoun = {
11448
12611
  var events_default = eventsNoun;
11449
12612
 
11450
12613
  // src/cli/commands/orchestration.ts
11451
- import path34 from "path";
12614
+ import path35 from "path";
11452
12615
  import { Command as Command12, Option as Option12 } from "clipanion";
11453
12616
  var DEFAULT_PATTERN_GLOBS = ["src/patterns/*.pattern.ts"];
11454
12617
  function resolvePatternGlobs(ctx) {
@@ -11462,10 +12625,10 @@ function resolveOrchestrationOutputRoot(ctx) {
11462
12625
  const paths = ctx.config?.paths;
11463
12626
  const explicit = paths?.orchestration_src;
11464
12627
  if (typeof explicit === "string" && explicit.length > 0) {
11465
- return path34.resolve(ctx.cwd, explicit);
12628
+ return path35.resolve(ctx.cwd, explicit);
11466
12629
  }
11467
12630
  const backendSrc = typeof paths?.backend_src === "string" && paths.backend_src.length > 0 ? paths.backend_src : "app/backend/src";
11468
- return path34.resolve(ctx.cwd, backendSrc, "orchestration");
12631
+ return path35.resolve(ctx.cwd, backendSrc, "orchestration");
11469
12632
  }
11470
12633
  async function reloadRegistry(ctx) {
11471
12634
  _resetRegistryForTests({ includeLibrary: false });
@@ -11554,12 +12717,12 @@ var OrchestrationGenCommand = class extends Command12 {
11554
12717
  );
11555
12718
  for (const f of result.files) {
11556
12719
  console.log(
11557
- ` ${theme.muted(icons.arrow)} ${path34.relative(ctx.cwd, f.outputPath)}`
12720
+ ` ${theme.muted(icons.arrow)} ${path35.relative(ctx.cwd, f.outputPath)}`
11558
12721
  );
11559
12722
  }
11560
12723
  } else {
11561
12724
  printSuccess(
11562
- `Emitted ${result.files.length} file(s) across ${targets.length} pattern(s) \u2192 ${path34.relative(ctx.cwd, outputRoot)}`
12725
+ `Emitted ${result.files.length} file(s) across ${targets.length} pattern(s) \u2192 ${path35.relative(ctx.cwd, outputRoot)}`
11563
12726
  );
11564
12727
  }
11565
12728
  return 0;
@@ -11725,7 +12888,7 @@ var update_default = UpdateShortcut;
11725
12888
  // src/cli/index.ts
11726
12889
  function readVersion() {
11727
12890
  try {
11728
- const pkgPath = join10(import.meta.dirname, "..", "..", "package.json");
12891
+ const pkgPath = join17(import.meta.dirname, "..", "..", "package.json");
11729
12892
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
11730
12893
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
11731
12894
  } catch {