@pattern-stack/codegen 0.17.2 → 0.19.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 (68) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +157 -2
  3. package/consumer-skills/codegen/SKILL.md +32 -0
  4. package/consumer-skills/entities/SKILL.md +2 -0
  5. package/dist/{chunk-I6UXRJ3Q.js → chunk-43SBT72G.js} +4 -4
  6. package/dist/{chunk-T6SCOJF4.js → chunk-7LKAMLV4.js} +4 -4
  7. package/dist/{chunk-IOQMMH6C.js → chunk-F7KN3U6U.js} +122 -8
  8. package/dist/chunk-F7KN3U6U.js.map +1 -0
  9. package/dist/{chunk-CZQUOIDY.js → chunk-J7JMVS2B.js} +4 -4
  10. package/dist/{chunk-KSTZIULO.js → chunk-K2I6XIK5.js} +4 -4
  11. package/dist/{chunk-ATVGYF3D.js → chunk-PKDS6QIJ.js} +7 -7
  12. package/dist/{chunk-KZDHMZ45.js → chunk-SNH35CNA.js} +8 -8
  13. package/dist/runtime/base-classes/index.js +24 -24
  14. package/dist/runtime/subsystems/analytics/analytics.module.js +2 -2
  15. package/dist/runtime/subsystems/analytics/index.js +4 -4
  16. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  17. package/dist/runtime/subsystems/auth/index.js +14 -14
  18. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +2 -2
  19. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +1 -1
  20. package/dist/runtime/subsystems/bridge/bridge.module.js +5 -5
  21. package/dist/runtime/subsystems/bridge/index.js +13 -13
  22. package/dist/runtime/subsystems/cache/cache.module.js +1 -1
  23. package/dist/runtime/subsystems/cache/index.js +3 -3
  24. package/dist/runtime/subsystems/index.js +94 -94
  25. package/dist/runtime/subsystems/integration/build-change-source.js +2 -2
  26. package/dist/runtime/subsystems/integration/index.js +36 -36
  27. package/dist/runtime/subsystems/integration/integration.module.js +4 -4
  28. package/dist/src/cli/index.js +1552 -254
  29. package/dist/src/cli/index.js.map +1 -1
  30. package/dist/src/index.d.ts +18 -0
  31. package/dist/src/index.js +12 -12
  32. package/package.json +1 -1
  33. package/src/config/locations.mjs +0 -6
  34. package/src/config/paths.mjs +0 -13
  35. package/templates/entity/new/prompt.js +12 -88
  36. package/dist/chunk-IOQMMH6C.js.map +0 -1
  37. package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +0 -7
  38. package/templates/entity/new/frontend/_inject-entities-import.ejs.t +0 -7
  39. package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +0 -10
  40. package/templates/entity/new/frontend/collections/_inject-index.ejs.t +0 -9
  41. package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +0 -9
  42. package/templates/entity/new/frontend/collections/collection.ejs.t +0 -86
  43. package/templates/entity/new/frontend/collections/collections-base.ejs.t +0 -35
  44. package/templates/entity/new/frontend/entity/collection.ejs.t +0 -173
  45. package/templates/entity/new/frontend/entity/combined.ejs.t +0 -505
  46. package/templates/entity/new/frontend/entity/fields.ejs.t +0 -105
  47. package/templates/entity/new/frontend/entity/hooks.ejs.t +0 -74
  48. package/templates/entity/new/frontend/entity/index.ejs.t +0 -22
  49. package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +0 -85
  50. package/templates/entity/new/frontend/entity/mutations.ejs.t +0 -39
  51. package/templates/entity/new/frontend/entity/types.ejs.t +0 -60
  52. package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +0 -7
  53. package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +0 -7
  54. package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +0 -7
  55. package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +0 -9
  56. package/templates/entity/new/frontend/store/_inject-collections.ejs.t +0 -9
  57. package/templates/entity/new/frontend/store/_inject-entity.ejs.t +0 -9
  58. package/templates/entity/new/frontend/store/_inject-import.ejs.t +0 -9
  59. package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +0 -9
  60. package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +0 -10
  61. package/templates/entity/new/frontend/store/hooks.ejs.t +0 -73
  62. package/templates/entity/new/frontend/unified-entity.ejs.t +0 -29
  63. /package/dist/{chunk-I6UXRJ3Q.js.map → chunk-43SBT72G.js.map} +0 -0
  64. /package/dist/{chunk-T6SCOJF4.js.map → chunk-7LKAMLV4.js.map} +0 -0
  65. /package/dist/{chunk-CZQUOIDY.js.map → chunk-J7JMVS2B.js.map} +0 -0
  66. /package/dist/{chunk-KSTZIULO.js.map → chunk-K2I6XIK5.js.map} +0 -0
  67. /package/dist/{chunk-ATVGYF3D.js.map → chunk-PKDS6QIJ.js.map} +0 -0
  68. /package/dist/{chunk-KZDHMZ45.js.map → chunk-SNH35CNA.js.map} +0 -0
@@ -17,11 +17,13 @@ import {
17
17
  getManifestDir,
18
18
  getOrchestrationPatternNames,
19
19
  getPendingSuggestions,
20
+ isActiveProvider,
20
21
  isManifestStale,
21
22
  loadAppPatterns,
22
23
  loadEntities,
23
24
  loadEntitiesFromYaml,
24
25
  loadEntityFromYaml,
26
+ loadEntityRegistry,
25
27
  loadEventFromYaml,
26
28
  loadJunctionFromYaml,
27
29
  loadProvidersFromYaml,
@@ -36,35 +38,35 @@ import {
36
38
  validateOrchestrationProject,
37
39
  validateProviders,
38
40
  writeManifest
39
- } from "../../chunk-IOQMMH6C.js";
41
+ } from "../../chunk-F7KN3U6U.js";
40
42
  import "../../chunk-KVOWSC5S.js";
41
- import "../../chunk-ATVGYF3D.js";
43
+ import "../../chunk-PKDS6QIJ.js";
44
+ import "../../chunk-PRWIX6UW.js";
42
45
  import "../../chunk-YK5JEVLX.js";
43
46
  import "../../chunk-EO2QPOKH.js";
44
- import "../../chunk-PRWIX6UW.js";
47
+ import "../../chunk-SQDOBLBP.js";
48
+ import "../../chunk-TDEHU73T.js";
49
+ import "../../chunk-LG57S2SC.js";
45
50
  import "../../chunk-XWBK3XJK.js";
51
+ import "../../chunk-S7C6TIIF.js";
52
+ import "../../chunk-MZ6GV4YF.js";
53
+ import "../../chunk-HNWZFNKP.js";
46
54
  import "../../chunk-AHV4GDYM.js";
47
- import "../../chunk-SQDOBLBP.js";
55
+ import "../../chunk-43SBT72G.js";
56
+ import "../../chunk-4MF3HKJA.js";
57
+ import "../../chunk-TIZXQU26.js";
48
58
  import "../../chunk-JEINYUJH.js";
49
59
  import {
50
60
  isDivisibleCursor
51
61
  } from "../../chunk-5TK7MEN4.js";
52
62
  import "../../chunk-4KNXX6TI.js";
53
63
  import "../../chunk-3CJFPU6Q.js";
54
- import "../../chunk-TDEHU73T.js";
55
- import "../../chunk-S7C6TIIF.js";
56
- import "../../chunk-MZ6GV4YF.js";
57
- import "../../chunk-LG57S2SC.js";
58
- import "../../chunk-HNWZFNKP.js";
59
- import "../../chunk-I6UXRJ3Q.js";
60
- import "../../chunk-TIZXQU26.js";
61
- import "../../chunk-4MF3HKJA.js";
62
64
  import "../../chunk-U64T4YZE.js";
63
65
  import "../../chunk-2E224ZSN.js";
64
66
 
65
67
  // src/cli/index.ts
66
68
  import { readFileSync as readFileSync6 } from "fs";
67
- import { join as join10 } from "path";
69
+ import { join as join18 } from "path";
68
70
  import { Builtins, Cli, Command as Command13 } from "clipanion";
69
71
 
70
72
  // src/cli/noun-module.ts
