@noego/app 0.0.3 → 0.0.5

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/src/config.js CHANGED
@@ -1,584 +1,104 @@
1
- import fs from 'node:fs/promises';
2
1
  import path from 'node:path';
3
-
4
- import deepmerge from 'deepmerge';
5
- import YAML from 'yaml';
6
-
7
- const CONFIG_FILENAMES = [
8
- // Primary App config filenames
9
- 'noego.yaml',
10
- 'noego.yml',
11
- 'noego.config.yaml',
12
- 'noego.config.yml',
13
- 'noego.config.json',
14
- // Common App config filenames
15
- 'app.yaml',
16
- 'app.yml',
17
- 'app.config.yaml',
18
- 'app.config.yml',
19
- 'app.config.json',
20
- // Back-compat with legacy Hammer config filenames
21
- 'hammer.yaml',
22
- 'hammer.yml',
23
- 'hammer.config.yaml',
24
- 'hammer.config.yml',
25
- 'hammer.config.json'
26
- ];
27
- const OVERRIDE_FILE_KEY = '__appFromFile';
28
-
29
- const DEFAULTS = {
30
- mode: 'production',
31
- outDir: 'dist',
32
- assets: ['ui/resources/**', 'ui/styles/**', 'database/**'],
33
- server: {
34
- entry: 'index.ts',
35
- rootDir: '.',
36
- controllers: 'server/controller',
37
- middleware: 'middleware',
38
- openapi: 'server/stitch.yaml',
39
- sqlGlobs: []
40
- },
41
- ui: {
42
- page: 'ui/index.html',
43
- rootDir: null,
44
- openapi: 'ui/stitch.yaml',
45
- assets: [],
46
- clientExclude: ['server/**', 'middleware/**'],
47
- options: 'ui/options.ts'
48
- },
49
- vite: {
50
- client: {
51
- configFile: null,
52
- override: null
53
- },
54
- ssr: {
55
- configFile: null,
56
- override: null
57
- }
58
- },
59
- dev: {
60
- watch: false,
61
- watchPaths: [],
62
- splitServe: false,
63
- frontendCmd: 'vite'
64
- }
65
- };
2
+ import { findConfigFile, loadConfig } from './runtime/config-loader.js';
3
+ import { resolvePath } from './build/helpers.js';
66
4
 
