@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.
- package/README.md +30 -0
- package/bin/wood.js +12 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/type_generator.cjs +34 -9
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +34 -9
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/dev/wood_dev_server.cjs +2 -0
- package/dist/dev/wood_dev_server.cjs.map +1 -1
- package/dist/dev/wood_dev_server.js +2 -0
- package/dist/dev/wood_dev_server.js.map +1 -1
- package/dist/dev/yaml_watcher.cjs +8 -1
- package/dist/dev/yaml_watcher.cjs.map +1 -1
- package/dist/dev/yaml_watcher.d.cts +1 -0
- package/dist/dev/yaml_watcher.d.ts +1 -0
- package/dist/dev/yaml_watcher.js +8 -1
- package/dist/dev/yaml_watcher.js.map +1 -1
- package/dist/main/index.cjs +154 -7
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +28 -2
- package/dist/main/index.d.ts +28 -2
- package/dist/main/index.js +155 -7
- package/dist/main/index.js.map +1 -1
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +12 -0
- package/dist/types/config.d.ts +12 -0
- package/dist/window/window_manager.cjs +3 -0
- package/dist/window/window_manager.cjs.map +1 -1
- package/dist/window/window_manager.js +3 -0
- package/dist/window/window_manager.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAuD;AACvD,kBAAiB;AACjB,oBAA0B;AAC1B,2BAA4B;AAC5B,+BAAgC;AAChC,0BAA2B;AAC3B,+BAAgC;AAChC,4BAA8B;AAC9B,iCAAiC;AACjC,+CAA+C;AAC/C,+BAAgC;AAChC,oCAAoC;AACpC,4BAA8B;AAC9B,6BAA+B;AAC/B,0BAA4B;AAC5B,6BAA+B;AAG/B,MAAM,aAAS,yBAAU,UAAU;AAO5B,MAAM,cAAc;AAAA,EAQzB,YAAY,UAA4B,CAAC,GAAG;AAP5C,SAAQ,SAA4B;AACpC,SAAQ,SAA+B;AACvC,SAAQ,iBAAwC;AAChD,SAAQ,cAAkC;AAC1C,SAAQ,UAAiC;AAIvC,SAAK,MAAM,QAAQ,OAAO,QAAQ,IAAI;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,QAAuB;AAC3B,YAAQ,IAAI,YAAY;AAGxB,UAAM,aAAa,YAAAA,QAAK,QAAQ,KAAK,KAAK,iBAAiB;AAC3D,SAAK,aAAS,kCAAY,UAAU;AACpC,WAAO,KAAK,iBAAiB,EAAE,QAAQ,KAAK,OAAO,OAAO,CAAC;AAG3D,UAAM,cAAc,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO;AACnE,UAAM,iBAAa,wBAAa,aAAa,OAAO;AACpD,UAAM,iBAAa,0CAAgB,UAAU;AAC7C,WAAO,KAAK,qBAAqB,EAAE,OAAO,WAAW,KAAK,CAAC;AAG3D,UAAM,gBAAgB,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,SAAS,OAAO;AACzE,UAAM,mBAAe,wBAAa,eAAe,OAAO;AACxD,UAAM,YAAQ,gCAAW,YAAY;AACrC,WAAO,KAAK,gBAAgB,EAAE,OAAO,MAAM,OAAO,CAAC;AAGnD,UAAM,SAAS,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,MAAM;AACxD,6BAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAErC,UAAM,kBAAc,0CAAgB,UAAU;AAC9C,iCAAc,YAAAA,QAAK,KAAK,QAAQ,sBAAsB,GAAG,WAAW;AAEpE,UAAM,gBAAY,qCAAc,UAAU;AAC1C,iCAAc,YAAAA,QAAK,KAAK,QAAQ,WAAW,GAAG,SAAS;AAEvD,UAAM,eAAW,6CAAiB,UAAU;AAC5C,iCAAc,YAAAA,QAAK,KAAK,QAAQ,cAAc,GAAG,QAAQ;AAEzD,UAAM,iBAAiB,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,KAAK,WAAW;AAC1E,UAAM,wBAAoB,yEAA+B,YAAY;AAAA,MACnE;AAAA,MACA;AAAA,IACF,CAAC;AACD,iCAAc,YAAAA,QAAK,KAAK,QAAQ,0BAA0B,GAAG,iBAAiB;AAE9E,UAAM,kBAAc,0CAAgB,YAAY;AAAA,MAC9C;AAAA,MACA;AAAA,IACF,CAAC;AACD,iCAAc,YAAAA,QAAK,KAAK,QAAQ,sBAAsB,GAAG,WAAW;AAEpE,UAAM,uBAAuB,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,SAAS,YAAY;AACrF,UAAM,iBAAiB,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAO,SAAS,cAAc,WAAW;AAC5F,6BAAU,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAE7C;AAAA,MACE,YAAAA,QAAK,KAAK,gBAAgB,QAAQ;AAAA,UAClC,mDAAoB,OAAO;AAAA,QACzB,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,cAAc,KAAK,OAAO,SAAS;AAAA,MACrC,CAAC;AAAA,IACH;AAEA,WAAO,KAAK,oBAAoB,EAAE,OAAO,CAAC;AAG1C,UAAM,eAAe,KAAK,OAAO,KAAK;AACtC,UAAM,iBAAiB,YAAAA,QAAK,KAAK,KAAK,OAAO,QAAQ,WAAW,UAAU;AAE1E,SAAK,UAAU,IAAI,sCAAe;AAAA,MAChC,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,KAAK,KAAK;AAAA,MACV,UAAU,KAAK,OAAO,QAAQ,CAAC,IAAI;AAAA,IACrC,CAAC;AAED,UAAM,KAAK,QAAQ,MAAM;AACzB,UAAM,KAAK,QAAQ,cAAc;AAGjC,QAAI,KAAK,OAAO,KAAK,eAAe,OAAO;AACzC,YAAM,uBAAuB,YAAAA,QAAK,QAAQ,KAAK,KAAK,cAAc;AAClE,WAAK,SAAS,IAAI,oCAAc;AAAA,QAC9B,cAAc;AAAA,QACd,SAAS,MAAM;AACb,iBAAO,KAAK,6BAA6B;AAAA,QAC3C;AAAA,QACA,QAAQ,CAAC,SAAS,SAAS;AACzB,iBAAO,MAAM,gBAAgB,EAAE,SAAS,KAAK,CAAC;AAAA,QAChD;AAAA,QACA,QAAQ,CAAC,MAAM,WAAW;AACxB,iBAAO,MAAM,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAAA,QACjD;AAAA,MACF,CAAC;AACD,YAAM,KAAK,OAAO,MAAM;AAAA,IAC1B;AAGA,QAAI,KAAK,OAAO,KAAK,UAAU,OAAO;AACpC,WAAK,cAAc,MAAM;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,SAAK,gBAAgB,KAAK;AAC1B,SAAK,aAAa,KAAK;AAEvB,QAAI,KAAK,QAAQ;AACf,YAAM,KAAK,OAAO,KAAK;AACvB,aAAO,KAAK,wBAAwB;AAAA,IACtC;AAEA,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,QAAQ;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA,EAGA,YAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,cAAc,QAAsB;AAC1C,QAAI,CAAC,KAAK,OAAQ;AAGlB,QAAI,KAAK,UAAU,KAAK,SAAS;AAC/B,YAAM,kBAAkB;AAAA,QACtB,GAAG,KAAK,OAAO,KAAK,WAAW;AAAA,QAC/B,GAAG,KAAK,OAAO,KAAK,UAAU;AAAA,QAC9B,GAAG,KAAK,OAAO,KAAK,KAAK;AAAA,QACzB,GAAI,KAAK,OAAO,KAAK,SAAS,CAAC;AAAA,MACjC;AAEA,YAAM,UAAU,KAAK;AACrB,WAAK,iBAAiB,IAAI,sCAAe;AAAA,QACvC,UAAU;AAAA,QACV,QAAQ,KAAK;AAAA,QACb,SAAS,MAAM,QAAQ,QAAQ,EAAE,KAAK,MAAM;AAAA,QAAC,CAAC;AAAA,QAC9C,KAAK,KAAK;AAAA,MACZ,CAAC;AACD,WAAK,eAAe,MAAM;AAC1B,aAAO,KAAK,2BAA2B,EAAE,UAAU,gBAAgB,CAAC;AAAA,IACtE;AAGA,SAAK,cAAc,IAAI,gCAAY;AAAA,MACjC,oBAAoB,KAAK,OAAO,KAAK;AAAA,MACrC,eAAe,KAAK,OAAO,SAAS;AAAA,MACpC,gBAAgB,KAAK,OAAO,KAAK;AAAA,MACjC;AAAA,MACA,KAAK,KAAK;AAAA,MACV,eAAe,YAAY;AAEzB,cAAM,mBAAe;AAAA,UACnB,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAQ,SAAS,OAAO;AAAA,UACpD;AAAA,QACF;AACA,cAAM,YAAQ,gCAAW,YAAY;AAErC,cAAM,uBAAuB,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAQ,SAAS,YAAY;AACtF,cAAM,iBAAiB,YAAAA,QAAK,QAAQ,KAAK,KAAK,KAAK,OAAQ,SAAS,cAAc,WAAW;AAC7F,iCAAU,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAE7C;AAAA,UACE,YAAAA,QAAK,KAAK,gBAAgB,QAAQ;AAAA,cAClC,mDAAoB,OAAO;AAAA,YACzB,cAAc;AAAA,YACd,QAAQ;AAAA,YACR,cAAc,KAAK,OAAQ,SAAS;AAAA,UACtC,CAAC;AAAA,QACH;AAEA,eAAO,KAAK,sCAAsC,EAAE,eAAe,CAAC;AAAA,MACtE;AAAA,IACF,CAAC;AACD,SAAK,YAAY,MAAM;AACvB,WAAO,KAAK,sBAAsB;AAAA,EACpC;AACF;","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
|
-
|
|
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
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAsC;AACtC,kBAAuD;AACvD,gBAA4C;AAC5C,kBAAiB;AACjB,oBAA0B;AAC1B,+BAAgC;AAChC,+BAAgC;AAChC,4BAA8B;AAC9B,iCAAiC;AACjC,+CAA+C;AAC/C,+BAAgC;AAEhC,MAAM,aAAS,yBAAU,uBAAuB;AAazC,MAAM,YAAY;AAAA,EAIvB,YAAoB,SAA6B;AAA7B;AAHpB,SAAQ,UAA4B;AACpC,SAAQ,UAAU,IAAI,oBAAgB;AAAA,EAEY;AAAA,EAElD,QAAc;AACZ,UAAM,WAAW;AAAA,MACf,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,YAAAA,QAAK,QAAQ,KAAK,QAAQ,kBAAkB,IAAI;AAAA,MAChD,YAAAA,QAAK,QAAQ,KAAK,QAAQ,kBAAkB,IAAI;AAAA,MAChD,YAAAA,QAAK,QAAQ,KAAK,QAAQ,aAAa,IAAI;AAAA,MAC3C,YAAAA,QAAK,QAAQ,KAAK,QAAQ,aAAa,IAAI;AAAA,IAC7C;AAEA,SAAK,cAAU,uBAAM,UAAU;AAAA,MAC7B,KAAK,KAAK,QAAQ;AAAA,MAClB,eAAe;AAAA,IACjB,CAAC;AAED,SAAK,QAAQ,GAAG,UAAU,CAAC,aAAqB;AAC9C,aAAO,MAAM,qBAAqB,EAAE,SAAS,CAAC;AAC9C,WAAK,QAAQ,KAAK,QAAQ;AAAA,IAC5B,CAAC;AAED,SAAK,QAAQ;AAAA,UACX,0BAAa,GAAG;AAAA,UAChB,uBAAU,CAAC,iBAAa,kBAAK,KAAK,WAAW,QAAQ,CAAC,CAAC;AAAA,IACzD,EAAE,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ;AACd,eAAO,MAAM,+BAA+B,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,OAAa;AACX,SAAK,SAAS,MAAM;AACpB,SAAK,QAAQ,SAAS;AAAA,EACxB;AAAA,EAEQ,iBAAiB,UAA2B;AAClD,UAAM,SAAS,YAAAA,QAAK,QAAQ,KAAK,QAAQ,kBAAkB;AAC3D,WAAO,SAAS,WAAW,MAAM,KAAK,aAAa,KAAK,QAAQ;AAAA,EAClE;AAAA,EAEQ,YAAY,UAA2B;AAC7C,UAAM,WAAW,YAAAA,QAAK,QAAQ,KAAK,QAAQ,aAAa;AACxD,WAAO,SAAS,WAAW,QAAQ,KAAK,aAAa,KAAK,QAAQ;AAAA,EACpE;AAAA,EAEA,MAAc,WAAW,UAAiC;AACxD,QAAI;AACF,UAAI,KAAK,iBAAiB,QAAQ,GAAG;AAEnC,cAAM,iBAAa;AAAA,UACjB,YAAAA,QAAK,QAAQ,KAAK,QAAQ,KAAK,KAAK,QAAQ,kBAAkB;AAAA,UAC9D;AAAA,QACF;AACA,cAAM,iBAAa,0CAAgB,UAAU;AAC7C,cAAM,gBAAY,qCAAc,UAAU;AAE1C;AAAA,UACE,YAAAA,QAAK,KAAK,KAAK,QAAQ,QAAQ,sBAAsB;AAAA,cACrD,0CAAgB,UAAU;AAAA,QAC5B;AACA;AAAA,UACE,YAAAA,QAAK,KAAK,KAAK,QAAQ,QAAQ,WAAW;AAAA,UAC1C;AAAA,QACF;AACA,YAAI,KAAK,QAAQ,gBAAgB;AAC/B;AAAA,YACE,YAAAA,QAAK,KAAK,KAAK,QAAQ,gBAAgB,WAAW;AAAA,YAClD;AAAA,UACF;AAAA,QACF;AACA;AAAA,UACE,YAAAA,QAAK,KAAK,KAAK,QAAQ,QAAQ,cAAc;AAAA,cAC7C,6CAAiB,UAAU;AAAA,QAC7B;AAEA,cAAM,iBAAiB,YAAAA,QAAK,QAAQ,KAAK,QAAQ,KAAK,KAAK,QAAQ,cAAc;AACjF;AAAA,UACE,YAAAA,QAAK,KAAK,KAAK,QAAQ,QAAQ,0BAA0B;AAAA,cACzD,yEAA+B,YAAY;AAAA,YACzC;AAAA,YACA,QAAQ,KAAK,QAAQ;AAAA,UACvB,CAAC;AAAA,QACH;AAEA;AAAA,UACE,YAAAA,QAAK,KAAK,KAAK,QAAQ,QAAQ,sBAAsB;AAAA,cACrD,0CAAgB,YAAY;AAAA,YAC1B;AAAA,YACA,QAAQ,KAAK,QAAQ;AAAA,UACvB,CAAC;AAAA,QACH;AAEA,eAAO,KAAK,gCAAgC;AAAA,MAC9C;AAEA,UAAI,KAAK,YAAY,QAAQ,GAAG;AAE9B,YAAI,KAAK,QAAQ,eAAe;AAC9B,gBAAM,KAAK,QAAQ,cAAc;AAAA,QACnC;AAEA,eAAO,KAAK,4BAA4B;AAAA,MAC1C;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,MAAM,4BAA4B,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,IACjE;AAAA,EACF;AACF;","names":["path"]}
|
package/dist/dev/yaml_watcher.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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":[]}
|