@@ -1099,7 +1101,7 @@ var icons = {
1099
1101
 
1100
1102
  // src/cli/commands/entity.ts
1101
1103
  import fs10 from "fs";
1102
- import path13 from "path";
1104
+ import path14 from "path";
1103
1105
  import { Command as Command2, Option as Option2 } from "clipanion";
1104
1106
 
1105
1107
  // src/cli/shared/hygen.ts
@@ -3847,8 +3849,8 @@ function generateIntegrationAggregator(surface, entries) {
3847
3849
  );
3848
3850
  const importLines = sorted.map((e) => {
3849
3851
  const cls = assemblyModuleClass(e.entityName, e.provider);
3850
- const path35 = `./modules/${e.provider}/${e.entityName}-integration.module`;
3851
- return `import { ${cls} } from '${path35}';`;
3852
+ const path36 = `./modules/${e.provider}/${e.entityName}-integration.module`;
3853
+ return `import { ${cls} } from '${path36}';`;
3852
3854
  }).join("\n");
3853
3855
  const membersInline = moduleClasses.join(", ");
3854
3856
  return `${generatedBanner(`surface: ${surface}`)}
@@ -4414,8 +4416,9 @@ function emitAdapters(opts) {
4414
4416
  };
4415
4417
  const entitiesBySurface = collectEntitiesBySurface(opts.entities);
4416
4418
  const entityByName = new Map(opts.entities.map((e) => [e.entity.name, e]));
4419
+ const activeProviders = opts.providers.filter((p) => isActiveProvider(p.definition));
4417
4420
  const bySurface = /* @__PURE__ */ new Map();
4418
- for (const { definition } of opts.providers) {
4421
+ for (const { definition } of activeProviders) {
4419
4422
  for (const surface of definition.surfaces) {
4420
4423
  if (!SURFACE_REGISTRY[surface]) {
4421
4424
  result.skippedSurfaces.push({
@@ -4430,7 +4433,7 @@ function emitAdapters(opts) {
4430
4433
  bySurface.set(surface, list);
4431
4434
  }
4432
4435
  }
4433
- const defBySlug = new Map(opts.providers.map((p) => [p.definition.slug, p.definition]));
4436
+ const defBySlug = new Map(activeProviders.map((p) => [p.definition.slug, p.definition]));
4434
4437
  for (const [surface, slugs] of bySurface) {
4435
4438
  const surfaceDir = join8(opts.outputRoot, surface);
4436
4439
  const adaptersDir = join8(surfaceDir, "adapters");
@@ -4472,9 +4475,9 @@ function emitAdapters(opts) {
4472
4475
  [aggregatorPath, generateSurfaceAggregator(surface, slugs, mode)],
4473
4476
  [typedViewPath, generateTypedView(surface, slugs, entitiesBySurface.get(surface) ?? [])]
4474
4477
  ];
4475
- for (const [path35, content] of files) {
4476
- if (!opts.dryRun) writeIfChanged(path35, content);
4477
- result.written.push(path35);
4478
+ for (const [path36, content] of files) {
4479
+ if (!opts.dryRun) writeIfChanged(path36, content);
4480
+ result.written.push(path36);
4478
4481
  }
4479
4482
  if (opts.backendSrcAbs) {
4480
4483
  const aliases = opts.aliases ?? {};
@@ -4792,6 +4795,7 @@ function generateProviderModules(opts) {
4792
4795
  const mode = opts.mode ?? "package";
4793
4796
  const written = [];
4794
4797
  for (const { definition, filePath } of loaded) {
4798
+ if (!isActiveProvider(definition)) continue;
4795
4799
  const sourceYaml = relativeSource(filePath);
4796
4800
  const content = generateProviderModule(definition, sourceYaml, mode);
4797
4801
  const outPath = join9(
@@ -4819,15 +4823,1199 @@ function relativeSource(filePath) {
4819
4823
  return slash === -1 ? filePath : `definitions/providers/${filePath.slice(slash + 1)}`;
4820
4824
  }
4821
4825
 
4822
- // src/cli/shared/events-path.ts
4826
+ // src/emitters/frontend/emit-base.ts
4827
+ import { join as join10 } from "path";
4828
+
4829
+ // src/emitters/frontend/types.ts
4830
+ function sortEntities(entities) {
4831
+ return [...entities].sort((a, b) => a.name.localeCompare(b.name));
4832
+ }
4833
+ function resolveSyncMode(entity, config) {
4834
+ return entity.sync ?? config.globalSyncMode;
4835
+ }
4836
+
4837
+ // src/emitters/frontend/emit-utils.ts
4838
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
4839
+ import { dirname as dirname3 } from "path";
4840
+ function generatedBanner3(sourceDesc) {
4841
+ return `// @generated by @pattern-stack/codegen from ${sourceDesc} \u2014 DO NOT EDIT.
4842
+ // Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`.`;
4843
+ }
4844
+ function withBanner(sourceDesc, body) {
4845
+ return `${generatedBanner3(sourceDesc)}
4846
+
4847
+ ${body}`;
4848
+ }
4849
+ function writeFile2(outPath, content) {
4850
+ mkdirSync3(dirname3(outPath), { recursive: true });
4851
+ writeFileSync3(outPath, content);
4852
+ }
4853
+
4854
+ // src/emitters/frontend/emit-base.ts
4855
+ var SOURCE_DESC = "the entity set";
4856
+ function buildQueryClientFile() {
4857
+ const body = `import { QueryClient } from '@tanstack/react-query';
4858
+
4859
+ /**
4860
+ * Shared QueryClient for REST-backed (\`api\` sync mode) collections.
4861
+ *
4862
+ * Replaceable: swap this for your app's own QueryClient if you already create
4863
+ * one \u2014 every generated collection imports \`queryClient\` from this module.
4864
+ */
4865
+ export const queryClient = new QueryClient({
4866
+ defaultOptions: {
4867
+ queries: {
4868
+ staleTime: 60 * 1000, // 60s
4869
+ gcTime: 5 * 60 * 1000, // 5m
4870
+ },
4871
+ },
4872
+ });
4873
+ `;
4874
+ return withBanner(SOURCE_DESC, body);
4875
+ }
4876
+ function buildConfigFile(ctx) {
4877
+ const entities = sortEntities(ctx.entities);
4878
+ const entityNameUnion = entities.length > 0 ? entities.map((e) => `'${e.name}'`).join(" | ") : "string";
4879
+ const defaultEntries = entities.map((e) => ` ${e.name}: { mode: '${resolveSyncMode(e, ctx.config)}' },`).join("\n");
4880
+ const body = `/**
4881
+ * Per-entity sync configuration + runtime overrides.
4882
+ *
4883
+ * \`mode\` selects the collection backing for an entity:
4884
+ * - 'electric' \u2192 real-time shape sync (electricCollectionOptions)
4885
+ * - 'api' \u2192 REST via TanStack Query (queryCollectionOptions)
4886
+ *
4887
+ * The offline mode (Electric + Dexie) is deferred \u2014 see
4888
+ * docs/specs/2026-06-04-frontend-pipeline-rebuild.md OQ-6.
4889
+ */
4890
+
4891
+ export type SyncMode = 'api' | 'electric';
4892
+
4893
+ export type EntityName = ${entityNameUnion};
4894
+
4895
+ export interface EntitySyncConfig {
4896
+ mode: SyncMode;
4897
+ }
4898
+
4899
+ /** Resolved per-entity sync modes (per-entity \`sync:\` over global default). */
4900
+ export const defaultConfig: Record<EntityName, EntitySyncConfig> = {
4901
+ ${defaultEntries}
4902
+ };
4903
+
4904
+ /** Runtime overrides, layered over \`defaultConfig\`. */
4905
+ const overrides: Partial<Record<EntityName, EntitySyncConfig>> = {};
4906
+
4907
+ /** Resolve an entity's effective sync mode (override wins over default). */
4908
+ export function getSyncMode(entity: EntityName): SyncMode {
4909
+ return (overrides[entity] ?? defaultConfig[entity]).mode;
4910
+ }
4911
+
4912
+ /** Override an entity's sync config at runtime. */
4913
+ export function setEntityConfig(entity: EntityName, config: EntitySyncConfig): void {
4914
+ overrides[entity] = config;
4915
+ }
4916
+ `;
4917
+ return withBanner(SOURCE_DESC, body);
4918
+ }
4919
+ function emitBase(ctx, outDir) {
4920
+ const written = [];
4921
+ const queryClientPath = join10(outDir, "query-client.ts");
4922
+ writeFile2(queryClientPath, buildQueryClientFile());
4923
+ written.push(queryClientPath);
4924
+ const configPath = join10(outDir, "config.ts");
4925
+ writeFile2(configPath, buildConfigFile(ctx));
4926
+ written.push(configPath);
4927
+ return written;
4928
+ }
4929
+
4930
+ // src/emitters/frontend/emit-api.ts
4931
+ import { join as join11 } from "path";
4932
+ var SOURCE_DESC_SET = "the entity set";
4933
+ function updateVerb(architecture) {
4934
+ return architecture === "clean-lite-ps" ? "PATCH" : "PUT";
4935
+ }
4936
+ function buildClientFile(ctx) {
4937
+ const { config } = ctx;
4938
+ const importLines = [];
4939
+ if (config.apiBaseUrlImport) {
4940
+ importLines.push(`import { API_BASE_URL } from '${config.apiBaseUrlImport}';`);
4941
+ }
4942
+ if (config.authFunction) {
4943
+ importLines.push(`import { ${config.authFunction} } from '${config.authImport}';`);
4944
+ }
4945
+ const baseUrlConst = config.apiBaseUrlImport ? "const BASE_URL = API_BASE_URL;" : `const BASE_URL = '${config.apiUrl}';`;
4946
+ const authHeaderBlock = config.authFunction ? ` const headers: Record<string, string> = {
4947
+ 'Content-Type': 'application/json',
4948
+ Authorization: ${config.authFunction}(),
4949
+ };` : ` const headers: Record<string, string> = {
4950
+ 'Content-Type': 'application/json',
4951
+ };`;
4952
+ const imports = importLines.length > 0 ? `${importLines.join("\n")}
4953
+
4954
+ ` : "";
4955
+ const body = `${imports}${baseUrlConst}
4956
+
4957
+ /**
4958
+ * Base REST transport for \`api\` sync-mode collections and entity api clients.
4959
+ * Throws on non-2xx; returns parsed JSON, or \`undefined\` for 204 No Content.
4960
+ */
4961
+ export async function request<T>(
4962
+ method: string,
4963
+ path: string,
4964
+ body?: unknown,
4965
+ ): Promise<T> {
4966
+ ${authHeaderBlock}
4967
+
4968
+ const res = await fetch(\`\${BASE_URL}\${path}\`, {
4969
+ method,
4970
+ headers,
4971
+ body: body === undefined ? undefined : JSON.stringify(body),
4972
+ });
4973
+
4974
+ if (!res.ok) {
4975
+ throw new Error(\`\${method} \${path} \u2192 \${res.status} \${res.statusText}\`);
4976
+ }
4977
+
4978
+ if (res.status === 204) {
4979
+ return undefined as T;
4980
+ }
4981
+
4982
+ return res.json() as Promise<T>;
4983
+ }
4984
+ `;
4985
+ return withBanner(SOURCE_DESC_SET, body);
4986
+ }
4987
+ function buildEntityApiFile(entity, ctx) {
4988
+ const { config } = ctx;
4989
+ const { camelName, plural, className, name } = entity;
4990
+ const verb = updateVerb(config.architecture);
4991
+ const body = `import { request } from './client';
4992
+ import type { ${className} } from '${config.dbEntitiesImport}/${name}';
4993
+
4994
+ export const ${camelName}Api = {
4995
+ list: (): Promise<${className}[]> => request<${className}[]>('GET', '/${plural}'),
4996
+
4997
+ get: (id: string): Promise<${className}> =>
4998
+ request<${className}>('GET', \`/${plural}/\${id}\`),
4999
+
5000
+ create: (data: Partial<${className}>): Promise<${className}> =>
5001
+ request<${className}>('POST', '/${plural}', data),
5002
+
5003
+ update: (id: string, data: Partial<${className}>): Promise<${className}> =>
5004
+ request<${className}>('${verb}', \`/${plural}/\${id}\`, data),
5005
+
5006
+ delete: (id: string): Promise<void> =>
5007
+ request<void>('DELETE', \`/${plural}/\${id}\`),
5008
+ };
5009
+ `;
5010
+ return withBanner(`entities/${name}.yaml`, body);
5011
+ }
5012
+ function buildApiIndexFile(ctx) {
5013
+ const entities = sortEntities(ctx.entities);
5014
+ const lines = [
5015
+ "export * from './client';",
5016
+ ...entities.map((e) => `export * from './${e.name}';`)
5017
+ ];
5018
+ return withBanner(SOURCE_DESC_SET, `${lines.join("\n")}
5019
+ `);
5020
+ }
5021
+ function emitApi(ctx, outDir) {
5022
+ const apiDir = join11(outDir, "api");
5023
+ const entities = sortEntities(ctx.entities);
5024
+ const written = [];
5025
+ const clientPath = join11(apiDir, "client.ts");
5026
+ writeFile2(clientPath, buildClientFile(ctx));
5027
+ written.push(clientPath);
5028
+ for (const entity of entities) {
5029
+ const entityPath = join11(apiDir, `${entity.name}.ts`);
5030
+ writeFile2(entityPath, buildEntityApiFile(entity, ctx));
5031
+ written.push(entityPath);
5032
+ }
5033
+ const indexPath = join11(apiDir, "index.ts");
5034
+ writeFile2(indexPath, buildApiIndexFile(ctx));
5035
+ written.push(indexPath);
5036
+ return written;
5037
+ }
5038
+
5039
+ // src/emitters/frontend/emit-collections.ts
5040
+ import { join as join12 } from "path";
5041
+ var SOURCE_DESC_SET2 = "the entity set";
5042
+ function baseUrlExpr(base, plural, apiBaseUrlImport) {
5043
+ return apiBaseUrlImport ? `\`\${API_BASE_URL}/${plural}\`` : `\`${base}/${plural}\``;
5044
+ }
5045
+ var SSR_ORIGIN_EXPR = "typeof window !== 'undefined' ? window.location.origin : ''";
5046
+ function buildElectricCollection(entity, ctx) {
5047
+ const { config } = ctx;
5048
+ const { camelName, plural, name } = entity;
5049
+ const imports = [
5050
+ "import { electricCollectionOptions } from '@tanstack/electric-db-collection';",
5051
+ "import { createCollection } from '@tanstack/react-db';"
5052
+ ];
5053
+ if (config.columnMapper) {
5054
+ imports.push(`import { ${config.columnMapper} } from '@electric-sql/client';`);
5055
+ }
5056
+ if (config.authFunction) {
5057
+ imports.push(`import { ${config.authFunction} } from '${config.authImport}';`);
5058
+ }
5059
+ if (config.apiBaseUrlImport) {
5060
+ imports.push(`import { API_BASE_URL } from '${config.apiBaseUrlImport}';`);
5061
+ }
5062
+ imports.push(`import { ${camelName}Schema } from '${config.dbEntitiesImport}/${name}';`);
5063
+ let urlBlock;
5064
+ if (config.useTableParam) {
5065
+ urlBlock = ` url: new URL(
5066
+ '${config.shapeUrl}',
5067
+ ${SSR_ORIGIN_EXPR},
5068
+ ).toString(),
5069
+ params: {
5070
+ table: '${plural}',
5071
+ },`;
5072
+ } else {
5073
+ const shapeUrl = baseUrlExpr(config.shapeUrl, plural, config.apiBaseUrlImport);
5074
+ urlBlock = ` url: new URL(
5075
+ ${shapeUrl},
5076
+ ${SSR_ORIGIN_EXPR},
5077
+ ).toString(),`;
5078
+ }
5079
+ const headersBlock = config.authFunction ? `
5080
+ headers: {
5081
+ Authorization: ${config.authFunction}(),
5082
+ },` : "";
5083
+ const parserEntries = Object.entries(config.parsers).map(([type, fn]) => ` ${type}: ${fn},`).join("\n");
5084
+ const parserBlock = parserEntries ? `
5085
+ parser: {
5086
+ ${parserEntries}
5087
+ },` : `
5088
+ parser: {},`;
5089
+ let columnMapperBlock = "";
5090
+ if (config.columnMapper) {
5091
+ const mapperExpr = config.columnMapperNeedsCall ? `${config.columnMapper}()` : config.columnMapper;
5092
+ columnMapperBlock = `
5093
+ columnMapper: ${mapperExpr},`;
5094
+ }
5095
+ const body = `${imports.join("\n")}
5096
+
5097
+ export const ${camelName}Collection = createCollection(
5098
+ electricCollectionOptions({
5099
+ id: '${plural}',
5100
+ shapeOptions: {
5101
+ ${urlBlock}${headersBlock}${parserBlock}${columnMapperBlock}
5102
+ },
5103
+ schema: ${camelName}Schema,
5104
+ getKey: (item) => item.id,
5105
+ }),
5106
+ );
5107
+ `;
5108
+ return withBanner(`entities/${name}.yaml`, body);
5109
+ }
5110
+ function buildApiCollection(entity, ctx) {
5111
+ const { config } = ctx;
5112
+ const { camelName, plural, name } = entity;
5113
+ const imports = [
5114
+ "import { queryCollectionOptions } from '@tanstack/query-db-collection';",
5115
+ "import { createCollection } from '@tanstack/react-db';",
5116
+ "import { queryClient } from '../query-client';",
5117
+ `import { ${camelName}Api } from '../api/${name}';`,
5118
+ `import { ${camelName}Schema } from '${config.dbEntitiesImport}/${name}';`
5119
+ ];
5120
+ const body = `${imports.join("\n")}
5121
+
5122
+ export const ${camelName}Collection = createCollection(
5123
+ queryCollectionOptions({
5124
+ id: '${plural}',
5125
+ queryKey: ['${plural}'],
5126
+ queryClient,
5127
+ queryFn: () => ${camelName}Api.list(),
5128
+ getKey: (item) => item.id,
5129
+ schema: ${camelName}Schema,
5130
+ }),
5131
+ );
5132
+ `;
5133
+ return withBanner(`entities/${name}.yaml`, body);
5134
+ }
5135
+ function buildCollectionFile(entity, ctx) {
5136
+ const mode = resolveSyncMode(entity, ctx.config);
5137
+ return mode === "api" ? buildApiCollection(entity, ctx) : buildElectricCollection(entity, ctx);
5138
+ }
5139
+ function buildCollectionsIndexFile(ctx) {
5140
+ const entities = sortEntities(ctx.entities);
5141
+ const lines = entities.map((e) => `export * from './${e.name}';`);
5142
+ return withBanner(SOURCE_DESC_SET2, `${lines.join("\n")}
5143
+ `);
5144
+ }
5145
+ function emitCollections(ctx, outDir) {
5146
+ const collectionsDir = join12(outDir, "collections");
5147
+ const entities = sortEntities(ctx.entities);
5148
+ const written = [];
5149
+ for (const entity of entities) {
5150
+ const filePath = join12(collectionsDir, `${entity.name}.ts`);
5151
+ writeFile2(filePath, buildCollectionFile(entity, ctx));
5152
+ written.push(filePath);
5153
+ }
5154
+ const indexPath = join12(collectionsDir, "index.ts");
5155
+ writeFile2(indexPath, buildCollectionsIndexFile(ctx));
5156
+ written.push(indexPath);
5157
+ return written;
5158
+ }
5159
+
5160
+ // src/emitters/frontend/emit-entities.ts
5161
+ import { join as join13 } from "path";
5162
+ var SOURCE_DESC_SET3 = "the entity set";
5163
+ function buildEntityHooksFile(entity, ctx) {
5164
+ const { camelName, className, name } = entity;
5165
+ const body = `import { createEntityHooks } from '@pattern-stack/frontend-patterns';
5166
+ import { ${camelName}Collection } from '../collections/${name}';
5167
+ import { ${camelName}Api } from '../api/${name}';
5168
+ import { getSyncMode } from '../config';
5169
+ import type { ${className} } from '${ctx.config.dbEntitiesImport}/${name}';
5170
+
5171
+ /**
5172
+ * Typed hooks for ${className}, wired via the framework factory.
5173
+ *
5174
+ * \`localFirst\` is resolved at call time from the entity's runtime sync mode
5175
+ * (\`getSyncMode('${name}')\`) \u2014 \`api\` mode is confirmed-write, everything else
5176
+ * is local-first (optimistic).
5177
+ */
5178
+ export const ${camelName}Hooks = createEntityHooks<${className}>({
5179
+ name: '${name}',
5180
+ collection: ${camelName}Collection,
5181
+ api: ${camelName}Api,
5182
+ localFirst: () => getSyncMode('${name}') !== 'api',
5183
+ });
5184
+
5185
+ // Per-entity hook re-exports for direct imports.
5186
+ export const {
5187
+ useList: use${className}List,
5188
+ useGet: use${className},
5189
+ useCreate: useCreate${className},
5190
+ useUpdate: useUpdate${className},
5191
+ useDelete: useDelete${className},
5192
+ keys: ${camelName}Keys,
5193
+ } = ${camelName}Hooks;
5194
+ `;
5195
+ return withBanner(`entities/${name}.yaml`, body);
5196
+ }
5197
+ function buildEntitiesIndexFile(ctx) {
5198
+ const entities = sortEntities(ctx.entities);
5199
+ const blocks = entities.map((e) => {
5200
+ const { camelName, className, name } = e;
5201
+ return `export {
5202
+ ${camelName}Hooks,
5203
+ use${className}List,
5204
+ use${className},
5205
+ useCreate${className},
5206
+ useUpdate${className},
5207
+ useDelete${className},
5208
+ ${camelName}Keys,
5209
+ } from './${name}';`;
5210
+ });
5211
+ return withBanner(SOURCE_DESC_SET3, `${blocks.join("\n\n")}
5212
+ `);
5213
+ }
5214
+ function emitEntities(ctx, outDir) {
5215
+ const entitiesDir = join13(outDir, "entities");
5216
+ const entities = sortEntities(ctx.entities);
5217
+ const written = [];
5218
+ for (const entity of entities) {
5219
+ const filePath = join13(entitiesDir, `${entity.name}.ts`);
5220
+ writeFile2(filePath, buildEntityHooksFile(entity, ctx));
5221
+ written.push(filePath);
5222
+ }
5223
+ const indexPath = join13(entitiesDir, "index.ts");
5224
+ writeFile2(indexPath, buildEntitiesIndexFile(ctx));
5225
+ written.push(indexPath);
5226
+ return written;
5227
+ }
5228
+
5229
+ // src/emitters/frontend/emit-store.ts
5230
+ import { join as join14 } from "path";
5231
+ var SOURCE_DESC_SET4 = "the entity set";
5232
+ var CAMEL = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
5233
+ function resolvableRels(entity, ctx) {
5234
+ const parsed = ctx.parsed.get(entity.name);
5235
+ if (!parsed) return [];
5236
+ const registryByName = new Map(ctx.entities.map((e) => [e.name, e]));
5237
+ const out = [];
5238
+ for (const rel2 of parsed.relationships.values()) {
5239
+ if (rel2.type !== "belongs_to") continue;
5240
+ const target = registryByName.get(rel2.target);
5241
+ if (!target) continue;
5242
+ out.push({
5243
+ propertyName: CAMEL(rel2.name),
5244
+ fieldNameCamel: CAMEL(fkField(rel2)),
5245
+ target
5246
+ });
5247
+ }
5248
+ out.sort((a, b) => a.propertyName.localeCompare(b.propertyName));
5249
+ return out;
5250
+ }
5251
+ function fkField(rel2) {
5252
+ return rel2.foreignKey && rel2.foreignKey.length > 0 ? rel2.foreignKey : `${rel2.target}_id`;
5253
+ }
5254
+ function buildStoreIndexFile(ctx) {
5255
+ const entities = sortEntities(ctx.entities);
5256
+ const hookImports = entities.map((e) => `import { ${e.camelName}Hooks } from '../entities/${e.name}';`).join("\n");
5257
+ const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
5258
+ const entityEntries = entities.map((e) => ` ${e.plural}: ${e.camelName}Hooks,`).join("\n");
5259
+ const collectionEntries = entities.map((e) => ` ${e.plural}: ${e.camelName}Collection,`).join("\n");
5260
+ const body = `import { createStore } from '@pattern-stack/frontend-patterns';
5261
+
5262
+ ${hookImports}
5263
+
5264
+ ${collectionImports}
5265
+
5266
+ /**
5267
+ * The application store \u2014 unified access to every entity.
5268
+ *
5269
+ * Entities and collections are keyed by their plural name:
5270
+ * store.${entities[0]?.plural ?? "things"}.useList()
5271
+ * store.resolve.<entity>(id)
5272
+ * store.lookups.build()
5273
+ */
5274
+ export const store = createStore({
5275
+ entities: {
5276
+ ${entityEntries}
5277
+ },
5278
+ collections: {
5279
+ ${collectionEntries}
5280
+ },
5281
+ });
5282
+
5283
+ /** Store type for the \`useStore\` hook. */
5284
+ export type AppStore = typeof store;
5285
+ `;
5286
+ return withBanner(SOURCE_DESC_SET4, body);
5287
+ }
5288
+ function buildResolversFile(ctx) {
5289
+ const entities = sortEntities(ctx.entities);
5290
+ const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
5291
+ const typeImports = entities.map((e) => `import type { ${e.className} } from '${ctx.config.dbEntitiesImport}/${e.name}';`).join("\n");
5292
+ const resolverIface = entities.map(
5293
+ (e) => ` ${e.camelName}: (id: string | null | undefined) => ${e.className} | undefined;`
5294
+ ).join("\n");
5295
+ const resolverImpls = entities.map(
5296
+ (e) => ` ${e.camelName}: (id) => {
5297
+ if (!id) return undefined;
5298
+ return ${e.camelName}Collection.state.get(id) as ${e.className} | undefined;
5299
+ },`
5300
+ ).join("\n");
5301
+ const refBlocks = [];
5302
+ for (const e of entities) {
5303
+ const rels = resolvableRels(e, ctx);
5304
+ if (rels.length === 0) continue;
5305
+ const refFields = rels.map(
5306
+ (r) => ` ${r.propertyName}: ${r.target.className} | undefined;`
5307
+ ).join("\n");
5308
+ const hydrateFields = rels.map(
5309
+ (r) => ` ${r.propertyName}: resolvers.${r.target.camelName}(entity.${r.fieldNameCamel}),`
5310
+ ).join("\n");
5311
+ refBlocks.push(`/** Resolved FK references for ${e.className}. */
5312
+ export interface ${e.className}Refs {
5313
+ ${refFields}
5314
+ }
5315
+
5316
+ /** Hydrate a ${e.className} with its resolved FK references. */
5317
+ export function resolve${e.className}Refs(
5318
+ entity: ${e.className},
5319
+ resolvers: Resolvers,
5320
+ ): ${e.className} & ${e.className}Refs {
5321
+ return {
5322
+ ...entity,
5323
+ ${hydrateFields}
5324
+ };
5325
+ }`);
5326
+ }
5327
+ const refsSection = refBlocks.length > 0 ? `
5328
+ // ${"=".repeat(73)}
5329
+ // WithResolved helpers \u2014 hydrate entities with resolved FKs
5330
+ // ${"=".repeat(73)}
5331
+
5332
+ ${refBlocks.join("\n\n")}
5333
+ ` : "";
5334
+ const body = `${collectionImports}
5335
+ ${typeImports}
5336
+
5337
+ /**
5338
+ * FK resolvers \u2014 resolve a foreign-key id to the full entity object via the
5339
+ * backing collection's local state (\`O(1)\` \`Map.get\`).
5340
+ *
5341
+ * Usage:
5342
+ * const ${entities[0]?.camelName ?? "thing"} = resolvers.${entities[0]?.camelName ?? "thing"}(other.${entities[0]?.camelName ?? "thing"}Id);
5343
+ */
5344
+ export interface Resolvers {
5345
+ ${resolverIface}
5346
+ }
5347
+
5348
+ /** Build the resolver table over the generated collections. */
5349
+ export function createResolvers(): Resolvers {
5350
+ return {
5351
+ ${resolverImpls}
5352
+ };
5353
+ }
5354
+ ${refsSection}`;
5355
+ return withBanner(SOURCE_DESC_SET4, body);
5356
+ }
5357
+ function buildLookupsFile(ctx) {
5358
+ const entities = sortEntities(ctx.entities);
5359
+ const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
5360
+ const typeImports = entities.map((e) => `import type { ${e.className} } from '${ctx.config.dbEntitiesImport}/${e.name}';`).join("\n");
5361
+ const lookupIface = entities.map((e) => ` ${e.plural}: Map<string, ${e.className}>;`).join("\n");
5362
+ const lookupBuild = entities.map(
5363
+ (e) => ` ${e.plural}: new Map(
5364
+ Array.from(${e.camelName}Collection.state.values()).map((item) => [
5365
+ (item as ${e.className}).id as string,
5366
+ item as ${e.className},
5367
+ ]),
5368
+ ),`
5369
+ ).join("\n");
5370
+ const body = `${collectionImports}
5371
+ ${typeImports}
5372
+
5373
+ /** All entity lookup maps, keyed by plural entity name (id \u2192 entity). */
5374
+ export interface EntityLookups {
5375
+ ${lookupIface}
5376
+ }
5377
+
5378
+ /** Build fresh lookup maps from current collection state. */
5379
+ export function buildLookups(): EntityLookups {
5380
+ return {
5381
+ ${lookupBuild}
5382
+ };
5383
+ }
5384
+
5385
+ /** Caching lookup factory: \`build()\` (re)computes, \`current\` reads, \`clear()\` resets. */
5386
+ export function createLookups() {
5387
+ let cache: EntityLookups | null = null;
5388
+ return {
5389
+ build: (): EntityLookups => {
5390
+ cache = buildLookups();
5391
+ return cache;
5392
+ },
5393
+ get current(): EntityLookups | null {
5394
+ return cache;
5395
+ },
5396
+ clear: (): void => {
5397
+ cache = null;
5398
+ },
5399
+ };
5400
+ }
5401
+ `;
5402
+ return withBanner(SOURCE_DESC_SET4, body);
5403
+ }
5404
+ function buildStoreModuleIndexFile(ctx) {
5405
+ const entities = sortEntities(ctx.entities);
5406
+ const lines = [
5407
+ "export { store, type AppStore } from './index';",
5408
+ "export { createResolvers, type Resolvers } from './resolvers';",
5409
+ "export { buildLookups, createLookups, type EntityLookups } from './lookups';"
5410
+ ];
5411
+ const refExports = entities.filter((e) => resolvableRels(e, ctx).length > 0).map(
5412
+ (e) => `export { resolve${e.className}Refs, type ${e.className}Refs } from './resolvers';`
5413
+ );
5414
+ if (refExports.length > 0) {
5415
+ lines.push("", ...refExports);
5416
+ }
5417
+ return withBanner(SOURCE_DESC_SET4, `${lines.join("\n")}
5418
+ `);
5419
+ }
5420
+ function emitStore(ctx, outDir) {
5421
+ const storeDir = join14(outDir, "store");
5422
+ const written = [];
5423
+ const files = [
5424
+ ["index.ts", buildStoreIndexFile(ctx)],
5425
+ ["resolvers.ts", buildResolversFile(ctx)],
5426
+ ["lookups.ts", buildLookupsFile(ctx)],
5427
+ ["module-index.ts", buildStoreModuleIndexFile(ctx)]
5428
+ ];
5429
+ for (const [fileName, content] of files) {
5430
+ const filePath = join14(storeDir, fileName);
5431
+ writeFile2(filePath, content);
5432
+ written.push(filePath);
5433
+ }
5434
+ return written;
5435
+ }
5436
+
5437
+ // src/emitters/frontend/emit-fields.ts
5438
+ import { join as join15 } from "path";
5439
+
5440
+ // src/emitters/frontend/field-meta.ts
5441
+ var CAMEL2 = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
5442
+ function formatLabel(fieldName) {
5443
+ return fieldName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
5444
+ }
5445
+ function inferUiType(field) {
5446
+ if (field.ui.type) return field.ui.type;
5447
+ if (Array.isArray(field.choices) && field.choices.length > 0) return "enum";
5448
+ if (field.foreignKey) return "reference";
5449
+ const nameLower = field.name.toLowerCase();
5450
+ if (nameLower.includes("email")) return "email";
5451
+ if (nameLower.includes("url") || nameLower.includes("website")) return "url";
5452
+ if (nameLower.includes("password")) return "password";
5453
+ if (nameLower.includes("price") || nameLower.includes("amount") || nameLower.includes("cost") || nameLower.includes("value") || nameLower.includes("revenue")) {
5454
+ return "money";
5455
+ }
5456
+ if (nameLower.includes("percent") || nameLower.includes("rate")) {
5457
+ return "percentage";
5458
+ }
5459
+ switch (field.type) {
5460
+ case "string":
5461
+ return field.constraints.maxLength && field.constraints.maxLength > 500 ? "textarea" : "text";
5462
+ case "integer":
5463
+ case "decimal":
5464
+ return "number";
5465
+ case "boolean":
5466
+ return "boolean";
5467
+ case "uuid":
5468
+ return "text";
5469
+ case "date":
5470
+ return "date";
5471
+ case "datetime":
5472
+ return "datetime";
5473
+ case "json":
5474
+ return "json";
5475
+ default:
5476
+ return "text";
5477
+ }
5478
+ }
5479
+ function inferUiImportance(field) {
5480
+ if (field.ui.importance) return field.ui.importance;
5481
+ const nameLower = field.name.toLowerCase();
5482
+ if (["id", "created_at", "updated_at", "deleted_at"].includes(nameLower)) {
5483
+ return "tertiary";
5484
+ }
5485
+ if (field.foreignKey && nameLower.endsWith("_id")) return "secondary";
5486
+ if (field.required) return "primary";
5487
+ if (nameLower.includes("name") || nameLower.includes("title")) return "primary";
5488
+ return "secondary";
5489
+ }
5490
+ function isEntityRefField(field) {
5491
+ if (field.type === "entity_ref") return true;
5492
+ return field.name.endsWith("_entity_type") || field.name.endsWith("_entity_id");
5493
+ }
5494
+ function deriveFieldMeta(field) {
5495
+ const hasChoices = Array.isArray(field.choices) && field.choices.length > 0;
5496
+ const meta = {
5497
+ field: CAMEL2(field.name),
5498
+ label: field.ui.label ?? formatLabel(field.name),
5499
+ type: inferUiType(field),
5500
+ importance: inferUiImportance(field),
5501
+ sortable: field.ui.sortable ?? false,
5502
+ filterable: field.ui.filterable ?? false
5503
+ };
5504
+ if (hasChoices) meta.choices = field.choices;
5505
+ if (field.foreignKey) meta.reference = field.foreignKey.table;
5506
+ return meta;
5507
+ }
5508
+
5509
+ // src/emitters/frontend/emit-fields.ts
5510
+ var SOURCE_DESC_SET5 = "the entity set";
5511
+ function buildFieldMetaTypeFile() {
5512
+ const body = `/**
5513
+ * Field metadata types for DataGrid, forms, and admin surfaces.
5514
+ */
5515
+
5516
+ export type FieldType =
5517
+ | 'text'
5518
+ | 'textarea'
5519
+ | 'number'
5520
+ | 'boolean'
5521
+ | 'date'
5522
+ | 'datetime'
5523
+ | 'email'
5524
+ | 'url'
5525
+ | 'password'
5526
+ | 'money'
5527
+ | 'percentage'
5528
+ | 'json'
5529
+ | 'enum'
5530
+ | 'reference'
5531
+ | 'entity';
5532
+
5533
+ export type FieldImportance = 'primary' | 'secondary' | 'tertiary';
5534
+
5535
+ export interface FieldMeta<T = unknown> {
5536
+ /** Property key on the entity (\`keyof T\` for typed access). */
5537
+ field: keyof T & string;
5538
+ label: string;
5539
+ type: FieldType;
5540
+ importance: FieldImportance;
5541
+ sortable?: boolean;
5542
+ filterable?: boolean;
5543
+ format?: Record<string, unknown>;
5544
+ choices?: string[];
5545
+ reference?: string;
5546
+ }
5547
+ `;
5548
+ return withBanner(SOURCE_DESC_SET5, body);
5549
+ }
5550
+ function hasTimestamps(parsed) {
5551
+ return parsed?.behaviors.includes("timestamps") ?? false;
5552
+ }
5553
+ function displayFields(parsed) {
5554
+ if (!parsed) return [];
5555
+ const out = [];
5556
+ for (const field of parsed.fields.values()) {
5557
+ if (field.name === "id") continue;
5558
+ if (isEntityRefField(field)) continue;
5559
+ out.push(deriveFieldMeta(field));
5560
+ }
5561
+ return out;
5562
+ }
5563
+ function renderFieldMeta(meta) {
5564
+ const lines = [
5565
+ ` field: '${meta.field}',`,
5566
+ ` label: '${meta.label}',`,
5567
+ ` type: '${meta.type}' as FieldType,`,
5568
+ ` importance: '${meta.importance}' as FieldImportance,`
5569
+ ];
5570
+ if (meta.sortable) lines.push(" sortable: true,");
5571
+ if (meta.filterable) lines.push(" filterable: true,");
5572
+ if (meta.choices) lines.push(` choices: ${JSON.stringify(meta.choices)},`);
5573
+ if (meta.reference) lines.push(` reference: '${meta.reference}',`);
5574
+ return ` ${meta.field}: {
5575
+ ${lines.join("\n")}
5576
+ },`;
5577
+ }
5578
+ function humanizeClass(className) {
5579
+ return className.replace(/([A-Z])/g, " $1").trim();
5580
+ }
5581
+ function buildEntityFieldsFile(entity, ctx) {
5582
+ const parsed = ctx.parsed.get(entity.name);
5583
+ const { camelName, className, classNamePlural, name, plural } = entity;
5584
+ const fields = displayFields(parsed);
5585
+ const rels = resolvableRels(entity, ctx);
5586
+ const ts3 = hasTimestamps(parsed);
5587
+ const fieldEntries = fields.map(renderFieldMeta);
5588
+ const relEntries = rels.map(
5589
+ (r) => ` ${r.propertyName}: {
5590
+ field: '${r.propertyName}',
5591
+ label: '${humanizeClass(r.target.className)}',
5592
+ type: 'entity' as FieldType,
5593
+ importance: 'secondary' as FieldImportance,
5594
+ reference: '${r.target.plural}',
5595
+ },`
5596
+ );
5597
+ const tsEntries = ts3 ? [
5598
+ ` createdAt: {
5599
+ field: 'createdAt',
5600
+ label: 'Created',
5601
+ type: 'datetime' as FieldType,
5602
+ importance: 'tertiary' as FieldImportance,
5603
+ format: { dateFormat: 'relative' },
5604
+ },`,
5605
+ ` updatedAt: {
5606
+ field: 'updatedAt',
5607
+ label: 'Updated',
5608
+ type: 'datetime' as FieldType,
5609
+ importance: 'tertiary' as FieldImportance,
5610
+ format: { dateFormat: 'relative' },
5611
+ },`
5612
+ ] : [];
5613
+ const allEntries = [...fieldEntries, ...relEntries, ...tsEntries].join("\n");
5614
+ const primaryFields = fields.filter((f) => f.importance === "primary").map((f) => ` '${f.field}',`).join("\n");
5615
+ const searchFields = fields.filter((f) => f.filterable).map((f) => ` '${f.field}',`).join("\n");
5616
+ const defaultSortField = ts3 ? "createdAt" : "id";
5617
+ const expose = parsed?.expose ?? ["repository", "rest", "trpc"];
5618
+ const canWrite = expose.includes("repository") || expose.includes("trpc");
5619
+ const body = `import type { FieldMeta, FieldType, FieldImportance } from './field-meta';
5620
+ import type { ${className} } from '${ctx.config.dbEntitiesImport}/${name}';
5621
+
5622
+ export const ${camelName}Fields: Record<string, FieldMeta<${className}>> = {
5623
+ ${allEntries}
5624
+ };
5625
+
5626
+ export const ${camelName}Metadata = {
5627
+ name: '${name}',
5628
+ plural: '${plural}',
5629
+ displayName: '${humanizeClass(className)}',
5630
+ displayNamePlural: '${humanizeClass(classNamePlural)}',
5631
+
5632
+ fields: ${camelName}Fields,
5633
+
5634
+ primaryFields: [
5635
+ ${primaryFields}
5636
+ ],
5637
+ searchFields: [
5638
+ ${searchFields}
5639
+ ],
5640
+ defaultSort: { field: '${defaultSortField}', direction: 'desc' as const },
5641
+
5642
+ capabilities: {
5643
+ create: ${canWrite},
5644
+ update: ${canWrite},
5645
+ delete: ${canWrite},
5646
+ list: true,
5647
+ get: true,
5648
+ },
5649
+ } as const;
5650
+ `;
5651
+ return withBanner(`entities/${name}.yaml`, body);
5652
+ }
5653
+ function buildFieldsIndexFile(ctx) {
5654
+ const entities = sortEntities(ctx.entities);
5655
+ const lines = entities.map((e) => `export * from './${e.name}';`);
5656
+ return withBanner(SOURCE_DESC_SET5, `${lines.join("\n")}
5657
+ `);
5658
+ }
5659
+ function emitFields(ctx, outDir) {
5660
+ const fieldsDir = join15(outDir, "fields");
5661
+ const entities = sortEntities(ctx.entities);
5662
+ const written = [];
5663
+ const typePath = join15(fieldsDir, "field-meta.ts");
5664
+ writeFile2(typePath, buildFieldMetaTypeFile());
5665
+ written.push(typePath);
5666
+ for (const entity of entities) {
5667
+ const filePath = join15(fieldsDir, `${entity.name}.ts`);
5668
+ writeFile2(filePath, buildEntityFieldsFile(entity, ctx));
5669
+ written.push(filePath);
5670
+ }
5671
+ const indexPath = join15(fieldsDir, "index.ts");
5672
+ writeFile2(indexPath, buildFieldsIndexFile(ctx));
5673
+ written.push(indexPath);
5674
+ return written;
5675
+ }
5676
+
5677
+ // src/emitters/frontend/emit-providers.ts
5678
+ import { join as join16 } from "path";
5679
+ var SOURCE_DESC2 = "definitions/providers";
5680
+ function vendorLiteral(p, indent) {
5681
+ const lines = [
5682
+ `${indent}{`,
5683
+ `${indent} provider: '${p.slug}',`,
5684
+ `${indent} name: '${(p.displayName ?? p.slug).replace(/'/g, "\\'")}',`,
5685
+ `${indent} planned: ${p.status === "planned"},`,
5686
+ `${indent} surfaces: [${p.surfaces.map((s) => `'${s}'`).join(", ")}],`
5687
+ ];
5688
+ if (p.display?.blurb) {
5689
+ lines.push(`${indent} blurb: '${p.display.blurb.replace(/'/g, "\\'")}',`);
5690
+ }
5691
+ if (p.display?.hint) {
5692
+ lines.push(`${indent} hint: '${p.display.hint.replace(/'/g, "\\'")}',`);
5693
+ }
5694
+ lines.push(`${indent}},`);
5695
+ return lines.join("\n");
5696
+ }
5697
+ function sortProviders(providers) {
5698
+ return [...providers].sort((a, b) => a.slug.localeCompare(b.slug));
5699
+ }
5700
+ function buildProvidersFile(ctx) {
5701
+ const providers = sortProviders(ctx.providers ?? []);
5702
+ const categories = ctx.config.catalogCategories;
5703
+ const flat = providers.map((p) => vendorLiteral(p, " ")).join("\n");
5704
+ const groups = categories.map((cat) => {
5705
+ const vendors = providers.filter((p) => p.display?.category === cat.id);
5706
+ const vendorBlock = vendors.map((p) => vendorLiteral(p, " ")).join("\n");
5707
+ return [
5708
+ " {",
5709
+ ` id: '${cat.id}',`,
5710
+ ` name: '${cat.name.replace(/'/g, "\\'")}',`,
5711
+ ` blurb: '${cat.blurb.replace(/'/g, "\\'")}',`,
5712
+ vendors.length > 0 ? ` vendors: [
5713
+ ${vendorBlock}
5714
+ ],` : " vendors: [],",
5715
+ " },"
5716
+ ].join("\n");
5717
+ }).join("\n");
5718
+ const body = `/**
5719
+ * Providers catalog \u2014 emitted from \`definitions/providers/*.yaml\` (slug,
5720
+ * display_name, surfaces, status, display) + \`frontend.catalog.categories\`
5721
+ * (codegen.config.yaml). Provider truth lives in the definitions; this file
5722
+ * is a projection \u2014 never hand-edit, never hand-duplicate.
5723
+ *
5724
+ * \`planned: true\` vendors are roadmap stubs (no backend integration yet) \u2014
5725
+ * render them as unconnectable tiles. Join live rows on \`provider\` (the
5726
+ * canonical slug, e.g. \`Connection.provider\`).
5727
+ */
5728
+
5729
+ export type ProviderStatus = 'active' | 'planned';
5730
+
5731
+ export interface CatalogVendor {
5732
+ /** Provider slug \u2014 joins to \`Connection.provider\` / STRATEGY_REGISTRY keys. */
5733
+ provider: string;
5734
+ name: string;
5735
+ /** True for roadmap stubs (\`status: planned\`) \u2014 no backend integration yet. */
5736
+ planned: boolean;
5737
+ /** Surfaces this provider serves (ADR-0006). */
5738
+ surfaces: string[];
5739
+ blurb?: string;
5740
+ /** Sub-line shown on an unconnected ("available") tile. */
5741
+ hint?: string;
5742
+ }
5743
+
5744
+ export interface CatalogCategory {
5745
+ id: string;
5746
+ name: string;
5747
+ blurb: string;
5748
+ vendors: CatalogVendor[];
5749
+ }
5750
+
5751
+ /** Every provider definition, flat (active + planned), slug-sorted. */
5752
+ export const PROVIDERS: CatalogVendor[] = [
5753
+ ${flat}
5754
+ ];
5755
+
5756
+ /**
5757
+ * Category-grouped catalog (\`frontend.catalog.categories\` order). Providers
5758
+ * join a group via \`display.category\`; uncategorized providers appear only
5759
+ * in \`PROVIDERS\`.
5760
+ */
5761
+ export const PROVIDER_CATALOG: CatalogCategory[] = [
5762
+ ${groups}
5763
+ ];
5764
+ `;
5765
+ return withBanner(SOURCE_DESC2, body);
5766
+ }
5767
+ function emitProviders(ctx, outDir) {
5768
+ if (!ctx.providers || ctx.providers.length === 0) return [];
5769
+ const path36 = join16(outDir, "providers.ts");
5770
+ writeFile2(path36, buildProvidersFile(ctx));
5771
+ return [path36];
5772
+ }
5773
+
5774
+ // src/emitters/frontend/emit-index.ts
5775
+ import { join as join17 } from "path";
5776
+
5777
+ // src/emitters/frontend/deps.ts
5778
+ var FRONTEND_EMITTED_DEPS = {
5779
+ "@pattern-stack/frontend-patterns": "^0.2.0-alpha.18",
5780
+ "@tanstack/react-db": "^0.1.55",
5781
+ "@tanstack/electric-db-collection": "^0.2.11",
5782
+ "@tanstack/query-db-collection": "^1.0.6",
5783
+ "@tanstack/react-query": "^5.0.0"
5784
+ };
5785
+
5786
+ // src/emitters/frontend/emit-index.ts
5787
+ var SOURCE_DESC_SET6 = "the entity set";
5788
+ function buildVersionPairingComment() {
5789
+ const entries = Object.entries(FRONTEND_EMITTED_DEPS);
5790
+ const nameWidth = Math.max(...entries.map(([name]) => name.length));
5791
+ const rows = entries.map(([name, range]) => ` * ${name.padEnd(nameWidth)} ${range}`).join("\n");
5792
+ return ` * Version pairing \u2014 the emitted imports require these package ranges in the
5793
+ * consumer's frontend package.json:
5794
+ *
5795
+ ${rows}`;
5796
+ }
5797
+ function buildRootIndexFile(ctx) {
5798
+ const entities = sortEntities(ctx.entities);
5799
+ const entityList = entities.map((e) => ` * - ${e.className}`).join("\n");
5800
+ const body = `/**
5801
+ * Generated frontend data layer.
5802
+ *
5803
+ * Entities:
5804
+ ${entityList || " * (none)"}
5805
+ *
5806
+ ${buildVersionPairingComment()}
5807
+ */
5808
+
5809
+ // Per-entity sync configuration + runtime overrides
5810
+ export * from './config';
5811
+
5812
+ // Shared TanStack QueryClient
5813
+ export * from './query-client';
5814
+
5815
+ // REST api client
5816
+ export * from './api/index';
5817
+
5818
+ // TanStack DB collections (per-entity sync mode)
5819
+ export * from './collections/index';
5820
+
5821
+ // Entity hooks (createEntityHooks wiring)
5822
+ export * from './entities/index';
5823
+
5824
+ // Field metadata (DataGrid / forms / admin)
5825
+ export * from './fields/index';
5826
+ ${ctx.providers && ctx.providers.length > 0 ? `
5827
+ // Providers catalog (definitions/providers + frontend.catalog.categories)
5828
+ export * from './providers';
5829
+ ` : ""}
5830
+ // Unified store (entities + collections + resolvers + lookups)
5831
+ export * from './store/module-index';
5832
+ `;
5833
+ return withBanner(SOURCE_DESC_SET6, body);
5834
+ }
5835
+ function emitIndex(ctx, outDir) {
5836
+ const indexPath = join17(outDir, "index.ts");
5837
+ writeFile2(indexPath, buildRootIndexFile(ctx));
5838
+ return [indexPath];
5839
+ }
5840
+
5841
+ // src/emitters/frontend/load-context.ts
5842
+ import { existsSync as existsSync8, statSync as statSync5 } from "fs";
4823
5843
  import path12 from "path";