67
5
  export async function loadBuildConfig(cliOptions = {}, { cwd = process.cwd() } = {}) {
68
6
  const rootDir = resolvePath(cliOptions.root ?? '.', cwd);
69
7
  const configFilePath = await findConfigFile(rootDir, cliOptions.configFile);
70
8
 
71
- const fileRaw =
72
- configFilePath != null ? await loadConfigFile(configFilePath) : {};
73
- const fileNormalized =
74
- configFilePath != null
75
- ? normalizeExternalConfig(fileRaw, { baseDir: path.dirname(configFilePath) })
76
- : {};
77
-
78
- const cliNormalized = normalizeCliOptions(cliOptions);
79
-
80
- const mergedRaw = deepmerge.all(
81
- [DEFAULTS, fileNormalized, cliNormalized],
82
- {
83
- arrayMerge: (_target, source) => Array.isArray(source) ? source : []
84
- }
85
- );
86
-
87
- const overrides = await resolveOverrides(mergedRaw);
88
- const finalConfig = finalizeConfig(overrides, { rootDir });
89
-
90
- return {
91
- ...finalConfig,
92
- sources: {
93
- rootDir,
94
- configFile: configFilePath ?? null
95
- }
9
+ // The new config-loader will handle everything: finding, loading, parsing, validating, and resolving paths.
10
+ const { config } = await loadConfig(configFilePath, rootDir);
11
+
12
+ // The build commands expect a slightly different structure, so we adapt it here.
13
+
14
+ const outDir = config.outDir ? path.resolve(config.root, config.outDir) : path.resolve(config.root, 'dist');
15
+ config.outDir = outDir; // Ensure outDir is absolute for later use
16
+ config.outDir_abs = outDir;
17
+
18
+ const rawAssets = Array.isArray(config.assets) ? config.assets : [];
19
+ const rawServerSqlGlobs = Array.isArray(config.server?.sqlGlobs) ? config.server.sqlGlobs : [];
20
+ const rawClientExclude = Array.isArray(config.client?.exclude) ? config.client.exclude : [];
21
+ const rawViteClient = config.vite?.client ?? {};
22
+ const rawViteSsr = config.vite?.ssr ?? {};
23
+
24
+ const toGlobSpec = (pattern) => {
25
+ const isAbsolute = path.isAbsolute(pattern);
26
+ return {
27
+ pattern,
28
+ isAbsolute,
29
+ cwd: isAbsolute ? undefined : config.root,
30
+ absolute: isAbsolute ? path.normalize(pattern) : path.resolve(config.root, pattern)
31
+ };
32
+ };
33
+
34
+ const resolveOptionalPath = (value) => {
35
+ if (!value) return null;
36
+ return path.isAbsolute(value) ? value : path.resolve(config.root, value);
96
37
  };
97
- }
98
-
99
- async function findConfigFile(rootDir, explicit) {
100
- if (explicit) {
101
- const candidate = resolvePath(explicit, rootDir);
102
- await assertFile(candidate, `Config file not found: ${explicit}`);
103
- return candidate;
104
- }
105
-
106
- for (const fileName of CONFIG_FILENAMES) {
107
- const candidate = path.join(rootDir, fileName);
108
- if (await pathExists(candidate)) {
109
- return candidate;
110
- }
111
- }
112
- return null;
113
- }
114
-
115
- async function loadConfigFile(filePath) {
116
- const ext = path.extname(filePath).toLowerCase();
117
- if (ext === '.yaml' || ext === '.yml') {
118
- return loadYamlConfig(filePath);
119
- }
120
- if (ext === '.json') {
121
- return loadJsonConfig(filePath);
122
- }
123
- throw new Error(`Unsupported App config extension for ${filePath}`);
124
- }
125
-
126
- async function loadYamlConfig(filePath) {
127
- const content = await fs.readFile(filePath, 'utf8');
128
- const parsed = YAML.parse(content);
129
- if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
130
- throw new Error(`Invalid YAML configuration in ${filePath}: expected an object`);
131
- }
132
- return parsed;
133
- }
134
-
135
- async function loadJsonConfig(filePath) {
136
- const content = await fs.readFile(filePath, 'utf8');
137
- try {
138
- const parsed = JSON.parse(content);
139
- if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
140
- throw new Error('expected an object');
141
- }
142
- return parsed;
143
- } catch (error) {
144
- throw new Error(`Invalid JSON configuration in ${filePath}: ${error.message}`);
145
- }
146
- }
147
-
148
- function normalizeExternalConfig(raw, { baseDir }) {
149
- const result = {};
150
-
151
- if (raw.mode != null) {
152
- result.mode = raw.mode;
153
- }
154
-
155
- if (raw.outDir != null) {
156
- result.outDir = resolveMaybePath(raw.outDir, baseDir);
157
- }
158
-
159
- // Top-level page shortcut
160
- if (raw.page != null) {
161
- result.ui = result.ui || {};
162
- result.ui.page = resolveMaybePath(raw.page, baseDir);
163
- }
164
- if (raw.options != null) {
165
- result.ui = result.ui || {};
166
- result.ui.options = resolveMaybePath(raw.options, baseDir);
167
- }
168
-
169
- if (raw.assets != null) {
170
- result.assets = normalizeList(raw.assets).map((glob) =>
171
- resolveMaybePath(glob, baseDir)
172
- );
173
- }
174
-
175
- if (raw.server) {
176
- result.server = {};
177
- if (raw.server.entry != null) {
178
- result.server.entry = resolveMaybePath(raw.server.entry, baseDir);
179
- }
180
- if (raw.server.rootDir != null) {
181
- result.server.rootDir = resolveMaybePath(raw.server.rootDir, baseDir);
182
- }
183
- if (raw.server.controllers != null) {
184
- result.server.controllers = resolveMaybePath(raw.server.controllers, baseDir);
185
- }
186
- if (raw.server.middleware != null) {
187
- result.server.middleware = resolveMaybePath(raw.server.middleware, baseDir);
188
- }
189
- if (raw.server.openapi != null) {
190
- result.server.openapi = resolveMaybePath(raw.server.openapi, baseDir);
191
- }
192
- if (raw.server.sqlGlobs != null) {
193
- result.server.sqlGlobs = normalizeList(raw.server.sqlGlobs).map((glob) =>
194
- resolveMaybePath(glob, baseDir)
195
- );
196
- }
197
- }
198
-
199
- if (raw.ui) {
200
- result.ui = {};
201
- if (raw.ui.page != null) {
202
- result.ui.page = resolveMaybePath(raw.ui.page, baseDir);
203
- }
204
- if (raw.ui.options != null) {
205
- result.ui.options = resolveMaybePath(raw.ui.options, baseDir);
206
- }
207
- if (raw.ui.rootDir != null) {
208
- result.ui.rootDir = resolveMaybePath(raw.ui.rootDir, baseDir);
209
- }
210
- if (raw.ui.openapi != null) {
211
- result.ui.openapi = resolveMaybePath(raw.ui.openapi, baseDir);
212
- }
213
- if (raw.ui.assets != null) {
214
- result.ui.assets = normalizeList(raw.ui.assets).map((glob) =>
215
- resolveMaybePath(glob, baseDir)
216
- );
217
- }
218
- if (raw.ui.clientExclude != null) {
219
- result.ui.clientExclude = normalizeList(raw.ui.clientExclude).map((glob) =>
220
- resolveMaybePath(glob, baseDir)
221
- );
222
- }
223
- }
224
-
225
- if (raw.vite) {
226
- result.vite = {};
227
- if (raw.vite.client) {
228
- result.vite.client = {};
229
- if (raw.vite.client.configFile != null) {
230
- result.vite.client.configFile = resolveMaybePath(raw.vite.client.configFile, baseDir);
231
- }
232
- if (raw.vite.client.override != null) {
233
- result.vite.client.override = wrapOverride(raw.vite.client.override, baseDir);
234
- }
235
- }
236
- if (raw.vite.ssr) {
237
- result.vite.ssr = {};
238
- if (raw.vite.ssr.configFile != null) {
239
- result.vite.ssr.configFile = resolveMaybePath(raw.vite.ssr.configFile, baseDir);
240
- }
241
- if (raw.vite.ssr.override != null) {
242
- result.vite.ssr.override = wrapOverride(raw.vite.ssr.override, baseDir);
243
- }
244
- }
245
- }
246
-
247
- if (raw.dev) {
248
- result.dev = {};
249
- if (raw.dev.watch != null) {
250
- result.dev.watch = Boolean(raw.dev.watch);
251
- }
252
- if (raw.dev.watchPaths != null) {
253
- result.dev.watchPaths = normalizeList(raw.dev.watchPaths).map((glob) =>
254
- resolveMaybePath(glob, baseDir)
255
- );
256
- }
257
- if (raw.dev.splitServe != null) {
258
- result.dev.splitServe = Boolean(raw.dev.splitServe);
259
- }
260
- if (raw.dev.frontendCmd != null) {
261
- result.dev.frontendCmd = raw.dev.frontendCmd;
262
- }
263
- }
264
-
265
- return result;
266
- }
267
-
268
- function normalizeCliOptions(options) {
269
- const result = {};
270
-
271
- if (options.out != null) {
272
- result.outDir = options.out;
273
- }
274
- if (options.mode != null) {
275
- result.mode = options.mode;
276
- }
277
-
278
- if (options.server != null || options.serverRoot != null || options.controllers != null || options.middleware != null || options.openapi != null || options.sqlGlob != null) {
279
- result.server = {};
280
- if (options.server != null) result.server.entry = options.server;
281
- if (options.serverRoot != null) result.server.rootDir = options.serverRoot;
282
- if (options.controllers != null) result.server.controllers = options.controllers;
283
- if (options.middleware != null) result.server.middleware = options.middleware;
284
- if (options.openapi != null) result.server.openapi = options.openapi;
285
- if (options.sqlGlob != null) result.server.sqlGlobs = normalizeList(options.sqlGlob);
286
- }
287
-
288
- if (options.page != null || options.uiRoot != null || options.uiOpenapi != null || options.uiOptions != null || options.clientExclude != null) {
289
- result.ui = {};
290
- if (options.page != null) result.ui.page = options.page;
291
- if (options.uiRoot != null) result.ui.rootDir = options.uiRoot;
292
- if (options.uiOpenapi != null) result.ui.openapi = options.uiOpenapi;
293
- if (options.uiOptions != null) result.ui.options = options.uiOptions;
294
- if (options.clientExclude != null) result.ui.clientExclude = normalizeList(options.clientExclude);
295
- }
296
-
297
- if (options.assets != null) {
298
- result.assets = normalizeList(options.assets);
299
- }
300
-
301
- if (options.clientViteConfig != null || options.clientViteOverride != null || options.ssrViteConfig != null || options.ssrViteOverride != null) {
302
- result.vite = {};
303
- if (options.clientViteConfig != null || options.clientViteOverride != null) {
304
- result.vite.client = {};
305
- if (options.clientViteConfig != null) {
306
- result.vite.client.configFile = options.clientViteConfig;
307
- }
308
- if (options.clientViteOverride != null) {
309
- result.vite.client.override = parseJsonOption(options.clientViteOverride, '--client-vite-override');
310
- }
311
- }
312
- if (options.ssrViteConfig != null || options.ssrViteOverride != null) {
313
- result.vite.ssr = {};
314
- if (options.ssrViteConfig != null) {
315
- result.vite.ssr.configFile = options.ssrViteConfig;
316
- }
317
- if (options.ssrViteOverride != null) {
318
- result.vite.ssr.override = parseJsonOption(options.ssrViteOverride, '--ssr-vite-override');
319
- }
320
- }
321
- }
322
-
323
- if (options.watch != null || options.watchPath != null || options.splitServe != null || options.frontendCmd != null) {
324
- result.dev = {};
325
- if (options.watch != null) result.dev.watch = Boolean(options.watch);
326
- if (options.watchPath != null) result.dev.watchPaths = normalizeList(options.watchPath);
327
- if (options.splitServe != null) result.dev.splitServe = Boolean(options.splitServe);
328
- if (options.frontendCmd != null) result.dev.frontendCmd = options.frontendCmd;
329
- }
330
-
331
- return result;
332
- }
333
-
334
- async function resolveOverrides(raw) {
335
- const resolved = structuredClone(raw);
336
-
337
- const clientOverride = raw?.vite?.client?.override;
338
- if (clientOverride && typeof clientOverride === 'object' && OVERRIDE_FILE_KEY in clientOverride) {
339
- const loaded = await readJsonFile(clientOverride[OVERRIDE_FILE_KEY]);
340
- resolved.vite.client.override = loaded;
341
- }
342
-
343
- const ssrOverride = raw?.vite?.ssr?.override;
344
- if (ssrOverride && typeof ssrOverride === 'object' && OVERRIDE_FILE_KEY in ssrOverride) {
345
- const loaded = await readJsonFile(ssrOverride[OVERRIDE_FILE_KEY]);
346
- resolved.vite.ssr.override = loaded;
347
- }
348
-
349
- return resolved;
350
- }
351
-
352
- function finalizeConfig(raw, { rootDir }) {
353
- const outDir = resolvePath(raw.outDir ?? DEFAULTS.outDir, rootDir);
354
-
355
- const serverRootDir = resolvePath(raw.server?.rootDir ?? DEFAULTS.server.rootDir ?? '.', rootDir);
356
- const serverEntryRaw = raw.server?.entry ?? DEFAULTS.server.entry;
357
- const serverEntryAbs = resolveWithBase(serverEntryRaw, serverRootDir);
358
38
 
359
- const controllersDir = resolvePath(raw.server?.controllers ?? DEFAULTS.server.controllers, rootDir);
360
- const middlewareDir = resolvePath(raw.server?.middleware ?? DEFAULTS.server.middleware, rootDir);
361
- const openapiFile = resolvePath(raw.server?.openapi ?? DEFAULTS.server.openapi, rootDir);
362
- const sqlGlobs = normalizeGlobList(raw.server?.sqlGlobs ?? DEFAULTS.server.sqlGlobs, rootDir);
39
+ const serverSqlPatterns = rawServerSqlGlobs.length > 0
40
+ ? rawServerSqlGlobs
41
+ : rawAssets.filter((pattern) => pattern.toLowerCase().includes('.sql'));
363
42
 
364
- const pageRaw = raw.ui?.page ?? DEFAULTS.ui.page;
365
- const pageAbs = resolveWithBase(pageRaw, rootDir);
366
- const uiRootDir = resolvePath(raw.ui?.rootDir ?? path.dirname(pageAbs), rootDir);
367
- const optionsRaw = raw.ui?.options ?? DEFAULTS.ui.options;
368
- const optionsAbs = resolveWithBase(optionsRaw, rootDir);
369
- const uiOpenapiFile = resolvePath(raw.ui?.openapi ?? DEFAULTS.ui.openapi, rootDir);
370
- const assetSources = collectAssetPatterns({
371
- topLevel: raw.assets,
372
- topLevelDefault: DEFAULTS.assets,
373
- uiLevel: raw.ui?.assets,
374
- uiDefault: DEFAULTS.ui.assets
375
- });
376
- const assetGlobs = normalizeGlobList(assetSources, rootDir);
377
- const clientExclude = normalizeGlobList(raw.ui?.clientExclude ?? DEFAULTS.ui.clientExclude, rootDir);
43
+ const serverSqlGlobs = serverSqlPatterns.map(toGlobSpec);
44
+ const assetSpecs = rawAssets.map(toGlobSpec);
45
+ const clientExcludeSpecs = rawClientExclude.map(toGlobSpec);
46
+
47
+ const uiRootDir = config.client?.main_abs ? path.dirname(config.client.main_abs) : null;
48
+ const uiRelRoot = uiRootDir ? path.relative(config.root, uiRootDir) : null;
378
49
 
379
- const uiRelRoot = path.relative(rootDir, uiRootDir);
380
50
  const layout = {
381
51
  outDir,
382
52
  serverOutDir: path.join(outDir, 'server'),
383
- // Place client bundle under .app
384
53
  clientOutDir: path.join(outDir, '.app', 'assets'),
385
- // Mirror UI outputs under the same relative root (no hardcoded 'ui')
386
- uiOutDir: path.join(outDir, uiRelRoot),
387
- // SSR precompile staging (build-only); will be overlaid into uiOutDir
54
+ uiOutDir: uiRelRoot ? path.join(outDir, uiRelRoot) : null,
388
55
  ssrOutDir: path.join(outDir, '.app', 'ssr'),
389
56
  middlewareOutDir: path.join(outDir, 'middleware'),
390
57
  tempDir: path.join(outDir, '.app'),
391
58
  tsOutDir: path.join(outDir, '.app', 'tsc')
392
59
  };
393
60
 
394
- return {
395
- mode: raw.mode ?? DEFAULTS.mode,
396
- rootDir,
397
- outDir,
398
- // publicPaths removed; client base is fixed to '/.app/client'
61
+ const buildConfig = {
62
+ ...config,
63
+ rootDir: config.root,
399
64
  layout,
400
- server: {
401
- rootDir: serverRootDir,
402
- entry: {
403
- raw: serverEntryRaw,
404
- absolute: serverEntryAbs,
405
- relativeToRoot: path.relative(rootDir, serverEntryAbs),
406
- relativeToServerRoot: path.relative(serverRootDir, serverEntryAbs)
407
- },
408
- controllersDir,
409
- middlewareDir,
410
- openapiFile,
411
- sqlGlobs
412
- },
413
- ui: {
65
+ server: config.server ? {
66
+ rootDir: config.server.main_abs ? path.dirname(config.server.main_abs) : config.root,
67
+ entry: config.server.main_abs ? {
68
+ raw: config.server.main,
69
+ absolute: config.server.main_abs,
70
+ relativeToRoot: path.relative(config.root, config.server.main_abs),
71
+ relativeToServerRoot: path.relative(path.dirname(config.server.main_abs), config.server.main_abs)
72
+ } : null,
73
+ controllersDir: config.server.controllers_abs,
74
+ middlewareDir: config.server.middleware_abs,
75
+ openapiFile: config.server.openapi_abs,
76
+ sqlGlobs: serverSqlGlobs
77
+ } : null,
78
+ ui: config.client ? {
414
79
  rootDir: uiRootDir,
415
- page: {
416
- raw: pageRaw,
417
- absolute: pageAbs,
418
- relativeToRoot: path.relative(rootDir, pageAbs)
419
- },
420
- options: {
421
- raw: optionsRaw,
422
- absolute: optionsAbs,
423
- relativeToRoot: path.relative(rootDir, optionsAbs),
424
- relativeToUiRoot: path.relative(uiRootDir, optionsAbs)
425
- },
426
- openapiFile: uiOpenapiFile,
427
- assets: assetGlobs,
428
- clientExclude
429
- },
430
- assets: assetGlobs,
80
+ page: config.client.shell_abs ? {
81
+ raw: config.client.shell,
82
+ absolute: config.client.shell_abs,
83
+ relativeToRoot: path.relative(config.root, config.client.shell_abs)
84
+ } : null,
85
+ options: {}, // This seems to be deprecated
86
+ openapiFile: config.client.openapi_abs,
87
+ assets: assetSpecs,
88
+ clientExclude: clientExcludeSpecs
89
+ } : null,
90
+ assets: assetSpecs,
431
91
  vite: {
432
92
  client: {
433
- configFile: raw?.vite?.client?.configFile
434
- ? resolveMaybePath(raw.vite.client.configFile, rootDir)
435
- : null,
436
- override: raw?.vite?.client?.override ?? null
93
+ configFile: resolveOptionalPath(rawViteClient.configFile),
94
+ override: rawViteClient.override ?? null
437
95
  },
438
96
  ssr: {
439
- configFile: raw?.vite?.ssr?.configFile
440
- ? resolveMaybePath(raw.vite.ssr.configFile, rootDir)
441
- : null,
442
- override: raw?.vite?.ssr?.override ?? null
97
+ configFile: resolveOptionalPath(rawViteSsr.configFile),
98
+ override: rawViteSsr.override ?? null
443
99
  }
444
- },
445
- dev: {
446
- watch: Boolean(raw?.dev?.watch ?? DEFAULTS.dev.watch),
447
- watchPaths: normalizeGlobList(raw?.dev?.watchPaths ?? DEFAULTS.dev.watchPaths, rootDir),
448
- splitServe: Boolean(raw?.dev?.splitServe ?? DEFAULTS.dev.splitServe),
449
- frontendCmd: raw?.dev?.frontendCmd ?? DEFAULTS.dev.frontendCmd
450
- }
451
- };
452
- }
453
-
454
- function collectAssetPatterns({ topLevel, topLevelDefault, uiLevel, uiDefault }) {
455
- const patterns = new Set();
456
-
457
- const addAll = (value) => {
458
- if (value == null) return;
459
- for (const entry of normalizeList(value)) {
460
- if (entry == null || entry === '') continue;
461
- patterns.add(entry);
462
100
  }
463
101
  };
464
102
 
465
- if (topLevel != null) {
466
- addAll(topLevel);
467
- } else {
468
- addAll(topLevelDefault);
469
- }
470
-
471
- if (uiLevel != null) {
472
- addAll(uiLevel);
473
- } else {
474
- addAll(uiDefault);
475
- }
476
-
477
- return Array.from(patterns);
478
- }
479
-
480
- function normalizeGlobList(values, baseDir) {
481
- return normalizeList(values).map((pattern) => ({
482
- pattern,
483
- isAbsolute: path.isAbsolute(pattern),
484
- cwd: path.isAbsolute(pattern) ? undefined : baseDir,
485
- absolute: path.isAbsolute(pattern) ? pattern : path.resolve(baseDir, pattern)
486
- }));
487
- }
488
-
489
- function wrapOverride(value, baseDir) {
490
- if (value == null) return null;
491
- if (typeof value === 'object' && !Array.isArray(value)) {
492
- return value;
493
- }
494
- if (typeof value === 'string') {
495
- const trimmed = value.trim();
496
- try {
497
- return JSON.parse(trimmed);
498
- } catch {
499
- const resolved = resolveMaybePath(trimmed, baseDir);
500
- return { [OVERRIDE_FILE_KEY]: resolved };
501
- }
502
- }
503
- throw new Error('Unsupported override configuration value');
504
- }
505
-
506
- function normalizeList(value) {
507
- if (Array.isArray(value)) {
508
- return value.flatMap((item) => normalizeList(item));
509
- }
510
- if (typeof value === 'string') {
511
- if (value.includes(',')) {
512
- return value
513
- .split(',')
514
- .map((part) => part.trim())
515
- .filter(Boolean);
516
- }
517
- return value.trim() ? [value.trim()] : [];
518
- }
519
- if (value == null) {
520
- return [];
521
- }
522
- return [String(value)];
523
- }
524
-
525
- function resolveMaybePath(value, baseDir) {
526
- if (value == null || value === '') {
527
- return value;
528
- }
529
- if (path.isAbsolute(value)) {
530
- return path.normalize(value);
531
- }
532
- return path.resolve(baseDir, value);
533
- }
534
-
535
- function resolveWithBase(value, baseDir) {
536
- if (path.isAbsolute(value)) {
537
- return path.normalize(value);
538
- }
539
- return path.resolve(baseDir, value);
540
- }
541
-
542
- function resolvePath(input, baseDir) {
543
- if (input == null || input === '') {
544
- return path.resolve(baseDir);
545
- }
546
- if (path.isAbsolute(input)) {
547
- return path.normalize(input);
548
- }
549
- return path.resolve(baseDir, input);
550
- }
551
-
552
- // No public base normalization; client assets served from '/.app/client'
553
-
554
- async function pathExists(target) {
555
- try {
556
- await fs.access(target);
557
- return true;
558
- } catch {
559
- return false;
560
- }
561
- }
562
-
563
- async function assertFile(filePath, message) {
564
- if (!(await pathExists(filePath))) {
565
- throw new Error(message);
566
- }
567
- }
568
-
569
- async function readJsonFile(filePath) {
570
- const content = await fs.readFile(filePath, 'utf8');
571
- try {
572
- return JSON.parse(content);
573
- } catch (error) {
574
- throw new Error(`Failed to parse JSON override at ${filePath}: ${error.message}`);
575
- }
576
- }
577
-
578
- function parseJsonOption(value, flagName) {
579
- try {
580
- return JSON.parse(value);
581
- } catch (error) {
582
- throw new Error(`Invalid JSON passed to ${flagName}: ${error.message}`);
583
- }
103
+ return buildConfig;
584
104
  }
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Main entry point for @noego/app package
3
+ * Exports the runtime functions and client helpers
4
+ */
5
+
6
+ export { runCombinedServices } from './runtime/runtime.js';
7
+ export { boot, client } from './client.js';