juxscript 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/machinery/build3.js +7 -91
  2. package/machinery/compiler3.js +3 -209
  3. package/machinery/config.js +93 -6
  4. package/machinery/serve.js +255 -0
  5. package/machinery/watcher.js +49 -161
  6. package/package.json +19 -5
  7. package/lib/components/alert.ts +0 -200
  8. package/lib/components/app.ts +0 -247
  9. package/lib/components/badge.ts +0 -101
  10. package/lib/components/base/BaseComponent.ts +0 -421
  11. package/lib/components/base/FormInput.ts +0 -227
  12. package/lib/components/button.ts +0 -178
  13. package/lib/components/card.ts +0 -173
  14. package/lib/components/chart.ts +0 -231
  15. package/lib/components/checkbox.ts +0 -242
  16. package/lib/components/code.ts +0 -123
  17. package/lib/components/container.ts +0 -140
  18. package/lib/components/data.ts +0 -135
  19. package/lib/components/datepicker.ts +0 -234
  20. package/lib/components/dialog.ts +0 -172
  21. package/lib/components/divider.ts +0 -100
  22. package/lib/components/dropdown.ts +0 -186
  23. package/lib/components/element.ts +0 -267
  24. package/lib/components/fileupload.ts +0 -309
  25. package/lib/components/grid.ts +0 -291
  26. package/lib/components/guard.ts +0 -92
  27. package/lib/components/heading.ts +0 -96
  28. package/lib/components/helpers.ts +0 -41
  29. package/lib/components/hero.ts +0 -224
  30. package/lib/components/icon.ts +0 -178
  31. package/lib/components/icons.ts +0 -464
  32. package/lib/components/include.ts +0 -410
  33. package/lib/components/input.ts +0 -457
  34. package/lib/components/list.ts +0 -419
  35. package/lib/components/loading.ts +0 -100
  36. package/lib/components/menu.ts +0 -275
  37. package/lib/components/modal.ts +0 -284
  38. package/lib/components/nav.ts +0 -257
  39. package/lib/components/paragraph.ts +0 -97
  40. package/lib/components/progress.ts +0 -159
  41. package/lib/components/radio.ts +0 -278
  42. package/lib/components/req.ts +0 -303
  43. package/lib/components/script.ts +0 -41
  44. package/lib/components/select.ts +0 -252
  45. package/lib/components/sidebar.ts +0 -275
  46. package/lib/components/style.ts +0 -41
  47. package/lib/components/switch.ts +0 -246
  48. package/lib/components/table.ts +0 -1249
  49. package/lib/components/tabs.ts +0 -250
  50. package/lib/components/theme-toggle.ts +0 -293
  51. package/lib/components/tooltip.ts +0 -144
  52. package/lib/components/view.ts +0 -190
  53. package/lib/components/write.ts +0 -272
  54. package/lib/layouts/default.css +0 -260
  55. package/lib/layouts/figma.css +0 -334
  56. package/lib/reactivity/state.ts +0 -78
  57. package/lib/utils/fetch.ts +0 -553
  58. package/machinery/ast.js +0 -347
  59. package/machinery/build.js +0 -466
  60. package/machinery/bundleAssets.js +0 -0
  61. package/machinery/bundleJux.js +0 -0
  62. package/machinery/bundleVendors.js +0 -0
  63. package/machinery/doc-generator.js +0 -136
  64. package/machinery/imports.js +0 -155
  65. package/machinery/server.js +0 -166
  66. package/machinery/ts-shim.js +0 -46
  67. package/machinery/validators/file-validator.js +0 -123
@@ -18,31 +18,23 @@ try {
18
18
  }
19
19
 
20
20
  // ═══════════════════════════════════════════════════════════════
21
- // EXPLODE CONFIG INTO NAMED OBJECTS
21
+ // EXPLODE CONFIG
22
22
  // ═══════════════════════════════════════════════════════════════
23
23
  const directories = {
24
24
  source: rawConfig.directories?.source || './jux',
25
- distribution: rawConfig.directories?.distribution || './.jux-dist',
26
- themes: rawConfig.directories?.themes || './themes',
27
- layouts: rawConfig.directories?.layouts || './themes/layouts',
28
- assets: rawConfig.directories?.assets || './themes/assets'
25
+ distribution: rawConfig.directories?.distribution || './.jux-dist'
29
26
  };