5844
+
5845
+ // src/schema/codegen-config.schema.ts
5846
+ import { z } from "zod";
5847
+ var GenerateConfigSchema = z.object({
5848
+ /**
5849
+ * Backend architecture to generate. One of:
5850
+ * - 'clean' — Full Clean Architecture (domain + application + infrastructure + presentation)
5851
+ * - 'clean-lite-ps' — Clean-Lite-PS modules/{plural}/ layout
5852
+ *
5853
+ * Default: 'clean'.
5854
+ */
5855
+ architecture: z.enum(["clean", "clean-lite-ps"]).default("clean"),
5856
+ /**
5857
+ * Whether to emit the frontend pipeline (collections, hooks, entity metadata).
5858
+ * Default: false — backend-only projects opt out by default.
5859
+ */
5860
+ frontend: z.boolean().default(false),
5861
+ /**
5862
+ * Analytics backend to generate.
5863
+ * - 'none': no analytics layer (default)
5864
+ * - 'cube': generate cube.js semantic layer and analytics providers
5865
+ */
5866
+ analytics: z.enum(["none", "cube"]).default("none")
5867
+ }).passthrough();
5868
+ var PathsConfigSchema = z.object({
5869
+ events_dir: z.string().optional(),
5870
+ generated: z.string().default("src/generated")
5871
+ }).passthrough();
5872
+ var PatternsConfigSchema = z.array(z.string()).optional().default(["src/patterns/*.pattern.ts"]);
5873
+ var RuntimeModeSchema = z.enum(["package", "vendored"]).default("package");
5874
+ var FrontendAuthConfigSchema = z.object({
5875
+ function: z.string().nullable().default("getAuthorizationHeader")
5876
+ }).default({ function: "getAuthorizationHeader" });
5877
+ var FrontendSyncConfigSchema = z.object({
5878
+ mode: z.enum(["api", "electric"]).default("electric"),
5879
+ shapeUrl: z.string().default("/v1/shape"),
5880
+ useTableParam: z.boolean().default(true),
5881
+ columnMapper: z.string().nullable().default("snakeCamelMapper"),
5882
+ columnMapperNeedsCall: z.boolean().default(true),
5883
+ apiBaseUrlImport: z.string().nullable().default(null),
5884
+ apiUrl: z.string().default("/api")
5885
+ }).default({});
5886
+ var FrontendCatalogConfigSchema = z.object({
5887
+ categories: z.array(
5888
+ z.object({
5889
+ id: z.string(),
5890
+ name: z.string(),
5891
+ blurb: z.string().default("")
5892
+ }).strict()
5893
+ ).default([])
5894
+ }).default({});
5895
+ var FrontendConfigSchema = z.object({
5896
+ auth: FrontendAuthConfigSchema,
5897
+ parsers: z.record(z.string()).default({ timestamptz: "(date: string) => new Date(date)" }),
5898
+ sync: FrontendSyncConfigSchema,
5899
+ catalog: FrontendCatalogConfigSchema
5900
+ }).strict().default({});
5901
+
5902
+ // src/emitters/frontend/load-context.ts
5903
+ var DEFAULT_DB_ENTITIES = {
5904
+ path: "packages/db/src/entities",
5905
+ import: "@repo/db/entities"
5906
+ };
5907
+ var DEFAULT_FRONTEND_GENERATED = {
5908
+ path: "apps/frontend/src/generated",
5909
+ import: "@/generated"
5910
+ };
5911
+ var DEFAULT_FRONTEND_COLLECTIONS_AUTH = {
5912
+ path: "apps/frontend/src/lib/collections/auth",
5913
+ import: "@/lib/collections/auth"
5914
+ };
5915
+ function resolveLocation(config, key, fallback) {
5916
+ const override = config.locations?.[key];
5917
+ return {
5918
+ path: override?.path ?? fallback.path,
5919
+ import: override?.import ?? fallback.import
5920
+ };
5921
+ }
5922
+ function mapFrontendEmitConfig(config) {
5923
+ const parsed = FrontendConfigSchema.safeParse(config.frontend ?? {});
5924
+ const fe = parsed.success ? parsed.data : FrontendConfigSchema.parse({});
5925
+ const dbEntities = resolveLocation(config, "dbEntities", DEFAULT_DB_ENTITIES);
5926
+ const collectionsAuth = resolveLocation(
5927
+ config,
5928
+ "frontendCollectionsAuth",
5929
+ DEFAULT_FRONTEND_COLLECTIONS_AUTH
5930
+ );
5931
+ const architecture = config.generate?.architecture === "clean-lite-ps" ? "clean-lite-ps" : "clean";
5932
+ return {
5933
+ globalSyncMode: fe.sync.mode,
5934
+ // auth.function: absent → 'getAuthorizationHeader' (schema default), explicit
5935
+ // null → disabled (no header lines emitted).
5936
+ authFunction: fe.auth.function,
5937
+ authImport: collectionsAuth.import,
5938
+ shapeUrl: fe.sync.shapeUrl,
5939
+ useTableParam: fe.sync.useTableParam,
5940
+ columnMapper: fe.sync.columnMapper,
5941
+ columnMapperNeedsCall: fe.sync.columnMapperNeedsCall,
5942
+ apiUrl: fe.sync.apiUrl,
5943
+ apiBaseUrlImport: fe.sync.apiBaseUrlImport,
5944
+ parsers: fe.parsers,
5945
+ architecture,
5946
+ dbEntitiesImport: dbEntities.import,
5947
+ catalogCategories: fe.catalog.categories
5948
+ };
5949
+ }
5950
+ function loadProviderCatalogInputs(cwd, config) {
5951
+ const providersDir = path12.resolve(
5952
+ cwd,
5953
+ config.paths?.providers ?? "definitions/providers"
5954
+ );
5955
+ if (!existsSync8(providersDir) || !statSync5(providersDir).isDirectory()) {
5956
+ return [];
5957
+ }
5958
+ const files = findYamlFiles(providersDir);
5959
+ if (files.length === 0) return [];
5960
+ return loadProvidersFromYaml(files).successes.map((s) => ({
5961
+ slug: s.definition.slug,
5962
+ displayName: s.definition.display_name,
5963
+ surfaces: s.definition.surfaces,
5964
+ status: s.definition.status,
5965
+ display: s.definition.display
5966
+ }));
5967
+ }
5968
+ function loadFrontendEmitContext(cwd, config, opts = {}) {
5969
+ const entitiesDir = opts.entitiesDir ?? path12.resolve(cwd, config.paths?.entities_dir ?? "entities");
5970
+ const { registry } = loadEntityRegistry(entitiesDir);
5971
+ const entities = sortEntities([...registry.values()]);
5972
+ if (entities.length === 0) {
5973
+ return {
5974
+ skip: `no entities found in ${path12.relative(cwd, entitiesDir) || entitiesDir}`
5975
+ };
5976
+ }
5977
+ const parsedList = loadEntities(entitiesDir).entities;
5978
+ const parsed = new Map(
5979
+ parsedList.map((p) => [p.name, p])
5980
+ );
5981
+ const emitConfig = mapFrontendEmitConfig(config);
5982
+ const providers = loadProviderCatalogInputs(cwd, config);
5983
+ const generated = resolveLocation(
5984
+ config,
5985
+ "frontendGenerated",
5986
+ DEFAULT_FRONTEND_GENERATED
5987
+ );
5988
+ const outDir = path12.resolve(cwd, generated.path);
5989
+ return {
5990
+ skip: void 0,
5991
+ ctx: { entities, parsed, config: emitConfig, providers },
5992
+ outDir
5993
+ };
5994
+ }
5995
+
5996
+ // src/emitters/frontend/index.ts
5997
+ function emitFrontendSet(ctx, outDir) {
5998
+ return [
5999
+ ...emitBase(ctx, outDir),
6000
+ ...emitApi(ctx, outDir),
6001
+ ...emitCollections(ctx, outDir),
6002
+ ...emitEntities(ctx, outDir),
6003
+ ...emitStore(ctx, outDir),
6004
+ ...emitFields(ctx, outDir),
6005
+ ...emitProviders(ctx, outDir),
6006
+ ...emitIndex(ctx, outDir)
6007
+ ];
6008
+ }
6009
+
6010
+ // src/cli/shared/events-path.ts
6011
+ import path13 from "path";
4824
6012
  var FALLBACK = "events";
