@noego/wood 0.4.4 → 0.4.6

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.
@@ -1,3 +1,9 @@
1
+ function formatPropertyKey(key) {
2
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
3
+ return key;
4
+ }
5
+ return JSON.stringify(key);
6
+ }
1
7
  function operationHasInput(op) {
2
8
  return Boolean(
3
9
  op.inputSchema && op.inputSchema.type === "object" && Object.keys(op.inputSchema.properties ?? {}).length > 0
@@ -26,7 +32,7 @@ function jsonSchemaToTS(schema, indent = "") {
26
32
  const lines = entries.map(([key, val]) => {
27
33
  const optional = required.has(key) ? "" : "?";
28
34
  const valType = jsonSchemaToTS(val, indent + " ");
29
- return `${indent} ${key}${optional}: ${valType};`;
35
+ return `${indent} ${formatPropertyKey(key)}${optional}: ${valType};`;
30
36
  });
31
37
  return `{
32
38
  ${lines.join("\n")}
@@ -57,12 +63,12 @@ function generateTypeTree(node, indent = " ") {
57
63
  const outputType = jsonSchemaToTS(op.outputSchema, indent + " ");
58
64
  const hasInput = operationHasInput(op);
59
65
  if (hasInput) {
60
- lines.push(`${indent}${key}(data: ${inputType}): Promise<${outputType}>;`);
66
+ lines.push(`${indent}${formatPropertyKey(key)}(data: ${inputType}): Promise<${outputType}>;`);
61
67
  } else {
62
- lines.push(`${indent}${key}(): Promise<${outputType}>;`);
68
+ lines.push(`${indent}${formatPropertyKey(key)}(): Promise<${outputType}>;`);
63
69
  }
64
70
  } else {
65
- lines.push(`${indent}${key}: {`);
71
+ lines.push(`${indent}${formatPropertyKey(key)}: {`);
66
72
  lines.push(generateTypeTree(entry, indent + " "));
67
73
  lines.push(`${indent}};`);
68
74
  }
@@ -96,15 +102,15 @@ function generateRpcTypeTree(operations) {
96
102
  const controllerNames = Object.keys(controllerTree).sort((a, b) => a.localeCompare(b));
97
103
  const lines = [];
98
104
  for (const controllerName of controllerNames) {
99
- lines.push(` ${controllerName}: {`);
105
+ lines.push(` ${formatPropertyKey(controllerName)}: {`);
100
106
  const actions = controllerTree[controllerName];
101
107
  const actionNames = Object.keys(actions).sort((a, b) => a.localeCompare(b));
102
108
  for (const actionName of actionNames) {
103
109
  const action = actions[actionName];
104
110
  if (action.hasInput) {
105
- lines.push(` ${actionName}(data: ${action.inputType}): Promise<${action.outputType}>;`);
111
+ lines.push(` ${formatPropertyKey(actionName)}(data: ${action.inputType}): Promise<${action.outputType}>;`);
106
112
  } else {
107
- lines.push(` ${actionName}(): Promise<${action.outputType}>;`);
113
+ lines.push(` ${formatPropertyKey(actionName)}(): Promise<${action.outputType}>;`);
108
114
  }
109
115
  }
110
116
  lines.push(" };");
@@ -124,12 +130,30 @@ export interface WoodRpcManifestEntry {
124
130
  hasInput: boolean;
125
131
  }
126
132
 
127
- export interface WoodRpcApi {
133
+ /**
134
+ * Project-specific renderer App facade contract.
135
+ *
136
+ * This matches @noego/wood/client getApp() and injected App instances:
137
+ * controller name -> controller action.
138
+ */
139
+ export interface GeneratedApp {
128
140
  ${rpcTypeCode}
129
141
  }
130
142
 
131
- export interface WoodApi {
143
+ /** Backward-compatible name for the generated client App facade contract. */
144
+ export interface WoodRpcApi extends GeneratedApp {}
145
+
146
+ /**
147
+ * Project-specific raw preload bridge contract.
148
+ *
149
+ * This matches the channel path exposed by preload.generated.ts:
150
+ * operation channel segments -> invoke function.
151
+ */
152
+ export interface GeneratedWoodBridge {
132
153
  ${typesCode}
154
+ }
155
+
156
+ export interface WoodApi extends GeneratedWoodBridge {
133
157
  __rpcManifest: WoodRpcManifestEntry[];
134
158
  __load(route: string, params: Record<string, string>): Promise<unknown>;
135
159
  __navigate(windowId: string, route: string): Promise<void>;
@@ -153,6 +177,7 @@ ${typesCode}
153
177
  webContentsId: number;
154
178
  url: string;
155
179
  focused: boolean;
180
+ rendererHealth?: 'ready' | 'loading' | 'unresponsive' | 'gone' | 'destroyed';
156
181
  }>;
157
182
  }>;
158
183
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/codegen/type_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.js';\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys((op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {}).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string = ''): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n const itemType = jsonSchemaToTS(schema.items as Record<string, unknown>, indent);\n return `Array<${itemType}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n return `${indent} ${key}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent}}`;\n }\n\n return 'unknown';\n}\n\nfunction buildNestedTypeTree(operations: Map<string, OperationDef>): Record<string, unknown> {\n const tree: Record<string, unknown> = {};\n for (const [channel, op] of operations) {\n const parts = channel.split('.');\n let current: Record<string, unknown> = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) current[parts[i]] = {};\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = { __operation: op };\n }\n return tree;\n}\n\nfunction generateTypeTree(node: Record<string, unknown>, indent: string = ' '): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(node)) {\n const entry = value as Record<string, unknown>;\n if (entry.__operation) {\n const op = entry.__operation as OperationDef;\n const inputType = jsonSchemaToTS(op.inputSchema as Record<string, unknown>, indent + ' ');\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, indent + ' ');\n const hasInput = operationHasInput(op);\n\n if (hasInput) {\n lines.push(`${indent}${key}(data: ${inputType}): Promise<${outputType}>;`);\n } else {\n lines.push(`${indent}${key}(): Promise<${outputType}>;`);\n }\n } else {\n lines.push(`${indent}${key}: {`);\n lines.push(generateTypeTree(entry, indent + ' '));\n lines.push(`${indent}};`);\n }\n }\n return lines.join('\\n');\n}\n\ntype RpcActionTypeEntry = {\n channel: string;\n inputType: string;\n outputType: string;\n hasInput: boolean;\n};\n\nfunction buildRpcControllerTree(\n operations: Map<string, OperationDef>,\n): Record<string, Record<string, RpcActionTypeEntry>> {\n const tree: Record<string, Record<string, RpcActionTypeEntry>> = {};\n\n for (const operation of operations.values()) {\n const controller = operation.controller;\n const action = operation.action;\n\n if (!tree[controller]) {\n tree[controller] = {};\n }\n\n if (tree[controller][action]) {\n throw new Error(\n `Type codegen failed: duplicate RPC action \"${controller}.${action}\" found in channel \"${operation.channel}\".`,\n );\n }\n\n tree[controller][action] = {\n channel: operation.channel,\n inputType: jsonSchemaToTS(operation.inputSchema as Record<string, unknown>, ' '),\n outputType: jsonSchemaToTS(operation.outputSchema as Record<string, unknown>, ' '),\n hasInput: operationHasInput(operation),\n };\n }\n\n return tree;\n}\n\nfunction generateRpcTypeTree(operations: Map<string, OperationDef>): string {\n const controllerTree = buildRpcControllerTree(operations);\n const controllerNames = Object.keys(controllerTree).sort((a, b) => a.localeCompare(b));\n const lines: string[] = [];\n\n for (const controllerName of controllerNames) {\n lines.push(` ${controllerName}: {`);\n const actions = controllerTree[controllerName];\n const actionNames = Object.keys(actions).sort((a, b) => a.localeCompare(b));\n for (const actionName of actionNames) {\n const action = actions[actionName];\n if (action.hasInput) {\n lines.push(` ${actionName}(data: ${action.inputType}): Promise<${action.outputType}>;`);\n } else {\n lines.push(` ${actionName}(): Promise<${action.outputType}>;`);\n }\n }\n lines.push(' };');\n }\n\n return lines.join('\\n');\n}\n\nexport function generateTypes(operations: Map<string, OperationDef>): string {\n const tree = buildNestedTypeTree(operations);\n const typesCode = generateTypeTree(tree);\n const rpcTypeCode = generateRpcTypeTree(operations);\n\n return `// AUTO-GENERATED by @noego/wood -- do not edit\nexport interface WoodRpcManifestEntry {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n}\n\nexport interface WoodRpcApi {\n${rpcTypeCode}\n}\n\nexport interface WoodApi {\n${typesCode}\n __rpcManifest: WoodRpcManifestEntry[];\n __load(route: string, params: Record<string, string>): Promise<unknown>;\n __navigate(windowId: string, route: string): Promise<void>;\n __window: {\n current(): Promise<{ ok: boolean; windowId?: string; defaultRoute?: string }>;\n open(windowId: string, options?: Record<string, unknown>): Promise<void>;\n close(windowId?: string): Promise<void>;\n focus(windowId: string): Promise<void>;\n minimize(windowId?: string): Promise<void>;\n maximize(windowId?: string): Promise<void>;\n };\n __debug: {\n current(): Promise<{\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: Array<{\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n }>;\n }>;\n };\n __contextMenu: {\n show(data: {\n items: Array<{ label?: string; action?: string; data?: string; _callbackId?: string; disabled?: boolean; type?: 'separator' }>;\n position: { x: number; y: number };\n }): Promise<void>;\n };\n on(channel: string, callback: (...args: unknown[]) => void): () => void;\n __log(level: string, logger: string, message: string, context?: unknown): Promise<{\n ok: boolean;\n forwarded: boolean;\n configured: boolean;\n reason?: 'unconfigured' | 'callback-error';\n errorMessage?: string;\n }>;\n}\n`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/codegen/type_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.js';\n\nfunction formatPropertyKey(key: string): string {\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {\n return key;\n }\n\n return JSON.stringify(key);\n}\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys((op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {}).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string = ''): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n const itemType = jsonSchemaToTS(schema.items as Record<string, unknown>, indent);\n return `Array<${itemType}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n return `${indent} ${formatPropertyKey(key)}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent}}`;\n }\n\n return 'unknown';\n}\n\nfunction buildNestedTypeTree(operations: Map<string, OperationDef>): Record<string, unknown> {\n const tree: Record<string, unknown> = {};\n for (const [channel, op] of operations) {\n const parts = channel.split('.');\n let current: Record<string, unknown> = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) current[parts[i]] = {};\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = { __operation: op };\n }\n return tree;\n}\n\nfunction generateTypeTree(node: Record<string, unknown>, indent: string = ' '): string {\n const lines: string[] = [];\n for (const [key, value] of Object.entries(node)) {\n const entry = value as Record<string, unknown>;\n if (entry.__operation) {\n const op = entry.__operation as OperationDef;\n const inputType = jsonSchemaToTS(op.inputSchema as Record<string, unknown>, indent + ' ');\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, indent + ' ');\n const hasInput = operationHasInput(op);\n\n if (hasInput) {\n lines.push(`${indent}${formatPropertyKey(key)}(data: ${inputType}): Promise<${outputType}>;`);\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}(): Promise<${outputType}>;`);\n }\n } else {\n lines.push(`${indent}${formatPropertyKey(key)}: {`);\n lines.push(generateTypeTree(entry, indent + ' '));\n lines.push(`${indent}};`);\n }\n }\n return lines.join('\\n');\n}\n\ntype RpcActionTypeEntry = {\n channel: string;\n inputType: string;\n outputType: string;\n hasInput: boolean;\n};\n\nfunction buildRpcControllerTree(\n operations: Map<string, OperationDef>,\n): Record<string, Record<string, RpcActionTypeEntry>> {\n const tree: Record<string, Record<string, RpcActionTypeEntry>> = {};\n\n for (const operation of operations.values()) {\n const controller = operation.controller;\n const action = operation.action;\n\n if (!tree[controller]) {\n tree[controller] = {};\n }\n\n if (tree[controller][action]) {\n throw new Error(\n `Type codegen failed: duplicate RPC action \"${controller}.${action}\" found in channel \"${operation.channel}\".`,\n );\n }\n\n tree[controller][action] = {\n channel: operation.channel,\n inputType: jsonSchemaToTS(operation.inputSchema as Record<string, unknown>, ' '),\n outputType: jsonSchemaToTS(operation.outputSchema as Record<string, unknown>, ' '),\n hasInput: operationHasInput(operation),\n };\n }\n\n return tree;\n}\n\nfunction generateRpcTypeTree(operations: Map<string, OperationDef>): string {\n const controllerTree = buildRpcControllerTree(operations);\n const controllerNames = Object.keys(controllerTree).sort((a, b) => a.localeCompare(b));\n const lines: string[] = [];\n\n for (const controllerName of controllerNames) {\n lines.push(` ${formatPropertyKey(controllerName)}: {`);\n const actions = controllerTree[controllerName];\n const actionNames = Object.keys(actions).sort((a, b) => a.localeCompare(b));\n for (const actionName of actionNames) {\n const action = actions[actionName];\n if (action.hasInput) {\n lines.push(` ${formatPropertyKey(actionName)}(data: ${action.inputType}): Promise<${action.outputType}>;`);\n } else {\n lines.push(` ${formatPropertyKey(actionName)}(): Promise<${action.outputType}>;`);\n }\n }\n lines.push(' };');\n }\n\n return lines.join('\\n');\n}\n\nexport function generateTypes(operations: Map<string, OperationDef>): string {\n const tree = buildNestedTypeTree(operations);\n const typesCode = generateTypeTree(tree);\n const rpcTypeCode = generateRpcTypeTree(operations);\n\n return `// AUTO-GENERATED by @noego/wood -- do not edit\nexport interface WoodRpcManifestEntry {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n}\n\n/**\n * Project-specific renderer App facade contract.\n *\n * This matches @noego/wood/client getApp() and injected App instances:\n * controller name -> controller action.\n */\nexport interface GeneratedApp {\n${rpcTypeCode}\n}\n\n/** Backward-compatible name for the generated client App facade contract. */\nexport interface WoodRpcApi extends GeneratedApp {}\n\n/**\n * Project-specific raw preload bridge contract.\n *\n * This matches the channel path exposed by preload.generated.ts:\n * operation channel segments -> invoke function.\n */\nexport interface GeneratedWoodBridge {\n${typesCode}\n}\n\nexport interface WoodApi extends GeneratedWoodBridge {\n __rpcManifest: WoodRpcManifestEntry[];\n __load(route: string, params: Record<string, string>): Promise<unknown>;\n __navigate(windowId: string, route: string): Promise<void>;\n __window: {\n current(): Promise<{ ok: boolean; windowId?: string; defaultRoute?: string }>;\n open(windowId: string, options?: Record<string, unknown>): Promise<void>;\n close(windowId?: string): Promise<void>;\n focus(windowId: string): Promise<void>;\n minimize(windowId?: string): Promise<void>;\n maximize(windowId?: string): Promise<void>;\n };\n __debug: {\n current(): Promise<{\n ok: boolean;\n enabled: boolean;\n mode: 'development' | 'production';\n port?: number;\n windows: Array<{\n windowId: string;\n title: string;\n webContentsId: number;\n url: string;\n focused: boolean;\n rendererHealth?: 'ready' | 'loading' | 'unresponsive' | 'gone' | 'destroyed';\n }>;\n }>;\n };\n __contextMenu: {\n show(data: {\n items: Array<{ label?: string; action?: string; data?: string; _callbackId?: string; disabled?: boolean; type?: 'separator' }>;\n position: { x: number; y: number };\n }): Promise<void>;\n };\n on(channel: string, callback: (...args: unknown[]) => void): () => void;\n __log(level: string, logger: string, message: string, context?: unknown): Promise<{\n ok: boolean;\n forwarded: boolean;\n configured: boolean;\n reason?: 'unconfigured' | 'callback-error';\n errorMessage?: string;\n }>;\n}\n`;\n}\n"],"mappings":"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","names":[]}
@@ -108,6 +108,7 @@ class WoodDevServer {
108
108
  const rendererComponentDir = import_path.default.resolve(this.cwd, this.config.renderer.componentDir);
109
109
  const rendererOutDir = import_path.default.resolve(this.cwd, this.config.renderer.componentDir, "generated");
110
110
  (0, import_fs.mkdirSync)(rendererOutDir, { recursive: true });
111
+ (0, import_fs.writeFileSync)(import_path.default.join(rendererOutDir, "wood.d.ts"), typesCode);
111
112
  (0, import_fs.writeFileSync)(
112
113
  import_path.default.join(rendererOutDir, "app.ts"),
113
114
  (0, import_renderer_app_generator.generateRendererApp)(views, {
@@ -188,6 +189,7 @@ class WoodDevServer {
188
189
  viewsYamlPath: this.config.renderer.openapi,
189
190
  controllersDir: this.config.main.controllers,
190
191
  outDir,
192
+ rendererOutDir: import_path.default.resolve(this.cwd, this.config.renderer.componentDir, "generated"),
191
193
  cwd: this.cwd,
192
194
  onViewsChange: async () => {
193
195
  const viewsContent = (0, import_fs.readFileSync)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/dev/wood_dev_server.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseConfig } from '../config/config_parser.cjs';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport { parseViews } from '../parser/views_parser.cjs';\nimport { generatePreload } from '../codegen/preload_generator.cjs';\nimport { generateTypes } from '../codegen/type_generator.cjs';\nimport { generateTestMock } from '../codegen/test_mock_generator.cjs';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.cjs';\nimport { generateRuntime } from '../codegen/runtime_generator.cjs';\nimport { generateRendererApp } from '../codegen/renderer_app_generator.cjs';\nimport { BackendBridge } from '../bridge/backend_bridge.cjs';\nimport { BackendWatcher } from './backend_watcher.cjs';\nimport { YamlWatcher } from './yaml_watcher.cjs';\nimport { EsbuildBackend } from './esbuild_backend.cjs';\nimport type { WoodConfig } from '../types/index.cjs';\n\nconst logger = getLogger('wood:dev');\n\nexport interface DevServerOptions {\n configPath?: string;\n cwd?: string;\n}\n\nexport class WoodDevServer {\n private config: WoodConfig | null = null;\n private bridge: BackendBridge | null = null;\n private backendWatcher: BackendWatcher | null = null;\n private yamlWatcher: YamlWatcher | null = null;\n private esbuild: EsbuildBackend | null = null;\n private cwd: string;\n\n constructor(options: DevServerOptions = {}) {\n this.cwd = options.cwd ?? process.cwd();\n }\n\n /**\n * Start the Wood development server.\n *\n * Automatically sets process.env.WOOD_MODE = 'development' so the app\n * can conditionally use dynamic controller imports (dev) vs pre-registered\n * controllers from the generated file (production).\n *\n * Steps:\n * 1. Parse config\n * 2. Parse + stitch YAML\n * 3. Generate preload, types, mock\n * 4. Build backend with esbuild\n * 5. (electron-vite would be started here -- omitted for MVP)\n * 6. Spawn backend via bridge\n * 7. Start watchers\n */\n async start(): Promise<void> {\n process.env.WOOD_MODE = 'development';\n\n // 1. Parse config\n const configPath = path.resolve(this.cwd, 'wood.config.yml');\n this.config = parseConfig(configPath);\n logger.info('config loaded', { outDir: this.config.outDir });\n\n // 2. Parse operations YAML\n const opsYamlPath = path.resolve(this.cwd, this.config.main.openapi);\n const opsContent = readFileSync(opsYamlPath, 'utf-8');\n const operations = parseOperations(opsContent);\n logger.info('operations parsed', { count: operations.size });\n\n // 3. Parse views YAML\n const viewsYamlPath = path.resolve(this.cwd, this.config.renderer.openapi);\n const viewsContent = readFileSync(viewsYamlPath, 'utf-8');\n const views = parseViews(viewsContent);\n logger.info('views parsed', { count: views.length });\n\n // 4. Generate files\n const outDir = path.resolve(this.cwd, this.config.outDir);\n mkdirSync(outDir, { recursive: true });\n\n const preloadCode = generatePreload(operations);\n writeFileSync(path.join(outDir, 'preload.generated.ts'), preloadCode);\n\n const typesCode = generateTypes(operations);\n writeFileSync(path.join(outDir, 'wood.d.ts'), typesCode);\n\n const mockCode = generateTestMock(operations);\n writeFileSync(path.join(outDir, 'wood.mock.ts'), mockCode);\n\n const controllersDir = path.resolve(this.cwd, this.config.main.controllers);\n const controllerRegCode = generateControllerRegistration(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'controllers.generated.ts'), controllerRegCode);\n\n const runtimeCode = generateRuntime(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'runtime.generated.ts'), runtimeCode);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config.renderer.entry,\n }),\n );\n\n logger.info('codegen complete', { outDir });\n\n // 5. Build backend with esbuild (initial build + create incremental context)\n const backendEntry = this.config.main.entry;\n const backendOutfile = path.join(this.config.outDir, 'backend', 'index.js');\n\n this.esbuild = new EsbuildBackend({\n entryPoint: backendEntry,\n outfile: backendOutfile,\n cwd: this.cwd,\n external: this.config.build ? [] : undefined,\n });\n\n await this.esbuild.build();\n await this.esbuild.createContext();\n\n // 6. Start backend bridge (split-serve)\n if (this.config.dev?.splitServe !== false) {\n const resolvedBackendEntry = path.resolve(this.cwd, backendOutfile);\n this.bridge = new BackendBridge({\n backendEntry: resolvedBackendEntry,\n onReady: () => {\n logger.info('backend ready (split-serve)');\n },\n onPush: (channel, data) => {\n logger.debug('backend push', { channel, data });\n },\n onExit: (code, signal) => {\n logger.debug('backend exited', { code, signal });\n },\n });\n await this.bridge.start();\n }\n\n // 7. Start watchers\n if (this.config.dev?.watch !== false) {\n this.startWatchers(outDir);\n }\n }\n\n /** Stop the dev server and clean up all resources. */\n async stop(): Promise<void> {\n this.backendWatcher?.stop();\n this.yamlWatcher?.stop();\n\n if (this.bridge) {\n await this.bridge.stop();\n logger.info('backend bridge stopped');\n }\n\n if (this.esbuild) {\n await this.esbuild.dispose();\n }\n }\n\n /** Get the parsed config. */\n getConfig(): WoodConfig | null {\n return this.config;\n }\n\n private startWatchers(outDir: string): void {\n if (!this.config) return;\n\n // Backend watcher: server + middleware files -> rebuild + restart\n if (this.bridge && this.esbuild) {\n const backendPatterns = [\n `${this.config.main.controllers}/**/*.ts`,\n `${this.config.main.middleware}/**/*.ts`,\n `${this.config.main.entry}`,\n ...(this.config.main.watch ?? []),\n ];\n\n const esbuild = this.esbuild;\n this.backendWatcher = new BackendWatcher({\n patterns: backendPatterns,\n bridge: this.bridge,\n buildFn: () => esbuild.rebuild().then(() => {}),\n cwd: this.cwd,\n });\n this.backendWatcher.start();\n logger.info('backend watcher started', { patterns: backendPatterns });\n }\n\n // YAML watcher: operations + views YAML -> regenerate codegen\n this.yamlWatcher = new YamlWatcher({\n operationsYamlPath: this.config.main.openapi,\n viewsYamlPath: this.config.renderer.openapi,\n controllersDir: this.config.main.controllers,\n outDir,\n cwd: this.cwd,\n onViewsChange: async () => {\n // Re-parse views and regenerate client router manifest\n const viewsContent = readFileSync(\n path.resolve(this.cwd, this.config!.renderer.openapi),\n 'utf-8',\n );\n const views = parseViews(viewsContent);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config!.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config!.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config!.renderer.entry,\n }),\n );\n\n logger.info('views renderer codegen regenerated', { rendererOutDir });\n },\n });\n this.yamlWatcher.start();\n logger.info('yaml watcher started');\n }\n}\n"],"mappings":"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","names":["path"]}
1
+ {"version":3,"sources":["../../src/dev/wood_dev_server.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseConfig } from '../config/config_parser.cjs';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport { parseViews } from '../parser/views_parser.cjs';\nimport { generatePreload } from '../codegen/preload_generator.cjs';\nimport { generateTypes } from '../codegen/type_generator.cjs';\nimport { generateTestMock } from '../codegen/test_mock_generator.cjs';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.cjs';\nimport { generateRuntime } from '../codegen/runtime_generator.cjs';\nimport { generateRendererApp } from '../codegen/renderer_app_generator.cjs';\nimport { BackendBridge } from '../bridge/backend_bridge.cjs';\nimport { BackendWatcher } from './backend_watcher.cjs';\nimport { YamlWatcher } from './yaml_watcher.cjs';\nimport { EsbuildBackend } from './esbuild_backend.cjs';\nimport type { WoodConfig } from '../types/index.cjs';\n\nconst logger = getLogger('wood:dev');\n\nexport interface DevServerOptions {\n configPath?: string;\n cwd?: string;\n}\n\nexport class WoodDevServer {\n private config: WoodConfig | null = null;\n private bridge: BackendBridge | null = null;\n private backendWatcher: BackendWatcher | null = null;\n private yamlWatcher: YamlWatcher | null = null;\n private esbuild: EsbuildBackend | null = null;\n private cwd: string;\n\n constructor(options: DevServerOptions = {}) {\n this.cwd = options.cwd ?? process.cwd();\n }\n\n /**\n * Start the Wood development server.\n *\n * Automatically sets process.env.WOOD_MODE = 'development' so the app\n * can conditionally use dynamic controller imports (dev) vs pre-registered\n * controllers from the generated file (production).\n *\n * Steps:\n * 1. Parse config\n * 2. Parse + stitch YAML\n * 3. Generate preload, types, mock\n * 4. Build backend with esbuild\n * 5. (electron-vite would be started here -- omitted for MVP)\n * 6. Spawn backend via bridge\n * 7. Start watchers\n */\n async start(): Promise<void> {\n process.env.WOOD_MODE = 'development';\n\n // 1. Parse config\n const configPath = path.resolve(this.cwd, 'wood.config.yml');\n this.config = parseConfig(configPath);\n logger.info('config loaded', { outDir: this.config.outDir });\n\n // 2. Parse operations YAML\n const opsYamlPath = path.resolve(this.cwd, this.config.main.openapi);\n const opsContent = readFileSync(opsYamlPath, 'utf-8');\n const operations = parseOperations(opsContent);\n logger.info('operations parsed', { count: operations.size });\n\n // 3. Parse views YAML\n const viewsYamlPath = path.resolve(this.cwd, this.config.renderer.openapi);\n const viewsContent = readFileSync(viewsYamlPath, 'utf-8');\n const views = parseViews(viewsContent);\n logger.info('views parsed', { count: views.length });\n\n // 4. Generate files\n const outDir = path.resolve(this.cwd, this.config.outDir);\n mkdirSync(outDir, { recursive: true });\n\n const preloadCode = generatePreload(operations);\n writeFileSync(path.join(outDir, 'preload.generated.ts'), preloadCode);\n\n const typesCode = generateTypes(operations);\n writeFileSync(path.join(outDir, 'wood.d.ts'), typesCode);\n\n const mockCode = generateTestMock(operations);\n writeFileSync(path.join(outDir, 'wood.mock.ts'), mockCode);\n\n const controllersDir = path.resolve(this.cwd, this.config.main.controllers);\n const controllerRegCode = generateControllerRegistration(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'controllers.generated.ts'), controllerRegCode);\n\n const runtimeCode = generateRuntime(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'runtime.generated.ts'), runtimeCode);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(path.join(rendererOutDir, 'wood.d.ts'), typesCode);\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config.renderer.entry,\n }),\n );\n\n logger.info('codegen complete', { outDir });\n\n // 5. Build backend with esbuild (initial build + create incremental context)\n const backendEntry = this.config.main.entry;\n const backendOutfile = path.join(this.config.outDir, 'backend', 'index.js');\n\n this.esbuild = new EsbuildBackend({\n entryPoint: backendEntry,\n outfile: backendOutfile,\n cwd: this.cwd,\n external: this.config.build ? [] : undefined,\n });\n\n await this.esbuild.build();\n await this.esbuild.createContext();\n\n // 6. Start backend bridge (split-serve)\n if (this.config.dev?.splitServe !== false) {\n const resolvedBackendEntry = path.resolve(this.cwd, backendOutfile);\n this.bridge = new BackendBridge({\n backendEntry: resolvedBackendEntry,\n onReady: () => {\n logger.info('backend ready (split-serve)');\n },\n onPush: (channel, data) => {\n logger.debug('backend push', { channel, data });\n },\n onExit: (code, signal) => {\n logger.debug('backend exited', { code, signal });\n },\n });\n await this.bridge.start();\n }\n\n // 7. Start watchers\n if (this.config.dev?.watch !== false) {\n this.startWatchers(outDir);\n }\n }\n\n /** Stop the dev server and clean up all resources. */\n async stop(): Promise<void> {\n this.backendWatcher?.stop();\n this.yamlWatcher?.stop();\n\n if (this.bridge) {\n await this.bridge.stop();\n logger.info('backend bridge stopped');\n }\n\n if (this.esbuild) {\n await this.esbuild.dispose();\n }\n }\n\n /** Get the parsed config. */\n getConfig(): WoodConfig | null {\n return this.config;\n }\n\n private startWatchers(outDir: string): void {\n if (!this.config) return;\n\n // Backend watcher: server + middleware files -> rebuild + restart\n if (this.bridge && this.esbuild) {\n const backendPatterns = [\n `${this.config.main.controllers}/**/*.ts`,\n `${this.config.main.middleware}/**/*.ts`,\n `${this.config.main.entry}`,\n ...(this.config.main.watch ?? []),\n ];\n\n const esbuild = this.esbuild;\n this.backendWatcher = new BackendWatcher({\n patterns: backendPatterns,\n bridge: this.bridge,\n buildFn: () => esbuild.rebuild().then(() => {}),\n cwd: this.cwd,\n });\n this.backendWatcher.start();\n logger.info('backend watcher started', { patterns: backendPatterns });\n }\n\n // YAML watcher: operations + views YAML -> regenerate codegen\n this.yamlWatcher = new YamlWatcher({\n operationsYamlPath: this.config.main.openapi,\n viewsYamlPath: this.config.renderer.openapi,\n controllersDir: this.config.main.controllers,\n outDir,\n rendererOutDir: path.resolve(this.cwd, this.config.renderer.componentDir, 'generated'),\n cwd: this.cwd,\n onViewsChange: async () => {\n // Re-parse views and regenerate client router manifest\n const viewsContent = readFileSync(\n path.resolve(this.cwd, this.config!.renderer.openapi),\n 'utf-8',\n );\n const views = parseViews(viewsContent);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config!.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config!.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config!.renderer.entry,\n }),\n );\n\n logger.info('views renderer codegen regenerated', { rendererOutDir });\n },\n });\n this.yamlWatcher.start();\n logger.info('yaml watcher started');\n }\n}\n"],"mappings":"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","names":["path"]}
@@ -75,6 +75,7 @@ class WoodDevServer {
75
75
  const rendererComponentDir = path.resolve(this.cwd, this.config.renderer.componentDir);
76
76
  const rendererOutDir = path.resolve(this.cwd, this.config.renderer.componentDir, "generated");
77
77
  mkdirSync(rendererOutDir, { recursive: true });
78
+ writeFileSync(path.join(rendererOutDir, "wood.d.ts"), typesCode);
78
79
  writeFileSync(
79
80
  path.join(rendererOutDir, "app.ts"),
80
81
  generateRendererApp(views, {
@@ -155,6 +156,7 @@ class WoodDevServer {
155
156
  viewsYamlPath: this.config.renderer.openapi,
156
157
  controllersDir: this.config.main.controllers,
157
158
  outDir,
159
+ rendererOutDir: path.resolve(this.cwd, this.config.renderer.componentDir, "generated"),
158
160
  cwd: this.cwd,
159
161
  onViewsChange: async () => {
160
162
  const viewsContent = readFileSync(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/dev/wood_dev_server.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseConfig } from '../config/config_parser.js';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport { parseViews } from '../parser/views_parser.js';\nimport { generatePreload } from '../codegen/preload_generator.js';\nimport { generateTypes } from '../codegen/type_generator.js';\nimport { generateTestMock } from '../codegen/test_mock_generator.js';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.js';\nimport { generateRuntime } from '../codegen/runtime_generator.js';\nimport { generateRendererApp } from '../codegen/renderer_app_generator.js';\nimport { BackendBridge } from '../bridge/backend_bridge.js';\nimport { BackendWatcher } from './backend_watcher.js';\nimport { YamlWatcher } from './yaml_watcher.js';\nimport { EsbuildBackend } from './esbuild_backend.js';\nimport type { WoodConfig } from '../types/index.js';\n\nconst logger = getLogger('wood:dev');\n\nexport interface DevServerOptions {\n configPath?: string;\n cwd?: string;\n}\n\nexport class WoodDevServer {\n private config: WoodConfig | null = null;\n private bridge: BackendBridge | null = null;\n private backendWatcher: BackendWatcher | null = null;\n private yamlWatcher: YamlWatcher | null = null;\n private esbuild: EsbuildBackend | null = null;\n private cwd: string;\n\n constructor(options: DevServerOptions = {}) {\n this.cwd = options.cwd ?? process.cwd();\n }\n\n /**\n * Start the Wood development server.\n *\n * Automatically sets process.env.WOOD_MODE = 'development' so the app\n * can conditionally use dynamic controller imports (dev) vs pre-registered\n * controllers from the generated file (production).\n *\n * Steps:\n * 1. Parse config\n * 2. Parse + stitch YAML\n * 3. Generate preload, types, mock\n * 4. Build backend with esbuild\n * 5. (electron-vite would be started here -- omitted for MVP)\n * 6. Spawn backend via bridge\n * 7. Start watchers\n */\n async start(): Promise<void> {\n process.env.WOOD_MODE = 'development';\n\n // 1. Parse config\n const configPath = path.resolve(this.cwd, 'wood.config.yml');\n this.config = parseConfig(configPath);\n logger.info('config loaded', { outDir: this.config.outDir });\n\n // 2. Parse operations YAML\n const opsYamlPath = path.resolve(this.cwd, this.config.main.openapi);\n const opsContent = readFileSync(opsYamlPath, 'utf-8');\n const operations = parseOperations(opsContent);\n logger.info('operations parsed', { count: operations.size });\n\n // 3. Parse views YAML\n const viewsYamlPath = path.resolve(this.cwd, this.config.renderer.openapi);\n const viewsContent = readFileSync(viewsYamlPath, 'utf-8');\n const views = parseViews(viewsContent);\n logger.info('views parsed', { count: views.length });\n\n // 4. Generate files\n const outDir = path.resolve(this.cwd, this.config.outDir);\n mkdirSync(outDir, { recursive: true });\n\n const preloadCode = generatePreload(operations);\n writeFileSync(path.join(outDir, 'preload.generated.ts'), preloadCode);\n\n const typesCode = generateTypes(operations);\n writeFileSync(path.join(outDir, 'wood.d.ts'), typesCode);\n\n const mockCode = generateTestMock(operations);\n writeFileSync(path.join(outDir, 'wood.mock.ts'), mockCode);\n\n const controllersDir = path.resolve(this.cwd, this.config.main.controllers);\n const controllerRegCode = generateControllerRegistration(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'controllers.generated.ts'), controllerRegCode);\n\n const runtimeCode = generateRuntime(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'runtime.generated.ts'), runtimeCode);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config.renderer.entry,\n }),\n );\n\n logger.info('codegen complete', { outDir });\n\n // 5. Build backend with esbuild (initial build + create incremental context)\n const backendEntry = this.config.main.entry;\n const backendOutfile = path.join(this.config.outDir, 'backend', 'index.js');\n\n this.esbuild = new EsbuildBackend({\n entryPoint: backendEntry,\n outfile: backendOutfile,\n cwd: this.cwd,\n external: this.config.build ? [] : undefined,\n });\n\n await this.esbuild.build();\n await this.esbuild.createContext();\n\n // 6. Start backend bridge (split-serve)\n if (this.config.dev?.splitServe !== false) {\n const resolvedBackendEntry = path.resolve(this.cwd, backendOutfile);\n this.bridge = new BackendBridge({\n backendEntry: resolvedBackendEntry,\n onReady: () => {\n logger.info('backend ready (split-serve)');\n },\n onPush: (channel, data) => {\n logger.debug('backend push', { channel, data });\n },\n onExit: (code, signal) => {\n logger.debug('backend exited', { code, signal });\n },\n });\n await this.bridge.start();\n }\n\n // 7. Start watchers\n if (this.config.dev?.watch !== false) {\n this.startWatchers(outDir);\n }\n }\n\n /** Stop the dev server and clean up all resources. */\n async stop(): Promise<void> {\n this.backendWatcher?.stop();\n this.yamlWatcher?.stop();\n\n if (this.bridge) {\n await this.bridge.stop();\n logger.info('backend bridge stopped');\n }\n\n if (this.esbuild) {\n await this.esbuild.dispose();\n }\n }\n\n /** Get the parsed config. */\n getConfig(): WoodConfig | null {\n return this.config;\n }\n\n private startWatchers(outDir: string): void {\n if (!this.config) return;\n\n // Backend watcher: server + middleware files -> rebuild + restart\n if (this.bridge && this.esbuild) {\n const backendPatterns = [\n `${this.config.main.controllers}/**/*.ts`,\n `${this.config.main.middleware}/**/*.ts`,\n `${this.config.main.entry}`,\n ...(this.config.main.watch ?? []),\n ];\n\n const esbuild = this.esbuild;\n this.backendWatcher = new BackendWatcher({\n patterns: backendPatterns,\n bridge: this.bridge,\n buildFn: () => esbuild.rebuild().then(() => {}),\n cwd: this.cwd,\n });\n this.backendWatcher.start();\n logger.info('backend watcher started', { patterns: backendPatterns });\n }\n\n // YAML watcher: operations + views YAML -> regenerate codegen\n this.yamlWatcher = new YamlWatcher({\n operationsYamlPath: this.config.main.openapi,\n viewsYamlPath: this.config.renderer.openapi,\n controllersDir: this.config.main.controllers,\n outDir,\n cwd: this.cwd,\n onViewsChange: async () => {\n // Re-parse views and regenerate client router manifest\n const viewsContent = readFileSync(\n path.resolve(this.cwd, this.config!.renderer.openapi),\n 'utf-8',\n );\n const views = parseViews(viewsContent);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config!.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config!.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config!.renderer.entry,\n }),\n );\n\n logger.info('views renderer codegen regenerated', { rendererOutDir });\n },\n });\n this.yamlWatcher.start();\n logger.info('yaml watcher started');\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/dev/wood_dev_server.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseConfig } from '../config/config_parser.js';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport { parseViews } from '../parser/views_parser.js';\nimport { generatePreload } from '../codegen/preload_generator.js';\nimport { generateTypes } from '../codegen/type_generator.js';\nimport { generateTestMock } from '../codegen/test_mock_generator.js';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.js';\nimport { generateRuntime } from '../codegen/runtime_generator.js';\nimport { generateRendererApp } from '../codegen/renderer_app_generator.js';\nimport { BackendBridge } from '../bridge/backend_bridge.js';\nimport { BackendWatcher } from './backend_watcher.js';\nimport { YamlWatcher } from './yaml_watcher.js';\nimport { EsbuildBackend } from './esbuild_backend.js';\nimport type { WoodConfig } from '../types/index.js';\n\nconst logger = getLogger('wood:dev');\n\nexport interface DevServerOptions {\n configPath?: string;\n cwd?: string;\n}\n\nexport class WoodDevServer {\n private config: WoodConfig | null = null;\n private bridge: BackendBridge | null = null;\n private backendWatcher: BackendWatcher | null = null;\n private yamlWatcher: YamlWatcher | null = null;\n private esbuild: EsbuildBackend | null = null;\n private cwd: string;\n\n constructor(options: DevServerOptions = {}) {\n this.cwd = options.cwd ?? process.cwd();\n }\n\n /**\n * Start the Wood development server.\n *\n * Automatically sets process.env.WOOD_MODE = 'development' so the app\n * can conditionally use dynamic controller imports (dev) vs pre-registered\n * controllers from the generated file (production).\n *\n * Steps:\n * 1. Parse config\n * 2. Parse + stitch YAML\n * 3. Generate preload, types, mock\n * 4. Build backend with esbuild\n * 5. (electron-vite would be started here -- omitted for MVP)\n * 6. Spawn backend via bridge\n * 7. Start watchers\n */\n async start(): Promise<void> {\n process.env.WOOD_MODE = 'development';\n\n // 1. Parse config\n const configPath = path.resolve(this.cwd, 'wood.config.yml');\n this.config = parseConfig(configPath);\n logger.info('config loaded', { outDir: this.config.outDir });\n\n // 2. Parse operations YAML\n const opsYamlPath = path.resolve(this.cwd, this.config.main.openapi);\n const opsContent = readFileSync(opsYamlPath, 'utf-8');\n const operations = parseOperations(opsContent);\n logger.info('operations parsed', { count: operations.size });\n\n // 3. Parse views YAML\n const viewsYamlPath = path.resolve(this.cwd, this.config.renderer.openapi);\n const viewsContent = readFileSync(viewsYamlPath, 'utf-8');\n const views = parseViews(viewsContent);\n logger.info('views parsed', { count: views.length });\n\n // 4. Generate files\n const outDir = path.resolve(this.cwd, this.config.outDir);\n mkdirSync(outDir, { recursive: true });\n\n const preloadCode = generatePreload(operations);\n writeFileSync(path.join(outDir, 'preload.generated.ts'), preloadCode);\n\n const typesCode = generateTypes(operations);\n writeFileSync(path.join(outDir, 'wood.d.ts'), typesCode);\n\n const mockCode = generateTestMock(operations);\n writeFileSync(path.join(outDir, 'wood.mock.ts'), mockCode);\n\n const controllersDir = path.resolve(this.cwd, this.config.main.controllers);\n const controllerRegCode = generateControllerRegistration(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'controllers.generated.ts'), controllerRegCode);\n\n const runtimeCode = generateRuntime(operations, {\n controllersDir,\n outDir,\n });\n writeFileSync(path.join(outDir, 'runtime.generated.ts'), runtimeCode);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(path.join(rendererOutDir, 'wood.d.ts'), typesCode);\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config.renderer.entry,\n }),\n );\n\n logger.info('codegen complete', { outDir });\n\n // 5. Build backend with esbuild (initial build + create incremental context)\n const backendEntry = this.config.main.entry;\n const backendOutfile = path.join(this.config.outDir, 'backend', 'index.js');\n\n this.esbuild = new EsbuildBackend({\n entryPoint: backendEntry,\n outfile: backendOutfile,\n cwd: this.cwd,\n external: this.config.build ? [] : undefined,\n });\n\n await this.esbuild.build();\n await this.esbuild.createContext();\n\n // 6. Start backend bridge (split-serve)\n if (this.config.dev?.splitServe !== false) {\n const resolvedBackendEntry = path.resolve(this.cwd, backendOutfile);\n this.bridge = new BackendBridge({\n backendEntry: resolvedBackendEntry,\n onReady: () => {\n logger.info('backend ready (split-serve)');\n },\n onPush: (channel, data) => {\n logger.debug('backend push', { channel, data });\n },\n onExit: (code, signal) => {\n logger.debug('backend exited', { code, signal });\n },\n });\n await this.bridge.start();\n }\n\n // 7. Start watchers\n if (this.config.dev?.watch !== false) {\n this.startWatchers(outDir);\n }\n }\n\n /** Stop the dev server and clean up all resources. */\n async stop(): Promise<void> {\n this.backendWatcher?.stop();\n this.yamlWatcher?.stop();\n\n if (this.bridge) {\n await this.bridge.stop();\n logger.info('backend bridge stopped');\n }\n\n if (this.esbuild) {\n await this.esbuild.dispose();\n }\n }\n\n /** Get the parsed config. */\n getConfig(): WoodConfig | null {\n return this.config;\n }\n\n private startWatchers(outDir: string): void {\n if (!this.config) return;\n\n // Backend watcher: server + middleware files -> rebuild + restart\n if (this.bridge && this.esbuild) {\n const backendPatterns = [\n `${this.config.main.controllers}/**/*.ts`,\n `${this.config.main.middleware}/**/*.ts`,\n `${this.config.main.entry}`,\n ...(this.config.main.watch ?? []),\n ];\n\n const esbuild = this.esbuild;\n this.backendWatcher = new BackendWatcher({\n patterns: backendPatterns,\n bridge: this.bridge,\n buildFn: () => esbuild.rebuild().then(() => {}),\n cwd: this.cwd,\n });\n this.backendWatcher.start();\n logger.info('backend watcher started', { patterns: backendPatterns });\n }\n\n // YAML watcher: operations + views YAML -> regenerate codegen\n this.yamlWatcher = new YamlWatcher({\n operationsYamlPath: this.config.main.openapi,\n viewsYamlPath: this.config.renderer.openapi,\n controllersDir: this.config.main.controllers,\n outDir,\n rendererOutDir: path.resolve(this.cwd, this.config.renderer.componentDir, 'generated'),\n cwd: this.cwd,\n onViewsChange: async () => {\n // Re-parse views and regenerate client router manifest\n const viewsContent = readFileSync(\n path.resolve(this.cwd, this.config!.renderer.openapi),\n 'utf-8',\n );\n const views = parseViews(viewsContent);\n\n const rendererComponentDir = path.resolve(this.cwd, this.config!.renderer.componentDir);\n const rendererOutDir = path.resolve(this.cwd, this.config!.renderer.componentDir, 'generated');\n mkdirSync(rendererOutDir, { recursive: true });\n\n writeFileSync(\n path.join(rendererOutDir, 'app.ts'),\n generateRendererApp(views, {\n componentDir: rendererComponentDir,\n outDir: rendererOutDir,\n entryViewKey: this.config!.renderer.entry,\n }),\n );\n\n logger.info('views renderer codegen regenerated', { rendererOutDir });\n },\n });\n this.yamlWatcher.start();\n logger.info('yaml watcher started');\n }\n}\n"],"mappings":"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","names":[]}
@@ -95,14 +95,21 @@ class YamlWatcher {
95
95
  "utf-8"
96
96
  );
97
97
  const operations = (0, import_operations_parser.parseOperations)(opsContent);
98
+ const typesCode = (0, import_type_generator.generateTypes)(operations);
98
99
  (0, import_fs.writeFileSync)(
99
100
  import_path.default.join(this.options.outDir, "preload.generated.ts"),
100
101
  (0, import_preload_generator.generatePreload)(operations)
101
102
  );
102
103
  (0, import_fs.writeFileSync)(
103
104
  import_path.default.join(this.options.outDir, "wood.d.ts"),
104
- (0, import_type_generator.generateTypes)(operations)
105
+ typesCode
105
106
  );
107
+ if (this.options.rendererOutDir) {
108
+ (0, import_fs.writeFileSync)(
109
+ import_path.default.join(this.options.rendererOutDir, "wood.d.ts"),
110
+ typesCode
111
+ );
112
+ }
106
113
  (0, import_fs.writeFileSync)(
107
114
  import_path.default.join(this.options.outDir, "wood.mock.ts"),
108
115
  (0, import_test_mock_generator.generateTestMock)(operations)
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/dev/yaml_watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from 'chokidar';\nimport { Subject, debounceTime, switchMap, from } from 'rxjs';\nimport { readFileSync, writeFileSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport { generatePreload } from '../codegen/preload_generator.cjs';\nimport { generateTypes } from '../codegen/type_generator.cjs';\nimport { generateTestMock } from '../codegen/test_mock_generator.cjs';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.cjs';\nimport { generateRuntime } from '../codegen/runtime_generator.cjs';\n\nconst logger = getLogger('wood:dev:yaml-watcher');\n\nexport interface YamlWatcherOptions {\n operationsYamlPath: string;\n viewsYamlPath: string;\n controllersDir: string;\n outDir: string;\n cwd: string;\n /** Called after views YAML changes to regenerate the client router manifest. */\n onViewsChange?: () => Promise<void>;\n}\n\nexport class YamlWatcher {\n private watcher: FSWatcher | null = null;\n private change$ = new Subject<string>();\n\n constructor(private options: YamlWatcherOptions) {}\n\n start(): void {\n const patterns = [\n this.options.operationsYamlPath,\n this.options.viewsYamlPath,\n path.dirname(this.options.operationsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.operationsYamlPath) + '/**/*.yml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yml',\n ];\n\n this.watcher = watch(patterns, {\n cwd: this.options.cwd,\n ignoreInitial: true,\n });\n\n this.watcher.on('change', (filePath: string) => {\n logger.debug('yaml file changed', { filePath });\n this.change$.next(filePath);\n });\n\n this.change$.pipe(\n debounceTime(100),\n switchMap((filePath) => from(this.regenerate(filePath))),\n ).subscribe({\n error: (err) => {\n logger.error('yaml watcher pipeline error', { error: String(err) });\n },\n });\n }\n\n stop(): void {\n this.watcher?.close();\n this.change$.complete();\n }\n\n private isOperationsYaml(filePath: string): boolean {\n const opsDir = path.dirname(this.options.operationsYamlPath);\n return filePath.startsWith(opsDir) || filePath === this.options.operationsYamlPath;\n }\n\n private isViewsYaml(filePath: string): boolean {\n const viewsDir = path.dirname(this.options.viewsYamlPath);\n return filePath.startsWith(viewsDir) || filePath === this.options.viewsYamlPath;\n }\n\n private async regenerate(filePath: string): Promise<void> {\n try {\n if (this.isOperationsYaml(filePath)) {\n // Operations change -> regenerate preload + types + mock\n const opsContent = readFileSync(\n path.resolve(this.options.cwd, this.options.operationsYamlPath),\n 'utf-8',\n );\n const operations = parseOperations(opsContent);\n\n writeFileSync(\n path.join(this.options.outDir, 'preload.generated.ts'),\n generatePreload(operations),\n );\n writeFileSync(\n path.join(this.options.outDir, 'wood.d.ts'),\n generateTypes(operations),\n );\n writeFileSync(\n path.join(this.options.outDir, 'wood.mock.ts'),\n generateTestMock(operations),\n );\n\n const controllersDir = path.resolve(this.options.cwd, this.options.controllersDir);\n writeFileSync(\n path.join(this.options.outDir, 'controllers.generated.ts'),\n generateControllerRegistration(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n writeFileSync(\n path.join(this.options.outDir, 'runtime.generated.ts'),\n generateRuntime(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n logger.info('operations codegen regenerated');\n }\n\n if (this.isViewsYaml(filePath)) {\n // Views change -> regenerate client router manifest\n if (this.options.onViewsChange) {\n await this.options.onViewsChange();\n }\n\n logger.info('views manifest regenerated');\n }\n } catch (err) {\n logger.error('yaml regeneration failed', { error: String(err) });\n }\n }\n}\n"],"mappings":"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","names":["path"]}
1
+ {"version":3,"sources":["../../src/dev/yaml_watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from 'chokidar';\nimport { Subject, debounceTime, switchMap, from } from 'rxjs';\nimport { readFileSync, writeFileSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseOperations } from '../parser/operations_parser.cjs';\nimport { generatePreload } from '../codegen/preload_generator.cjs';\nimport { generateTypes } from '../codegen/type_generator.cjs';\nimport { generateTestMock } from '../codegen/test_mock_generator.cjs';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.cjs';\nimport { generateRuntime } from '../codegen/runtime_generator.cjs';\n\nconst logger = getLogger('wood:dev:yaml-watcher');\n\nexport interface YamlWatcherOptions {\n operationsYamlPath: string;\n viewsYamlPath: string;\n controllersDir: string;\n outDir: string;\n rendererOutDir?: string;\n cwd: string;\n /** Called after views YAML changes to regenerate the client router manifest. */\n onViewsChange?: () => Promise<void>;\n}\n\nexport class YamlWatcher {\n private watcher: FSWatcher | null = null;\n private change$ = new Subject<string>();\n\n constructor(private options: YamlWatcherOptions) {}\n\n start(): void {\n const patterns = [\n this.options.operationsYamlPath,\n this.options.viewsYamlPath,\n path.dirname(this.options.operationsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.operationsYamlPath) + '/**/*.yml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yml',\n ];\n\n this.watcher = watch(patterns, {\n cwd: this.options.cwd,\n ignoreInitial: true,\n });\n\n this.watcher.on('change', (filePath: string) => {\n logger.debug('yaml file changed', { filePath });\n this.change$.next(filePath);\n });\n\n this.change$.pipe(\n debounceTime(100),\n switchMap((filePath) => from(this.regenerate(filePath))),\n ).subscribe({\n error: (err) => {\n logger.error('yaml watcher pipeline error', { error: String(err) });\n },\n });\n }\n\n stop(): void {\n this.watcher?.close();\n this.change$.complete();\n }\n\n private isOperationsYaml(filePath: string): boolean {\n const opsDir = path.dirname(this.options.operationsYamlPath);\n return filePath.startsWith(opsDir) || filePath === this.options.operationsYamlPath;\n }\n\n private isViewsYaml(filePath: string): boolean {\n const viewsDir = path.dirname(this.options.viewsYamlPath);\n return filePath.startsWith(viewsDir) || filePath === this.options.viewsYamlPath;\n }\n\n private async regenerate(filePath: string): Promise<void> {\n try {\n if (this.isOperationsYaml(filePath)) {\n // Operations change -> regenerate preload + types + mock\n const opsContent = readFileSync(\n path.resolve(this.options.cwd, this.options.operationsYamlPath),\n 'utf-8',\n );\n const operations = parseOperations(opsContent);\n const typesCode = generateTypes(operations);\n\n writeFileSync(\n path.join(this.options.outDir, 'preload.generated.ts'),\n generatePreload(operations),\n );\n writeFileSync(\n path.join(this.options.outDir, 'wood.d.ts'),\n typesCode,\n );\n if (this.options.rendererOutDir) {\n writeFileSync(\n path.join(this.options.rendererOutDir, 'wood.d.ts'),\n typesCode,\n );\n }\n writeFileSync(\n path.join(this.options.outDir, 'wood.mock.ts'),\n generateTestMock(operations),\n );\n\n const controllersDir = path.resolve(this.options.cwd, this.options.controllersDir);\n writeFileSync(\n path.join(this.options.outDir, 'controllers.generated.ts'),\n generateControllerRegistration(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n writeFileSync(\n path.join(this.options.outDir, 'runtime.generated.ts'),\n generateRuntime(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n logger.info('operations codegen regenerated');\n }\n\n if (this.isViewsYaml(filePath)) {\n // Views change -> regenerate client router manifest\n if (this.options.onViewsChange) {\n await this.options.onViewsChange();\n }\n\n logger.info('views manifest regenerated');\n }\n } catch (err) {\n logger.error('yaml regeneration failed', { error: String(err) });\n }\n }\n}\n"],"mappings":"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","names":["path"]}
@@ -3,6 +3,7 @@ interface YamlWatcherOptions {
3
3
  viewsYamlPath: string;
4
4
  controllersDir: string;
5
5
  outDir: string;
6
+ rendererOutDir?: string;
6
7
  cwd: string;
7
8
  /** Called after views YAML changes to regenerate the client router manifest. */
8
9
  onViewsChange?: () => Promise<void>;
@@ -3,6 +3,7 @@ interface YamlWatcherOptions {
3
3
  viewsYamlPath: string;
4
4
  controllersDir: string;
5
5
  outDir: string;
6
+ rendererOutDir?: string;
6
7
  cwd: string;
7
8
  /** Called after views YAML changes to regenerate the client router manifest. */
8
9
  onViewsChange?: () => Promise<void>;
@@ -62,14 +62,21 @@ class YamlWatcher {
62
62
  "utf-8"
63
63
  );
64
64
  const operations = parseOperations(opsContent);
65
+ const typesCode = generateTypes(operations);
65
66
  writeFileSync(
66
67
  path.join(this.options.outDir, "preload.generated.ts"),
67
68
  generatePreload(operations)
68
69
  );
69
70
  writeFileSync(
70
71
  path.join(this.options.outDir, "wood.d.ts"),
71
- generateTypes(operations)
72
+ typesCode
72
73
  );
74
+ if (this.options.rendererOutDir) {
75
+ writeFileSync(
76
+ path.join(this.options.rendererOutDir, "wood.d.ts"),
77
+ typesCode
78
+ );
79
+ }
73
80
  writeFileSync(
74
81
  path.join(this.options.outDir, "wood.mock.ts"),
75
82
  generateTestMock(operations)
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/dev/yaml_watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from 'chokidar';\nimport { Subject, debounceTime, switchMap, from } from 'rxjs';\nimport { readFileSync, writeFileSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport { generatePreload } from '../codegen/preload_generator.js';\nimport { generateTypes } from '../codegen/type_generator.js';\nimport { generateTestMock } from '../codegen/test_mock_generator.js';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.js';\nimport { generateRuntime } from '../codegen/runtime_generator.js';\n\nconst logger = getLogger('wood:dev:yaml-watcher');\n\nexport interface YamlWatcherOptions {\n operationsYamlPath: string;\n viewsYamlPath: string;\n controllersDir: string;\n outDir: string;\n cwd: string;\n /** Called after views YAML changes to regenerate the client router manifest. */\n onViewsChange?: () => Promise<void>;\n}\n\nexport class YamlWatcher {\n private watcher: FSWatcher | null = null;\n private change$ = new Subject<string>();\n\n constructor(private options: YamlWatcherOptions) {}\n\n start(): void {\n const patterns = [\n this.options.operationsYamlPath,\n this.options.viewsYamlPath,\n path.dirname(this.options.operationsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.operationsYamlPath) + '/**/*.yml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yml',\n ];\n\n this.watcher = watch(patterns, {\n cwd: this.options.cwd,\n ignoreInitial: true,\n });\n\n this.watcher.on('change', (filePath: string) => {\n logger.debug('yaml file changed', { filePath });\n this.change$.next(filePath);\n });\n\n this.change$.pipe(\n debounceTime(100),\n switchMap((filePath) => from(this.regenerate(filePath))),\n ).subscribe({\n error: (err) => {\n logger.error('yaml watcher pipeline error', { error: String(err) });\n },\n });\n }\n\n stop(): void {\n this.watcher?.close();\n this.change$.complete();\n }\n\n private isOperationsYaml(filePath: string): boolean {\n const opsDir = path.dirname(this.options.operationsYamlPath);\n return filePath.startsWith(opsDir) || filePath === this.options.operationsYamlPath;\n }\n\n private isViewsYaml(filePath: string): boolean {\n const viewsDir = path.dirname(this.options.viewsYamlPath);\n return filePath.startsWith(viewsDir) || filePath === this.options.viewsYamlPath;\n }\n\n private async regenerate(filePath: string): Promise<void> {\n try {\n if (this.isOperationsYaml(filePath)) {\n // Operations change -> regenerate preload + types + mock\n const opsContent = readFileSync(\n path.resolve(this.options.cwd, this.options.operationsYamlPath),\n 'utf-8',\n );\n const operations = parseOperations(opsContent);\n\n writeFileSync(\n path.join(this.options.outDir, 'preload.generated.ts'),\n generatePreload(operations),\n );\n writeFileSync(\n path.join(this.options.outDir, 'wood.d.ts'),\n generateTypes(operations),\n );\n writeFileSync(\n path.join(this.options.outDir, 'wood.mock.ts'),\n generateTestMock(operations),\n );\n\n const controllersDir = path.resolve(this.options.cwd, this.options.controllersDir);\n writeFileSync(\n path.join(this.options.outDir, 'controllers.generated.ts'),\n generateControllerRegistration(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n writeFileSync(\n path.join(this.options.outDir, 'runtime.generated.ts'),\n generateRuntime(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n logger.info('operations codegen regenerated');\n }\n\n if (this.isViewsYaml(filePath)) {\n // Views change -> regenerate client router manifest\n if (this.options.onViewsChange) {\n await this.options.onViewsChange();\n }\n\n logger.info('views manifest regenerated');\n }\n } catch (err) {\n logger.error('yaml regeneration failed', { error: String(err) });\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/dev/yaml_watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from 'chokidar';\nimport { Subject, debounceTime, switchMap, from } from 'rxjs';\nimport { readFileSync, writeFileSync } from 'fs';\nimport path from 'path';\nimport { getLogger } from '@noego/logger';\nimport { parseOperations } from '../parser/operations_parser.js';\nimport { generatePreload } from '../codegen/preload_generator.js';\nimport { generateTypes } from '../codegen/type_generator.js';\nimport { generateTestMock } from '../codegen/test_mock_generator.js';\nimport { generateControllerRegistration } from '../codegen/controller_registration_generator.js';\nimport { generateRuntime } from '../codegen/runtime_generator.js';\n\nconst logger = getLogger('wood:dev:yaml-watcher');\n\nexport interface YamlWatcherOptions {\n operationsYamlPath: string;\n viewsYamlPath: string;\n controllersDir: string;\n outDir: string;\n rendererOutDir?: string;\n cwd: string;\n /** Called after views YAML changes to regenerate the client router manifest. */\n onViewsChange?: () => Promise<void>;\n}\n\nexport class YamlWatcher {\n private watcher: FSWatcher | null = null;\n private change$ = new Subject<string>();\n\n constructor(private options: YamlWatcherOptions) {}\n\n start(): void {\n const patterns = [\n this.options.operationsYamlPath,\n this.options.viewsYamlPath,\n path.dirname(this.options.operationsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.operationsYamlPath) + '/**/*.yml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yaml',\n path.dirname(this.options.viewsYamlPath) + '/**/*.yml',\n ];\n\n this.watcher = watch(patterns, {\n cwd: this.options.cwd,\n ignoreInitial: true,\n });\n\n this.watcher.on('change', (filePath: string) => {\n logger.debug('yaml file changed', { filePath });\n this.change$.next(filePath);\n });\n\n this.change$.pipe(\n debounceTime(100),\n switchMap((filePath) => from(this.regenerate(filePath))),\n ).subscribe({\n error: (err) => {\n logger.error('yaml watcher pipeline error', { error: String(err) });\n },\n });\n }\n\n stop(): void {\n this.watcher?.close();\n this.change$.complete();\n }\n\n private isOperationsYaml(filePath: string): boolean {\n const opsDir = path.dirname(this.options.operationsYamlPath);\n return filePath.startsWith(opsDir) || filePath === this.options.operationsYamlPath;\n }\n\n private isViewsYaml(filePath: string): boolean {\n const viewsDir = path.dirname(this.options.viewsYamlPath);\n return filePath.startsWith(viewsDir) || filePath === this.options.viewsYamlPath;\n }\n\n private async regenerate(filePath: string): Promise<void> {\n try {\n if (this.isOperationsYaml(filePath)) {\n // Operations change -> regenerate preload + types + mock\n const opsContent = readFileSync(\n path.resolve(this.options.cwd, this.options.operationsYamlPath),\n 'utf-8',\n );\n const operations = parseOperations(opsContent);\n const typesCode = generateTypes(operations);\n\n writeFileSync(\n path.join(this.options.outDir, 'preload.generated.ts'),\n generatePreload(operations),\n );\n writeFileSync(\n path.join(this.options.outDir, 'wood.d.ts'),\n typesCode,\n );\n if (this.options.rendererOutDir) {\n writeFileSync(\n path.join(this.options.rendererOutDir, 'wood.d.ts'),\n typesCode,\n );\n }\n writeFileSync(\n path.join(this.options.outDir, 'wood.mock.ts'),\n generateTestMock(operations),\n );\n\n const controllersDir = path.resolve(this.options.cwd, this.options.controllersDir);\n writeFileSync(\n path.join(this.options.outDir, 'controllers.generated.ts'),\n generateControllerRegistration(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n writeFileSync(\n path.join(this.options.outDir, 'runtime.generated.ts'),\n generateRuntime(operations, {\n controllersDir,\n outDir: this.options.outDir,\n }),\n );\n\n logger.info('operations codegen regenerated');\n }\n\n if (this.isViewsYaml(filePath)) {\n // Views change -> regenerate client router manifest\n if (this.options.onViewsChange) {\n await this.options.onViewsChange();\n }\n\n logger.info('views manifest regenerated');\n }\n } catch (err) {\n logger.error('yaml regeneration failed', { error: String(err) });\n }\n }\n}\n"],"mappings":"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","names":[]}