30
27
 
31
28
  const defaults = {
32
29
  httpPort: rawConfig.defaults?.httpPort || 3000,
33
30
  wsPort: rawConfig.defaults?.wsPort || 3001,
34
- autoRoute: rawConfig.defaults?.autoRoute ?? true,
35
- layout: rawConfig.defaults?.layout || null,
36
- theme: rawConfig.defaults?.theme || null
31
+ autoRoute: rawConfig.defaults?.autoRoute ?? true
37
32
  };
38
33
 
39
34
  // Resolve absolute paths
40
35
  const paths = {
41
36
  source: path.resolve(PROJECT_ROOT, directories.source),
42
- distribution: path.resolve(PROJECT_ROOT, directories.distribution),
43
- themes: path.resolve(PROJECT_ROOT, directories.source, directories.themes),
44
- layouts: path.resolve(PROJECT_ROOT, directories.source, directories.layouts),
45
- assets: path.resolve(PROJECT_ROOT, directories.source, directories.assets)
37
+ distribution: path.resolve(PROJECT_ROOT, directories.distribution)
46
38
  };
47
39
 
48
40
  // ═══════════════════════════════════════════════════════════════
@@ -50,84 +42,10 @@ const paths = {
50
42
  // ═══════════════════════════════════════════════════════════════
51
43
  console.log(`\n📁 Directory Check:`);
52
44
 
53
- const dirStatus = {};
54
- for (const [name, dirPath] of Object.entries(paths)) {
55
- if (name === 'distribution') continue;
56
-
57
- const exists = fs.existsSync(dirPath);
58
- dirStatus[name] = exists;
59
-
60
- const icon = exists ? '✓' : '✗';
61
- const suffix = exists ? '' : ' (will be skipped)';
62
- console.log(` ${icon} ${name}: ${dirPath}${suffix}`);
63
- }
64
-
65
- // ═══════════════════════════════════════════════════════════════
66
- // RESOLVE DEFAULT LAYOUT & THEME
67
- // ═══════════════════════════════════════════════════════════════
68
- function resolveFile(filename, ...searchPaths) {
69
- if (!filename) return null;
70
-
71
- // First try the filename as-is
72
- if (fs.existsSync(filename)) {
73
- return path.resolve(filename);
74
- }
75
-
76
- // Try each search path
77
- for (const searchPath of searchPaths) {
78
- if (!searchPath || !fs.existsSync(searchPath)) continue;
79
-
80
- const fullPath = path.resolve(searchPath, filename);
81
- if (fs.existsSync(fullPath)) {
82
- return fullPath;
83
- }
84
- }
85
-
86
- return null;
87
- }
88
-
89
- console.log(`\n🎨 Defaults Resolution:`);
90
-
91
- // Resolve layout
92
- const layoutPath = defaults.layout ? resolveFile(
93
- defaults.layout,
94
- paths.layouts,
95
- paths.source,
96
- PROJECT_ROOT
97
- ) : null;
98
-
99
- if (defaults.layout) {
100
- if (layoutPath) {
101
- console.log(` ✓ layout: ${layoutPath}`);
102
- } else {
103
- console.log(` ✗ layout: "${defaults.layout}" not found, will be skipped`);
104
- }
45
+ if (fs.existsSync(paths.source)) {
46
+ console.log(` ✓ source: ${paths.source}`);
105
47
  } else {
106
- console.log(` - layout: not configured`);
107
- }
108
-
109
- // Resolve theme
110
- const themePath = defaults.theme ? resolveFile(
111
- defaults.theme,
112
- paths.themes,
113
- paths.source,
114
- PROJECT_ROOT
115
- ) : null;
116
-
117
- if (defaults.theme) {
118
- if (themePath) {
119
- console.log(` ✓ theme: ${themePath}`);
120
- } else {
121
- console.log(` ✗ theme: "${defaults.theme}" not found, will be skipped`);
122
- }
123
- } else {
124
- console.log(` - theme: not configured`);
125
- }
126
-
127
- // ═══════════════════════════════════════════════════════════════
128
- // VALIDATE SOURCE DIRECTORY EXISTS
129
- // ═══════════════════════════════════════════════════════════════
130
- if (!dirStatus.source) {
48
+ console.log(` source: ${paths.source} (not found)`);
131
49
  console.error(`\n❌ Source directory not found: ${paths.source}`);
132
50
  console.error(` Create the directory or update juxconfig.js`);
133
51
  process.exit(1);
@@ -141,8 +59,6 @@ console.log(`\n`);
141
59
  const compiler = new JuxCompiler({
142
60
  srcDir: paths.source,
143
61
  distDir: paths.distribution,
144
- layoutPath,
145
- themePath,
146
62
  defaults,
147
63
  paths
148
64
  });
@@ -13,8 +13,6 @@ export class JuxCompiler {
13
13
  this.config = config;
14
14
  this.srcDir = config.srcDir || './jux';
15
15
  this.distDir = config.distDir || './.jux-dist';
16
- this.layoutPath = config.layoutPath || null;
17
- this.themePath = config.themePath || null;
18
16
  this.defaults = config.defaults || {};
19
17
  this.paths = config.paths || {};
20
18
  this._juxscriptExports = null;
@@ -94,163 +92,8 @@ export class JuxCompiler {
94
92
  }
95
93
 
96
94
  /**
97
- * Copy all source assets (themes, layouts, assets folders) to dist
95
+ * Generate entry point without layout/theme logic
98
96
  */
99
- copySourceAssets() {
100
- const copied = { themes: 0, layouts: 0, assets: 0, other: 0 };
101
-
102
- const copyDir = (srcDir, destDir, category) => {
103
- if (!fs.existsSync(srcDir)) return 0;
104
-
105
- let count = 0;
106
- fs.mkdirSync(destDir, { recursive: true });
107
-
108
- const entries = fs.readdirSync(srcDir, { withFileTypes: true });
109
- for (const entry of entries) {
110
- const srcPath = path.join(srcDir, entry.name);
111
- const destPath = path.join(destDir, entry.name);
112
-
113
- if (entry.isDirectory()) {
114
- count += copyDir(srcPath, destPath, category);
115
- } else {
116
- fs.copyFileSync(srcPath, destPath);
117
- count++;
118
- }
119
- }
120
- return count;
121
- };
122
-
123
- // Copy themes folder
124
- if (this.paths.themes && fs.existsSync(this.paths.themes)) {
125
- copied.themes = copyDir(this.paths.themes, path.join(this.distDir, 'themes'), 'themes');
126
- }
127
-
128
- // Copy layouts folder (if separate from themes)
129
- if (this.paths.layouts && fs.existsSync(this.paths.layouts) && this.paths.layouts !== this.paths.themes) {
130
- copied.layouts = copyDir(this.paths.layouts, path.join(this.distDir, 'layouts'), 'layouts');
131
- }
132
-
133
- // Copy assets folder
134
- if (this.paths.assets && fs.existsSync(this.paths.assets)) {
135
- copied.assets = copyDir(this.paths.assets, path.join(this.distDir, 'assets'), 'assets');
136
- }
137
-
138
- const total = copied.themes + copied.layouts + copied.assets;
139
- if (total > 0) {
140
- console.log(`📂 Copied source assets: ${copied.themes} themes, ${copied.layouts} layouts, ${copied.assets} assets`);
141
- }
142
-
143
- return copied;
144
- }
145
-
146
- /**
147
- * Generate CSS links including theme if configured
148
- */
149
- generateCssLinks() {
150
- const links = [];
151
-
152
- // Note: Component CSS is no longer bundled (handled by Styler)
153
-
154
- // User theme CSS
155
- if (this.themePath) {
156
- const themeName = path.basename(this.themePath);
157
- links.push(` <link rel="stylesheet" href="./themes/${themeName}" id="jux-theme">`);
158
- console.log(`🎨 🎨 🎨 🎨 Included theme: ${themeName} 🎨 🎨 🎨 🎨 `);
159
- }
160
-
161
- return links.join('\n');
162
- }
163
-
164
- async loadJuxscriptExports() {
165
- if (this._juxscriptExports) return this._juxscriptExports;
166
-
167
- try {
168
- const juxscriptPath = this.findJuxscriptPath();
169
- if (juxscriptPath) {
170
- const indexContent = fs.readFileSync(juxscriptPath, 'utf8');
171
- const exports = new Set();
172
-
173
- for (const match of indexContent.matchAll(/export\s*\{\s*([^}]+)\s*\}/g)) {
174
- match[1].split(',').forEach(exp => {
175
- const name = exp.trim().split(/\s+as\s+/)[0].trim();
176
- if (name) exports.add(name);
177
- });
178
- }
179
-
180
- this._juxscriptExports = [...exports];
181
- if (this._juxscriptExports.length > 0) {
182
- console.log(`📦 juxscript exports: ${this._juxscriptExports.join(', ')}`);
183
- }
184
- }
185
- } catch (err) {
186
- this._juxscriptExports = [];
187
- }
188
-
189
- return this._juxscriptExports;
190
- }
191
-
192
- validateViewCode(viewName, code) {
193
- const issues = [];
194
-
195
- let ast;
196
- try {
197
- ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
198
- } catch (parseError) {
199
- issues.push({
200
- type: 'error',
201
- view: viewName,
202
- line: parseError.loc?.line || 0,
203
- message: `Syntax error: ${parseError.message}`,
204
- code: ''
205
- });
206
- return issues;
207
- }
208
-
209
- const allImports = new Set();
210
-
211
- walk(ast, {
212
- ImportDeclaration(node) {
213
- node.specifiers.forEach(spec => {
214
- allImports.add(spec.local.name);
215
- });
216
- }
217
- });
218
-
219
- // Default known facades/components if load fails
220
- const knownComponents = this._juxscriptExports || ['element', 'input', 'buttonGroup'];
221
-
222
- walk(ast, {
223
- Identifier(node, parent) {
224
- if (parent?.type === 'CallExpression' && parent.callee === node) {
225
- const name = node.name;
226
- // Updated: Removed strict Capitalization check to allow facades (lowercase)
227
- if (!allImports.has(name) && knownComponents.includes(name)) {
228
- issues.push({
229
- type: 'warning',
230
- view: viewName,
231
- line: node.loc?.start?.line || 0,
232
- message: `"${name}" is used but not imported from juxscript`,
233
- code: ''
234
- });
235
- }
236
- }
237
- }
238
- });
239
-
240
- return issues;
241
- }
242
-
243
- /**
244
- * Extract layout function name from layout file
245
- */
246
- getLayoutFunctionName() {
247
- if (!this.layoutPath || !fs.existsSync(this.layoutPath)) return null;
248
-
249
- const content = fs.readFileSync(this.layoutPath, 'utf8');
250
- const match = content.match(/export\s+function\s+(\w+)/);
251
- return match ? match[1] : null;
252
- }
253
-
254
97
  generateEntryPoint(views, dataModules, sharedModules) {
255
98
  let entry = `// Auto-generated JUX entry point\n\n`;
256
99
  const allIssues = [];
@@ -265,16 +108,6 @@ export class JuxCompiler {
265
108
  }
266
109
  });
267
110
 
268
- // Check layout file for juxscript imports too
269
- if (this.layoutPath && fs.existsSync(this.layoutPath)) {
270
- const layoutContent = fs.readFileSync(this.layoutPath, 'utf8');
271
- for (const match of layoutContent.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]juxscript['"]/g)) {
272
- match[1].split(',').map(s => s.trim()).forEach(imp => {
273
- if (imp) juxImports.add(imp);
274
- });
275
- }
276
- }
277
-
278
111
  if (juxImports.size > 0) {
279
112
  entry += `import { ${[...juxImports].sort().join(', ')} } from 'juxscript';\n\n`;
280
113
  }
@@ -294,25 +127,6 @@ export class JuxCompiler {
294
127
  entry += `\nObject.assign(window, { ${[...juxImports].join(', ')} });\n`;
295
128
  }
296
129
 
297
- // Add layout function if configured
298
- const layoutFnName = this.getLayoutFunctionName();
299
- if (layoutFnName && this.layoutPath) {
300
- const layoutContent = fs.readFileSync(this.layoutPath, 'utf8');
301
- const layoutCode = this.removeImports(layoutContent);
302
- entry += `\n// --- DEFAULT LAYOUT ---\n`;
303
- entry += layoutCode;
304
- entry += `\n`;
305
-
306
- // Store in source snapshot
307
- const layoutFile = path.basename(this.layoutPath);
308
- sourceSnapshot[layoutFile] = {
309
- name: 'layout',
310
- file: layoutFile,
311
- content: layoutContent,
312
- lines: layoutContent.split('\n')
313
- };
314
- }
315
-
316
130
  entry += `\n// --- VIEW FUNCTIONS ---\n`;
317
131
 
318
132
  views.forEach(v => {
@@ -341,7 +155,6 @@ export class JuxCompiler {
341
155
 
342
156
  this._sourceSnapshot = sourceSnapshot;
343
157
  this._validationIssues = allIssues;
344
- this._layoutFnName = layoutFnName;
345
158
  entry += this._generateRouter(views);
346
159
  return entry;
347
160
  }
@@ -371,11 +184,6 @@ export class JuxCompiler {
371
184
  routeMap += ` '/${v.name.toLowerCase()}': render${cap},\n`;
372
185
  });
373
186
 
374
- // Initialize layout call if configured
375
- const layoutInit = this._layoutFnName
376
- ? `\n// Initialize default layout\nif (typeof ${this._layoutFnName} === 'function') {\n ${this._layoutFnName}();\n}\n`
377
- : '';
378
-
379
187
  return `
380
188
  // --- JUX SOURCE LOADER ---
381
189
  var __juxSources = null;
@@ -400,14 +208,6 @@ function __juxFindSource(stack) {
400
208
  }
401
209
  }
402
210
  }
403
- // Also check for layout
404
- if (stack.indexOf('Layout') > -1) {
405
- for (var file in __juxSources || {}) {
406
- if (file.indexOf('layout') > -1) {
407
- return { file: file, source: __juxSources[file] };
408
- }
409
- }
410
- }
411
211
  return null;
412
212
  }
413
213
 
@@ -508,7 +308,7 @@ window.addEventListener('unhandledrejection', function(e) { __juxErrorOverlay.sh
508
308
 
509
309
  // --- JUX ROUTER ---
510
310
  const routes = {\n${routeMap}};
511
- ${layoutInit}
311
+
512
312
  async function navigate(path) {
513
313
  const view = routes[path];
514
314
  if (!view) {
@@ -555,9 +355,6 @@ navigate(location.pathname);
555
355
  const { views, dataModules, sharedModules } = this.scanFiles();
556
356
  console.log(`📁 Found ${views.length} views, ${sharedModules.length} shared, ${dataModules.length} data`);
557
357
 
558
- // Copy source assets (themes, layouts, assets)
559
- this.copySourceAssets();
560
-
561
358
  // Copy data/shared modules to dist
562
359
  const juxDistDir = path.join(this.distDir, 'jux');
563
360
  fs.mkdirSync(juxDistDir, { recursive: true });
@@ -602,7 +399,6 @@ navigate(location.pathname);
602
399
  return { success: false, errors: [{ message: err.message }], warnings: [] };
603
400
  }
604
401
 
605
- // Generate HTML with layout container sibling to app
606
402
  const html = `<!DOCTYPE html>
607
403
  <html lang="en">
608
404
  <head>
@@ -612,9 +408,7 @@ navigate(location.pathname);
612
408
  <script type="module" src="./bundle.js"></script>
613
409
  </head>
614
410
  <body>
615
- <div id="jux-layout">
616
- <div id="app"></div>
617
- </div>
411
+ <div id="app"></div>
618
412
  </body>
619
413
  </html>`;
620
414
  fs.writeFileSync(path.join(this.distDir, 'index.html'), html);
@@ -23,12 +23,12 @@ export const defaultConfig = {
23
23
  // Load user configuration
24
24
  export async function loadConfig(projectRoot) {
25
25
  const configPath = path.join(projectRoot, 'juxconfig.js');
26
-
26
+
27
27
  if (fs.existsSync(configPath)) {
28
28
  try {
29
29
  const configModule = await import(`file://${configPath}`);
30
30
  const userConfig = configModule.default || configModule;
31
-
31
+
32
32
  return {
33
33
  ...defaultConfig,
34
34
  ...userConfig,
@@ -41,7 +41,7 @@ export async function loadConfig(projectRoot) {
41
41
  return defaultConfig;
42
42
  }
43
43
  }
44
-
44
+
45
45
  console.log('ℹ️ No juxconfig.js found, using defaults');
46
46
  return defaultConfig;
47
47
  }
@@ -51,9 +51,9 @@ export async function runBootstrap(bootstrapFunctions = []) {
51
51
  if (!Array.isArray(bootstrapFunctions) || bootstrapFunctions.length === 0) {
52
52
  return;
53
53
  }
54
-
54
+
55
55
  console.log('🚀 Running bootstrap functions...\n');
56
-
56
+
57
57
  for (const fn of bootstrapFunctions) {
58
58
  if (typeof fn === 'function') {
59
59
  try {
@@ -63,6 +63,93 @@ export async function runBootstrap(bootstrapFunctions = []) {
63
63
  }
64
64
  }
65
65
  }
66
-
66
+
67
67
  console.log('✅ Bootstrap complete\n');
68
+ }
69
+
70
+ /**
71
+ * Type helper for JUX Configuration (Identity function for Autocomplete)
72
+ * Used in juxconfig.js to provide Intellisense.
73
+ * @param {Object} config
74
+ * @returns {Object}
75
+ */
76
+ export function defineConfig(config) {
77
+ return config;
78
+ }
79
+
80
+ /**
81
+ * Normalizes user configuration into the strict format used by the compiler/server.
82
+ * Handles "DX Sugar" like:
83
+ * - Inferring file extensions (.jux, .css)
84
+ * - Mapping 'dist' -> 'distribution'
85
+ * - Resolving shortcuts (e.g. layouts)
86
+ * - Defaulting ports
87
+ */
88
+ export function resolveConfig(userConfig, projectRoot = process.cwd()) {
89
+ const root = userConfig.root || projectRoot;
90
+
91
+ // 1. Directories (Map 'dist' alias to 'distribution')
92
+ const dirs = userConfig.directories || {};
93
+ const directories = {
94
+ source: dirs.source || 'jux',
95
+ distribution: dirs.dist || dirs.distribution || '.jux-dist',
96
+ themes: dirs.themes || 'themes',
97
+ layouts: dirs.layouts || 'themes/layouts',
98
+ assets: dirs.assets || 'themes/assets'
99
+ };
100
+
101
+ // 2. Defaults (Infer extensions)
102
+ const defs = userConfig.defaults || {};
103
+ const httpPort = defs.port || defs.httpPort || 3000;
104
+
105
+ const defaults = {
106
+ httpPort,
107
+ wsPort: defs.wsPort || (httpPort + 1),
108
+ autoRoute: defs.autoRoute !== false, // default true
109
+ layout: ensureExt(defs.layout, '.jux') || 'base.jux',
110
+ theme: ensureExt(defs.theme, '.css') || 'base.css'
111
+ };
112
+
113
+ // 3. Pages Normalization (Recursively fix route paths)
114
+ const pages = normalizePages(userConfig.pages || {});
115
+
116
+ // 4. Return Normalized Config
117
+ return {
118
+ root,
119
+ directories,
120
+ defaults,
121
+ pages,
122
+ hooks: userConfig.hooks || {}
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Helper: Appends extension if missing
128
+ */
129
+ function ensureExt(file, ext) {
130
+ if (!file) return file;
131
+ return file.endsWith(ext) ? file : `${file}${ext}`;
132
+ }
133
+
134
+ /**
135
+ * Helper: Recursively normalizes page routes
136
+ */
137
+ function normalizePages(pages) {
138
+ const normalized = {};
139
+ for (const [key, value] of Object.entries(pages)) {
140
+ if (typeof value === 'string') {
141
+ // Direct route: '/' -> 'experiments/index'
142
+ // Becomes: '/' -> 'experiments/index.jux'
143
+ normalized[key] = ensureExt(value, '.jux');
144
+ } else if (typeof value === 'object') {
145
+ // Route Group
146
+ normalized[key] = {
147
+ ...value,
148
+ layout: value.layout ? ensureExt(value.layout, '.jux') : undefined,
149
+ theme: value.theme ? ensureExt(value.theme, '.css') : undefined,
150
+ routes: normalizePages(value.routes || {})
151
+ };
152
+ }
153
+ }
154
+ return normalized;
68
155
  }