4825
6013
  function resolveEventsDirFromConfig(cwd, config) {
4826
6014
  const configured = config?.paths?.events_dir;
4827
6015
  if (typeof configured === "string" && configured.length > 0) {
4828
- return path12.resolve(cwd, configured);
6016
+ return path13.resolve(cwd, configured);
4829
6017
  }
4830
- return path12.resolve(cwd, FALLBACK);
6018
+ return path13.resolve(cwd, FALLBACK);
4831
6019
  }
4832
6020
  function resolveEventsDir(ctx) {
4833
6021
  return resolveEventsDirFromConfig(ctx.cwd, ctx.config);
@@ -4856,7 +6044,7 @@ function printInfo(msg) {
4856
6044
  // src/cli/commands/entity.ts
4857
6045
  function resolveProvidersDir(ctx) {
4858
6046
  const fromConfig = ctx.config?.paths?.providers;
4859
- return fromConfig != null ? path13.resolve(ctx.cwd, fromConfig) : path13.resolve(ctx.cwd, "definitions/providers");
6047
+ return fromConfig != null ? path14.resolve(ctx.cwd, fromConfig) : path14.resolve(ctx.cwd, "definitions/providers");
4860
6048
  }
4861
6049
  function listEntityYamls2(dir, providersDir) {
4862
6050
  if (!fs10.existsSync(dir)) return [];
@@ -4938,10 +6126,10 @@ async function hints(ctx) {
4938
6126
  { command: "codegen entity validate", description: "Validate YAML definitions" },
4939
6127
  { command: "codegen entity list", description: "List entities as a table" }
4940
6128
  ];
4941
- const providersDir = ctx.config?.paths?.providers != null ? path13.resolve(
6129
+ const providersDir = ctx.config?.paths?.providers != null ? path14.resolve(
4942
6130
  ctx.cwd,
4943
6131
  ctx.config.paths.providers
4944
- ) : path13.resolve(ctx.cwd, "definitions/providers");
6132
+ ) : path14.resolve(ctx.cwd, "definitions/providers");
4945
6133
  if (fs10.existsSync(providersDir)) {
4946
6134
  baseHints.push({
4947
6135
  command: "codegen entity new --all",
@@ -4997,14 +6185,14 @@ var EntityNewCommand = class extends Command2 {
4997
6185
  }
4998
6186
  let targets = [];
4999
6187
  if (this.all) {
5000
- const dir = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
6188
+ const dir = ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
5001
6189
  targets = listEntityYamls2(dir, resolveProvidersDir(ctx));
5002
6190
  if (targets.length === 0) {
5003
6191
  printError(`No entity YAML files found in ${dir}`);
5004
6192
  return 1;
5005
6193
  }
5006
6194
  } else if (this.yaml) {
5007
- targets = [path13.resolve(ctx.cwd, this.yaml)];
6195
+ targets = [path14.resolve(ctx.cwd, this.yaml)];
5008
6196
  } else {
5009
6197
  printError("Missing YAML path. Pass a file or --all.");
5010
6198
  return 2;
@@ -5021,7 +6209,7 @@ var EntityNewCommand = class extends Command2 {
5021
6209
  }
5022
6210
  if (invalid.length > 0 && !this.continueOnError) {
5023
6211
  for (const i of invalid) {
5024
- printError(`${path13.basename(i.file)} \u2014 ${i.message}`);
6212
+ printError(`${path14.basename(i.file)} \u2014 ${i.message}`);
5025
6213
  for (const detail of i.details ?? []) {
5026
6214
  printError(` \u2022 ${detail}`);
5027
6215
  }
@@ -5030,7 +6218,7 @@ var EntityNewCommand = class extends Command2 {
5030
6218
  return 1;
5031
6219
  }
5032
6220
  }
5033
- const entitiesDirForEmits = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
6221
+ const entitiesDirForEmits = ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
5034
6222
  const eventsDirForEmits = resolveEventsDir(ctx);
5035
6223
  const allEntitiesForEmits = loadEntities(entitiesDirForEmits, {
5036
6224
  excludeDirs: [resolveProvidersDir(ctx)]
@@ -5079,29 +6267,29 @@ var EntityNewCommand = class extends Command2 {
5079
6267
  if (!isJsonMode()) return 1;
5080
6268
  }
5081
6269
  }
5082
- const entitiesDir = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
5083
- const relationshipsDir = path13.resolve(ctx.cwd, "relationships");
6270
+ const entitiesDir = ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
6271
+ const relationshipsDir = path14.resolve(ctx.cwd, "relationships");
5084
6272
  const generatedDir = resolveGeneratedDir(ctx);
5085
6273
  const architecture = resolveArchitecture(ctx);
5086
6274
  const subsystemsRoot = resolveSubsystemsRoot(ctx);
5087
6275
  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");
6276
+ const scopeEntityTypePath = runtimeMode === "package" ? path14.resolve(generatedDir, "scope-entity-type.ts") : path14.resolve(subsystemsRoot, "jobs/generated/scope-entity-type.ts");
5089
6277
  const eventsDir = resolveEventsDir(ctx);
5090
- const eventCodegenOutputDir = runtimeMode === "package" ? path13.resolve(generatedDir, "events") : path13.resolve(subsystemsRoot, "events/generated");
6278
+ const eventCodegenOutputDir = runtimeMode === "package" ? path14.resolve(generatedDir, "events") : path14.resolve(subsystemsRoot, "events/generated");
5091
6279
  const bridgeInstalledForRegistry = configuredSubsystemNames(
5092
6280
  ctx.config
5093
6281
  ).includes("bridge");
5094
- const bridgeRegistryOutputDir = runtimeMode === "package" ? generatedDir : path13.resolve(subsystemsRoot, "bridge/generated");
6282
+ const bridgeRegistryOutputDir = runtimeMode === "package" ? generatedDir : path14.resolve(subsystemsRoot, "bridge/generated");
5095
6283
  const backendSrcForHandlers = ctx.config?.paths?.backend_src ?? "src";
5096
- const bridgeHandlersDir = path13.resolve(
6284
+ const bridgeHandlersDir = path14.resolve(
5097
6285
  ctx.cwd,
5098
6286
  backendSrcForHandlers,
5099
6287
  "jobs"
5100
6288
  );
5101
6289
  const orchestrationConfigured = ctx.config?.paths?.orchestration_src;
5102
- const orchestrationOutputRoot = path13.resolve(
6290
+ const orchestrationOutputRoot = path14.resolve(
5103
6291
  ctx.cwd,
5104
- typeof orchestrationConfigured === "string" && orchestrationConfigured.length > 0 ? orchestrationConfigured : path13.join(backendSrcForHandlers, "orchestration")
6292
+ typeof orchestrationConfigured === "string" && orchestrationConfigured.length > 0 ? orchestrationConfigured : path14.join(backendSrcForHandlers, "orchestration")
5105
6293
  );
5106
6294
  const orchestrationGlobs = (() => {
5107
6295
  const fromCfg = ctx.config?.patterns;
@@ -5232,7 +6420,7 @@ var EntityNewCommand = class extends Command2 {
5232
6420
  }
5233
6421
  if (invalid.length > 0) {
5234
6422
  for (const i of invalid) {
5235
- printWarning(`${path13.basename(i.file)} \u2014 ${i.message}`);
6423
+ printWarning(`${path14.basename(i.file)} \u2014 ${i.message}`);
5236
6424
  }
5237
6425
  }
5238
6426
  console.log("");
@@ -5250,7 +6438,7 @@ var EntityNewCommand = class extends Command2 {
5250
6438
  }
5251
6439
  const succeeded = [];
5252
6440
  const failed = [
5253
- ...invalid.map((i) => ({ name: path13.basename(i.file), file: i.file, message: i.message }))
6441
+ ...invalid.map((i) => ({ name: path14.basename(i.file), file: i.file, message: i.message }))
5254
6442
  ];
5255
6443
  for (const v of validated) {
5256
6444
  if (!isJsonMode()) {
@@ -5373,10 +6561,40 @@ var EntityNewCommand = class extends Command2 {
5373
6561
  }
5374
6562
  }
5375
6563
  }
6564
+ let frontendResult = null;
6565
+ const frontendEnabled = ctx.config?.generate?.frontend === true;
6566
+ if (frontendEnabled) {
6567
+ try {
6568
+ const loaded = loadFrontendEmitContext(
6569
+ ctx.cwd,
6570
+ ctx.config,
6571
+ { entitiesDir }
6572
+ );
6573
+ if (loaded.skip !== void 0) {
6574
+ if (!isJsonMode()) {
6575
+ printInfo(`frontend emission skipped \u2014 ${loaded.skip}`);
6576
+ }
6577
+ } else {
6578
+ const { ctx: frontendCtx, outDir: frontendOutDir } = loaded;
6579
+ const written = emitFrontendSet(frontendCtx, frontendOutDir);
6580
+ frontendResult = { written, outDir: frontendOutDir };
6581
+ if (!isJsonMode()) {
6582
+ printInfo(
6583
+ `frontend emitted (${written.length} files) \u2192 ${path14.relative(ctx.cwd, frontendOutDir)}`
6584
+ );
6585
+ }
6586
+ }
6587
+ } catch (err) {
6588
+ const msg = err instanceof Error ? err.message : String(err);
6589
+ if (!isJsonMode()) {
6590
+ printWarning(`frontend emission failed \u2014 ${msg}`);
6591
+ }
6592
+ }
6593
+ }
5376
6594
  let providerResult = null;
5377
6595
  try {
5378
6596
  const providersDir = resolveProvidersDir(ctx);
5379
- const providerOutputRoot = path13.resolve(
6597
+ const providerOutputRoot = path14.resolve(
5380
6598
  ctx.cwd,
5381
6599
  backendSrcForHandlers,
5382
6600
  "integrations/providers"
@@ -5418,7 +6636,7 @@ var EntityNewCommand = class extends Command2 {
5418
6636
  try {
5419
6637
  if (providerResult && !providerResult.skipped && providerResult.issues.length === 0) {
5420
6638
  const providersDir = providerResult.providersDir;
5421
- const adapterOutputRoot = path13.resolve(
6639
+ const adapterOutputRoot = path14.resolve(
5422
6640
  ctx.cwd,
5423
6641
  backendSrcForHandlers,
5424
6642
  "integrations"
@@ -5437,7 +6655,7 @@ var EntityNewCommand = class extends Command2 {
5437
6655
  providers: loadedProviders,
5438
6656
  entities: entityDefs,
5439
6657
  outputRoot: adapterOutputRoot,
5440
- backendSrcAbs: path13.resolve(ctx.cwd, backendSrcForHandlers),
6658
+ backendSrcAbs: path14.resolve(ctx.cwd, backendSrcForHandlers),
5441
6659
  aliases: assemblyTsAliases?.aliases ?? {},
5442
6660
  mode: runtimeMode
5443
6661
  });
@@ -5514,6 +6732,11 @@ var EntityNewCommand = class extends Command2 {
5514
6732
  relativePath: f.relativePath
5515
6733
  }))
5516
6734
  } : null,
6735
+ frontend: frontendResult ? {
6736
+ outDir: frontendResult.outDir,
6737
+ written: frontendResult.written,
6738
+ fileCount: frontendResult.written.length
6739
+ } : null,
5517
6740
  emits: {
5518
6741
  warnings: emitsWarnings.map((w) => ({
5519
6742
  entity: w.entity ?? null,
@@ -5539,22 +6762,27 @@ var EntityNewCommand = class extends Command2 {
5539
6762
  }
5540
6763
  if (barrelResult) {
5541
6764
  printInfo(
5542
- `barrels regenerated (${barrelResult.entityCount} entities) \u2192 ${path13.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path13.relative(ctx.cwd, barrelResult.schemaBarrel)}`
6765
+ `barrels regenerated (${barrelResult.entityCount} entities) \u2192 ${path14.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path14.relative(ctx.cwd, barrelResult.schemaBarrel)}`
5543
6766
  );
5544
6767
  }
5545
6768
  if (scopeResult) {
5546
6769
  printInfo(
5547
- `scope-entity-type regenerated (${scopeResult.scopeableNames.length} scopeable) \u2192 ${path13.relative(ctx.cwd, scopeResult.outputPath)}`
6770
+ `scope-entity-type regenerated (${scopeResult.scopeableNames.length} scopeable) \u2192 ${path14.relative(ctx.cwd, scopeResult.outputPath)}`
5548
6771
  );
5549
6772
  }
5550
6773
  if (eventCodegenResult) {
5551
6774
  printInfo(
5552
- `event codegen regenerated (${eventCodegenResult.eventCount} events) \u2192 ${path13.relative(ctx.cwd, eventCodegenResult.outputDir)}`
6775
+ `event codegen regenerated (${eventCodegenResult.eventCount} events) \u2192 ${path14.relative(ctx.cwd, eventCodegenResult.outputDir)}`
5553
6776
  );
5554
6777
  }
5555
6778
  if (orchestrationResult && orchestrationResult.patterns.length > 0) {
5556
6779
  printInfo(
5557
- `orchestration regenerated (${orchestrationResult.patterns.length} patterns, ${orchestrationResult.files.length} files) \u2192 ${path13.relative(ctx.cwd, orchestrationResult.outputRoot)}`
6780
+ `orchestration regenerated (${orchestrationResult.patterns.length} patterns, ${orchestrationResult.files.length} files) \u2192 ${path14.relative(ctx.cwd, orchestrationResult.outputRoot)}`
6781
+ );
6782
+ }
6783
+ if (frontendResult) {
6784
+ printInfo(
6785
+ `frontend regenerated (${frontendResult.written.length} files) \u2192 ${path14.relative(ctx.cwd, frontendResult.outDir)}`
5558
6786
  );
5559
6787
  }
5560
6788
  }
@@ -5640,7 +6868,7 @@ var EntityValidateCommand = class extends Command2 {
5640
6868
  json: this.json,
5641
6869
  skipDetection: true
5642
6870
  });
5643
- const targetDir = this.dir ? path13.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
6871
+ const targetDir = this.dir ? path14.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path14.resolve(ctx.cwd, "entities");
5644
6872
  if (!fs10.existsSync(targetDir)) {
5645
6873
  printError(`Directory not found: ${targetDir}`);
5646
6874
  return 1;
@@ -5693,7 +6921,7 @@ var entity_default = entityNoun;
5693
6921
 
5694
6922
  // src/cli/commands/subsystem.ts
5695
6923
  import fs13 from "fs";
5696
- import path23 from "path";
6924
+ import path24 from "path";
5697
6925
  import { Command as Command3, Option as Option3 } from "clipanion";
5698
6926
 
5699
6927
  // src/cli/shared/config-block-detect.ts
@@ -5726,26 +6954,26 @@ function stripConfigBlock(yamlSource, subsystem) {
5726
6954
  }
5727
6955
 
5728
6956
  // src/cli/shared/events-scaffold-locals.ts
5729
- import path14 from "path";
6957
+ import path15 from "path";
5730
6958
  function resolveEventsScaffoldLocals(input) {
5731
6959
  const { cwd, config } = input;
5732
6960
  void input.fileExists;
5733
6961
  const eventsBlock = config?.events ?? {};
5734
6962
  const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
5735
- const configPath = path14.resolve(cwd, "codegen.config.yaml");
5736
- const schemaPath = path14.resolve(
6963
+ const configPath = path15.resolve(cwd, "codegen.config.yaml");
6964
+ const schemaPath = path15.resolve(
5737
6965
  subsystemsRoot,
5738
6966
  "events",
5739
6967
  "domain-events.schema.ts"
5740
6968
  );
5741
- const generatedKeepPath = path14.resolve(
6969
+ const generatedKeepPath = path15.resolve(
5742
6970
  subsystemsRoot,
5743
6971
  "events",
5744
6972
  "generated",
5745
6973
  ".gitkeep"
5746
6974
  );
5747
6975
  return {
5748
- appName: path14.basename(cwd),
6976
+ appName: path15.basename(cwd),
5749
6977
  multiTenant: normaliseMultiTenant(eventsBlock.multi_tenant),
5750
6978
  configPath,
5751
6979
  schemaPath,
@@ -5771,7 +6999,7 @@ function localsToHygenArgs(locals) {
5771
6999
  }
5772
7000
 
5773
7001
  // src/cli/shared/jobs-scaffold-locals.ts
5774
- import path15 from "path";
7002
+ import path16 from "path";
5775
7003
  var MAIN_HOOK_SENTINEL = "JOBS \u2014 Embedded worker mode (optional)";
5776
7004
  function workerSkipValue(exists) {
5777
7005
  return exists ? "true" : "";
@@ -5780,10 +7008,10 @@ function resolveJobsScaffoldLocals(input) {
5780
7008
  const { cwd, config, fileExists, readFile } = input;
5781
7009
  const jobsBlock = config?.jobs ?? {};
5782
7010
  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(
7011
+ const workerPath = path16.resolve(cwd, "worker.ts");
7012
+ const mainTsPath = path16.resolve(cwd, "src/main.ts");
7013
+ const configPath = path16.resolve(cwd, "codegen.config.yaml");
7014
+ const schemaPath = path16.resolve(
5787
7015
  subsystemsRoot,
5788
7016
  "jobs",
5789
7017
  "job-orchestration.schema.ts"
@@ -5791,7 +7019,7 @@ function resolveJobsScaffoldLocals(input) {
5791
7019
  const mainContent = readFile(mainTsPath);
5792
7020
  const mainHookInjected = mainContent !== null && mainContent.includes(MAIN_HOOK_SENTINEL);
5793
7021
  return {
5794
- appName: path15.basename(cwd),
7022
+ appName: path16.basename(cwd),
5795
7023
  workerMode: normaliseWorkerMode(jobsBlock.worker_mode),
5796
7024
  multiTenant: normaliseMultiTenant2(jobsBlock.multi_tenant),
5797
7025
  mainTsPath,
@@ -5833,20 +7061,20 @@ function localsToHygenArgs2(locals) {
5833
7061
  }
5834
7062
 
5835
7063
  // src/cli/shared/integration-scaffold-locals.ts
5836
- import path16 from "path";
7064
+ import path17 from "path";
5837
7065
  function resolveIntegrationScaffoldLocals(input) {
5838
7066
  const { cwd, config } = input;
5839
7067
  void input.fileExists;
5840
7068
  const integrationBlock = config?.integration ?? {};
5841
7069
  const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
5842
- const configPath = path16.resolve(cwd, "codegen.config.yaml");
5843
- const schemaPath = path16.resolve(
7070
+ const configPath = path17.resolve(cwd, "codegen.config.yaml");
7071
+ const schemaPath = path17.resolve(
5844
7072
  subsystemsRoot,
5845
7073
  "integration",
5846
7074
  "integration-audit.schema.ts"
5847
7075
  );
5848
7076
  return {
5849
- appName: path16.basename(cwd),
7077
+ appName: path17.basename(cwd),
5850
7078
  multiTenant: normaliseMultiTenant3(integrationBlock.multi_tenant),
5851
7079
  configPath,
5852
7080
  schemaPath
@@ -5869,21 +7097,21 @@ function localsToHygenArgs3(locals) {
5869
7097
  }
5870
7098
 
5871
7099
  // src/cli/shared/bridge-scaffold-locals.ts
5872
- import path17 from "path";
7100
+ import path18 from "path";
5873
7101
  function resolveBridgeScaffoldLocals(input) {
5874
7102
  const { cwd, config } = input;
5875
7103
  void input.fileExists;
5876
7104
  const bridgeBlock = config?.bridge ?? {};
5877
7105
  const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
5878
- const configPath = path17.resolve(cwd, "codegen.config.yaml");
5879
- const generatedKeepPath = path17.resolve(
7106
+ const configPath = path18.resolve(cwd, "codegen.config.yaml");
7107
+ const generatedKeepPath = path18.resolve(
5880
7108
  subsystemsRoot,
5881
7109
  "bridge",
5882
7110
  "generated",
5883
7111
  ".gitkeep"
5884
7112
  );
5885
7113
  return {
5886
- appName: path17.basename(cwd),
7114
+ appName: path18.basename(cwd),
5887
7115
  multiTenant: normaliseMultiTenant4(bridgeBlock.multi_tenant),
5888
7116
  configPath,
5889
7117
  generatedKeepPath
@@ -5906,19 +7134,19 @@ function localsToHygenArgs4(locals) {
5906
7134
  }
5907
7135
 
5908
7136
  // src/cli/shared/observability-scaffold-locals.ts
5909
- import path18 from "path";
7137
+ import path19 from "path";
5910
7138
  var FALLBACK_BACKEND_SRC2 = "src";
5911
7139
  function resolveObservabilityScaffoldLocals(input) {
5912
7140
  const { cwd, config } = input;
5913
7141
  void input.fileExists;
5914
7142
  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");
7143
+ const appModulePath = path19.resolve(cwd, backendSrc, "app.module.ts");
7144
+ const configPath = path19.resolve(cwd, "codegen.config.yaml");
5917
7145
  const obsBlock = config?.observability ?? {};
5918
7146
  const reporters = obsBlock.reporters ?? {};
5919
7147
  const bridgeMetrics = reporters.bridgeMetrics ?? {};
5920
7148
  return {
5921
- appName: path18.basename(cwd),
7149
+ appName: path19.basename(cwd),
5922
7150
  appModulePath,
5923
7151
  configPath,
5924
7152
  bridgeMetricsEnabled: bridgeMetrics.enabled === true
@@ -5939,7 +7167,7 @@ function localsToHygenArgs5(locals) {
5939
7167
 
5940
7168
  // src/cli/shared/auth-scaffold-locals.ts
5941
7169
  import crypto from "crypto";
5942
- import path19 from "path";
7170
+ import path20 from "path";
5943
7171
  var FALLBACK_BACKEND_SRC3 = "src";
5944
7172
  var DEFAULT_REDIRECT_URI_BASE = "http://localhost:3000";
5945
7173
  function resolveAuthScaffoldLocals(input) {
@@ -5951,15 +7179,15 @@ function resolveAuthScaffoldLocals(input) {
5951
7179
  const redirectUriBase = typeof redirectRaw === "string" && redirectRaw.length > 0 ? redirectRaw : DEFAULT_REDIRECT_URI_BASE;
5952
7180
  const tokenEncryptionKey = crypto.randomBytes(32).toString("base64");
5953
7181
  return {
5954
- appName: path19.basename(cwd),
5955
- configPath: path19.resolve(cwd, "codegen.config.yaml"),
5956
- schemaPath: path19.resolve(
7182
+ appName: path20.basename(cwd),
7183
+ configPath: path20.resolve(cwd, "codegen.config.yaml"),
7184
+ schemaPath: path20.resolve(
5957
7185
  subsystemsRoot,
5958
7186
  "auth",
5959
7187
  "auth-oauth-state.schema.ts"
5960
7188
  ),
5961
- appModulePath: path19.resolve(cwd, backendSrc, "app.module.ts"),
5962
- envConfigPath: path19.resolve(cwd, ".env.config"),
7189
+ appModulePath: path20.resolve(cwd, backendSrc, "app.module.ts"),
7190
+ envConfigPath: path20.resolve(cwd, ".env.config"),
5963
7191
  redirectUriBase,
5964
7192
  tokenEncryptionKey
5965
7193
  };
@@ -5984,7 +7212,7 @@ function localsToHygenArgs6(locals) {
5984
7212
  }
5985
7213
 
5986
7214
  // src/cli/shared/auth-integrations-scaffold-locals.ts
5987
- import path20 from "path";
7215
+ import path21 from "path";
5988
7216
  var FALLBACK_BACKEND_SRC4 = "src";
5989
7217
  var DEFAULT_MODULES_DIR = "modules";
5990
7218
  var DEFAULT_DEFINITIONS_DIR = "definitions/entities";
@@ -5993,17 +7221,17 @@ function resolveAuthIntegrationsScaffoldLocals(input) {
5993
7221
  const backendSrc = typeof config?.paths?.backend_src === "string" && config.paths.backend_src.length > 0 ? config.paths.backend_src : FALLBACK_BACKEND_SRC4;
5994
7222
  const pathsAny = config?.paths;
5995
7223
  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);
7224
+ const vendorRoot = typeof modulesConfigured === "string" && modulesConfigured.length > 0 ? path21.resolve(cwd, modulesConfigured) : path21.resolve(cwd, backendSrc, DEFAULT_MODULES_DIR);
5997
7225
  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");
7226
+ const definitionsPath = entitiesConfigured !== null ? path21.resolve(cwd, entitiesConfigured, "connection.yaml") : path21.resolve(cwd, DEFAULT_DEFINITIONS_DIR, "connection.yaml");
7227
+ const appModulePath = path21.resolve(cwd, backendSrc, "app.module.ts");
6000
7228
  let authModuleRegistered = false;
6001
7229
  const appModuleSource = input.readFile(appModulePath);
6002
7230
  if (appModuleSource && appModuleSource.includes("AuthModule.forRoot")) {
6003
7231
  authModuleRegistered = true;
6004
7232
  }
6005
7233
  return {
6006
- appName: path20.basename(cwd),
7234
+ appName: path21.basename(cwd),
6007
7235
  appModulePath,
6008
7236
  vendorRoot,
6009
7237
  definitionsPath,
@@ -6021,7 +7249,7 @@ function localsToHygenArgs7(locals) {
6021
7249
 
6022
7250
  // src/cli/shared/runtime-copier.ts
6023
7251
  import fs11 from "fs";
6024
- import path21 from "path";
7252
+ import path22 from "path";
6025
7253
  function readIfExists(p) {
6026
7254
  try {
6027
7255
  return fs11.readFileSync(p, "utf-8");
@@ -6039,8 +7267,8 @@ function extractRelativeImports(source) {
6039
7267
  return out;
6040
7268
  }
6041
7269
  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")];
7270
+ const base = path22.resolve(path22.dirname(sourceFile), specifier);
7271
+ const candidates = [base + ".ts", base + ".tsx", path22.join(base, "index.ts")];
6044
7272
  for (const c of candidates) {
6045
7273
  if (fs11.existsSync(c)) return c;
6046
7274
  }
@@ -6051,8 +7279,8 @@ async function copyRuntime(opts) {
6051
7279
  if (!fs11.existsSync(sourceDir) || !fs11.statSync(sourceDir).isDirectory()) {
6052
7280
  throw new Error(`runtime source directory not found: ${sourceDir}`);
6053
7281
  }
6054
- const runtimeRoot4 = opts.runtimeRoot ? path21.resolve(opts.runtimeRoot) : path21.resolve(sourceDir, "..", "..");
6055
- const depsTargetRoot = opts.depsTargetRoot ?? path21.resolve(targetDir, "..");
7282
+ const runtimeRoot4 = opts.runtimeRoot ? path22.resolve(opts.runtimeRoot) : path22.resolve(sourceDir, "..", "..");
7283
+ const depsTargetRoot = opts.depsTargetRoot ?? path22.resolve(targetDir, "..");
6056
7284
  const result = {
6057
7285
  written: [],
6058
7286
  updated: [],
@@ -6063,7 +7291,7 @@ async function copyRuntime(opts) {
6063
7291
  const queue = [];
6064
7292
  function walk(dir) {
6065
7293
  for (const entry of fs11.readdirSync(dir)) {
6066
- const src = path21.join(dir, entry);
7294
+ const src = path22.join(dir, entry);
6067
7295
  const stat = fs11.statSync(src);
6068
7296
  if (stat.isDirectory()) {
6069
7297
  if (entry === "generated") continue;
@@ -6072,9 +7300,9 @@ async function copyRuntime(opts) {
6072
7300
  }
6073
7301
  if (!stat.isFile()) continue;
6074
7302
  if (!entry.endsWith(".ts") && !entry.endsWith(".tsx")) continue;
6075
- const rel2 = path21.relative(sourceDir, src);
7303
+ const rel2 = path22.relative(sourceDir, src);
6076
7304
  if (filter && !filter(rel2) && !filter(entry)) continue;
6077
- queue.push({ src, dest: path21.join(targetDir, rel2), isDep: false });
7305
+ queue.push({ src, dest: path22.join(targetDir, rel2), isDep: false });
6078
7306
  }
6079
7307
  }
6080
7308
  walk(sourceDir);
@@ -6095,18 +7323,18 @@ async function copyRuntime(opts) {
6095
7323
  else result.unchanged.push(next.dest);
6096
7324
  if (next.isDep) result.dependenciesCopied.push(next.dest);
6097
7325
  if (!dryRun && status !== "unchanged") {
6098
- fs11.mkdirSync(path21.dirname(next.dest), { recursive: true });
7326
+ fs11.mkdirSync(path22.dirname(next.dest), { recursive: true });
6099
7327
  fs11.writeFileSync(next.dest, content);
6100
7328
  }
6101
7329
  if (resolveDeps) {
6102
7330
  for (const spec of extractRelativeImports(content)) {
6103
7331
  const resolvedSrc = resolveSourceImport(next.src, spec);
6104
7332
  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);
7333
+ const relToRuntime = path22.relative(runtimeRoot4, resolvedSrc);
7334
+ if (relToRuntime.startsWith("..") || path22.isAbsolute(relToRuntime)) continue;
7335
+ const relToSource = path22.relative(sourceDir, resolvedSrc);
7336
+ if (!relToSource.startsWith("..") && !path22.isAbsolute(relToSource)) continue;
7337
+ const depDest = path22.join(depsTargetRoot, relToRuntime);
6110
7338
  queue.push({ src: resolvedSrc, dest: depDest, isDep: true });
6111
7339
  }
6112
7340
  }
@@ -6116,7 +7344,7 @@ async function copyRuntime(opts) {
6116
7344
 
6117
7345
  // src/cli/shared/subsystems-install-config.ts
6118
7346
  import fs12 from "fs";
6119
- import path22 from "path";
7347
+ import path23 from "path";
6120
7348
  import yaml2 from "yaml";
6121
7349
  function readInstallList(config) {
6122
7350
  const raw = config?.subsystems?.install;
@@ -6125,7 +7353,7 @@ function readInstallList(config) {
6125
7353
  }
6126
7354
  function ensureSubsystemInstalled(configPath, name) {
6127
7355
  if (!fs12.existsSync(configPath)) {
6128
- fs12.mkdirSync(path22.dirname(configPath), { recursive: true });
7356
+ fs12.mkdirSync(path23.dirname(configPath), { recursive: true });
6129
7357
  fs12.writeFileSync(
6130
7358
  configPath,
6131
7359
  `subsystems:
@@ -6176,13 +7404,13 @@ ${indent}- ${name}${after}`;
6176
7404
 
6177
7405
  // src/cli/commands/subsystem.ts
6178
7406
  function runtimeRoot() {
6179
- const pkgRoot = path23.resolve(import.meta.dirname, "..", "..", "..");
6180
- const topLevel = path23.join(pkgRoot, "runtime");
7407
+ const pkgRoot = path24.resolve(import.meta.dirname, "..", "..", "..");
7408
+ const topLevel = path24.join(pkgRoot, "runtime");
6181
7409
  if (fs13.existsSync(topLevel)) return topLevel;
6182
- return path23.join(pkgRoot, "dist", "runtime");
7410
+ return path24.join(pkgRoot, "dist", "runtime");
6183
7411
  }
6184
7412
  function subsystemSource(name) {
6185
- return path23.join(runtimeRoot(), "subsystems", name);
7413
+ return path24.join(runtimeRoot(), "subsystems", name);
6186
7414
  }
6187
7415
  function describeSubsystem(name) {
6188
7416
  return SUBSYSTEMS.find((s) => s.name === name) ?? null;
@@ -6217,7 +7445,7 @@ async function summary2(ctx) {
6217
7445
  }
6218
7446
  body.push(theme.muted("Installed:"));
6219
7447
  for (const i of installed) {
6220
- const rel2 = path23.relative(ctx.cwd, i.path) || i.path;
7448
+ const rel2 = path24.relative(ctx.cwd, i.path) || i.path;
6221
7449
  body.push(
6222
7450
  ` ${theme.success(icons.check)} ${i.name.padEnd(10)} ${theme.muted(
6223
7451
  `${i.backend} backend`
@@ -6360,14 +7588,14 @@ var SubsystemInstallCommand = class extends Command3 {
6360
7588
  return 0;
6361
7589
  }
6362
7590
  const targetRoot = resolveSubsystemsRoot(ctx, this.target);
6363
- const subsystemTarget = path23.join(targetRoot, desc.name);
7591
+ const subsystemTarget = path24.join(targetRoot, desc.name);
6364
7592
  const source = subsystemSource(desc.name);
6365
7593
  if (!fs13.existsSync(source)) {
6366
7594
  printError(`Runtime subsystem source missing: ${source}`);
6367
7595
  return 1;
6368
7596
  }
6369
7597
  if (!this.force) {
6370
- const gitCheck = checkGitSafety([path23.relative(ctx.cwd, subsystemTarget) || subsystemTarget], ctx.cwd);
7598
+ const gitCheck = checkGitSafety([path24.relative(ctx.cwd, subsystemTarget) || subsystemTarget], ctx.cwd);
6371
7599
  if (gitCheck.inRepo && !gitCheck.clean) {
6372
7600
  printWarning(
6373
7601
  `Uncommitted changes under ${subsystemTarget}. Pass --force to overwrite.`
@@ -6376,7 +7604,7 @@ var SubsystemInstallCommand = class extends Command3 {
6376
7604
  }
6377
7605
  }
6378
7606
  if (!isJsonMode()) {
6379
- printInfo(`target = ${path23.relative(ctx.cwd, subsystemTarget) || subsystemTarget}`);
7607
+ printInfo(`target = ${path24.relative(ctx.cwd, subsystemTarget) || subsystemTarget}`);
6380
7608
  printInfo(`backend = ${backend}`);
6381
7609
  }
6382
7610
  const result = await copyRuntime({
@@ -6385,7 +7613,7 @@ var SubsystemInstallCommand = class extends Command3 {
6385
7613
  filter: backendFileFilter(backend, desc.name),
6386
7614
  resolveDeps: true,
6387
7615
  runtimeRoot: runtimeRoot(),
6388
- depsTargetRoot: path23.resolve(targetRoot, ".."),
7616
+ depsTargetRoot: path24.resolve(targetRoot, ".."),
6389
7617
  dryRun: this.dryRun
6390
7618
  });
6391
7619
  const jobsScaffold = desc.name === "jobs" ? runJobsScaffold(ctx.cwd, ctx.config, {
@@ -6480,14 +7708,14 @@ var SubsystemInstallCommand = class extends Command3 {
6480
7708
  if (this.dryRun) {
6481
7709
  printInfo(`Dry run \u2014 ${result.planned.length} files would be written`);
6482
7710
  for (const p of result.planned) {
6483
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7711
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6484
7712
  }
6485
7713
  if (jobsScaffold?.planned?.length) {
6486
7714
  printInfo(
6487
7715
  `Jobs scaffold \u2014 ${jobsScaffold.planned.length} template targets`
6488
7716
  );
6489
7717
  for (const p of jobsScaffold.planned) {
6490
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7718
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6491
7719
  }
6492
7720
  }
6493
7721
  if (eventsScaffold?.planned?.length) {
@@ -6495,7 +7723,7 @@ var SubsystemInstallCommand = class extends Command3 {
6495
7723
  `Events scaffold \u2014 ${eventsScaffold.planned.length} template targets`
6496
7724
  );
6497
7725
  for (const p of eventsScaffold.planned) {
6498
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7726
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6499
7727
  }
6500
7728
  }
6501
7729
  if (integrationScaffold?.planned?.length) {
@@ -6503,7 +7731,7 @@ var SubsystemInstallCommand = class extends Command3 {
6503
7731
  `Integration scaffold \u2014 ${integrationScaffold.planned.length} template targets`
6504
7732
  );
6505
7733
  for (const p of integrationScaffold.planned) {
6506
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7734
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6507
7735
  }
6508
7736
  }
6509
7737
  if (bridgeScaffold?.planned?.length) {
@@ -6511,7 +7739,7 @@ var SubsystemInstallCommand = class extends Command3 {
6511
7739
  `Bridge scaffold \u2014 ${bridgeScaffold.planned.length} template targets`
6512
7740
  );
6513
7741
  for (const p of bridgeScaffold.planned) {
6514
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7742
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6515
7743
  }
6516
7744
  }
6517
7745
  if (observabilityScaffold?.planned?.length) {
@@ -6519,7 +7747,7 @@ var SubsystemInstallCommand = class extends Command3 {
6519
7747
  `Observability scaffold \u2014 ${observabilityScaffold.planned.length} template targets`
6520
7748
  );
6521
7749
  for (const p of observabilityScaffold.planned) {
6522
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7750
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6523
7751
  }
6524
7752
  }
6525
7753
  if (authScaffold?.planned?.length) {
@@ -6527,7 +7755,7 @@ var SubsystemInstallCommand = class extends Command3 {
6527
7755
  `Auth scaffold \u2014 ${authScaffold.planned.length} template targets`
6528
7756
  );
6529
7757
  for (const p of authScaffold.planned) {
6530
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`);
7758
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`);
6531
7759
  }
6532
7760
  }
6533
7761
  return 0;
@@ -6657,7 +7885,7 @@ var SubsystemInstallCommand = class extends Command3 {
6657
7885
  * `--force-config` and always regenerates the barrels).
6658
7886
  */
6659
7887
  async executePackageMode(ctx, desc, backend) {
6660
- const configPath = path23.join(ctx.cwd, "codegen.config.yaml");
7888
+ const configPath = path24.join(ctx.cwd, "codegen.config.yaml");
6661
7889
  const installed = configuredSubsystemNames(
6662
7890
  ctx.config
6663
7891
  );
@@ -6784,7 +8012,7 @@ var SubsystemInstallCommand = class extends Command3 {
6784
8012
  * semantics as jobs/events/integration/bridge.
6785
8013
  */
6786
8014
  async executeOpenApiConfig(ctx) {
6787
- const configPath = path23.join(ctx.cwd, "codegen.config.yaml");
8015
+ const configPath = path24.join(ctx.cwd, "codegen.config.yaml");
6788
8016
  const outcome = planConfigBlockAction(configPath, "openapi", this.forceConfig);
6789
8017
  if (outcome === "parse-error") {
6790
8018
  printError(
@@ -6803,7 +8031,7 @@ var SubsystemInstallCommand = class extends Command3 {
6803
8031
  });
6804
8032
  } else {
6805
8033
  printInfo(`Dry run \u2014 openapi config block would be ${outcome}`);
6806
- console.log(` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, configPath) || configPath}`);
8034
+ console.log(` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, configPath) || configPath}`);
6807
8035
  }
6808
8036
  return 0;
6809
8037
  }
@@ -6898,7 +8126,7 @@ var SubsystemInstallCommand = class extends Command3 {
6898
8126
  );
6899
8127
  for (const p of scaffold.planned) {
6900
8128
  console.log(
6901
- ` ${theme.muted(icons.arrow)} ${path23.relative(ctx.cwd, p) || p}`
8129
+ ` ${theme.muted(icons.arrow)} ${path24.relative(ctx.cwd, p) || p}`
6902
8130
  );
6903
8131
  }
6904
8132
  return 0;
@@ -7281,7 +8509,7 @@ function runAuthScaffold(cwd, config, opts) {
7281
8509
  return { ok: true, planned, configBlockOutcome };
7282
8510
  }
7283
8511
  if (!fs13.existsSync(locals.envConfigPath)) {
7284
- fs13.mkdirSync(path23.dirname(locals.envConfigPath), { recursive: true });
8512
+ fs13.mkdirSync(path24.dirname(locals.envConfigPath), { recursive: true });
7285
8513
  fs13.writeFileSync(locals.envConfigPath, "", "utf-8");
7286
8514
  }
7287
8515
  const result = invokeHygen({
@@ -7318,10 +8546,10 @@ function runAuthScaffold(cwd, config, opts) {
7318
8546
  return { ok: true, planned, configBlockOutcome };
7319
8547
  }
7320
8548
  function authIntegrationsExamplesRoot() {
7321
- const pkgRoot = path23.resolve(import.meta.dirname, "..", "..", "..");
7322
- const topLevel = path23.join(pkgRoot, "examples", "auth-integrations");
8549
+ const pkgRoot = path24.resolve(import.meta.dirname, "..", "..", "..");
8550
+ const topLevel = path24.join(pkgRoot, "examples", "auth-integrations");
7323
8551
  if (fs13.existsSync(topLevel)) return topLevel;
7324
- return path23.join(pkgRoot, "dist", "examples", "auth-integrations");
8552
+ return path24.join(pkgRoot, "dist", "examples", "auth-integrations");
7325
8553
  }
7326
8554
  function copyTreeIdempotent(srcDir, destDir, force, transform) {
7327
8555
  const written = [];
@@ -7329,8 +8557,8 @@ function copyTreeIdempotent(srcDir, destDir, force, transform) {
7329
8557
  const walk = (src, dest) => {
7330
8558
  const entries = fs13.readdirSync(src, { withFileTypes: true });
7331
8559
  for (const entry of entries) {
7332
- const srcPath = path23.join(src, entry.name);
7333
- const destPath = path23.join(dest, entry.name);
8560
+ const srcPath = path24.join(src, entry.name);
8561
+ const destPath = path24.join(dest, entry.name);
7334
8562
  if (entry.isDirectory()) {
7335
8563
  fs13.mkdirSync(destPath, { recursive: true });
7336
8564
  walk(srcPath, destPath);
@@ -7341,7 +8569,7 @@ function copyTreeIdempotent(srcDir, destDir, force, transform) {
7341
8569
  skipped.push(destPath);
7342
8570
  continue;
7343
8571
  }
7344
- fs13.mkdirSync(path23.dirname(destPath), { recursive: true });
8572
+ fs13.mkdirSync(path24.dirname(destPath), { recursive: true });
7345
8573
  const isTextSource = transform && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"));
7346
8574
  if (isTextSource && transform) {
7347
8575
  const raw = fs13.readFileSync(srcPath, "utf-8");
@@ -7359,16 +8587,16 @@ function copyTreeIdempotent(srcDir, destDir, force, transform) {
7359
8587
  }
7360
8588
  var AUTH_BARE_IMPORT_RE = /(['"])@pattern-stack\/codegen\/runtime\/subsystems\/auth\1/g;
7361
8589
  function buildAuthImportRewriter(subsystemsRoot) {
7362
- const authRoot = path23.join(subsystemsRoot, "auth");
8590
+ const authRoot = path24.join(subsystemsRoot, "auth");
7363
8591
  return (content, destPath) => {
7364
8592
  if (!AUTH_BARE_IMPORT_RE.test(content)) {
7365
8593
  AUTH_BARE_IMPORT_RE.lastIndex = 0;
7366
8594
  return content;
7367
8595
  }
7368
8596
  AUTH_BARE_IMPORT_RE.lastIndex = 0;
7369
- let rel2 = path23.relative(path23.dirname(destPath), authRoot);
8597
+ let rel2 = path24.relative(path24.dirname(destPath), authRoot);
7370
8598
  if (!rel2.startsWith(".")) rel2 = `./${rel2}`;
7371
- const relPosix = rel2.split(path23.sep).join("/");
8599
+ const relPosix = rel2.split(path24.sep).join("/");
7372
8600
  return content.replace(
7373
8601
  AUTH_BARE_IMPORT_RE,
7374
8602
  (_match, quote) => `${quote}${relPosix}${quote}`
@@ -7390,9 +8618,9 @@ function runAuthIntegrationsScaffold(cwd, config, opts) {
7390
8618
  error: `auth-integrations starter source missing: ${examplesRoot}`
7391
8619
  };
7392
8620
  }
7393
- const adaptersSrc = path23.join(examplesRoot, "runtime", "connections");
7394
- const adaptersDest = path23.join(locals.vendorRoot, "connections");
7395
- const connectionYamlSrc = path23.join(
8621
+ const adaptersSrc = path24.join(examplesRoot, "runtime", "connections");
8622
+ const adaptersDest = path24.join(locals.vendorRoot, "connections");
8623
+ const connectionYamlSrc = path24.join(
7396
8624
  examplesRoot,
7397
8625
  "definitions",
7398
8626
  "entities",
@@ -7424,7 +8652,7 @@ function runAuthIntegrationsScaffold(cwd, config, opts) {
7424
8652
  if (fs13.existsSync(connectionYamlDest) && !opts.force) {
7425
8653
  yamlSkipped = true;
7426
8654
  } else if (fs13.existsSync(connectionYamlSrc)) {
7427
- fs13.mkdirSync(path23.dirname(connectionYamlDest), { recursive: true });
8655
+ fs13.mkdirSync(path24.dirname(connectionYamlDest), { recursive: true });
7428
8656
  fs13.copyFileSync(connectionYamlSrc, connectionYamlDest);
7429
8657
  yamlWritten = true;
7430
8658
  }
@@ -7490,7 +8718,7 @@ var SubsystemListCommand = class extends Command3 {
7490
8718
  name: s.name,
7491
8719
  status: inst ? inst.status : "available",
7492
8720
  backend: inst ? inst.backend : null,
7493
- path: inst ? path23.relative(ctx.cwd, inst.path) || inst.path : null
8721
+ path: inst ? path24.relative(ctx.cwd, inst.path) || inst.path : null
7494
8722
  };
7495
8723
  });
7496
8724
  if (isJsonMode()) {
@@ -7586,7 +8814,7 @@ var SubsystemRemoveCommand = class extends Command3 {
7586
8814
  return 1;
7587
8815
  }
7588
8816
  if (!this.force) {
7589
- const rel2 = path23.relative(ctx.cwd, subsystemDir) || subsystemDir;
8817
+ const rel2 = path24.relative(ctx.cwd, subsystemDir) || subsystemDir;
7590
8818
  const gitCheck = checkGitSafety([rel2], ctx.cwd);
7591
8819
  if (gitCheck.inRepo && !gitCheck.clean) {
7592
8820
  printWarning(
@@ -7622,7 +8850,7 @@ var SubsystemRemoveCommand = class extends Command3 {
7622
8850
  return 0;
7623
8851
  }
7624
8852
  printSuccess(
7625
- `${desc.name} subsystem removed (${path23.relative(ctx.cwd, subsystemDir) || subsystemDir}).`
8853
+ `${desc.name} subsystem removed (${path24.relative(ctx.cwd, subsystemDir) || subsystemDir}).`
7626
8854
  );
7627
8855
  if (barrelRegenerated) {
7628
8856
  printInfo("Regenerated <generated>/subsystems.ts barrel.");
@@ -7651,27 +8879,27 @@ var subsystem_default = subsystemNoun;
7651
8879
 
7652
8880
  // src/cli/commands/project.ts
7653
8881
  import fs19 from "fs";
7654
- import path29 from "path";
8882
+ import path30 from "path";
7655
8883
  import readline from "readline";
7656
8884
  import { Command as Command7, Option as Option7 } from "clipanion";
7657
8885
  import { stringify as stringifyYaml2 } from "yaml";
7658
8886
 
7659
8887
  // src/cli/shared/init-scaffold.ts
7660
8888
  import fs14 from "fs";
7661
- import path24 from "path";
8889
+ import path25 from "path";
7662
8890
  import { stringify as stringifyYaml } from "yaml";
7663
8891
  function runtimeRoot2() {
7664
- const pkgRoot = path24.resolve(import.meta.dirname, "..", "..", "..");
7665
- const topLevel = path24.join(pkgRoot, "runtime");
8892
+ const pkgRoot = path25.resolve(import.meta.dirname, "..", "..", "..");
8893
+ const topLevel = path25.join(pkgRoot, "runtime");
7666
8894
  if (fs14.existsSync(topLevel)) return topLevel;
7667
- return path24.join(pkgRoot, "dist", "runtime");
8895
+ return path25.join(pkgRoot, "dist", "runtime");
7668
8896
  }
7669
8897
  function resolveRuntimePath(cwd) {
7670
- const shimDir = path24.join(cwd, "src", "shared", "base-classes");
7671
- return path24.relative(shimDir, runtimeRoot2());
8898
+ const shimDir = path25.join(cwd, "src", "shared", "base-classes");
8899
+ return path25.relative(shimDir, runtimeRoot2());
7672
8900
  }
7673
8901
  function loadRuntimeFile(relPath) {
7674
- return fs14.readFileSync(path24.join(runtimeRoot2(), relPath), "utf-8");
8902
+ return fs14.readFileSync(path25.join(runtimeRoot2(), relPath), "utf-8");
7675
8903
  }
7676
8904
  var VENDORED_RUNTIME_FILES = [
7677
8905
  // base-classes — consumer-facing inheritance targets
@@ -8087,8 +9315,38 @@ function mergeTsconfig(raw) {
8087
9315
  unchanged: false
8088
9316
  };
8089
9317
  }
9318
+ function mergeFrontendDeps(raw) {
9319
+ let parsed;
9320
+ try {
9321
+ parsed = JSON.parse(raw);
9322
+ } catch (err) {
9323
+ return {
9324
+ content: raw,
9325
+ added: [],
9326
+ unchanged: true,
9327
+ parseError: err instanceof Error ? err.message : String(err)
9328
+ };
9329
+ }
9330
+ const deps = parsed.dependencies ?? {};
9331
+ const added = [];
9332
+ for (const [pkg, range] of Object.entries(FRONTEND_EMITTED_DEPS)) {
9333
+ if (!(pkg in deps)) {
9334
+ deps[pkg] = range;
9335
+ added.push(pkg);
9336
+ }
9337
+ }
9338
+ if (added.length === 0) {
9339
+ return { content: raw, added: [], unchanged: true };
9340
+ }
9341
+ parsed.dependencies = deps;
9342
+ return {
9343
+ content: JSON.stringify(parsed, null, 2) + "\n",
9344
+ added,
9345
+ unchanged: false
9346
+ };
9347
+ }
8090
9348
  function relOf(cwd, abs) {
8091
- return path24.relative(cwd, abs) || abs;
9349
+ return path25.relative(cwd, abs) || abs;
8092
9350
  }
8093
9351
  function fileEntry(cwd, absPath, content, opts) {
8094
9352
  const exists = fs14.existsSync(absPath);
@@ -8139,7 +9397,7 @@ async function buildInitPlan(ctx, options) {
8139
9397
  const runtimePath = options.runtimePath ?? resolveRuntimePath(cwd);
8140
9398
  const entries = [];
8141
9399
  {
8142
- const configPath = path24.join(cwd, "codegen.config.yaml");
9400
+ const configPath = path25.join(cwd, "codegen.config.yaml");
8143
9401
  const config = {
8144
9402
  // Runtime mode (ADR-037). `package` (default) imports the runtime from
8145
9403
  // `@pattern-stack/codegen/*`; `vendored` imports it via `@shared/*` and
@@ -8174,7 +9432,7 @@ async function buildInitPlan(ctx, options) {
8174
9432
  entries.push(fileEntry(cwd, configPath, content, { force }));
8175
9433
  }
8176
9434
  {
8177
- const tsconfigPath = path24.join(cwd, "tsconfig.json");
9435
+ const tsconfigPath = path25.join(cwd, "tsconfig.json");
8178
9436
  if (fs14.existsSync(tsconfigPath)) {
8179
9437
  const raw = fs14.readFileSync(tsconfigPath, "utf-8");
8180
9438
  const merged = mergeTsconfig(raw);
@@ -8221,7 +9479,7 @@ async function buildInitPlan(ctx, options) {
8221
9479
  entries.push(
8222
9480
  fileEntry(
8223
9481
  cwd,
8224
- path24.join(cwd, "src", "shared", "database", "database.module.ts"),
9482
+ path25.join(cwd, "src", "shared", "database", "database.module.ts"),
8225
9483
  databaseModuleContent(runtimeMode),
8226
9484
  { force }
8227
9485
  )
@@ -8229,14 +9487,14 @@ async function buildInitPlan(ctx, options) {
8229
9487
  if (runtimeMode === "vendored") {
8230
9488
  for (const v of VENDORED_RUNTIME_FILES) {
8231
9489
  entries.push(
8232
- fileEntry(cwd, path24.join(cwd, v.target), loadRuntimeFile(v.runtime), { force })
9490
+ fileEntry(cwd, path25.join(cwd, v.target), loadRuntimeFile(v.runtime), { force })
8233
9491
  );
8234
9492
  }
8235
9493
  }
8236
9494
  entries.push(
8237
9495
  fileEntry(
8238
9496
  cwd,
8239
- path24.join(cwd, "src", "generated", "modules.ts"),
9497
+ path25.join(cwd, "src", "generated", "modules.ts"),
8240
9498
  emptyModulesBarrel(),
8241
9499
  { force }
8242
9500
  )
@@ -8244,13 +9502,13 @@ async function buildInitPlan(ctx, options) {
8244
9502
  entries.push(
8245
9503
  fileEntry(
8246
9504
  cwd,
8247
- path24.join(cwd, "src", "generated", "schema.ts"),
9505
+ path25.join(cwd, "src", "generated", "schema.ts"),
8248
9506
  emptySchemaBarrel(),
8249
9507
  { force }
8250
9508
  )
8251
9509
  );
8252
9510
  {
8253
- const appModulePath = path24.join(cwd, "src", "app.module.ts");
9511
+ const appModulePath = path25.join(cwd, "src", "app.module.ts");
8254
9512
  if (!fs14.existsSync(appModulePath)) {
8255
9513
  entries.push({
8256
9514
  path: appModulePath,
@@ -8268,7 +9526,7 @@ async function buildInitPlan(ctx, options) {
8268
9526
  }
8269
9527
  }
8270
9528
  {
8271
- const mainPath = path24.join(cwd, "src", "main.ts");
9529
+ const mainPath = path25.join(cwd, "src", "main.ts");
8272
9530
  if (!fs14.existsSync(mainPath)) {
8273
9531
  entries.push({
8274
9532
  path: mainPath,
@@ -8286,7 +9544,7 @@ async function buildInitPlan(ctx, options) {
8286
9544
  }
8287
9545
  }
8288
9546
  {
8289
- const schemaPath = path24.join(cwd, "src", "schema.ts");
9547
+ const schemaPath = path25.join(cwd, "src", "schema.ts");
8290
9548
  if (!fs14.existsSync(schemaPath)) {
8291
9549
  entries.push({
8292
9550
  path: schemaPath,
@@ -8303,12 +9561,12 @@ async function buildInitPlan(ctx, options) {
8303
9561
  });
8304
9562
  }
8305
9563
  }
8306
- entries.push(dirEntry(cwd, path24.join(cwd, "entities")));
9564
+ entries.push(dirEntry(cwd, path25.join(cwd, "entities")));
8307
9565
  {
8308
- const entitiesDir = path24.join(cwd, "entities");
8309
- const examplePath = path24.join(entitiesDir, "example.yaml");
9566
+ const entitiesDir = path25.join(cwd, "entities");
9567
+ const examplePath = path25.join(entitiesDir, "example.yaml");
8310
9568
  const hasOtherYamls = fs14.existsSync(entitiesDir) && findYamlFiles(entitiesDir).some(
8311
- (f) => path24.basename(f) !== "example.yaml"
9569
+ (f) => path25.basename(f) !== "example.yaml"
8312
9570
  );
8313
9571
  if (fs14.existsSync(examplePath)) {
8314
9572
  entries.push({
@@ -8333,6 +9591,46 @@ async function buildInitPlan(ctx, options) {
8333
9591
  });
8334
9592
  }
8335
9593
  }
9594
+ if (frontend) {
9595
+ const frontendSrc = ctx.config?.paths?.frontend_src ?? "apps/frontend/src";
9596
+ const frontendRoot = path25.dirname(path25.join(cwd, frontendSrc));
9597
+ const pkgPath = path25.join(frontendRoot, "package.json");
9598
+ const depsList = Object.entries(FRONTEND_EMITTED_DEPS).map(([p, r]) => `${p}@${r}`).join(", ");
9599
+ if (fs14.existsSync(pkgPath)) {
9600
+ const raw = fs14.readFileSync(pkgPath, "utf-8");
9601
+ const merged = mergeFrontendDeps(raw);
9602
+ if (merged.parseError) {
9603
+ entries.push({
9604
+ path: pkgPath,
9605
+ relPath: relOf(cwd, pkgPath),
9606
+ action: "skip",
9607
+ reason: `unable to parse (${merged.parseError}); add frontend deps manually: ${depsList}`
9608
+ });
9609
+ } else if (merged.unchanged) {
9610
+ entries.push({
9611
+ path: pkgPath,
9612
+ relPath: relOf(cwd, pkgPath),
9613
+ action: "skip",
9614
+ reason: "frontend deps already present"
9615
+ });
9616
+ } else {
9617
+ entries.push({
9618
+ path: pkgPath,
9619
+ relPath: relOf(cwd, pkgPath),
9620
+ action: "merge",
9621
+ content: merged.content,
9622
+ reason: `add frontend deps: ${merged.added.join(", ")}`
9623
+ });
9624
+ }
9625
+ } else {
9626
+ entries.push({
9627
+ path: pkgPath,
9628
+ relPath: relOf(cwd, pkgPath),
9629
+ action: "skip",
9630
+ reason: `frontend enabled but ${relOf(cwd, pkgPath)} not found \u2014 install: ${depsList}`
9631
+ });
9632
+ }
9633
+ }
8336
9634
  return {
8337
9635
  entries,
8338
9636
  summary: {
@@ -8364,7 +9662,7 @@ function writePlan(plan) {
8364
9662
  skipped.push(e);
8365
9663
  continue;
8366
9664
  }
8367
- fs14.mkdirSync(path24.dirname(e.path), { recursive: true });
9665
+ fs14.mkdirSync(path25.dirname(e.path), { recursive: true });
8368
9666
  fs14.writeFileSync(e.path, e.content, "utf-8");
8369
9667
  if (e.action === "create") created.push(e);
8370
9668
  else if (e.action === "merge") merged.push(e);
@@ -8375,7 +9673,7 @@ function writePlan(plan) {
8375
9673
 
8376
9674
  // src/cli/commands/project-upgrade-openapi.ts
8377
9675
  import fs15 from "fs";
8378
- import path25 from "path";
9676
+ import path26 from "path";
8379
9677
  import { Command as Command4, Option as Option4 } from "clipanion";
8380
9678
  import { Project, IndentationText, QuoteKind, NewLineKind } from "ts-morph";
8381
9679
 
@@ -8614,31 +9912,31 @@ var MAIN_SWAGGER_IMPORTS = [
8614
9912
  "import { OPENAPI_REGISTRY, OpenApiRegistry } from './shared/openapi';"
8615
9913
  ];
8616
9914
  function runtimeRoot3() {
8617
- const pkgRoot = path25.resolve(import.meta.dirname, "..", "..", "..");
8618
- const topLevel = path25.join(pkgRoot, "runtime");
9915
+ const pkgRoot = path26.resolve(import.meta.dirname, "..", "..", "..");
9916
+ const topLevel = path26.join(pkgRoot, "runtime");
8619
9917
  if (fs15.existsSync(topLevel)) return topLevel;
8620
- return path25.join(pkgRoot, "dist", "runtime");
9918
+ return path26.join(pkgRoot, "dist", "runtime");
8621
9919
  }
8622
9920
  function loadRuntimeFile2(rel2) {
8623
- return fs15.readFileSync(path25.join(runtimeRoot3(), rel2), "utf-8");
9921
+ return fs15.readFileSync(path26.join(runtimeRoot3(), rel2), "utf-8");
8624
9922
  }
8625
9923
  function resolveProjectRoot(startDir) {
8626
- let dir = path25.resolve(startDir);
9924
+ let dir = path26.resolve(startDir);
8627
9925
  for (let i = 0; i < 16; i++) {
8628
- if (fs15.existsSync(path25.join(dir, "codegen.config.yaml")) || fs15.existsSync(path25.join(dir, "package.json"))) {
9926
+ if (fs15.existsSync(path26.join(dir, "codegen.config.yaml")) || fs15.existsSync(path26.join(dir, "package.json"))) {
8629
9927
  return dir;
8630
9928
  }
8631
- const parent = path25.dirname(dir);
9929
+ const parent = path26.dirname(dir);
8632
9930
  if (parent === dir) break;
8633
9931
  dir = parent;
8634
9932
  }
8635
- return path25.resolve(startDir);
9933
+ return path26.resolve(startDir);
8636
9934
  }
8637
9935
  async function runUpgradeOpenapi(opts) {
8638
9936
  const { projectRoot, dryRun, force } = opts;
8639
9937
  const changes = [];
8640
9938
  for (const v of OPENAPI_VENDORED_FILES) {
8641
- const target = path25.join(projectRoot, v.target);
9939
+ const target = path26.join(projectRoot, v.target);
8642
9940
  const exists = fs15.existsSync(target);
8643
9941
  const newContent = loadRuntimeFile2(v.runtime);
8644
9942
  if (exists && !force) {
@@ -8654,7 +9952,7 @@ async function runUpgradeOpenapi(opts) {
8654
9952
  }
8655
9953
  } else {
8656
9954
  if (!dryRun) {
8657
- fs15.mkdirSync(path25.dirname(target), { recursive: true });
9955
+ fs15.mkdirSync(path26.dirname(target), { recursive: true });
8658
9956
  fs15.writeFileSync(target, newContent);
8659
9957
  }
8660
9958
  changes.push({
@@ -8663,7 +9961,7 @@ async function runUpgradeOpenapi(opts) {
8663
9961
  });
8664
9962
  }
8665
9963
  }
8666
- const appModulePath = path25.join(projectRoot, "src", "app.module.ts");
9964
+ const appModulePath = path26.join(projectRoot, "src", "app.module.ts");
8667
9965
  if (!fs15.existsSync(appModulePath)) {
8668
9966
  return {
8669
9967
  projectRoot,
@@ -8747,7 +10045,7 @@ async function runUpgradeOpenapi(opts) {
8747
10045
  } else {
8748
10046
  changes.push({ path: "src/app.module.ts", action: "unchanged" });
8749
10047
  }
8750
- const mainPath = path25.join(projectRoot, "src", "main.ts");
10048
+ const mainPath = path26.join(projectRoot, "src", "main.ts");
8751
10049
  if (fs15.existsSync(mainPath)) {
8752
10050
  const mainSource = project.addSourceFileAtPath(mainPath);
8753
10051
  const mainBefore = mainSource.getFullText();
@@ -8828,7 +10126,7 @@ var ProjectUpgradeOpenapiCommand = class extends Command4 {
8828
10126
  json = Option4.Boolean("--json", false);
8829
10127
  async execute() {
8830
10128
  if (this.json) setJsonMode(true);
8831
- const startDir = this.pathOpt ? path25.resolve(this.pathOpt) : process.cwd();
10129
+ const startDir = this.pathOpt ? path26.resolve(this.pathOpt) : process.cwd();
8832
10130
  if (!fs15.existsSync(startDir)) {
8833
10131
  printError(`Directory not found: ${startDir}`);
8834
10132
  return 1;
@@ -8905,17 +10203,17 @@ ${CONSUMER_SETUP_POINTER}
8905
10203
 
8906
10204
  // src/cli/commands/project-update.ts
8907
10205
  import fs18 from "fs";
8908
- import path28 from "path";
10206
+ import path29 from "path";
8909
10207
  import { Command as Command6, Option as Option6 } from "clipanion";
8910
10208
 
8911
10209
  // src/cli/commands/skills.ts
8912
10210
  import fs17 from "fs";
8913
- import path27 from "path";
10211
+ import path28 from "path";
8914
10212
  import { Command as Command5, Option as Option5 } from "clipanion";
8915
10213
 
8916
10214
  // src/cli/shared/tree-copier.ts
8917
10215
  import fs16 from "fs";
8918
- import path26 from "path";
10216
+ import path27 from "path";
8919
10217
  var TEXT_EXTENSIONS = [".ts", ".tsx", ".md", ".mdx", ".yaml", ".yml", ".json"];
8920
10218
  function isTextFile(name) {
8921
10219
  return TEXT_EXTENSIONS.some((ext) => name.endsWith(ext));
@@ -8932,17 +10230,17 @@ function copyTreeWithReport(opts) {
8932
10230
  throw new Error(`tree-copier source directory not found: ${srcDir}`);
8933
10231
  }
8934
10232
  const walk = (relDir) => {
8935
- const absSrcDir = path26.join(srcDir, relDir);
10233
+ const absSrcDir = path27.join(srcDir, relDir);
8936
10234
  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);
10235
+ const relPath = relDir ? path27.posix.join(relDir, entry.name) : entry.name;
10236
+ const absSrc = path27.join(srcDir, relPath);
8939
10237
  if (entry.isDirectory()) {
8940
10238
  walk(relPath);
8941
10239
  continue;
8942
10240
  }
8943
10241
  if (!entry.isFile()) continue;
8944
10242
  if (include && !include(relPath)) continue;
8945
- const dest = path26.join(destDir, relPath);
10243
+ const dest = path27.join(destDir, relPath);
8946
10244
  let content = fs16.readFileSync(absSrc, "utf-8");
8947
10245
  if (transform && isTextFile(entry.name)) {
8948
10246
  content = transform(content, dest);
@@ -8957,7 +10255,7 @@ function copyTreeWithReport(opts) {
8957
10255
  action = "updated";
8958
10256
  }
8959
10257
  if (!dryRun && action !== "unchanged") {
8960
- fs16.mkdirSync(path26.dirname(dest), { recursive: true });
10258
+ fs16.mkdirSync(path27.dirname(dest), { recursive: true });
8961
10259
  fs16.writeFileSync(dest, content, "utf-8");
8962
10260
  }
8963
10261
  const record = { relPath, dest, action };
@@ -8971,13 +10269,13 @@ function copyTreeWithReport(opts) {
8971
10269
 
8972
10270
  // src/cli/commands/skills.ts
8973
10271
  function consumerSkillsRoot() {
8974
- const pkgRoot = path27.resolve(import.meta.dirname, "..", "..", "..");
8975
- const topLevel = path27.join(pkgRoot, "consumer-skills");
10272
+ const pkgRoot = path28.resolve(import.meta.dirname, "..", "..", "..");
10273
+ const topLevel = path28.join(pkgRoot, "consumer-skills");
8976
10274
  if (fs17.existsSync(topLevel)) return topLevel;
8977
- return path27.join(pkgRoot, "dist", "consumer-skills");
10275
+ return path28.join(pkgRoot, "dist", "consumer-skills");
8978
10276
  }
8979
10277
  function skillsTargetDir(cwd) {
8980
- return path27.join(cwd, ".claude", "skills");
10278
+ return path28.join(cwd, ".claude", "skills");
8981
10279
  }
8982
10280
  function availableSkills() {
8983
10281
  const root = consumerSkillsRoot();
@@ -9024,13 +10322,13 @@ async function summary3(ctx) {
9024
10322
  return {
9025
10323
  title: "skills",
9026
10324
  body,
9027
- footer: `${installedCount} of ${skills.length} skills installed \u2192 ${path27.relative(ctx.cwd, targetDir) || targetDir}`
10325
+ footer: `${installedCount} of ${skills.length} skills installed \u2192 ${path28.relative(ctx.cwd, targetDir) || targetDir}`
9028
10326
  };
9029
10327
  }
9030
10328
  async function hints3(ctx) {
9031
10329
  const skills = availableSkills();
9032
10330
  const targetDir = skillsTargetDir(ctx.cwd);
9033
- const allPresent = skills.length > 0 && fs17.existsSync(targetDir) && skills.every((s) => fs17.existsSync(path27.join(targetDir, s)));
10331
+ const allPresent = skills.length > 0 && fs17.existsSync(targetDir) && skills.every((s) => fs17.existsSync(path28.join(targetDir, s)));
9034
10332
  if (allPresent) {
9035
10333
  return [
9036
10334
  { command: "codegen update", description: "Re-sync skills + runtime after a package bump" }
@@ -9105,7 +10403,7 @@ var SkillsInstallCommand = class extends Command5 {
9105
10403
  });
9106
10404
  return 0;
9107
10405
  }
9108
- printInfo(`target = ${path27.relative(ctx.cwd, result.targetDir) || result.targetDir}`);
10406
+ printInfo(`target = ${path28.relative(ctx.cwd, result.targetDir) || result.targetDir}`);
9109
10407
  console.log("");
9110
10408
  renderTreeReport(report);
9111
10409
  console.log("");
@@ -9133,7 +10431,7 @@ var SkillsListCommand = class extends Command5 {
9133
10431
  const skills = availableSkills();
9134
10432
  const targetDir = skillsTargetDir(ctx.cwd);
9135
10433
  const rows = skills.map((name) => {
9136
- const dir = path27.join(targetDir, name);
10434
+ const dir = path28.join(targetDir, name);
9137
10435
  return { name, status: fs17.existsSync(dir) ? "installed" : "available" };
9138
10436
  });
9139
10437
  if (isJsonMode()) {
@@ -9163,7 +10461,7 @@ var NON_RUNTIME_SUBSYSTEMS = /* @__PURE__ */ new Set(["openapi-config", "auth-in
9163
10461
  function syncVendoredRuntime(cwd, write) {
9164
10462
  const changes = [];
9165
10463
  for (const v of VENDORED_RUNTIME_FILES) {
9166
- const dest = path28.join(cwd, v.target);
10464
+ const dest = path29.join(cwd, v.target);
9167
10465
  const content = loadRuntimeFile(v.runtime);
9168
10466
  const existing = fs18.existsSync(dest) ? fs18.readFileSync(dest, "utf-8") : null;
9169
10467
  let action;
@@ -9171,7 +10469,7 @@ function syncVendoredRuntime(cwd, write) {
9171
10469
  else if (existing === content) action = "unchanged";
9172
10470
  else action = "updated";
9173
10471
  if (write && action !== "unchanged") {
9174
- fs18.mkdirSync(path28.dirname(dest), { recursive: true });
10472
+ fs18.mkdirSync(path29.dirname(dest), { recursive: true });
9175
10473
  fs18.writeFileSync(dest, content, "utf-8");
9176
10474
  }
9177
10475
  changes.push({ path: v.target, action });
@@ -9186,14 +10484,14 @@ async function syncSubsystemRuntime(cwd, inst, write) {
9186
10484
  if (!fs18.existsSync(source)) {
9187
10485
  return { name: inst.name, changes: [], skippedReason: "no runtime source in package" };
9188
10486
  }
9189
- const subsystemsRoot = path28.dirname(inst.path);
10487
+ const subsystemsRoot = path29.dirname(inst.path);
9190
10488
  const result = await copyRuntime({
9191
10489
  sourceDir: source,
9192
10490
  targetDir: inst.path,
9193
10491
  filter: backendFileFilter(inst.backend, inst.name),
9194
10492
  resolveDeps: true,
9195
10493
  runtimeRoot: runtimeRoot2(),
9196
- depsTargetRoot: path28.resolve(subsystemsRoot, ".."),
10494
+ depsTargetRoot: path29.resolve(subsystemsRoot, ".."),
9197
10495
  dryRun: !write,
9198
10496
  // Refresh files already vendored for this subsystem; never install new
9199
10497
  // ones (that's `subsystem install`). copyRuntime classifies accurately
@@ -9207,7 +10505,7 @@ async function syncSubsystemRuntime(cwd, inst, write) {
9207
10505
  return { name: inst.name, changes };
9208
10506
  }
9209
10507
  function rel(cwd, abs) {
9210
- return path28.relative(cwd, abs) || abs;
10508
+ return path29.relative(cwd, abs) || abs;
9211
10509
  }
9212
10510
  var ProjectUpdateCommand = class extends Command6 {
9213
10511
  static paths = [["project", "update"]];
@@ -9246,7 +10544,7 @@ var ProjectUpdateCommand = class extends Command6 {
9246
10544
  const installed = this.skipSubsystems ? [] : await detectInstalledSubsystems(ctx);
9247
10545
  if (!this.dryRun && !this.force) {
9248
10546
  const vendoredDry = syncVendoredRuntime(ctx.cwd, false);
9249
- const vendoredDirtyCandidates = vendoredDry.filter((c) => c.action === "updated").map((c) => path28.join(ctx.cwd, c.path));
10547
+ const vendoredDirtyCandidates = vendoredDry.filter((c) => c.action === "updated").map((c) => path29.join(ctx.cwd, c.path));
9250
10548
  const skillDirtyCandidates = this.skipSkills ? [] : runSkillsInstall({ cwd: ctx.cwd, dryRun: true }).report?.updated.map((e) => e.dest) ?? [];
9251
10549
  const subsystemDirs = installed.filter((i) => !NON_RUNTIME_SUBSYSTEMS.has(i.name)).map((i) => i.path);
9252
10550
  const gate = checkGitSafety(
@@ -9597,8 +10895,8 @@ var ProjectScanCommand = class extends Command7 {
9597
10895
  cwd = Option7.String("--cwd", { required: false });
9598
10896
  async execute() {
9599
10897
  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;
10898
+ const baseCwd = this.cwd ? path30.resolve(this.cwd) : process.cwd();
10899
+ const target = this.directory ? path30.resolve(baseCwd, this.directory) : baseCwd;
9602
10900
  if (!fs19.existsSync(target)) {
9603
10901
  printError(`Directory not found: ${target}`);
9604
10902
  return 1;
@@ -9649,7 +10947,7 @@ var ProjectScanCommand = class extends Command7 {
9649
10947
  `architecture: ${profile.architecture.evidence.join(", ") || "\u2014"}`
9650
10948
  ]);
9651
10949
  }
9652
- const outPath = path29.join(target, "codegen.config.yaml");
10950
+ const outPath = path30.join(target, "codegen.config.yaml");
9653
10951
  const existsNow = fs19.existsSync(outPath);
9654
10952
  if (this.dryRun) {
9655
10953
  console.log("");
@@ -9770,8 +11068,8 @@ var ProjectInspectCommand = class extends Command7 {
9770
11068
  return 2;
9771
11069
  }
9772
11070
  resolveEntitiesDir(ctx) {
9773
- if (this.dir) return path29.resolve(ctx.cwd, this.dir);
9774
- return ctx.entitiesDir ?? path29.resolve(ctx.cwd, "entities");
11071
+ if (this.dir) return path30.resolve(ctx.cwd, this.dir);
11072
+ return ctx.entitiesDir ?? path30.resolve(ctx.cwd, "entities");
9775
11073
  }
9776
11074
  async runAnalysis(ctx, kind) {
9777
11075
  const entitiesDir = this.resolveEntitiesDir(ctx);
@@ -9974,15 +11272,15 @@ var ProjectGraphCommand = class extends Command7 {
9974
11272
  json: this.json,
9975
11273
  skipDetection: true
9976
11274
  });
9977
- const entitiesDir = this.dir ? path29.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path29.resolve(ctx.cwd, "entities");
11275
+ const entitiesDir = this.dir ? path30.resolve(ctx.cwd, this.dir) : ctx.entitiesDir ?? path30.resolve(ctx.cwd, "entities");
9978
11276
  if (!fs19.existsSync(entitiesDir)) {
9979
11277
  printError(`Entity directory not found: ${entitiesDir}`);
9980
11278
  return 1;
9981
11279
  }
9982
11280
  const relCandidates = [
9983
- path29.resolve(path29.dirname(entitiesDir), "relationships"),
9984
- path29.resolve(entitiesDir, "relationships"),
9985
- path29.resolve(ctx.cwd, "relationships")
11281
+ path30.resolve(path30.dirname(entitiesDir), "relationships"),
11282
+ path30.resolve(entitiesDir, "relationships"),
11283
+ path30.resolve(ctx.cwd, "relationships")
9986
11284
  ];
9987
11285
  const relationshipsDir = relCandidates.find((d) => fs19.existsSync(d));
9988
11286
  const result = await analyzeDomain(entitiesDir, relationshipsDir);
@@ -9998,20 +11296,20 @@ var ProjectGraphCommand = class extends Command7 {
9998
11296
  return 0;
9999
11297
  }
10000
11298
  if (this.output) {
10001
- const outPath = path29.resolve(ctx.cwd, this.output);
11299
+ const outPath = path30.resolve(ctx.cwd, this.output);
10002
11300
  fs19.writeFileSync(outPath, JSON.stringify(serialized, null, 2));
10003
11301
  printSuccess(`Graph written to ${outPath}`);
10004
11302
  printInfo(`${result.entities.length} entities, ${result.relationshipDefinitions.length} relationships, ${result.graph.edges.length} edges`);
10005
11303
  return 0;
10006
11304
  }
10007
11305
  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");
11306
+ const tmpDir = fs19.mkdtempSync(path30.join(os.default.tmpdir(), "codegen-graph-"));
11307
+ const graphPath = path30.join(tmpDir, "graph.json");
10010
11308
  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");
11309
+ const viewerDir = path30.resolve(import.meta.dirname, "..", "..", "..", "tools", "schema-graph-viewer");
11310
+ const viewerDist = path30.join(viewerDir, "dist", "index.html");
10013
11311
  if (fs19.existsSync(viewerDist)) {
10014
- fs19.copyFileSync(graphPath, path29.join(viewerDir, "dist", "graph.json"));
11312
+ fs19.copyFileSync(graphPath, path30.join(viewerDir, "dist", "graph.json"));
10015
11313
  printSuccess("Graph exported");
10016
11314
  printInfo(`${result.entities.length} entities, ${result.relationshipDefinitions.length} relationships, ${result.graph.edges.length} edges`);
10017
11315
  printInfo(`Graph JSON: ${graphPath}`);
@@ -10043,7 +11341,7 @@ var project_default = projectNoun;
10043
11341
 
10044
11342
  // src/cli/commands/dev.ts
10045
11343
  import fs20 from "fs";
10046
- import path30 from "path";
11344
+ import path31 from "path";
10047
11345
  import { execSync as execSync3, spawn, spawnSync } from "child_process";
10048
11346
  import { Command as Command8, Option as Option8 } from "clipanion";
10049
11347
  var DEFAULT_APP_PORT = 3e3;
@@ -10076,14 +11374,14 @@ function getRedisPort(_ctx) {
10076
11374
  return Number(process.env.DEV_REDIS_PORT ?? DEFAULT_REDIS_PORT);
10077
11375
  }
10078
11376
  function composeFilePath(cwd) {
10079
- const devPath = path30.join(cwd, COMPOSE_FILE);
11377
+ const devPath = path31.join(cwd, COMPOSE_FILE);
10080
11378
  if (fs20.existsSync(devPath)) return devPath;
10081
- const rootPath = path30.join(cwd, "docker-compose.yml");
11379
+ const rootPath = path31.join(cwd, "docker-compose.yml");
10082
11380
  if (fs20.existsSync(rootPath)) return rootPath;
10083
11381
  return devPath;
10084
11382
  }
10085
11383
  function pidFilePath(cwd) {
10086
- return path30.join(cwd, PID_FILE);
11384
+ return path31.join(cwd, PID_FILE);
10087
11385
  }
10088
11386
  function readAppPid(cwd) {
10089
11387
  const p = pidFilePath(cwd);
@@ -10144,7 +11442,7 @@ function checkApp(cwd, port) {
10144
11442
  function listEntityNames(ctx) {
10145
11443
  if (!ctx.entitiesDir || !fs20.existsSync(ctx.entitiesDir)) return [];
10146
11444
  return findYamlFiles(ctx.entitiesDir).map(
10147
- (f) => path30.basename(f).replace(/\.ya?ml$/, "")
11445
+ (f) => path31.basename(f).replace(/\.ya?ml$/, "")
10148
11446
  );
10149
11447
  }
10150
11448
  function formatServiceLine(svc) {
@@ -10154,7 +11452,7 @@ function formatServiceLine(svc) {
10154
11452
  return `${icon} ${svc.name.padEnd(12)} ${theme.muted(`${svc.host}:${svc.port}`)} ${status}${pidStr}`;
10155
11453
  }
10156
11454
  function ensureComposeFile(cwd, pgPort, redisPort) {
10157
- const composePath = path30.join(cwd, COMPOSE_FILE);
11455
+ const composePath = path31.join(cwd, COMPOSE_FILE);
10158
11456
  if (fs20.existsSync(composePath)) return composePath;
10159
11457
  const content = `# Auto-generated by codegen dev
10160
11458
  # Ports offset from defaults to avoid conflicts with other local services.
@@ -10239,7 +11537,7 @@ var DevUpCommand = class extends Command8 {
10239
11537
  if (!pgReady) printWarning("postgres did not become healthy in time");
10240
11538
  if (!redisReady) printWarning("redis did not become healthy in time");
10241
11539
  const drizzleConfig = ["drizzle.config.ts", "drizzle.config.js"].find(
10242
- (f) => fs20.existsSync(path30.join(ctx.cwd, f))
11540
+ (f) => fs20.existsSync(path31.join(ctx.cwd, f))
10243
11541
  );
10244
11542
  if (drizzleConfig) {
10245
11543
  if (!isJsonMode()) printInfo("pushing database schema...");
@@ -10259,7 +11557,7 @@ var DevUpCommand = class extends Command8 {
10259
11557
  if (!isJsonMode()) printInfo("starting NestJS app...");
10260
11558
  const dbUrl = `postgres://postgres:postgres@localhost:${pgPort}/codegen_dev`;
10261
11559
  const redisUrl = `redis://localhost:${redisPort}`;
10262
- const logFile = path30.join(ctx.cwd, ".dev-app.log");
11560
+ const logFile = path31.join(ctx.cwd, ".dev-app.log");
10263
11561
  const logFd = fs20.openSync(logFile, "a");
10264
11562
  const child = spawn("bun", ["src/main.ts"], {
10265
11563
  cwd: ctx.cwd,
@@ -10417,7 +11715,7 @@ var DevLogsCommand = class extends Command8 {
10417
11715
  }
10418
11716
  return 0;
10419
11717
  }
10420
- const logFile = path30.join(ctx.cwd, ".dev-app.log");
11718
+ const logFile = path31.join(ctx.cwd, ".dev-app.log");
10421
11719
  if (!fs20.existsSync(logFile)) {
10422
11720
  printInfo("no app logs found \u2014 is the app running?");
10423
11721
  return 0;
@@ -10461,7 +11759,7 @@ var DevRestartCommand = class extends Command8 {
10461
11759
  spawnSync("sleep", ["1"]);
10462
11760
  const dbUrl = `postgres://postgres:postgres@localhost:${pgPort}/codegen_dev`;
10463
11761
  const redisUrl = `redis://localhost:${redisPort}`;
10464
- const logFile = path30.join(ctx.cwd, ".dev-app.log");
11762
+ const logFile = path31.join(ctx.cwd, ".dev-app.log");
10465
11763
  const logFd = fs20.openSync(logFile, "a");
10466
11764
  const child = spawn("bun", ["src/main.ts"], {
10467
11765
  cwd: ctx.cwd,
@@ -10591,7 +11889,7 @@ var dev_default = devNoun;
10591
11889
 
10592
11890
  // src/cli/commands/relationship.ts
10593
11891
  import fs21 from "fs";
10594
- import path31 from "path";
11892
+ import path32 from "path";
10595
11893
  import { Command as Command9, Option as Option9 } from "clipanion";
10596
11894
  function listRelationshipYamls2(dir) {
10597
11895
  if (!fs21.existsSync(dir)) return [];
@@ -10618,7 +11916,7 @@ function padRight2(s, n) {
10618
11916
  return s.length >= n ? s : s + " ".repeat(n - s.length);
10619
11917
  }
10620
11918
  async function summary6(ctx) {
10621
- const relDir = path31.resolve(ctx.cwd, "relationships");
11919
+ const relDir = path32.resolve(ctx.cwd, "relationships");
10622
11920
  const files = listRelationshipYamls2(relDir);
10623
11921
  if (files.length === 0) {
10624
11922
  return {
@@ -10688,14 +11986,14 @@ var RelationshipNewCommand = class extends Command9 {
10688
11986
  }
10689
11987
  let targets = [];
10690
11988
  if (this.all) {
10691
- const dir = path31.resolve(ctx.cwd, "relationships");
11989
+ const dir = path32.resolve(ctx.cwd, "relationships");
10692
11990
  targets = listRelationshipYamls2(dir);
10693
11991
  if (targets.length === 0) {
10694
11992
  printError(`No relationship YAML files found in ${dir}`);
10695
11993
  return 1;
10696
11994
  }
10697
11995
  } else if (this.yaml) {
10698
- targets = [path31.resolve(ctx.cwd, this.yaml)];
11996
+ targets = [path32.resolve(ctx.cwd, this.yaml)];
10699
11997
  } else {
10700
11998
  printError("Missing YAML path. Pass a file or --all.");
10701
11999
  return 2;
@@ -10712,7 +12010,7 @@ var RelationshipNewCommand = class extends Command9 {
10712
12010
  }
10713
12011
  if (invalid.length > 0) {
10714
12012
  for (const i of invalid) {
10715
- printError(`${path31.basename(i.file)} \u2014 ${i.message}`);
12013
+ printError(`${path32.basename(i.file)} \u2014 ${i.message}`);
10716
12014
  }
10717
12015
  if (!isJsonMode()) return 1;
10718
12016
  }
@@ -10743,7 +12041,7 @@ var RelationshipNewCommand = class extends Command9 {
10743
12041
  }
10744
12042
  const succeeded = [];
10745
12043
  const failed = [
10746
- ...invalid.map((i) => ({ name: path31.basename(i.file), file: i.file, message: i.message }))
12044
+ ...invalid.map((i) => ({ name: path32.basename(i.file), file: i.file, message: i.message }))
10747
12045
  ];
10748
12046
  for (const v of validated) {
10749
12047
  if (!isJsonMode()) {
@@ -10762,8 +12060,8 @@ var RelationshipNewCommand = class extends Command9 {
10762
12060
  if (!isJsonMode()) printError(`${v.name} \u2014 ${res.stderr ?? "failed"}`);
10763
12061
  }
10764
12062
  }
10765
- const entitiesDir = ctx.entitiesDir ?? path31.resolve(ctx.cwd, "entities");
10766
- const relationshipsDir = path31.resolve(ctx.cwd, "relationships");
12063
+ const entitiesDir = ctx.entitiesDir ?? path32.resolve(ctx.cwd, "entities");
12064
+ const relationshipsDir = path32.resolve(ctx.cwd, "relationships");
10767
12065
  const generatedDir = resolveGeneratedDir(ctx);
10768
12066
  const architecture = resolveArchitecture(ctx);
10769
12067
  let barrelResult = null;
@@ -10808,7 +12106,7 @@ var RelationshipNewCommand = class extends Command9 {
10808
12106
  }
10809
12107
  if (barrelResult) {
10810
12108
  printInfo(
10811
- `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path31.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path31.relative(ctx.cwd, barrelResult.schemaBarrel)}`
12109
+ `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path32.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path32.relative(ctx.cwd, barrelResult.schemaBarrel)}`
10812
12110
  );
10813
12111
  }
10814
12112
  }
@@ -10831,7 +12129,7 @@ var RelationshipListCommand = class extends Command9 {
10831
12129
  json: this.json,
10832
12130
  skipDetection: true
10833
12131
  });
10834
- const relDir = path31.resolve(ctx.cwd, "relationships");
12132
+ const relDir = path32.resolve(ctx.cwd, "relationships");
10835
12133
  const files = listRelationshipYamls2(relDir);
10836
12134
  if (files.length === 0) {
10837
12135
  printInfo("No relationship definitions found.");
@@ -10871,7 +12169,7 @@ var relationshipNoun = {
10871
12169
  var relationship_default = relationshipNoun;
10872
12170
 
10873
12171
  // src/cli/commands/junction.ts
10874
- import path32 from "path";
12172
+ import path33 from "path";
10875
12173
  import { Command as Command10, Option as Option10 } from "clipanion";
10876
12174
  function summarizeJunctionFile(filePath) {
10877
12175
  const result = loadJunctionFromYaml(filePath);
@@ -10894,7 +12192,7 @@ function padRight3(s, n) {
10894
12192
  return s.length >= n ? s : s + " ".repeat(n - s.length);
10895
12193
  }
10896
12194
  async function summary7(ctx) {
10897
- const junctionDir = path32.resolve(ctx.cwd, "junctions");
12195
+ const junctionDir = path33.resolve(ctx.cwd, "junctions");
10898
12196
  const files = listJunctionYamls(junctionDir);
10899
12197
  if (files.length === 0) {
10900
12198
  return {
@@ -10964,14 +12262,14 @@ var JunctionNewCommand = class extends Command10 {
10964
12262
  }
10965
12263
  let targets = [];
10966
12264
  if (this.all) {
10967
- const dir = path32.resolve(ctx.cwd, "junctions");
12265
+ const dir = path33.resolve(ctx.cwd, "junctions");
10968
12266
  targets = listJunctionYamls(dir);
10969
12267
  if (targets.length === 0) {
10970
12268
  printError(`No junction YAML files found in ${dir}`);
10971
12269
  return 1;
10972
12270
  }
10973
12271
  } else if (this.yaml) {
10974
- targets = [path32.resolve(ctx.cwd, this.yaml)];
12272
+ targets = [path33.resolve(ctx.cwd, this.yaml)];
10975
12273
  } else {
10976
12274
  printError("Missing YAML path. Pass a file or --all.");
10977
12275
  return 2;
@@ -10990,7 +12288,7 @@ var JunctionNewCommand = class extends Command10 {
10990
12288
  }
10991
12289
  if (invalid.length > 0) {
10992
12290
  for (const i of invalid) {
10993
- printError(`${path32.basename(i.file)} \u2014 ${i.message}`);
12291
+ printError(`${path33.basename(i.file)} \u2014 ${i.message}`);
10994
12292
  }
10995
12293
  if (!isJsonMode()) return 1;
10996
12294
  }
@@ -11021,7 +12319,7 @@ var JunctionNewCommand = class extends Command10 {
11021
12319
  }
11022
12320
  const succeeded = [];
11023
12321
  const failed = [
11024
- ...invalid.map((i) => ({ name: path32.basename(i.file), file: i.file, message: i.message }))
12322
+ ...invalid.map((i) => ({ name: path33.basename(i.file), file: i.file, message: i.message }))
11025
12323
  ];
11026
12324
  for (const v of validated) {
11027
12325
  if (!isJsonMode()) {
@@ -11040,9 +12338,9 @@ var JunctionNewCommand = class extends Command10 {
11040
12338
  if (!isJsonMode()) printError(`${v.name} \u2014 ${res.stderr ?? "failed"}`);
11041
12339
  }
11042
12340
  }
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");
12341
+ const entitiesDir = ctx.entitiesDir ?? path33.resolve(ctx.cwd, "entities");
12342
+ const relationshipsDir = path33.resolve(ctx.cwd, "relationships");
12343
+ const junctionsDir = path33.resolve(ctx.cwd, "junctions");
11046
12344
  const generatedDir = resolveGeneratedDir(ctx);
11047
12345
  const architecture = resolveArchitecture(ctx);
11048
12346
  let barrelResult = null;
@@ -11088,7 +12386,7 @@ var JunctionNewCommand = class extends Command10 {
11088
12386
  }
11089
12387
  if (barrelResult) {
11090
12388
  printInfo(
11091
- `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path32.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path32.relative(ctx.cwd, barrelResult.schemaBarrel)}`
12389
+ `barrels regenerated (${barrelResult.entityCount} modules) \u2192 ${path33.relative(ctx.cwd, barrelResult.modulesBarrel)}, ${path33.relative(ctx.cwd, barrelResult.schemaBarrel)}`
11092
12390
  );
11093
12391
  }
11094
12392
  }
@@ -11111,7 +12409,7 @@ var JunctionListCommand = class extends Command10 {
11111
12409
  json: this.json,
11112
12410
  skipDetection: true
11113
12411
  });
11114
- const junctionDir = path32.resolve(ctx.cwd, "junctions");
12412
+ const junctionDir = path33.resolve(ctx.cwd, "junctions");
11115
12413
  const files = listJunctionYamls(junctionDir);
11116
12414
  if (files.length === 0) {
11117
12415
  printInfo("No junction definitions found.");
@@ -11152,7 +12450,7 @@ var junction_default = junctionNoun;
11152
12450
 
11153
12451
  // src/cli/commands/events.ts
11154
12452
  import fs22 from "fs";
11155
- import path33 from "path";
12453
+ import path34 from "path";
11156
12454
  import ts2 from "typescript";
11157
12455
  import { Command as Command11, Option as Option11 } from "clipanion";
11158
12456
  function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
@@ -11288,7 +12586,7 @@ function suggestEventTypes(target, known, limit = 3) {
11288
12586
  return known.map((t) => ({ t, d: levenshtein(target, t) })).sort((a, b) => a.d - b.d).slice(0, limit).map((x) => x.t);
11289
12587
  }
11290
12588
  function renderConsumerReport(result, cwd) {
11291
- const rel2 = (p) => path33.relative(cwd, p) || p;
12589
+ const rel2 = (p) => path34.relative(cwd, p) || p;
11292
12590
  const lines = [];
11293
12591
  const total = result.tier3.length + result.tier2.length + result.tier1.length;
11294
12592
  lines.push(`Event: ${result.eventType}`);
@@ -11324,7 +12622,7 @@ function renderConsumerReport(result, cwd) {
11324
12622
  return lines;
11325
12623
  }
11326
12624
  function runConsumersScan(opts) {
11327
- const scanRoot = opts.scanRoot ?? path33.join(opts.cwd, "src");
12625
+ const scanRoot = opts.scanRoot ?? path34.join(opts.cwd, "src");
11328
12626
  const handlersDir = opts.handlersDir ?? scanRoot;
11329
12627
  const allTriggers = scanHandlerFiles(handlersDir);
11330
12628
  const tier3 = allTriggers.filter((t) => t.event === opts.eventType).map((t) => ({
@@ -11334,7 +12632,7 @@ function runConsumersScan(opts) {
11334
12632
  sourceLine: t.sourceLine
11335
12633
  }));
11336
12634
  const tier21 = fs22.existsSync(scanRoot) ? scanDirectoryForConsumers(scanRoot, opts.eventType) : { tier2: [], tier1: [], hasEventFlowImport: false };
11337
- const eventsGeneratedDir = opts.eventsGeneratedDir ?? path33.join(
12635
+ const eventsGeneratedDir = opts.eventsGeneratedDir ?? path34.join(
11338
12636
  resolveSubsystemsRootFromContext(opts.cwd, opts.config),
11339
12637
  "events",
11340
12638
  "generated"
@@ -11355,11 +12653,11 @@ function runConsumersScan(opts) {
11355
12653
  function resolveSubsystemsRootFromContext(cwd, config) {
11356
12654
  const configured = config?.paths?.subsystems;
11357
12655
  if (typeof configured === "string" && configured.length > 0) {
11358
- return path33.resolve(cwd, configured);
12656
+ return path34.resolve(cwd, configured);
11359
12657
  }
11360
12658
  const backendSrc = config?.paths?.backend_src;
11361
12659
  const base = typeof backendSrc === "string" && backendSrc.length > 0 ? backendSrc : "src";
11362
- return path33.resolve(cwd, base, "shared", "subsystems");
12660
+ return path34.resolve(cwd, base, "shared", "subsystems");
11363
12661
  }
11364
12662
  var EventsConsumersCommand = class extends Command11 {
11365
12663
  static paths = [["events", "consumers"]];
@@ -11448,7 +12746,7 @@ var eventsNoun = {
11448
12746
  var events_default = eventsNoun;
11449
12747
 
11450
12748
  // src/cli/commands/orchestration.ts
11451
- import path34 from "path";
12749
+ import path35 from "path";
11452
12750
  import { Command as Command12, Option as Option12 } from "clipanion";
11453
12751
  var DEFAULT_PATTERN_GLOBS = ["src/patterns/*.pattern.ts"];
11454
12752
  function resolvePatternGlobs(ctx) {
@@ -11462,10 +12760,10 @@ function resolveOrchestrationOutputRoot(ctx) {
11462
12760
  const paths = ctx.config?.paths;
11463
12761
  const explicit = paths?.orchestration_src;
11464
12762
  if (typeof explicit === "string" && explicit.length > 0) {
11465
- return path34.resolve(ctx.cwd, explicit);
12763
+ return path35.resolve(ctx.cwd, explicit);
11466
12764
  }
11467
12765
  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");
12766
+ return path35.resolve(ctx.cwd, backendSrc, "orchestration");
11469
12767
  }
11470
12768
  async function reloadRegistry(ctx) {
11471
12769
  _resetRegistryForTests({ includeLibrary: false });
@@ -11554,12 +12852,12 @@ var OrchestrationGenCommand = class extends Command12 {
11554
12852
  );
11555
12853
  for (const f of result.files) {
11556
12854
  console.log(
11557
- ` ${theme.muted(icons.arrow)} ${path34.relative(ctx.cwd, f.outputPath)}`
12855
+ ` ${theme.muted(icons.arrow)} ${path35.relative(ctx.cwd, f.outputPath)}`
11558
12856
  );
11559
12857
  }
11560
12858
  } else {
11561
12859
  printSuccess(
11562
- `Emitted ${result.files.length} file(s) across ${targets.length} pattern(s) \u2192 ${path34.relative(ctx.cwd, outputRoot)}`
12860
+ `Emitted ${result.files.length} file(s) across ${targets.length} pattern(s) \u2192 ${path35.relative(ctx.cwd, outputRoot)}`
11563
12861
  );
11564
12862
  }
11565
12863
  return 0;
@@ -11725,7 +13023,7 @@ var update_default = UpdateShortcut;
11725
13023
  // src/cli/index.ts
11726
13024
  function readVersion() {
11727
13025
  try {
11728
- const pkgPath = join10(import.meta.dirname, "..", "..", "package.json");
13026
+ const pkgPath = join18(import.meta.dirname, "..", "..", "package.json");
11729
13027
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
11730
13028
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
11731
13029
  } catch {