@noego/wood 0.6.2 → 0.7.1
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 +7 -0
- package/dist/browser-test-setup.cjs +29 -0
- package/dist/browser-test-setup.cjs.map +1 -0
- package/dist/browser-test-setup.d.cts +8 -0
- package/dist/browser-test-setup.d.ts +8 -0
- package/dist/browser-test-setup.js +9 -0
- package/dist/browser-test-setup.js.map +1 -0
- package/dist/client/global_polyfill.d.cts +1 -0
- package/dist/client/global_polyfill.d.ts +1 -0
- package/dist/client/index.cjs +7 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +7 -4
- package/dist/client/index.d.ts +7 -4
- package/dist/client/index.js +6 -1
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +97 -24
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +97 -24
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +9 -0
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +9 -0
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/config/config_parser.cjs +10 -0
- package/dist/config/config_parser.cjs.map +1 -1
- package/dist/config/config_parser.js +10 -0
- package/dist/config/config_parser.js.map +1 -1
- package/dist/fetch.cjs +38 -0
- package/dist/fetch.cjs.map +1 -0
- package/dist/fetch.d.cts +7 -0
- package/dist/fetch.d.ts +7 -0
- package/dist/fetch.js +12 -0
- package/dist/fetch.js.map +1 -0
- package/dist/frontend/frontend_page.cjs +3 -3
- package/dist/frontend/frontend_page.cjs.map +1 -1
- package/dist/frontend/frontend_page.d.cts +1 -1
- package/dist/frontend/frontend_page.d.ts +1 -1
- package/dist/frontend/frontend_page.js +3 -3
- package/dist/frontend/frontend_page.js.map +1 -1
- package/dist/frontend/frontend_surface.cjs +31 -17
- package/dist/frontend/frontend_surface.cjs.map +1 -1
- package/dist/frontend/frontend_surface.d.cts +12 -2
- package/dist/frontend/frontend_surface.d.ts +12 -2
- package/dist/frontend/frontend_surface.js +37 -17
- package/dist/frontend/frontend_surface.js.map +1 -1
- package/dist/frontend/index.d.cts +2 -0
- package/dist/frontend/index.d.ts +2 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/loader/index.cjs +19 -2
- package/dist/loader/index.cjs.map +1 -1
- package/dist/loader/index.d.cts +1 -0
- package/dist/loader/index.d.ts +1 -0
- package/dist/loader/index.js +19 -1
- package/dist/loader/index.js.map +1 -1
- package/dist/loader/loader_orchestration.cjs +82 -0
- package/dist/loader/loader_orchestration.cjs.map +1 -0
- package/dist/loader/loader_orchestration.d.cts +66 -0
- package/dist/loader/loader_orchestration.d.ts +66 -0
- package/dist/loader/loader_orchestration.js +51 -0
- package/dist/loader/loader_orchestration.js.map +1 -0
- package/dist/loader/loader_runner.cjs +46 -41
- package/dist/loader/loader_runner.cjs.map +1 -1
- package/dist/loader/loader_runner.d.cts +12 -6
- package/dist/loader/loader_runner.d.ts +12 -6
- package/dist/loader/loader_runner.js +50 -41
- package/dist/loader/loader_runner.js.map +1 -1
- package/dist/native_browser.types-BY2daUOS.d.ts +189 -0
- package/dist/native_browser.types-vWXtE4rM.d.cts +189 -0
- package/dist/navigation/index.cjs +15 -2
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +2 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +8 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/runtime/get_container.cjs +2 -0
- package/dist/runtime/get_container.cjs.map +1 -1
- package/dist/runtime/get_container.js +2 -0
- package/dist/runtime/get_container.js.map +1 -1
- package/dist/runtime/index.cjs +5 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +2 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/runtime_capabilities.cjs +35 -0
- package/dist/runtime/runtime_capabilities.cjs.map +1 -0
- package/dist/runtime/runtime_capabilities.d.cts +6 -0
- package/dist/runtime/runtime_capabilities.d.ts +6 -0
- package/dist/runtime/runtime_capabilities.js +11 -0
- package/dist/runtime/runtime_capabilities.js.map +1 -0
- package/dist/runtime/runtime_module.cjs +2 -0
- package/dist/runtime/runtime_module.cjs.map +1 -1
- package/dist/runtime/runtime_module.js +2 -0
- package/dist/runtime/runtime_module.js.map +1 -1
- package/dist/test-wood.cjs +150 -0
- package/dist/test-wood.cjs.map +1 -0
- package/dist/test-wood.d.cts +62 -0
- package/dist/test-wood.d.ts +62 -0
- package/dist/test-wood.js +115 -0
- package/dist/test-wood.js.map +1 -0
- package/dist/testing/browser.cjs +92 -163
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +14 -13
- package/dist/testing/browser.d.ts +14 -13
- package/dist/testing/browser.js +88 -162
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +2 -1
- package/dist/testing/create_controller_harness.d.ts +2 -1
- package/dist/testing/create_router_bridge.cjs +19 -15
- package/dist/testing/create_router_bridge.cjs.map +1 -1
- package/dist/testing/create_router_bridge.d.cts +8 -1
- package/dist/testing/create_router_bridge.d.ts +8 -1
- package/dist/testing/create_router_bridge.js +18 -15
- package/dist/testing/create_router_bridge.js.map +1 -1
- package/dist/testing/index.cjs +12 -4
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +11 -5
- package/dist/testing/index.d.ts +11 -5
- package/dist/testing/index.js +9 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/native_browser/browser_pool.cjs +31 -0
- package/dist/testing/native_browser/browser_pool.cjs.map +1 -0
- package/dist/testing/native_browser/browser_pool.d.cts +1 -0
- package/dist/testing/native_browser/browser_pool.d.ts +1 -0
- package/dist/testing/native_browser/browser_pool.js +6 -0
- package/dist/testing/native_browser/browser_pool.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs +107 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.d.cts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.d.ts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.js +73 -0
- package/dist/testing/native_browser/happy_dom_tab.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs +29 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.cts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.ts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.js +5 -0
- package/dist/testing/native_browser/happy_dom_transfer.js.map +1 -0
- package/dist/testing/native_browser/host_transport.cjs +89 -0
- package/dist/testing/native_browser/host_transport.cjs.map +1 -0
- package/dist/testing/native_browser/host_transport.d.cts +44 -0
- package/dist/testing/native_browser/host_transport.d.ts +44 -0
- package/dist/testing/native_browser/host_transport.js +65 -0
- package/dist/testing/native_browser/host_transport.js.map +1 -0
- package/dist/testing/native_browser/native_browser.types.cjs +17 -0
- package/dist/testing/native_browser/native_browser.types.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser.types.d.cts +31 -0
- package/dist/testing/native_browser/native_browser.types.d.ts +31 -0
- package/dist/testing/native_browser/native_browser.types.js +1 -0
- package/dist/testing/native_browser/native_browser.types.js.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.cjs +143 -0
- package/dist/testing/native_browser/native_browser_environment.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.d.cts +36 -0
- package/dist/testing/native_browser/native_browser_environment.d.ts +36 -0
- package/dist/testing/native_browser/native_browser_environment.js +109 -0
- package/dist/testing/native_browser/native_browser_environment.js.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs +31 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.cts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.ts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js +6 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js.map +1 -0
- package/dist/testing/native_browser/playwright_tab.cjs +124 -0
- package/dist/testing/native_browser/playwright_tab.cjs.map +1 -0
- package/dist/testing/native_browser/playwright_tab.d.cts +35 -0
- package/dist/testing/native_browser/playwright_tab.d.ts +35 -0
- package/dist/testing/native_browser/playwright_tab.js +100 -0
- package/dist/testing/native_browser/playwright_tab.js.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.cjs +149 -0
- package/dist/testing/native_browser/renderer_compilation.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.d.cts +35 -0
- package/dist/testing/native_browser/renderer_compilation.d.ts +35 -0
- package/dist/testing/native_browser/renderer_compilation.js +115 -0
- package/dist/testing/native_browser/renderer_compilation.js.map +1 -0
- package/dist/testing/native_browser/renderer_entry.cjs +148 -0
- package/dist/testing/native_browser/renderer_entry.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_entry.d.cts +13 -0
- package/dist/testing/native_browser/renderer_entry.d.ts +13 -0
- package/dist/testing/native_browser/renderer_entry.js +114 -0
- package/dist/testing/native_browser/renderer_entry.js.map +1 -0
- package/dist/testing/registration_only_root.cjs +61 -5
- package/dist/testing/registration_only_root.cjs.map +1 -1
- package/dist/testing/registration_only_root.d.cts +25 -4
- package/dist/testing/registration_only_root.d.ts +25 -4
- package/dist/testing/registration_only_root.js +60 -5
- package/dist/testing/registration_only_root.js.map +1 -1
- package/dist/testing/runner_importer.cjs +75 -0
- package/dist/testing/runner_importer.cjs.map +1 -0
- package/dist/testing/runner_importer.d.cts +8 -0
- package/dist/testing/runner_importer.d.ts +8 -0
- package/dist/testing/runner_importer.js +41 -0
- package/dist/testing/runner_importer.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +407 -273
- package/dist/testing/test_wood_config.cjs.map +1 -1
- package/dist/testing/test_wood_config.d.cts +18 -123
- package/dist/testing/test_wood_config.d.ts +18 -123
- package/dist/testing/test_wood_config.js +416 -271
- package/dist/testing/test_wood_config.js.map +1 -1
- package/dist/testing/wood_config_source.cjs +12 -0
- package/dist/testing/wood_config_source.cjs.map +1 -1
- package/dist/testing/wood_config_source.d.cts +10 -0
- package/dist/testing/wood_config_source.d.ts +10 -0
- package/dist/testing/wood_config_source.js +12 -0
- package/dist/testing/wood_config_source.js.map +1 -1
- package/dist/testing/wood_env_renderer.cjs +31 -4
- package/dist/testing/wood_env_renderer.cjs.map +1 -1
- package/dist/testing/wood_env_renderer.d.cts +14 -1
- package/dist/testing/wood_env_renderer.d.ts +14 -1
- package/dist/testing/wood_env_renderer.js +31 -4
- package/dist/testing/wood_env_renderer.js.map +1 -1
- package/dist/testing/wood_lazy_runtime.cjs +109 -0
- package/dist/testing/wood_lazy_runtime.cjs.map +1 -0
- package/dist/testing/wood_lazy_runtime.d.cts +36 -0
- package/dist/testing/wood_lazy_runtime.d.ts +36 -0
- package/dist/testing/wood_lazy_runtime.js +84 -0
- package/dist/testing/wood_lazy_runtime.js.map +1 -0
- package/dist/testing/wood_test_selection.cjs +34 -55
- package/dist/testing/wood_test_selection.cjs.map +1 -1
- package/dist/testing/wood_test_selection.d.cts +18 -24
- package/dist/testing/wood_test_selection.d.ts +18 -24
- package/dist/testing/wood_test_selection.js +30 -54
- package/dist/testing/wood_test_selection.js.map +1 -1
- package/dist/testing-api.cjs +17 -0
- package/dist/testing-api.cjs.map +1 -0
- package/dist/testing-api.d.cts +18 -0
- package/dist/testing-api.d.ts +18 -0
- package/dist/testing-api.js +1 -0
- package/dist/testing-api.js.map +1 -0
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +4 -0
- package/dist/types/config.d.ts +4 -0
- package/docs/native-browser-testing.md +164 -0
- package/package.json +31 -5
- package/src/components/NavigationShell.browser.test.ts +10 -10
- package/src/components/NavigationShell.cleanup.browser.test.ts +57 -0
- package/src/components/NavigationShell.scopes.browser.test.ts +5 -5
- package/src/components/NavigationShell.svelte +50 -24
|
@@ -33,6 +33,7 @@ __export(renderer_app_generator_exports, {
|
|
|
33
33
|
module.exports = __toCommonJS(renderer_app_generator_exports);
|
|
34
34
|
var import_fs = require("fs");
|
|
35
35
|
var import_path = __toESM(require("path"), 1);
|
|
36
|
+
var import_loader_orchestration = require("../loader/loader_orchestration.cjs");
|
|
36
37
|
var import_generated_file_notice = require("./generated_file_notice.cjs");
|
|
37
38
|
const RENDERER_APP_SOURCE_INSTRUCTIONS = [
|
|
38
39
|
"To change routes, page components, layouts, or renderer controllers, edit the views YAML",
|
|
@@ -100,6 +101,15 @@ function resolveControllerFile(controllerRef, componentDir) {
|
|
|
100
101
|
}
|
|
101
102
|
return null;
|
|
102
103
|
}
|
|
104
|
+
function resolveLoaderFile(viewPath, componentDir) {
|
|
105
|
+
const present = (0, import_loader_orchestration.loaderCompanionCandidates)(viewPath).map((candidate) => resolveFromComponentDir(componentDir, candidate)).filter((candidate) => (0, import_fs.existsSync)(candidate));
|
|
106
|
+
if (present.length > 1) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Renderer app codegen failed: ambiguous data loader for "${viewPath}": ${present.join(", ")}. Keep exactly one .load companion.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return present[0] ?? null;
|
|
112
|
+
}
|
|
103
113
|
function resolveDefaultViewKey(sortedViews, entryViewKey) {
|
|
104
114
|
if (sortedViews.length === 0) return "";
|
|
105
115
|
const configuredEntry = entryViewKey?.trim();
|
|
@@ -121,6 +131,7 @@ function generateRendererApp(views, options) {
|
|
|
121
131
|
const viewIdentifiers = /* @__PURE__ */ new Map();
|
|
122
132
|
const layoutIdentifiers = /* @__PURE__ */ new Map();
|
|
123
133
|
const controllerIdentifiers = /* @__PURE__ */ new Map();
|
|
134
|
+
const loaderIdentifiers = /* @__PURE__ */ new Map();
|
|
124
135
|
const usedNames = /* @__PURE__ */ new Set();
|
|
125
136
|
const allocateName = (prefix, filePath) => {
|
|
126
137
|
const base = toIdentifier(prefix, filePath);
|
|
@@ -146,6 +157,14 @@ function generateRendererApp(views, options) {
|
|
|
146
157
|
controllerIdentifiers.set(controllerRef, { identifier, importPath });
|
|
147
158
|
importLines.push(`import ${identifier} from '${importPath}';`);
|
|
148
159
|
};
|
|
160
|
+
const ensureLoaderImported = (viewPath) => {
|
|
161
|
+
if (loaderIdentifiers.has(viewPath)) return;
|
|
162
|
+
const loaderFile = resolveLoaderFile(viewPath, options.componentDir);
|
|
163
|
+
if (!loaderFile) return;
|
|
164
|
+
const identifier = allocateName("Loader", viewPath);
|
|
165
|
+
loaderIdentifiers.set(viewPath, identifier);
|
|
166
|
+
importLines.push(`import ${identifier} from '${computeImportPath(options.outDir, loaderFile)}';`);
|
|
167
|
+
};
|
|
149
168
|
for (const view of sortedViews) {
|
|
150
169
|
if (!viewIdentifiers.has(view.viewPath)) {
|
|
151
170
|
const identifier = allocateName("View", view.viewPath);
|
|
@@ -154,7 +173,9 @@ function generateRendererApp(views, options) {
|
|
|
154
173
|
const importPath = computeImportPath(options.outDir, abs);
|
|
155
174
|
importLines.push(`import ${identifier} from '${importPath}';`);
|
|
156
175
|
}
|
|
176
|
+
ensureLoaderImported(view.viewPath);
|
|
157
177
|
for (const layoutPath of view.layouts) {
|
|
178
|
+
ensureLoaderImported(layoutPath);
|
|
158
179
|
if (layoutIdentifiers.has(layoutPath)) continue;
|
|
159
180
|
const identifier = allocateName("Layout", layoutPath);
|
|
160
181
|
layoutIdentifiers.set(layoutPath, identifier);
|
|
@@ -174,6 +195,8 @@ function generateRendererApp(views, options) {
|
|
|
174
195
|
const key = viewKey(view);
|
|
175
196
|
const viewIdentifier = viewIdentifiers.get(view.viewPath);
|
|
176
197
|
const layoutList = view.layouts.map((layoutPath) => layoutIdentifiers.get(layoutPath));
|
|
198
|
+
const layoutLoaders = view.layouts.map((layoutPath) => loaderIdentifiers.get(layoutPath) ?? "null");
|
|
199
|
+
const viewLoader = loaderIdentifiers.get(view.viewPath) ?? "null";
|
|
177
200
|
const controllerPath = view.controller ? `'${escapeSingleQuoted(view.controller)}'` : "undefined";
|
|
178
201
|
const controllerClass = view.controller ? controllerIdentifiers.get(view.controller).identifier : "undefined";
|
|
179
202
|
const layoutControllerRefs = view.layoutControllers;
|
|
@@ -210,12 +233,15 @@ function generateRendererApp(views, options) {
|
|
|
210
233
|
controllerClass: ${controllerClass},
|
|
211
234
|
view: ${viewIdentifier},
|
|
212
235
|
layouts: [${layoutList.join(", ")}],
|
|
236
|
+
viewLoader: ${viewLoader},
|
|
237
|
+
layoutLoaders: [${layoutLoaders.join(", ")}],
|
|
213
238
|
},`;
|
|
214
239
|
});
|
|
215
240
|
return `${(0, import_generated_file_notice.generatedFileNotice)(RENDERER_APP_SOURCE_INSTRUCTIONS)}
|
|
216
241
|
import { mount } from 'svelte';
|
|
217
242
|
import { App, getApp, getContainer } from '@noego/wood/client';
|
|
218
243
|
import { Navigation, getNavigation, installAutomationIntrospection } from '@noego/wood/navigation';
|
|
244
|
+
import { createLoaderRequest, initializeArgument, initializeController, runViewLoaders } from '@noego/wood/navigation';
|
|
219
245
|
import { Ipc, getIpc } from '@noego/wood/ipc';
|
|
220
246
|
import NavigationShell from '@noego/wood/navigation-shell';
|
|
221
247
|
${importLines.join("\n")}
|
|
@@ -233,8 +259,14 @@ interface GeneratedAppViewEntry {
|
|
|
233
259
|
controllerClass?: any;
|
|
234
260
|
view: any;
|
|
235
261
|
layouts: any[];
|
|
262
|
+
/** Companion data loader of the view (\`pages/x.svelte\` \u2192 \`pages/x.load.ts\`), discovered at build. */
|
|
263
|
+
viewLoader: ((request: any) => unknown) | null;
|
|
264
|
+
/** Index-aligned companion loaders of the layout stack. */
|
|
265
|
+
layoutLoaders: Array<((request: any) => unknown) | null>;
|
|
236
266
|
}
|
|
237
267
|
|
|
268
|
+
type RouteInput = { params: Record<string, string>; query: Record<string, string> };
|
|
269
|
+
|
|
238
270
|
const VIEW_MANIFEST: Record<string, GeneratedAppViewEntry> = {
|
|
239
271
|
${manifestEntries.join("\n")}
|
|
240
272
|
};
|
|
@@ -294,13 +326,25 @@ async function resolveControllerInScope(
|
|
|
294
326
|
return await scope.get(ControllerClass);
|
|
295
327
|
}
|
|
296
328
|
|
|
297
|
-
|
|
329
|
+
type RollbackStep = () => unknown | Promise<unknown>;
|
|
330
|
+
|
|
331
|
+
async function disposeScope(scope: { dispose?: () => Promise<void> } | null): Promise<void> {
|
|
298
332
|
if (!scope || typeof scope.dispose !== 'function') return;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
333
|
+
await scope.dispose();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Roll back after \`primary\` failed: every step is attempted exactly once and
|
|
338
|
+
* every cleanup failure is retained as its original error object next to the
|
|
339
|
+
* primary failure (\`errors\`, with \`cause\` = primary). Never swallows.
|
|
340
|
+
*/
|
|
341
|
+
async function failWithRollback(primary: unknown, steps: RollbackStep[], message: string): Promise<never> {
|
|
342
|
+
const errors: unknown[] = [primary];
|
|
343
|
+
for (const step of steps) {
|
|
344
|
+
try { await step(); } catch (error) { errors.push(error); }
|
|
303
345
|
}
|
|
346
|
+
if (errors.length === 1) throw primary;
|
|
347
|
+
throw Object.assign(new Error(message), { errors, cause: primary });
|
|
304
348
|
}
|
|
305
349
|
|
|
306
350
|
/**
|
|
@@ -309,10 +353,16 @@ async function disposeScopeQuietly(scope: { dispose?: () => Promise<void> } | nu
|
|
|
309
353
|
* services/state are shared within the page (the NoEgo renderer model).
|
|
310
354
|
* Layouts marked preservable in the manifest own dedicated scopes that
|
|
311
355
|
* survive page transitions and dispose when the layout finally leaves.
|
|
356
|
+
*
|
|
357
|
+
* Data loaders (shared orchestration) run first for this navigation \u2014
|
|
358
|
+
* non-preserved layouts in stack order, then the view \u2014 and each
|
|
359
|
+
* controller's optional \`initialize({ params, query[, data] })\` receives
|
|
360
|
+
* its loader's data. A loader failure disposes the scopes created here.
|
|
312
361
|
*/
|
|
313
362
|
async function createControllerSet(
|
|
314
363
|
entry: GeneratedAppViewEntry,
|
|
315
364
|
preservedDepth = 0,
|
|
365
|
+
route: RouteInput = { params: {}, query: {} },
|
|
316
366
|
): Promise<{
|
|
317
367
|
controller: unknown;
|
|
318
368
|
layoutControllers: Array<unknown>;
|
|
@@ -325,6 +375,12 @@ async function createControllerSet(
|
|
|
325
375
|
const createdLayoutScopes: WoodScopedContainer[] = [];
|
|
326
376
|
|
|
327
377
|
try {
|
|
378
|
+
const loaded = await runViewLoaders(
|
|
379
|
+
{ layouts: entry.layoutLoaders, view: entry.viewLoader },
|
|
380
|
+
createLoaderRequest(entry.key, route),
|
|
381
|
+
preservedDepth,
|
|
382
|
+
);
|
|
383
|
+
|
|
328
384
|
for (let i = 0; i < preservedDepth; i += 1) {
|
|
329
385
|
layoutControllers.push(undefined);
|
|
330
386
|
layoutScopes.push(null);
|
|
@@ -349,6 +405,7 @@ async function createControllerSet(
|
|
|
349
405
|
LayoutController,
|
|
350
406
|
\`layout[\${i}]\`,
|
|
351
407
|
);
|
|
408
|
+
await initializeController(controller, initializeArgument(route, loaded.layouts[i] ?? null));
|
|
352
409
|
layoutControllers.push(controller);
|
|
353
410
|
layoutScopes.push(preservable ? scope : null);
|
|
354
411
|
}
|
|
@@ -356,14 +413,19 @@ async function createControllerSet(
|
|
|
356
413
|
const controller = entry.controllerClass
|
|
357
414
|
? await resolveControllerInScope(pageScope, entry, entry.controllerClass, 'page')
|
|
358
415
|
: undefined;
|
|
416
|
+
if (controller !== undefined) {
|
|
417
|
+
await initializeController(controller, initializeArgument(route, loaded.view));
|
|
418
|
+
}
|
|
359
419
|
|
|
360
420
|
return { controller, layoutControllers, pageScope, layoutScopes };
|
|
361
421
|
} catch (error) {
|
|
362
|
-
// Failed transitions must not orphan scopes:
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
422
|
+
// Failed transitions must not orphan scopes: every scope this set created
|
|
423
|
+
// is disposed once, and every disposal failure is retained with the
|
|
424
|
+
// original initialization failure instead of being swallowed.
|
|
425
|
+
return failWithRollback(error, [
|
|
426
|
+
...createdLayoutScopes.map((scope) => () => disposeScope(scope as any)),
|
|
427
|
+
() => disposeScope(pageScope as any),
|
|
428
|
+
], 'Wood controller-set rollback failed');
|
|
367
429
|
}
|
|
368
430
|
}
|
|
369
431
|
|
|
@@ -430,28 +492,39 @@ function isAutomationBuild(): boolean {
|
|
|
430
492
|
}
|
|
431
493
|
}
|
|
432
494
|
|
|
433
|
-
export
|
|
495
|
+
export const supportsInitialRoute = true;
|
|
496
|
+
export async function app(target: Element, route: RouteInput = { params: {}, query: {} }) {
|
|
434
497
|
const entry = await resolveInitialEntry();
|
|
435
498
|
|
|
436
|
-
getNavigation().replace(entry.key,
|
|
499
|
+
getNavigation().replace(entry.key, route.params, route.query);
|
|
437
500
|
|
|
438
501
|
// Dev/test-only Axe introspection surface; never present in production builds.
|
|
439
502
|
if (isAutomationBuild()) {
|
|
440
503
|
const bootstrapWindow = await getWoodBridge()?.__window?.current?.();
|
|
441
504
|
installAutomationIntrospection({ windowId: bootstrapWindow?.windowId ?? null });
|
|
442
505
|
}
|
|
443
|
-
const initialSet = await createControllerSet(entry);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
506
|
+
const initialSet = await createControllerSet(entry, 0, route);
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
return mount(NavigationShell, {
|
|
510
|
+
target,
|
|
511
|
+
props: {
|
|
512
|
+
manifest: VIEW_MANIFEST,
|
|
513
|
+
initialEntry: entry,
|
|
514
|
+
initialRoute: route,
|
|
515
|
+
initialControllerSet: initialSet,
|
|
516
|
+
createControllerSetFn: createControllerSet,
|
|
517
|
+
getViewManifestEntryFn: getViewManifestEntry,
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
} catch (error) {
|
|
521
|
+
// A failed initial mount must not orphan the initial controller set: the
|
|
522
|
+
// shell never took ownership, so its scopes are disposed here once.
|
|
523
|
+
return failWithRollback(error, [
|
|
524
|
+
...initialSet.layoutScopes.map((scope) => () => disposeScope(scope as any)),
|
|
525
|
+
() => disposeScope(initialSet.pageScope as any),
|
|
526
|
+
], 'Wood initial mount rollback failed');
|
|
527
|
+
}
|
|
455
528
|
}
|
|
456
529
|
`;
|
|
457
530
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/codegen/renderer_app_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { ViewDef } from '../types/index.cjs';\nimport { generatedFileNotice } from './generated_file_notice.cjs';\n\nconst RENDERER_APP_SOURCE_INSTRUCTIONS = [\n 'To change routes, page components, layouts, or renderer controllers, edit the views YAML',\n 'configured by wood.config.yml renderer.openapi and the matching UI source files.',\n] as const;\n\nexport interface RendererAppGeneratorOptions {\n /** Absolute path to renderer component root (e.g. /app/ui) */\n componentDir: string;\n /** Absolute output directory where generated file will be written */\n outDir: string;\n /** Optional explicit default view key (e.g. \"main.chat\") */\n entryViewKey?: string;\n}\n\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n return rel.replace(/\\\\/g, '/');\n}\n\nfunction resolveFromComponentDir(componentDir: string, fileRef: string): string {\n if (path.isAbsolute(fileRef)) {\n return fileRef;\n }\n\n const normalizedRef = fileRef.replace(/\\\\/g, '/');\n const componentDirName = path.basename(componentDir).replace(/\\\\/g, '/');\n\n if (normalizedRef.startsWith(componentDirName + '/')) {\n const stripped = normalizedRef.slice(componentDirName.length + 1);\n return path.resolve(componentDir, stripped);\n }\n\n return path.resolve(componentDir, normalizedRef);\n}\n\nfunction viewKey(view: ViewDef): string {\n return `${view.windowName}.${view.pageName}`;\n}\n\nfunction toIdentifier(prefix: string, value: string): string {\n const core = value\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .split(/\\s+/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return `${prefix}${core || 'Component'}`;\n}\n\nfunction escapeSingleQuoted(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nconst CONTROLLER_FILE_SUFFIXES = [\n '.ts',\n '.js',\n '.controller.ts',\n '.controller.js',\n] as const;\n\nconst CONTROLLER_INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\nfunction resolveControllerFile(controllerRef: string, componentDir: string): string | null {\n const normalized = controllerRef.trim();\n const basePath = resolveFromComponentDir(componentDir, normalized);\n\n if (existsSync(basePath)) {\n return basePath;\n }\n\n if (path.extname(basePath)) {\n return null;\n }\n\n for (const suffix of CONTROLLER_FILE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n for (const indexFile of CONTROLLER_INDEX_CANDIDATES) {\n const candidate = path.resolve(basePath, indexFile);\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\nfunction resolveDefaultViewKey(sortedViews: ViewDef[], entryViewKey?: string): string {\n if (sortedViews.length === 0) return '';\n\n const configuredEntry = entryViewKey?.trim();\n if (!configuredEntry) {\n return viewKey(sortedViews[0]);\n }\n\n const availableKeys = sortedViews.map((view) => viewKey(view));\n if (availableKeys.includes(configuredEntry)) {\n return configuredEntry;\n }\n\n throw new Error(\n `Renderer entry \"${configuredEntry}\" was not found in parsed views. Available view keys: ${availableKeys.join(', ')}`,\n );\n}\n\nexport function generateRendererApp(\n views: ViewDef[],\n options: RendererAppGeneratorOptions,\n): string {\n const sortedViews = [...views].sort((a, b) => viewKey(a).localeCompare(viewKey(b)));\n const defaultViewKey = resolveDefaultViewKey(sortedViews, options.entryViewKey);\n\n const importLines: string[] = [];\n const viewIdentifiers = new Map<string, string>();\n const layoutIdentifiers = new Map<string, string>();\n const controllerIdentifiers = new Map<string, { identifier: string; importPath: string }>();\n const usedNames = new Set<string>();\n\n const allocateName = (prefix: string, filePath: string): string => {\n const base = toIdentifier(prefix, filePath);\n let name = base;\n let suffix = 2;\n while (usedNames.has(name)) {\n name = `${base}${suffix}`;\n suffix += 1;\n }\n usedNames.add(name);\n return name;\n };\n\n const ensureControllerImported = (controllerRef: string, owningViewKey: string): void => {\n if (controllerIdentifiers.has(controllerRef)) return;\n\n const resolvedControllerFile = resolveControllerFile(controllerRef, options.componentDir);\n if (!resolvedControllerFile) {\n throw new Error(\n `Renderer app codegen failed: controller \"${controllerRef}\" for view \"${owningViewKey}\" was not found. ` +\n 'Controller must point to an existing file with a default exported class.',\n );\n }\n\n const identifier = allocateName('PageController', controllerRef);\n const importPath = computeImportPath(options.outDir, resolvedControllerFile);\n controllerIdentifiers.set(controllerRef, { identifier, importPath });\n importLines.push(`import ${identifier} from '${importPath}';`);\n };\n\n for (const view of sortedViews) {\n if (!viewIdentifiers.has(view.viewPath)) {\n const identifier = allocateName('View', view.viewPath);\n viewIdentifiers.set(view.viewPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, view.viewPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n\n for (const layoutPath of view.layouts) {\n if (layoutIdentifiers.has(layoutPath)) continue;\n const identifier = allocateName('Layout', layoutPath);\n layoutIdentifiers.set(layoutPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, layoutPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n\n if (view.controller) {\n ensureControllerImported(view.controller, viewKey(view));\n }\n\n for (const layoutController of view.layoutControllers ?? []) {\n if (!layoutController) continue;\n ensureControllerImported(layoutController, viewKey(view));\n }\n }\n\n const manifestEntries = sortedViews.map((view) => {\n const key = viewKey(view);\n const viewIdentifier = viewIdentifiers.get(view.viewPath)!;\n const layoutList = view.layouts.map((layoutPath) => layoutIdentifiers.get(layoutPath)!);\n const controllerPath = view.controller ? `'${escapeSingleQuoted(view.controller)}'` : 'undefined';\n const controllerClass = view.controller\n ? controllerIdentifiers.get(view.controller)!.identifier\n : 'undefined';\n const layoutControllerRefs = view.layoutControllers;\n if (layoutControllerRefs && layoutControllerRefs.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout controller count for view \"${key}\" does not match layout count.`,\n );\n }\n const normalizedLayoutControllerRefs = layoutControllerRefs ?? Array.from(\n { length: view.layouts.length },\n () => undefined,\n );\n const layoutControllerPaths = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? `'${escapeSingleQuoted(controllerRef)}'` : 'undefined'))\n .join(', ')}]`;\n const layoutControllerClasses = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? controllerIdentifiers.get(controllerRef)!.identifier : 'undefined'))\n .join(', ')}]`;\n const layoutPreserve = view.layoutPreserve ?? Array.from(\n { length: view.layouts.length },\n () => false,\n );\n if (layoutPreserve.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout preserve count for view \"${key}\" does not match layout count.`,\n );\n }\n\n return ` '${escapeSingleQuoted(key)}': {\n key: '${escapeSingleQuoted(key)}',\n windowName: '${escapeSingleQuoted(view.windowName)}',\n pageName: '${escapeSingleQuoted(view.pageName)}',\n viewPath: '${escapeSingleQuoted(view.viewPath)}',\n layoutPaths: ${JSON.stringify(view.layouts)},\n layoutControllers: ${layoutControllerPaths},\n layoutPreserve: ${JSON.stringify(layoutPreserve)},\n controller: ${controllerPath},\n layoutControllerClasses: ${layoutControllerClasses},\n controllerClass: ${controllerClass},\n view: ${viewIdentifier},\n layouts: [${layoutList.join(', ')}],\n },`;\n });\n\n return `${generatedFileNotice(RENDERER_APP_SOURCE_INSTRUCTIONS)}\nimport { mount } from 'svelte';\nimport { App, getApp, getContainer } from '@noego/wood/client';\nimport { Navigation, getNavigation, installAutomationIntrospection } from '@noego/wood/navigation';\nimport { Ipc, getIpc } from '@noego/wood/ipc';\nimport NavigationShell from '@noego/wood/navigation-shell';\n${importLines.join('\\n')}\n\ninterface GeneratedAppViewEntry {\n key: string;\n windowName: string;\n pageName: string;\n viewPath: string;\n layoutPaths: string[];\n layoutControllers: Array<string | undefined>;\n layoutPreserve: boolean[];\n controller?: string;\n layoutControllerClasses: Array<any | undefined>;\n controllerClass?: any;\n view: any;\n layouts: any[];\n}\n\nconst VIEW_MANIFEST: Record<string, GeneratedAppViewEntry> = {\n${manifestEntries.join('\\n')}\n};\n\nfunction getViewManifestEntry(viewKey: string): GeneratedAppViewEntry | null {\n return VIEW_MANIFEST[viewKey] ?? null;\n}\n\ninterface WoodScopedContainer {\n get(token: any): unknown | Promise<unknown>;\n registerClass?(token: any): void;\n registerFunction?(\n token: any,\n resolver: (...args: any[]) => unknown,\n options?: Record<string, unknown>,\n ): void;\n}\n\ninterface WoodContainer extends WoodScopedContainer {\n extend?(): WoodScopedContainer;\n}\n\nfunction createScopedContainer(): WoodScopedContainer {\n const container = getContainer() as WoodContainer;\n if (typeof container.extend !== 'function') {\n throw new Error('Wood client container does not support extend().');\n }\n\n const scopedContainer = container.extend();\n\n if (!scopedContainer || typeof scopedContainer.get !== 'function') {\n throw new Error('Wood client container does not support get().');\n }\n\n // Renderer builtins belong to the root created by getContainer().\n // Page scopes inherit them; re-registering resolved ancestor singletons is invalid.\n\n return scopedContainer as WoodScopedContainer;\n}\n\nasync function resolveControllerInScope(\n scope: WoodScopedContainer,\n entry: GeneratedAppViewEntry,\n ControllerClass: any,\n contextLabel: string,\n): Promise<unknown> {\n if (typeof ControllerClass !== 'function') {\n throw new Error(\n \\`Controller for \\${contextLabel} in view \"\\${entry.key}\" is invalid. Expected a default exported class.\\`,\n );\n }\n\n if (typeof scope.registerClass === 'function') {\n scope.registerClass(ControllerClass);\n }\n\n return await scope.get(ControllerClass);\n}\n\nasync function disposeScopeQuietly(scope: { dispose?: () => Promise<void> } | null): Promise<void> {\n if (!scope || typeof scope.dispose !== 'function') return;\n try {\n await scope.dispose();\n } catch (error) {\n console.error('[wood] renderer scope disposal failed:', error);\n }\n}\n\n/**\n * One page scope per view lifecycle: the view controller and every\n * non-preservable layout controller resolve from the SAME scope, so Scoped\n * services/state are shared within the page (the NoEgo renderer model).\n * Layouts marked preservable in the manifest own dedicated scopes that\n * survive page transitions and dispose when the layout finally leaves.\n */\nasync function createControllerSet(\n entry: GeneratedAppViewEntry,\n preservedDepth = 0,\n): Promise<{\n controller: unknown;\n layoutControllers: Array<unknown>;\n pageScope: WoodScopedContainer;\n layoutScopes: Array<WoodScopedContainer | null>;\n}> {\n const pageScope = createScopedContainer();\n const layoutControllers: unknown[] = [];\n const layoutScopes: Array<WoodScopedContainer | null> = [];\n const createdLayoutScopes: WoodScopedContainer[] = [];\n\n try {\n for (let i = 0; i < preservedDepth; i += 1) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n }\n\n for (let i = preservedDepth; i < entry.layoutControllerClasses.length; i += 1) {\n const LayoutController = entry.layoutControllerClasses[i];\n if (!LayoutController) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n continue;\n }\n const preservable = entry.layoutPreserve?.[i] === true;\n let scope = pageScope;\n if (preservable) {\n scope = createScopedContainer();\n createdLayoutScopes.push(scope);\n }\n const controller = await resolveControllerInScope(\n scope,\n entry,\n LayoutController,\n \\`layout[\\${i}]\\`,\n );\n layoutControllers.push(controller);\n layoutScopes.push(preservable ? scope : null);\n }\n\n const controller = entry.controllerClass\n ? await resolveControllerInScope(pageScope, entry, entry.controllerClass, 'page')\n : undefined;\n\n return { controller, layoutControllers, pageScope, layoutScopes };\n } catch (error) {\n // Failed transitions must not orphan scopes: dispose everything this\n // set created before rethrowing.\n for (const scope of createdLayoutScopes) await disposeScopeQuietly(scope as any);\n await disposeScopeQuietly(pageScope as any);\n throw error;\n }\n}\n\nconst DEFAULT_VIEW_KEY = '${escapeSingleQuoted(defaultViewKey)}';\n\ntype WoodWindowBootstrapInfo = {\n ok?: boolean;\n windowId?: string;\n defaultRoute?: string;\n};\n\ntype WoodBootstrapBridgeLike = {\n __window?: {\n current?: () => Promise<WoodWindowBootstrapInfo>;\n };\n};\n\nfunction getWoodBridge(): WoodBootstrapBridgeLike | null {\n const globalObj = globalThis as typeof globalThis & {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n window?: {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n };\n };\n\n const bridge = globalObj.__NOEGO_WOOD_BRIDGE__ ?? globalObj.window?.__NOEGO_WOOD_BRIDGE__;\n if (!bridge || typeof bridge !== 'object') {\n return null;\n }\n\n return bridge as WoodBootstrapBridgeLike;\n}\n\nasync function resolveInitialEntry(): Promise<GeneratedAppViewEntry> {\n if (!DEFAULT_VIEW_KEY) {\n throw new Error('No views available in renderer manifest. Define at least one view in renderer OpenAPI YAML.');\n }\n\n const bridge = getWoodBridge();\n const bootstrapWindow = await bridge?.__window?.current?.();\n const requestedRoute = bootstrapWindow?.defaultRoute;\n\n if (requestedRoute) {\n const routeEntry = getViewManifestEntry(requestedRoute);\n if (routeEntry) {\n return routeEntry;\n }\n }\n\n const entry = getViewManifestEntry(DEFAULT_VIEW_KEY);\n if (!entry) {\n throw new Error(\\`Default view \"\\${DEFAULT_VIEW_KEY}\" is missing from generated view manifest.\\`);\n }\n\n return entry;\n}\n\nfunction isAutomationBuild(): boolean {\n try {\n const env = (import.meta as { env?: { DEV?: boolean; MODE?: string } }).env;\n return Boolean(env?.DEV) || env?.MODE === 'test';\n } catch {\n return false;\n }\n}\n\nexport async function app(target: Element) {\n const entry = await resolveInitialEntry();\n\n getNavigation().replace(entry.key, {}, {});\n\n // Dev/test-only Axe introspection surface; never present in production builds.\n if (isAutomationBuild()) {\n const bootstrapWindow = await getWoodBridge()?.__window?.current?.();\n installAutomationIntrospection({ windowId: bootstrapWindow?.windowId ?? null });\n }\n const initialSet = await createControllerSet(entry);\n\n return mount(NavigationShell, {\n target,\n props: {\n manifest: VIEW_MANIFEST,\n initialEntry: entry,\n initialControllerSet: initialSet,\n createControllerSetFn: createControllerSet,\n getViewManifestEntryFn: getViewManifestEntry,\n },\n });\n}\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAA2B;AAC3B,kBAAiB;AAEjB,mCAAoC;AAEpC,MAAM,mCAAmC;AAAA,EACvC;AAAA,EACA;AACF;AAWA,SAAS,kBAAkB,SAAiB,QAAwB;AAClE,MAAI,MAAM,YAAAA,QAAK,SAAS,SAAS,MAAM;AAEvC,MAAI,CAAC,IAAI,WAAW,GAAG,GAAG;AACxB,UAAM,OAAO;AAAA,EACf;AAEA,SAAO,IAAI,QAAQ,OAAO,GAAG;AAC/B;AAEA,SAAS,wBAAwB,cAAsB,SAAyB;AAC9E,MAAI,YAAAA,QAAK,WAAW,OAAO,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,QAAQ,QAAQ,OAAO,GAAG;AAChD,QAAM,mBAAmB,YAAAA,QAAK,SAAS,YAAY,EAAE,QAAQ,OAAO,GAAG;AAEvE,MAAI,cAAc,WAAW,mBAAmB,GAAG,GAAG;AACpD,UAAM,WAAW,cAAc,MAAM,iBAAiB,SAAS,CAAC;AAChE,WAAO,YAAAA,QAAK,QAAQ,cAAc,QAAQ;AAAA,EAC5C;AAEA,SAAO,YAAAA,QAAK,QAAQ,cAAc,aAAa;AACjD;AAEA,SAAS,QAAQ,MAAuB;AACtC,SAAO,GAAG,KAAK,UAAU,IAAI,KAAK,QAAQ;AAC5C;AAEA,SAAS,aAAa,QAAgB,OAAuB;AAC3D,QAAM,OAAO,MACV,QAAQ,kBAAkB,GAAG,EAC7B,KAAK,EACL,MAAM,KAAK,EACX,IAAI,CAAC,YAAY,QAAQ,OAAO,CAAC,EAAE,YAAY,IAAI,QAAQ,MAAM,CAAC,CAAC,EACnE,KAAK,EAAE;AAEV,SAAO,GAAG,MAAM,GAAG,QAAQ,WAAW;AACxC;AAEA,SAAS,mBAAmB,OAAuB;AACjD,SAAO,MAAM,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AACzD;AAEA,MAAM,2BAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,8BAA8B;AAAA,EAClC;AAAA,EACA;AACF;AAEA,SAAS,sBAAsB,eAAuB,cAAqC;AACzF,QAAM,aAAa,cAAc,KAAK;AACtC,QAAM,WAAW,wBAAwB,cAAc,UAAU;AAEjE,UAAI,sBAAW,QAAQ,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,YAAAA,QAAK,QAAQ,QAAQ,GAAG;AAC1B,WAAO;AAAA,EACT;AAEA,aAAW,UAAU,0BAA0B;AAC7C,UAAM,YAAY,WAAW;AAC7B,YAAI,sBAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,aAAW,aAAa,6BAA6B;AACnD,UAAM,YAAY,YAAAA,QAAK,QAAQ,UAAU,SAAS;AAClD,YAAI,sBAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,sBAAsB,aAAwB,cAA+B;AACpF,MAAI,YAAY,WAAW,EAAG,QAAO;AAErC,QAAM,kBAAkB,cAAc,KAAK;AAC3C,MAAI,CAAC,iBAAiB;AACpB,WAAO,QAAQ,YAAY,CAAC,CAAC;AAAA,EAC/B;AAEA,QAAM,gBAAgB,YAAY,IAAI,CAAC,SAAS,QAAQ,IAAI,CAAC;AAC7D,MAAI,cAAc,SAAS,eAAe,GAAG;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,IAAI;AAAA,IACR,mBAAmB,eAAe,yDAAyD,cAAc,KAAK,IAAI,CAAC;AAAA,EACrH;AACF;AAEO,SAAS,oBACd,OACA,SACQ;AACR,QAAM,cAAc,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,QAAQ,CAAC,EAAE,cAAc,QAAQ,CAAC,CAAC,CAAC;AAClF,QAAM,iBAAiB,sBAAsB,aAAa,QAAQ,YAAY;AAE9E,QAAM,cAAwB,CAAC;AAC/B,QAAM,kBAAkB,oBAAI,IAAoB;AAChD,QAAM,oBAAoB,oBAAI,IAAoB;AAClD,QAAM,wBAAwB,oBAAI,IAAwD;AAC1F,QAAM,YAAY,oBAAI,IAAY;AAElC,QAAM,eAAe,CAAC,QAAgB,aAA6B;AACjE,UAAM,OAAO,aAAa,QAAQ,QAAQ;AAC1C,QAAI,OAAO;AACX,QAAI,SAAS;AACb,WAAO,UAAU,IAAI,IAAI,GAAG;AAC1B,aAAO,GAAG,IAAI,GAAG,MAAM;AACvB,gBAAU;AAAA,IACZ;AACA,cAAU,IAAI,IAAI;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,2BAA2B,CAAC,eAAuB,kBAAgC;AACvF,QAAI,sBAAsB,IAAI,aAAa,EAAG;AAE9C,UAAM,yBAAyB,sBAAsB,eAAe,QAAQ,YAAY;AACxF,QAAI,CAAC,wBAAwB;AAC3B,YAAM,IAAI;AAAA,QACR,4CAA4C,aAAa,eAAe,aAAa;AAAA,MAEvF;AAAA,IACF;AAEA,UAAM,aAAa,aAAa,kBAAkB,aAAa;AAC/D,UAAM,aAAa,kBAAkB,QAAQ,QAAQ,sBAAsB;AAC3E,0BAAsB,IAAI,eAAe,EAAE,YAAY,WAAW,CAAC;AACnE,gBAAY,KAAK,UAAU,UAAU,UAAU,UAAU,IAAI;AAAA,EAC/D;AAEA,aAAW,QAAQ,aAAa;AAC9B,QAAI,CAAC,gBAAgB,IAAI,KAAK,QAAQ,GAAG;AACvC,YAAM,aAAa,aAAa,QAAQ,KAAK,QAAQ;AACrD,sBAAgB,IAAI,KAAK,UAAU,UAAU;AAC7C,YAAM,MAAM,wBAAwB,QAAQ,cAAc,KAAK,QAAQ;AACvE,YAAM,aAAa,kBAAkB,QAAQ,QAAQ,GAAG;AACxD,kBAAY,KAAK,UAAU,UAAU,UAAU,UAAU,IAAI;AAAA,IAC/D;AAEA,eAAW,cAAc,KAAK,SAAS;AACrC,UAAI,kBAAkB,IAAI,UAAU,EAAG;AACvC,YAAM,aAAa,aAAa,UAAU,UAAU;AACpD,wBAAkB,IAAI,YAAY,UAAU;AAC5C,YAAM,MAAM,wBAAwB,QAAQ,cAAc,UAAU;AACpE,YAAM,aAAa,kBAAkB,QAAQ,QAAQ,GAAG;AACxD,kBAAY,KAAK,UAAU,UAAU,UAAU,UAAU,IAAI;AAAA,IAC/D;AAEA,QAAI,KAAK,YAAY;AACnB,+BAAyB,KAAK,YAAY,QAAQ,IAAI,CAAC;AAAA,IACzD;AAEA,eAAW,oBAAoB,KAAK,qBAAqB,CAAC,GAAG;AAC3D,UAAI,CAAC,iBAAkB;AACvB,+BAAyB,kBAAkB,QAAQ,IAAI,CAAC;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM,kBAAkB,YAAY,IAAI,CAAC,SAAS;AAChD,UAAM,MAAM,QAAQ,IAAI;AACxB,UAAM,iBAAiB,gBAAgB,IAAI,KAAK,QAAQ;AACxD,UAAM,aAAa,KAAK,QAAQ,IAAI,CAAC,eAAe,kBAAkB,IAAI,UAAU,CAAE;AACtF,UAAM,iBAAiB,KAAK,aAAa,IAAI,mBAAmB,KAAK,UAAU,CAAC,MAAM;AACtF,UAAM,kBAAkB,KAAK,aACzB,sBAAsB,IAAI,KAAK,UAAU,EAAG,aAC5C;AACJ,UAAM,uBAAuB,KAAK;AAClC,QAAI,wBAAwB,qBAAqB,WAAW,KAAK,QAAQ,QAAQ;AAC/E,YAAM,IAAI;AAAA,QACR,kEAAkE,GAAG;AAAA,MACvE;AAAA,IACF;AACA,UAAM,iCAAiC,wBAAwB,MAAM;AAAA,MACnE,EAAE,QAAQ,KAAK,QAAQ,OAAO;AAAA,MAC9B,MAAM;AAAA,IACR;AACA,UAAM,wBAAwB,IAAI,+BAC/B,IAAI,CAAC,kBAAmB,gBAAgB,IAAI,mBAAmB,aAAa,CAAC,MAAM,WAAY,EAC/F,KAAK,IAAI,CAAC;AACb,UAAM,0BAA0B,IAAI,+BACjC,IAAI,CAAC,kBAAmB,gBAAgB,sBAAsB,IAAI,aAAa,EAAG,aAAa,WAAY,EAC3G,KAAK,IAAI,CAAC;AACb,UAAM,iBAAiB,KAAK,kBAAkB,MAAM;AAAA,MAClD,EAAE,QAAQ,KAAK,QAAQ,OAAO;AAAA,MAC9B,MAAM;AAAA,IACR;AACA,QAAI,eAAe,WAAW,KAAK,QAAQ,QAAQ;AACjD,YAAM,IAAI;AAAA,QACR,gEAAgE,GAAG;AAAA,MACrE;AAAA,IACF;AAEA,WAAO,MAAM,mBAAmB,GAAG,CAAC;AAAA,YAC5B,mBAAmB,GAAG,CAAC;AAAA,mBAChB,mBAAmB,KAAK,UAAU,CAAC;AAAA,iBACrC,mBAAmB,KAAK,QAAQ,CAAC;AAAA,iBACjC,mBAAmB,KAAK,QAAQ,CAAC;AAAA,mBAC/B,KAAK,UAAU,KAAK,OAAO,CAAC;AAAA,yBACtB,qBAAqB;AAAA,sBACxB,KAAK,UAAU,cAAc,CAAC;AAAA,kBAClC,cAAc;AAAA,+BACD,uBAAuB;AAAA,uBAC/B,eAAe;AAAA,YAC1B,cAAc;AAAA,gBACV,WAAW,KAAK,IAAI,CAAC;AAAA;AAAA,EAEnC,CAAC;AAED,SAAO,OAAG,kDAAoB,gCAAgC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/D,YAAY,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBtB,gBAAgB,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAmIA,mBAAmB,cAAc,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuF9D;","names":["path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/codegen/renderer_app_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { ViewDef } from '../types/index.cjs';\nimport { loaderCompanionCandidates } from '../loader/loader_orchestration.cjs';\nimport { generatedFileNotice } from './generated_file_notice.cjs';\n\nconst RENDERER_APP_SOURCE_INSTRUCTIONS = [\n 'To change routes, page components, layouts, or renderer controllers, edit the views YAML',\n 'configured by wood.config.yml renderer.openapi and the matching UI source files.',\n] as const;\n\nexport interface RendererAppGeneratorOptions {\n /** Absolute path to renderer component root (e.g. /app/ui) */\n componentDir: string;\n /** Absolute output directory where generated file will be written */\n outDir: string;\n /** Optional explicit default view key (e.g. \"main.chat\") */\n entryViewKey?: string;\n}\n\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n return rel.replace(/\\\\/g, '/');\n}\n\nfunction resolveFromComponentDir(componentDir: string, fileRef: string): string {\n if (path.isAbsolute(fileRef)) {\n return fileRef;\n }\n\n const normalizedRef = fileRef.replace(/\\\\/g, '/');\n const componentDirName = path.basename(componentDir).replace(/\\\\/g, '/');\n\n if (normalizedRef.startsWith(componentDirName + '/')) {\n const stripped = normalizedRef.slice(componentDirName.length + 1);\n return path.resolve(componentDir, stripped);\n }\n\n return path.resolve(componentDir, normalizedRef);\n}\n\nfunction viewKey(view: ViewDef): string {\n return `${view.windowName}.${view.pageName}`;\n}\n\nfunction toIdentifier(prefix: string, value: string): string {\n const core = value\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .split(/\\s+/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return `${prefix}${core || 'Component'}`;\n}\n\nfunction escapeSingleQuoted(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nconst CONTROLLER_FILE_SUFFIXES = [\n '.ts',\n '.js',\n '.controller.ts',\n '.controller.js',\n] as const;\n\nconst CONTROLLER_INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\nfunction resolveControllerFile(controllerRef: string, componentDir: string): string | null {\n const normalized = controllerRef.trim();\n const basePath = resolveFromComponentDir(componentDir, normalized);\n\n if (existsSync(basePath)) {\n return basePath;\n }\n\n if (path.extname(basePath)) {\n return null;\n }\n\n for (const suffix of CONTROLLER_FILE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n for (const indexFile of CONTROLLER_INDEX_CANDIDATES) {\n const candidate = path.resolve(basePath, indexFile);\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * The companion data loader of a view/layout (`pages/chat.svelte` →\n * `pages/chat.load.ts`), discovered at build. None is fine (`null`); more\n * than one companion is ambiguous and fails the build.\n */\nfunction resolveLoaderFile(viewPath: string, componentDir: string): string | null {\n const present = loaderCompanionCandidates(viewPath)\n .map((candidate) => resolveFromComponentDir(componentDir, candidate))\n .filter((candidate) => existsSync(candidate));\n if (present.length > 1) {\n throw new Error(\n `Renderer app codegen failed: ambiguous data loader for \"${viewPath}\": ${present.join(', ')}. ` +\n 'Keep exactly one .load companion.',\n );\n }\n return present[0] ?? null;\n}\n\nfunction resolveDefaultViewKey(sortedViews: ViewDef[], entryViewKey?: string): string {\n if (sortedViews.length === 0) return '';\n\n const configuredEntry = entryViewKey?.trim();\n if (!configuredEntry) {\n return viewKey(sortedViews[0]);\n }\n\n const availableKeys = sortedViews.map((view) => viewKey(view));\n if (availableKeys.includes(configuredEntry)) {\n return configuredEntry;\n }\n\n throw new Error(\n `Renderer entry \"${configuredEntry}\" was not found in parsed views. Available view keys: ${availableKeys.join(', ')}`,\n );\n}\n\nexport function generateRendererApp(\n views: ViewDef[],\n options: RendererAppGeneratorOptions,\n): string {\n const sortedViews = [...views].sort((a, b) => viewKey(a).localeCompare(viewKey(b)));\n const defaultViewKey = resolveDefaultViewKey(sortedViews, options.entryViewKey);\n\n const importLines: string[] = [];\n const viewIdentifiers = new Map<string, string>();\n const layoutIdentifiers = new Map<string, string>();\n const controllerIdentifiers = new Map<string, { identifier: string; importPath: string }>();\n /** view/layout path → generated loader identifier (only when a companion exists). */\n const loaderIdentifiers = new Map<string, string>();\n const usedNames = new Set<string>();\n\n const allocateName = (prefix: string, filePath: string): string => {\n const base = toIdentifier(prefix, filePath);\n let name = base;\n let suffix = 2;\n while (usedNames.has(name)) {\n name = `${base}${suffix}`;\n suffix += 1;\n }\n usedNames.add(name);\n return name;\n };\n\n const ensureControllerImported = (controllerRef: string, owningViewKey: string): void => {\n if (controllerIdentifiers.has(controllerRef)) return;\n\n const resolvedControllerFile = resolveControllerFile(controllerRef, options.componentDir);\n if (!resolvedControllerFile) {\n throw new Error(\n `Renderer app codegen failed: controller \"${controllerRef}\" for view \"${owningViewKey}\" was not found. ` +\n 'Controller must point to an existing file with a default exported class.',\n );\n }\n\n const identifier = allocateName('PageController', controllerRef);\n const importPath = computeImportPath(options.outDir, resolvedControllerFile);\n controllerIdentifiers.set(controllerRef, { identifier, importPath });\n importLines.push(`import ${identifier} from '${importPath}';`);\n };\n\n // Build discovers and IMPORTS companion loaders; none runs until a page\n // opens (createControllerSet executes them lazily per navigation).\n const ensureLoaderImported = (viewPath: string): void => {\n if (loaderIdentifiers.has(viewPath)) return;\n const loaderFile = resolveLoaderFile(viewPath, options.componentDir);\n if (!loaderFile) return;\n const identifier = allocateName('Loader', viewPath);\n loaderIdentifiers.set(viewPath, identifier);\n importLines.push(`import ${identifier} from '${computeImportPath(options.outDir, loaderFile)}';`);\n };\n\n for (const view of sortedViews) {\n if (!viewIdentifiers.has(view.viewPath)) {\n const identifier = allocateName('View', view.viewPath);\n viewIdentifiers.set(view.viewPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, view.viewPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n ensureLoaderImported(view.viewPath);\n\n for (const layoutPath of view.layouts) {\n ensureLoaderImported(layoutPath);\n if (layoutIdentifiers.has(layoutPath)) continue;\n const identifier = allocateName('Layout', layoutPath);\n layoutIdentifiers.set(layoutPath, identifier);\n const abs = resolveFromComponentDir(options.componentDir, layoutPath);\n const importPath = computeImportPath(options.outDir, abs);\n importLines.push(`import ${identifier} from '${importPath}';`);\n }\n\n if (view.controller) {\n ensureControllerImported(view.controller, viewKey(view));\n }\n\n for (const layoutController of view.layoutControllers ?? []) {\n if (!layoutController) continue;\n ensureControllerImported(layoutController, viewKey(view));\n }\n }\n\n const manifestEntries = sortedViews.map((view) => {\n const key = viewKey(view);\n const viewIdentifier = viewIdentifiers.get(view.viewPath)!;\n const layoutList = view.layouts.map((layoutPath) => layoutIdentifiers.get(layoutPath)!);\n const layoutLoaders = view.layouts.map((layoutPath) => loaderIdentifiers.get(layoutPath) ?? 'null');\n const viewLoader = loaderIdentifiers.get(view.viewPath) ?? 'null';\n const controllerPath = view.controller ? `'${escapeSingleQuoted(view.controller)}'` : 'undefined';\n const controllerClass = view.controller\n ? controllerIdentifiers.get(view.controller)!.identifier\n : 'undefined';\n const layoutControllerRefs = view.layoutControllers;\n if (layoutControllerRefs && layoutControllerRefs.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout controller count for view \"${key}\" does not match layout count.`,\n );\n }\n const normalizedLayoutControllerRefs = layoutControllerRefs ?? Array.from(\n { length: view.layouts.length },\n () => undefined,\n );\n const layoutControllerPaths = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? `'${escapeSingleQuoted(controllerRef)}'` : 'undefined'))\n .join(', ')}]`;\n const layoutControllerClasses = `[${normalizedLayoutControllerRefs\n .map((controllerRef) => (controllerRef ? controllerIdentifiers.get(controllerRef)!.identifier : 'undefined'))\n .join(', ')}]`;\n const layoutPreserve = view.layoutPreserve ?? Array.from(\n { length: view.layouts.length },\n () => false,\n );\n if (layoutPreserve.length !== view.layouts.length) {\n throw new Error(\n `Renderer app codegen failed: layout preserve count for view \"${key}\" does not match layout count.`,\n );\n }\n\n return ` '${escapeSingleQuoted(key)}': {\n key: '${escapeSingleQuoted(key)}',\n windowName: '${escapeSingleQuoted(view.windowName)}',\n pageName: '${escapeSingleQuoted(view.pageName)}',\n viewPath: '${escapeSingleQuoted(view.viewPath)}',\n layoutPaths: ${JSON.stringify(view.layouts)},\n layoutControllers: ${layoutControllerPaths},\n layoutPreserve: ${JSON.stringify(layoutPreserve)},\n controller: ${controllerPath},\n layoutControllerClasses: ${layoutControllerClasses},\n controllerClass: ${controllerClass},\n view: ${viewIdentifier},\n layouts: [${layoutList.join(', ')}],\n viewLoader: ${viewLoader},\n layoutLoaders: [${layoutLoaders.join(', ')}],\n },`;\n });\n\n return `${generatedFileNotice(RENDERER_APP_SOURCE_INSTRUCTIONS)}\nimport { mount } from 'svelte';\nimport { App, getApp, getContainer } from '@noego/wood/client';\nimport { Navigation, getNavigation, installAutomationIntrospection } from '@noego/wood/navigation';\nimport { createLoaderRequest, initializeArgument, initializeController, runViewLoaders } from '@noego/wood/navigation';\nimport { Ipc, getIpc } from '@noego/wood/ipc';\nimport NavigationShell from '@noego/wood/navigation-shell';\n${importLines.join('\\n')}\n\ninterface GeneratedAppViewEntry {\n key: string;\n windowName: string;\n pageName: string;\n viewPath: string;\n layoutPaths: string[];\n layoutControllers: Array<string | undefined>;\n layoutPreserve: boolean[];\n controller?: string;\n layoutControllerClasses: Array<any | undefined>;\n controllerClass?: any;\n view: any;\n layouts: any[];\n /** Companion data loader of the view (\\`pages/x.svelte\\` → \\`pages/x.load.ts\\`), discovered at build. */\n viewLoader: ((request: any) => unknown) | null;\n /** Index-aligned companion loaders of the layout stack. */\n layoutLoaders: Array<((request: any) => unknown) | null>;\n}\n\ntype RouteInput = { params: Record<string, string>; query: Record<string, string> };\n\nconst VIEW_MANIFEST: Record<string, GeneratedAppViewEntry> = {\n${manifestEntries.join('\\n')}\n};\n\nfunction getViewManifestEntry(viewKey: string): GeneratedAppViewEntry | null {\n return VIEW_MANIFEST[viewKey] ?? null;\n}\n\ninterface WoodScopedContainer {\n get(token: any): unknown | Promise<unknown>;\n registerClass?(token: any): void;\n registerFunction?(\n token: any,\n resolver: (...args: any[]) => unknown,\n options?: Record<string, unknown>,\n ): void;\n}\n\ninterface WoodContainer extends WoodScopedContainer {\n extend?(): WoodScopedContainer;\n}\n\nfunction createScopedContainer(): WoodScopedContainer {\n const container = getContainer() as WoodContainer;\n if (typeof container.extend !== 'function') {\n throw new Error('Wood client container does not support extend().');\n }\n\n const scopedContainer = container.extend();\n\n if (!scopedContainer || typeof scopedContainer.get !== 'function') {\n throw new Error('Wood client container does not support get().');\n }\n\n // Renderer builtins belong to the root created by getContainer().\n // Page scopes inherit them; re-registering resolved ancestor singletons is invalid.\n\n return scopedContainer as WoodScopedContainer;\n}\n\nasync function resolveControllerInScope(\n scope: WoodScopedContainer,\n entry: GeneratedAppViewEntry,\n ControllerClass: any,\n contextLabel: string,\n): Promise<unknown> {\n if (typeof ControllerClass !== 'function') {\n throw new Error(\n \\`Controller for \\${contextLabel} in view \"\\${entry.key}\" is invalid. Expected a default exported class.\\`,\n );\n }\n\n if (typeof scope.registerClass === 'function') {\n scope.registerClass(ControllerClass);\n }\n\n return await scope.get(ControllerClass);\n}\n\ntype RollbackStep = () => unknown | Promise<unknown>;\n\nasync function disposeScope(scope: { dispose?: () => Promise<void> } | null): Promise<void> {\n if (!scope || typeof scope.dispose !== 'function') return;\n await scope.dispose();\n}\n\n/**\n * Roll back after \\`primary\\` failed: every step is attempted exactly once and\n * every cleanup failure is retained as its original error object next to the\n * primary failure (\\`errors\\`, with \\`cause\\` = primary). Never swallows.\n */\nasync function failWithRollback(primary: unknown, steps: RollbackStep[], message: string): Promise<never> {\n const errors: unknown[] = [primary];\n for (const step of steps) {\n try { await step(); } catch (error) { errors.push(error); }\n }\n if (errors.length === 1) throw primary;\n throw Object.assign(new Error(message), { errors, cause: primary });\n}\n\n/**\n * One page scope per view lifecycle: the view controller and every\n * non-preservable layout controller resolve from the SAME scope, so Scoped\n * services/state are shared within the page (the NoEgo renderer model).\n * Layouts marked preservable in the manifest own dedicated scopes that\n * survive page transitions and dispose when the layout finally leaves.\n *\n * Data loaders (shared orchestration) run first for this navigation —\n * non-preserved layouts in stack order, then the view — and each\n * controller's optional \\`initialize({ params, query[, data] })\\` receives\n * its loader's data. A loader failure disposes the scopes created here.\n */\nasync function createControllerSet(\n entry: GeneratedAppViewEntry,\n preservedDepth = 0,\n route: RouteInput = { params: {}, query: {} },\n): Promise<{\n controller: unknown;\n layoutControllers: Array<unknown>;\n pageScope: WoodScopedContainer;\n layoutScopes: Array<WoodScopedContainer | null>;\n}> {\n const pageScope = createScopedContainer();\n const layoutControllers: unknown[] = [];\n const layoutScopes: Array<WoodScopedContainer | null> = [];\n const createdLayoutScopes: WoodScopedContainer[] = [];\n\n try {\n const loaded = await runViewLoaders(\n { layouts: entry.layoutLoaders, view: entry.viewLoader },\n createLoaderRequest(entry.key, route),\n preservedDepth,\n );\n\n for (let i = 0; i < preservedDepth; i += 1) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n }\n\n for (let i = preservedDepth; i < entry.layoutControllerClasses.length; i += 1) {\n const LayoutController = entry.layoutControllerClasses[i];\n if (!LayoutController) {\n layoutControllers.push(undefined);\n layoutScopes.push(null);\n continue;\n }\n const preservable = entry.layoutPreserve?.[i] === true;\n let scope = pageScope;\n if (preservable) {\n scope = createScopedContainer();\n createdLayoutScopes.push(scope);\n }\n const controller = await resolveControllerInScope(\n scope,\n entry,\n LayoutController,\n \\`layout[\\${i}]\\`,\n );\n await initializeController(controller, initializeArgument(route, loaded.layouts[i] ?? null));\n layoutControllers.push(controller);\n layoutScopes.push(preservable ? scope : null);\n }\n\n const controller = entry.controllerClass\n ? await resolveControllerInScope(pageScope, entry, entry.controllerClass, 'page')\n : undefined;\n if (controller !== undefined) {\n await initializeController(controller, initializeArgument(route, loaded.view));\n }\n\n return { controller, layoutControllers, pageScope, layoutScopes };\n } catch (error) {\n // Failed transitions must not orphan scopes: every scope this set created\n // is disposed once, and every disposal failure is retained with the\n // original initialization failure instead of being swallowed.\n return failWithRollback(error, [\n ...createdLayoutScopes.map((scope) => () => disposeScope(scope as any)),\n () => disposeScope(pageScope as any),\n ], 'Wood controller-set rollback failed');\n }\n}\n\nconst DEFAULT_VIEW_KEY = '${escapeSingleQuoted(defaultViewKey)}';\n\ntype WoodWindowBootstrapInfo = {\n ok?: boolean;\n windowId?: string;\n defaultRoute?: string;\n};\n\ntype WoodBootstrapBridgeLike = {\n __window?: {\n current?: () => Promise<WoodWindowBootstrapInfo>;\n };\n};\n\nfunction getWoodBridge(): WoodBootstrapBridgeLike | null {\n const globalObj = globalThis as typeof globalThis & {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n window?: {\n __NOEGO_WOOD_BRIDGE__?: unknown;\n };\n };\n\n const bridge = globalObj.__NOEGO_WOOD_BRIDGE__ ?? globalObj.window?.__NOEGO_WOOD_BRIDGE__;\n if (!bridge || typeof bridge !== 'object') {\n return null;\n }\n\n return bridge as WoodBootstrapBridgeLike;\n}\n\nasync function resolveInitialEntry(): Promise<GeneratedAppViewEntry> {\n if (!DEFAULT_VIEW_KEY) {\n throw new Error('No views available in renderer manifest. Define at least one view in renderer OpenAPI YAML.');\n }\n\n const bridge = getWoodBridge();\n const bootstrapWindow = await bridge?.__window?.current?.();\n const requestedRoute = bootstrapWindow?.defaultRoute;\n\n if (requestedRoute) {\n const routeEntry = getViewManifestEntry(requestedRoute);\n if (routeEntry) {\n return routeEntry;\n }\n }\n\n const entry = getViewManifestEntry(DEFAULT_VIEW_KEY);\n if (!entry) {\n throw new Error(\\`Default view \"\\${DEFAULT_VIEW_KEY}\" is missing from generated view manifest.\\`);\n }\n\n return entry;\n}\n\nfunction isAutomationBuild(): boolean {\n try {\n const env = (import.meta as { env?: { DEV?: boolean; MODE?: string } }).env;\n return Boolean(env?.DEV) || env?.MODE === 'test';\n } catch {\n return false;\n }\n}\n\nexport const supportsInitialRoute = true;\nexport async function app(target: Element, route: RouteInput = { params: {}, query: {} }) {\n const entry = await resolveInitialEntry();\n\n getNavigation().replace(entry.key, route.params, route.query);\n\n // Dev/test-only Axe introspection surface; never present in production builds.\n if (isAutomationBuild()) {\n const bootstrapWindow = await getWoodBridge()?.__window?.current?.();\n installAutomationIntrospection({ windowId: bootstrapWindow?.windowId ?? null });\n }\n const initialSet = await createControllerSet(entry, 0, route);\n\n try {\n return mount(NavigationShell, {\n target,\n props: {\n manifest: VIEW_MANIFEST,\n initialEntry: entry,\n initialRoute: route,\n initialControllerSet: initialSet,\n createControllerSetFn: createControllerSet,\n getViewManifestEntryFn: getViewManifestEntry,\n },\n });\n } catch (error) {\n // A failed initial mount must not orphan the initial controller set: the\n // shell never took ownership, so its scopes are disposed here once.\n return failWithRollback(error, [\n ...initialSet.layoutScopes.map((scope) => () => disposeScope(scope as any)),\n () => disposeScope(initialSet.pageScope as any),\n ], 'Wood initial mount rollback failed');\n }\n}\n`;\n}\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAiKA,mBAAmB,cAAc,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkG9D;","names":["path"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { loaderCompanionCandidates } from "../loader/loader_orchestration.js";
|
|
3
4
|
import { generatedFileNotice } from "./generated_file_notice.js";
|
|
4
5
|
const RENDERER_APP_SOURCE_INSTRUCTIONS = [
|
|
5
6
|
"To change routes, page components, layouts, or renderer controllers, edit the views YAML",
|
|
@@ -67,6 +68,15 @@ function resolveControllerFile(controllerRef, componentDir) {
|
|
|
67
68
|
}
|
|
68
69
|
return null;
|
|
69
70
|
}
|
|
71
|
+
function resolveLoaderFile(viewPath, componentDir) {
|
|
72
|
+
const present = loaderCompanionCandidates(viewPath).map((candidate) => resolveFromComponentDir(componentDir, candidate)).filter((candidate) => existsSync(candidate));
|
|
73
|
+
if (present.length > 1) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Renderer app codegen failed: ambiguous data loader for "${viewPath}": ${present.join(", ")}. Keep exactly one .load companion.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return present[0] ?? null;
|
|
79
|
+
}
|
|
70
80
|
function resolveDefaultViewKey(sortedViews, entryViewKey) {
|
|
71
81
|
if (sortedViews.length === 0) return "";
|
|
72
82
|
const configuredEntry = entryViewKey?.trim();
|
|
@@ -88,6 +98,7 @@ function generateRendererApp(views, options) {
|
|
|
88
98
|
const viewIdentifiers = /* @__PURE__ */ new Map();
|
|
89
99
|
const layoutIdentifiers = /* @__PURE__ */ new Map();
|
|
90
100
|
const controllerIdentifiers = /* @__PURE__ */ new Map();
|
|
101
|
+
const loaderIdentifiers = /* @__PURE__ */ new Map();
|
|
91
102
|
const usedNames = /* @__PURE__ */ new Set();
|
|
92
103
|
const allocateName = (prefix, filePath) => {
|
|
93
104
|
const base = toIdentifier(prefix, filePath);
|
|
@@ -113,6 +124,14 @@ function generateRendererApp(views, options) {
|
|
|
113
124
|
controllerIdentifiers.set(controllerRef, { identifier, importPath });
|
|
114
125
|
importLines.push(`import ${identifier} from '${importPath}';`);
|
|
115
126
|
};
|
|
127
|
+
const ensureLoaderImported = (viewPath) => {
|
|
128
|
+
if (loaderIdentifiers.has(viewPath)) return;
|
|
129
|
+
const loaderFile = resolveLoaderFile(viewPath, options.componentDir);
|
|
130
|
+
if (!loaderFile) return;
|
|
131
|
+
const identifier = allocateName("Loader", viewPath);
|
|
132
|
+
loaderIdentifiers.set(viewPath, identifier);
|
|
133
|
+
importLines.push(`import ${identifier} from '${computeImportPath(options.outDir, loaderFile)}';`);
|
|
134
|
+
};
|
|
116
135
|
for (const view of sortedViews) {
|
|
117
136
|
if (!viewIdentifiers.has(view.viewPath)) {
|
|
118
137
|
const identifier = allocateName("View", view.viewPath);
|
|
@@ -121,7 +140,9 @@ function generateRendererApp(views, options) {
|
|
|
121
140
|
const importPath = computeImportPath(options.outDir, abs);
|
|
122
141
|
importLines.push(`import ${identifier} from '${importPath}';`);
|
|
123
142
|
}
|
|
143
|
+
ensureLoaderImported(view.viewPath);
|
|
124
144
|
for (const layoutPath of view.layouts) {
|
|
145
|
+
ensureLoaderImported(layoutPath);
|
|
125
146
|
if (layoutIdentifiers.has(layoutPath)) continue;
|
|
126
147
|
const identifier = allocateName("Layout", layoutPath);
|
|
127
148
|
layoutIdentifiers.set(layoutPath, identifier);
|
|
@@ -141,6 +162,8 @@ function generateRendererApp(views, options) {
|
|
|
141
162
|
const key = viewKey(view);
|
|
142
163
|
const viewIdentifier = viewIdentifiers.get(view.viewPath);
|
|
143
164
|
const layoutList = view.layouts.map((layoutPath) => layoutIdentifiers.get(layoutPath));
|
|
165
|
+
const layoutLoaders = view.layouts.map((layoutPath) => loaderIdentifiers.get(layoutPath) ?? "null");
|
|
166
|
+
const viewLoader = loaderIdentifiers.get(view.viewPath) ?? "null";
|
|
144
167
|
const controllerPath = view.controller ? `'${escapeSingleQuoted(view.controller)}'` : "undefined";
|
|
145
168
|
const controllerClass = view.controller ? controllerIdentifiers.get(view.controller).identifier : "undefined";
|
|
146
169
|
const layoutControllerRefs = view.layoutControllers;
|
|
@@ -177,12 +200,15 @@ function generateRendererApp(views, options) {
|
|
|
177
200
|
controllerClass: ${controllerClass},
|
|
178
201
|
view: ${viewIdentifier},
|
|
179
202
|
layouts: [${layoutList.join(", ")}],
|
|
203
|
+
viewLoader: ${viewLoader},
|
|
204
|
+
layoutLoaders: [${layoutLoaders.join(", ")}],
|
|
180
205
|
},`;
|
|
181
206
|
});
|
|
182
207
|
return `${generatedFileNotice(RENDERER_APP_SOURCE_INSTRUCTIONS)}
|
|
183
208
|
import { mount } from 'svelte';
|
|
184
209
|
import { App, getApp, getContainer } from '@noego/wood/client';
|
|
185
210
|
import { Navigation, getNavigation, installAutomationIntrospection } from '@noego/wood/navigation';
|
|
211
|
+
import { createLoaderRequest, initializeArgument, initializeController, runViewLoaders } from '@noego/wood/navigation';
|
|
186
212
|
import { Ipc, getIpc } from '@noego/wood/ipc';
|
|
187
213
|
import NavigationShell from '@noego/wood/navigation-shell';
|
|
188
214
|
${importLines.join("\n")}
|
|
@@ -200,8 +226,14 @@ interface GeneratedAppViewEntry {
|
|
|
200
226
|
controllerClass?: any;
|
|
201
227
|
view: any;
|
|
202
228
|
layouts: any[];
|
|
229
|
+
/** Companion data loader of the view (\`pages/x.svelte\` \u2192 \`pages/x.load.ts\`), discovered at build. */
|
|
230
|
+
viewLoader: ((request: any) => unknown) | null;
|
|
231
|
+
/** Index-aligned companion loaders of the layout stack. */
|
|
232
|
+
layoutLoaders: Array<((request: any) => unknown) | null>;
|
|
203
233
|
}
|
|
204
234
|
|
|
235
|
+
type RouteInput = { params: Record<string, string>; query: Record<string, string> };
|
|
236
|
+
|
|
205
237
|
const VIEW_MANIFEST: Record<string, GeneratedAppViewEntry> = {
|
|
206
238
|
${manifestEntries.join("\n")}
|
|
207
239
|
};
|
|
@@ -261,13 +293,25 @@ async function resolveControllerInScope(
|
|
|
261
293
|
return await scope.get(ControllerClass);
|
|
262
294
|
}
|
|
263
295
|
|
|
264
|
-
|
|
296
|
+
type RollbackStep = () => unknown | Promise<unknown>;
|
|
297
|
+
|
|
298
|
+
async function disposeScope(scope: { dispose?: () => Promise<void> } | null): Promise<void> {
|
|
265
299
|
if (!scope || typeof scope.dispose !== 'function') return;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
300
|
+
await scope.dispose();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Roll back after \`primary\` failed: every step is attempted exactly once and
|
|
305
|
+
* every cleanup failure is retained as its original error object next to the
|
|
306
|
+
* primary failure (\`errors\`, with \`cause\` = primary). Never swallows.
|
|
307
|
+
*/
|
|
308
|
+
async function failWithRollback(primary: unknown, steps: RollbackStep[], message: string): Promise<never> {
|
|
309
|
+
const errors: unknown[] = [primary];
|
|
310
|
+
for (const step of steps) {
|
|
311
|
+
try { await step(); } catch (error) { errors.push(error); }
|
|
270
312
|
}
|
|
313
|
+
if (errors.length === 1) throw primary;
|
|
314
|
+
throw Object.assign(new Error(message), { errors, cause: primary });
|
|
271
315
|
}
|
|
272
316
|
|
|
273
317
|
/**
|
|
@@ -276,10 +320,16 @@ async function disposeScopeQuietly(scope: { dispose?: () => Promise<void> } | nu
|
|
|
276
320
|
* services/state are shared within the page (the NoEgo renderer model).
|
|
277
321
|
* Layouts marked preservable in the manifest own dedicated scopes that
|
|
278
322
|
* survive page transitions and dispose when the layout finally leaves.
|
|
323
|
+
*
|
|
324
|
+
* Data loaders (shared orchestration) run first for this navigation \u2014
|
|
325
|
+
* non-preserved layouts in stack order, then the view \u2014 and each
|
|
326
|
+
* controller's optional \`initialize({ params, query[, data] })\` receives
|
|
327
|
+
* its loader's data. A loader failure disposes the scopes created here.
|
|
279
328
|
*/
|
|
280
329
|
async function createControllerSet(
|
|
281
330
|
entry: GeneratedAppViewEntry,
|
|
282
331
|
preservedDepth = 0,
|
|
332
|
+
route: RouteInput = { params: {}, query: {} },
|
|
283
333
|
): Promise<{
|
|
284
334
|
controller: unknown;
|
|
285
335
|
layoutControllers: Array<unknown>;
|
|
@@ -292,6 +342,12 @@ async function createControllerSet(
|
|
|
292
342
|
const createdLayoutScopes: WoodScopedContainer[] = [];
|
|
293
343
|
|
|
294
344
|
try {
|
|
345
|
+
const loaded = await runViewLoaders(
|
|
346
|
+
{ layouts: entry.layoutLoaders, view: entry.viewLoader },
|
|
347
|
+
createLoaderRequest(entry.key, route),
|
|
348
|
+
preservedDepth,
|
|
349
|
+
);
|
|
350
|
+
|
|
295
351
|
for (let i = 0; i < preservedDepth; i += 1) {
|
|
296
352
|
layoutControllers.push(undefined);
|
|
297
353
|
layoutScopes.push(null);
|
|
@@ -316,6 +372,7 @@ async function createControllerSet(
|
|
|
316
372
|
LayoutController,
|
|
317
373
|
\`layout[\${i}]\`,
|
|
318
374
|
);
|
|
375
|
+
await initializeController(controller, initializeArgument(route, loaded.layouts[i] ?? null));
|
|
319
376
|
layoutControllers.push(controller);
|
|
320
377
|
layoutScopes.push(preservable ? scope : null);
|
|
321
378
|
}
|
|
@@ -323,14 +380,19 @@ async function createControllerSet(
|
|
|
323
380
|
const controller = entry.controllerClass
|
|
324
381
|
? await resolveControllerInScope(pageScope, entry, entry.controllerClass, 'page')
|
|
325
382
|
: undefined;
|
|
383
|
+
if (controller !== undefined) {
|
|
384
|
+
await initializeController(controller, initializeArgument(route, loaded.view));
|
|
385
|
+
}
|
|
326
386
|
|
|
327
387
|
return { controller, layoutControllers, pageScope, layoutScopes };
|
|
328
388
|
} catch (error) {
|
|
329
|
-
// Failed transitions must not orphan scopes:
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
389
|
+
// Failed transitions must not orphan scopes: every scope this set created
|
|
390
|
+
// is disposed once, and every disposal failure is retained with the
|
|
391
|
+
// original initialization failure instead of being swallowed.
|
|
392
|
+
return failWithRollback(error, [
|
|
393
|
+
...createdLayoutScopes.map((scope) => () => disposeScope(scope as any)),
|
|
394
|
+
() => disposeScope(pageScope as any),
|
|
395
|
+
], 'Wood controller-set rollback failed');
|
|
334
396
|
}
|
|
335
397
|
}
|
|
336
398
|
|
|
@@ -397,28 +459,39 @@ function isAutomationBuild(): boolean {
|
|
|
397
459
|
}
|
|
398
460
|
}
|
|
399
461
|
|
|
400
|
-
export
|
|
462
|
+
export const supportsInitialRoute = true;
|
|
463
|
+
export async function app(target: Element, route: RouteInput = { params: {}, query: {} }) {
|
|
401
464
|
const entry = await resolveInitialEntry();
|
|
402
465
|
|
|
403
|
-
getNavigation().replace(entry.key,
|
|
466
|
+
getNavigation().replace(entry.key, route.params, route.query);
|
|
404
467
|
|
|
405
468
|
// Dev/test-only Axe introspection surface; never present in production builds.
|
|
406
469
|
if (isAutomationBuild()) {
|
|
407
470
|
const bootstrapWindow = await getWoodBridge()?.__window?.current?.();
|
|
408
471
|
installAutomationIntrospection({ windowId: bootstrapWindow?.windowId ?? null });
|
|
409
472
|
}
|
|
410
|
-
const initialSet = await createControllerSet(entry);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
473
|
+
const initialSet = await createControllerSet(entry, 0, route);
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
return mount(NavigationShell, {
|
|
477
|
+
target,
|
|
478
|
+
props: {
|
|
479
|
+
manifest: VIEW_MANIFEST,
|
|
480
|
+
initialEntry: entry,
|
|
481
|
+
initialRoute: route,
|
|
482
|
+
initialControllerSet: initialSet,
|
|
483
|
+
createControllerSetFn: createControllerSet,
|
|
484
|
+
getViewManifestEntryFn: getViewManifestEntry,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
} catch (error) {
|
|
488
|
+
// A failed initial mount must not orphan the initial controller set: the
|
|
489
|
+
// shell never took ownership, so its scopes are disposed here once.
|
|
490
|
+
return failWithRollback(error, [
|
|
491
|
+
...initialSet.layoutScopes.map((scope) => () => disposeScope(scope as any)),
|
|
492
|
+
() => disposeScope(initialSet.pageScope as any),
|
|
493
|
+
], 'Wood initial mount rollback failed');
|
|
494
|
+
}
|
|
422
495
|
}
|
|
423
496
|
`;
|
|
424
497
|
}
|