@noego/app 0.0.5 → 0.0.7
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/.claude/settings.local.json +2 -1
- package/package.json +5 -1
- package/src/commands/dev.js +98 -69
- package/src/commands/serve.js +49 -26
- package/src/config.js +4 -2
- package/src/runtime/config.js +82 -0
- package/types/config.d.ts +23 -0
- /package/src/commands/{runtime-entry.ts → runtime-entry.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noego/app",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Production build tool for Dinner/Forge apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"./client": {
|
|
15
15
|
"import": "./src/client.js",
|
|
16
16
|
"types": "./types/client.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./config": {
|
|
19
|
+
"import": "./src/runtime/config.js",
|
|
20
|
+
"types": "./types/config.d.ts"
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
23
|
"scripts": {
|
package/src/commands/dev.js
CHANGED
|
@@ -86,7 +86,7 @@ export async function runDev(config) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// Runtime entry file (TypeScript)
|
|
89
|
-
const runtimeEntryPath = path.resolve(__dirname, 'runtime-entry.
|
|
89
|
+
const runtimeEntryPath = path.resolve(__dirname, 'runtime-entry.js');
|
|
90
90
|
tsxArgs.push(runtimeEntryPath);
|
|
91
91
|
|
|
92
92
|
// Set environment variables
|
|
@@ -296,34 +296,39 @@ async function runSplitServeWithWatch(context, tsxExecutable, tsxArgs, baseEnv,
|
|
|
296
296
|
const chokidar = requireFromRoot('chokidar');
|
|
297
297
|
const picomatch = (await import('picomatch')).default;
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
// Keep patterns relative for chokidar to work properly with globs
|
|
300
|
+
// Absolute patterns with globs don't work well, especially in chokidar v4
|
|
301
|
+
const normalizePattern = (pattern) => {
|
|
302
|
+
// If pattern is absolute, make it relative to root
|
|
303
|
+
if (path.isAbsolute(pattern)) {
|
|
304
|
+
return path.relative(root, pattern);
|
|
305
|
+
}
|
|
306
|
+
return pattern;
|
|
302
307
|
};
|
|
303
|
-
|
|
308
|
+
|
|
304
309
|
// Collect watch patterns
|
|
305
310
|
const backendPatterns = [];
|
|
306
311
|
const frontendPatterns = [];
|
|
307
312
|
const sharedPatterns = [];
|
|
308
|
-
|
|
313
|
+
|
|
309
314
|
// App watch patterns (restart both)
|
|
310
315
|
if (yamlConfig.app?.watch) {
|
|
311
316
|
for (const pattern of yamlConfig.app.watch) {
|
|
312
|
-
sharedPatterns.push(
|
|
317
|
+
sharedPatterns.push(normalizePattern(pattern));
|
|
313
318
|
}
|
|
314
319
|
}
|
|
315
|
-
|
|
320
|
+
|
|
316
321
|
// Server watch patterns (restart backend only)
|
|
317
322
|
if (yamlConfig.server?.watch) {
|
|
318
323
|
for (const pattern of yamlConfig.server.watch) {
|
|
319
|
-
backendPatterns.push(
|
|
324
|
+
backendPatterns.push(normalizePattern(pattern));
|
|
320
325
|
}
|
|
321
326
|
}
|
|
322
|
-
|
|
327
|
+
|
|
323
328
|
// Client watch patterns (restart frontend only)
|
|
324
329
|
if (yamlConfig.client?.watch) {
|
|
325
330
|
for (const pattern of yamlConfig.client.watch) {
|
|
326
|
-
frontendPatterns.push(
|
|
331
|
+
frontendPatterns.push(normalizePattern(pattern));
|
|
327
332
|
}
|
|
328
333
|
}
|
|
329
334
|
|
|
@@ -426,25 +431,27 @@ async function runSplitServeWithWatch(context, tsxExecutable, tsxArgs, baseEnv,
|
|
|
426
431
|
const handleFileChange = async (reason, file) => {
|
|
427
432
|
if (pending) return;
|
|
428
433
|
pending = true;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
434
|
+
|
|
435
|
+
// With cwd set, chokidar reports relative paths
|
|
436
|
+
// Our patterns are now relative, so match against the relative path
|
|
437
|
+
const relativePath = path.isAbsolute(file) ? path.relative(root, file) : file;
|
|
438
|
+
|
|
432
439
|
// Determine which service(s) to restart
|
|
433
440
|
let restartBackend = false;
|
|
434
441
|
let restartFrontend = false;
|
|
435
|
-
|
|
436
|
-
if (sharedMatcher && sharedMatcher(
|
|
442
|
+
|
|
443
|
+
if (sharedMatcher && sharedMatcher(relativePath)) {
|
|
437
444
|
// Shared file changed - restart both
|
|
438
|
-
logger.info(`Shared file changed (${reason}): ${
|
|
445
|
+
logger.info(`Shared file changed (${reason}): ${relativePath}`);
|
|
439
446
|
restartBackend = true;
|
|
440
447
|
restartFrontend = true;
|
|
441
|
-
} else if (backendMatcher && backendMatcher(
|
|
448
|
+
} else if (backendMatcher && backendMatcher(relativePath)) {
|
|
442
449
|
// Backend file changed
|
|
443
|
-
logger.info(`Backend file changed (${reason}): ${
|
|
450
|
+
logger.info(`Backend file changed (${reason}): ${relativePath}`);
|
|
444
451
|
restartBackend = true;
|
|
445
|
-
} else if (frontendMatcher && frontendMatcher(
|
|
452
|
+
} else if (frontendMatcher && frontendMatcher(relativePath)) {
|
|
446
453
|
// Frontend file changed
|
|
447
|
-
logger.info(`Frontend file changed (${reason}): ${
|
|
454
|
+
logger.info(`Frontend file changed (${reason}): ${relativePath}`);
|
|
448
455
|
restartFrontend = true;
|
|
449
456
|
}
|
|
450
457
|
|
|
@@ -462,15 +469,34 @@ async function runSplitServeWithWatch(context, tsxExecutable, tsxArgs, baseEnv,
|
|
|
462
469
|
};
|
|
463
470
|
|
|
464
471
|
// Create watcher
|
|
472
|
+
// Use cwd with relative patterns for proper glob support
|
|
465
473
|
const watcher = chokidar.watch(allPatterns, {
|
|
466
|
-
|
|
467
|
-
|
|
474
|
+
cwd: root,
|
|
475
|
+
ignoreInitial: true
|
|
468
476
|
});
|
|
469
|
-
|
|
477
|
+
|
|
478
|
+
// Debug: Log what patterns we're actually watching
|
|
479
|
+
logger.info('Watch patterns:', allPatterns);
|
|
480
|
+
|
|
470
481
|
watcher
|
|
471
|
-
.on('add', (p) =>
|
|
472
|
-
|
|
473
|
-
|
|
482
|
+
.on('add', (p) => {
|
|
483
|
+
logger.debug(`File add event: ${p}`);
|
|
484
|
+
handleFileChange('add', p);
|
|
485
|
+
})
|
|
486
|
+
.on('change', (p) => {
|
|
487
|
+
logger.debug(`File change event: ${p}`);
|
|
488
|
+
handleFileChange('change', p);
|
|
489
|
+
})
|
|
490
|
+
.on('unlink', (p) => {
|
|
491
|
+
logger.debug(`File unlink event: ${p}`);
|
|
492
|
+
handleFileChange('unlink', p);
|
|
493
|
+
})
|
|
494
|
+
.on('ready', () => {
|
|
495
|
+
logger.info('File watcher is ready');
|
|
496
|
+
})
|
|
497
|
+
.on('error', (error) => {
|
|
498
|
+
logger.error('Watcher error:', error);
|
|
499
|
+
});
|
|
474
500
|
|
|
475
501
|
// Start initial processes
|
|
476
502
|
startBackend();
|
|
@@ -518,48 +544,47 @@ async function createWatcher(context, yamlConfig, configFilePath) {
|
|
|
518
544
|
const chokidar = requireFromRoot('chokidar');
|
|
519
545
|
|
|
520
546
|
const patterns = new Set();
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
547
|
+
|
|
548
|
+
// Keep patterns relative for chokidar to work properly with globs
|
|
549
|
+
const normalizePattern = (pattern) => {
|
|
550
|
+
// If pattern is absolute, make it relative to root
|
|
551
|
+
if (path.isAbsolute(pattern)) {
|
|
552
|
+
return path.relative(root, pattern);
|
|
553
|
+
}
|
|
554
|
+
return pattern;
|
|
525
555
|
};
|
|
526
|
-
|
|
527
|
-
//
|
|
528
|
-
if (yamlConfig.
|
|
529
|
-
for (const pattern of yamlConfig.
|
|
530
|
-
patterns.add(
|
|
556
|
+
|
|
557
|
+
// App watch patterns
|
|
558
|
+
if (yamlConfig.app?.watch) {
|
|
559
|
+
for (const pattern of yamlConfig.app.watch) {
|
|
560
|
+
patterns.add(normalizePattern(pattern));
|
|
531
561
|
}
|
|
532
562
|
}
|
|
533
|
-
|
|
534
|
-
//
|
|
535
|
-
if (yamlConfig.server?.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
patterns.add(resolvePattern(`${yamlConfig.server.middleware}/**/*.{ts,js}`));
|
|
540
|
-
}
|
|
541
|
-
if (yamlConfig.server?.openapi) {
|
|
542
|
-
patterns.add(resolvePattern(yamlConfig.server.openapi));
|
|
543
|
-
const openapiDir = path.dirname(resolvePattern(yamlConfig.server.openapi));
|
|
544
|
-
patterns.add(path.join(openapiDir, 'openapi/**/*.yaml'));
|
|
545
|
-
}
|
|
546
|
-
if (yamlConfig.client?.openapi) {
|
|
547
|
-
patterns.add(resolvePattern(yamlConfig.client.openapi));
|
|
548
|
-
const openapiDir = path.dirname(resolvePattern(yamlConfig.client.openapi));
|
|
549
|
-
patterns.add(path.join(openapiDir, 'openapi/**/*.yaml'));
|
|
563
|
+
|
|
564
|
+
// Server watch patterns
|
|
565
|
+
if (yamlConfig.server?.watch) {
|
|
566
|
+
for (const pattern of yamlConfig.server.watch) {
|
|
567
|
+
patterns.add(normalizePattern(pattern));
|
|
568
|
+
}
|
|
550
569
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
570
|
+
|
|
571
|
+
// Client watch patterns
|
|
572
|
+
if (yamlConfig.client?.watch) {
|
|
573
|
+
for (const pattern of yamlConfig.client.watch) {
|
|
574
|
+
patterns.add(normalizePattern(pattern));
|
|
556
575
|
}
|
|
557
576
|
}
|
|
558
|
-
|
|
577
|
+
|
|
578
|
+
if (patterns.size === 0) {
|
|
579
|
+
logger.warn('No watch patterns configured. Watching disabled.');
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
|
|
559
583
|
logger.info('Watching for changes to restart server...');
|
|
584
|
+
// Use cwd with relative patterns for proper glob support
|
|
560
585
|
const watcher = chokidar.watch(Array.from(patterns), {
|
|
561
|
-
|
|
562
|
-
|
|
586
|
+
cwd: root,
|
|
587
|
+
ignoreInitial: true
|
|
563
588
|
});
|
|
564
589
|
return watcher;
|
|
565
590
|
}
|
|
@@ -604,19 +629,23 @@ async function runWithRestart(context, tsxExecutable, tsxArgs, env, watcher, log
|
|
|
604
629
|
start();
|
|
605
630
|
pending = false;
|
|
606
631
|
};
|
|
607
|
-
|
|
608
|
-
watcher
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
632
|
+
|
|
633
|
+
if (watcher) {
|
|
634
|
+
watcher
|
|
635
|
+
.on('add', (p) => scheduleRestart('add', p))
|
|
636
|
+
.on('change', (p) => scheduleRestart('change', p))
|
|
637
|
+
.on('unlink', (p) => scheduleRestart('unlink', p));
|
|
638
|
+
}
|
|
639
|
+
|
|
613
640
|
// Start initial server
|
|
614
641
|
start();
|
|
615
|
-
|
|
642
|
+
|
|
616
643
|
// Keep process alive until SIGINT/SIGTERM
|
|
617
644
|
const shutdown = async () => {
|
|
618
645
|
await stop();
|
|
619
|
-
|
|
646
|
+
if (watcher) {
|
|
647
|
+
await watcher.close();
|
|
648
|
+
}
|
|
620
649
|
process.exit(0);
|
|
621
650
|
};
|
|
622
651
|
process.on('SIGINT', shutdown);
|
package/src/commands/serve.js
CHANGED
|
@@ -74,39 +74,52 @@ async function createWatcher(context) {
|
|
|
74
74
|
patterns.add(value);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
// Keep patterns relative for chokidar to work properly with globs
|
|
78
|
+
const normalizePattern = (entry) => {
|
|
78
79
|
if (!entry) return null;
|
|
79
80
|
if (typeof entry === 'string') {
|
|
80
|
-
|
|
81
|
+
// If pattern is absolute, make it relative to rootDir
|
|
82
|
+
if (path.isAbsolute(entry)) {
|
|
83
|
+
return path.relative(config.rootDir, entry);
|
|
84
|
+
}
|
|
85
|
+
return entry;
|
|
81
86
|
}
|
|
87
|
+
// Handle object-style patterns
|
|
82
88
|
if (entry.isAbsolute) {
|
|
83
|
-
return entry.pattern;
|
|
89
|
+
return path.relative(config.rootDir, entry.pattern);
|
|
84
90
|
}
|
|
85
91
|
if (entry.cwd) {
|
|
86
|
-
|
|
92
|
+
const absolute = path.join(entry.cwd, entry.pattern);
|
|
93
|
+
return path.relative(config.rootDir, absolute);
|
|
87
94
|
}
|
|
88
95
|
return entry.pattern;
|
|
89
96
|
};
|
|
90
97
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
98
|
+
// App watch patterns - restart server
|
|
99
|
+
if (config.app?.watch) {
|
|
100
|
+
for (const entry of config.app.watch) {
|
|
101
|
+
const pattern = normalizePattern(entry);
|
|
102
|
+
if (pattern) addPattern(pattern);
|
|
103
|
+
}
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
// Server-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const pattern = resolveGlob(entry);
|
|
104
|
-
if (pattern) addPattern(pattern);
|
|
106
|
+
// Server watch patterns - restart server
|
|
107
|
+
if (config.server?.watch) {
|
|
108
|
+
for (const entry of config.server.watch) {
|
|
109
|
+
const pattern = normalizePattern(entry);
|
|
110
|
+
if (pattern) addPattern(pattern);
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
// Client watch patterns - restart server (in non-split mode)
|
|
115
|
+
// Check both ui.watch (from buildConfig) and client.watch (from spread)
|
|
116
|
+
const clientWatch = config.ui?.watch || config.client?.watch;
|
|
117
|
+
if (clientWatch) {
|
|
118
|
+
for (const entry of clientWatch) {
|
|
119
|
+
const pattern = normalizePattern(entry);
|
|
120
|
+
if (pattern) addPattern(pattern);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
110
123
|
|
|
111
124
|
// Ignore Svelte/client files to let Vite HMR handle them
|
|
112
125
|
const uiIgnores = [
|
|
@@ -114,11 +127,17 @@ async function createWatcher(context) {
|
|
|
114
127
|
path.join(config.ui.rootDir, '**/*.{ts,tsx,css,scss,html}')
|
|
115
128
|
];
|
|
116
129
|
|
|
130
|
+
if (patterns.size === 0) {
|
|
131
|
+
logger.warn('No watch patterns configured. Watching disabled.');
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
logger.info('Watching for changes to restart server...');
|
|
136
|
+
// Use cwd with relative patterns for proper glob support
|
|
118
137
|
const watcher = chokidar.watch(Array.from(patterns), {
|
|
138
|
+
cwd: config.rootDir,
|
|
119
139
|
ignoreInitial: true,
|
|
120
|
-
ignored: uiIgnores
|
|
121
|
-
cwd: config.rootDir
|
|
140
|
+
ignored: uiIgnores
|
|
122
141
|
});
|
|
123
142
|
return watcher;
|
|
124
143
|
}
|
|
@@ -166,10 +185,12 @@ async function runWithRestart(context, runner, watcher, frontendProc) {
|
|
|
166
185
|
pending = false;
|
|
167
186
|
};
|
|
168
187
|
|
|
169
|
-
watcher
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
188
|
+
if (watcher) {
|
|
189
|
+
watcher
|
|
190
|
+
.on('add', (p) => scheduleRestart('add', p))
|
|
191
|
+
.on('change', (p) => scheduleRestart('change', p))
|
|
192
|
+
.on('unlink', (p) => scheduleRestart('unlink', p));
|
|
193
|
+
}
|
|
173
194
|
|
|
174
195
|
// Start initial server
|
|
175
196
|
start();
|
|
@@ -177,7 +198,9 @@ async function runWithRestart(context, runner, watcher, frontendProc) {
|
|
|
177
198
|
// Keep process alive until SIGINT/SIGTERM
|
|
178
199
|
const shutdown = async () => {
|
|
179
200
|
await stop();
|
|
180
|
-
|
|
201
|
+
if (watcher) {
|
|
202
|
+
await watcher.close();
|
|
203
|
+
}
|
|
181
204
|
if (frontendProc) {
|
|
182
205
|
try { frontendProc.kill('SIGTERM'); } catch {}
|
|
183
206
|
}
|
package/src/config.js
CHANGED
|
@@ -73,7 +73,8 @@ export async function loadBuildConfig(cliOptions = {}, { cwd = process.cwd() } =
|
|
|
73
73
|
controllersDir: config.server.controllers_abs,
|
|
74
74
|
middlewareDir: config.server.middleware_abs,
|
|
75
75
|
openapiFile: config.server.openapi_abs,
|
|
76
|
-
sqlGlobs: serverSqlGlobs
|
|
76
|
+
sqlGlobs: serverSqlGlobs,
|
|
77
|
+
watch: config.server.watch // Preserve watch property
|
|
77
78
|
} : null,
|
|
78
79
|
ui: config.client ? {
|
|
79
80
|
rootDir: uiRootDir,
|
|
@@ -85,7 +86,8 @@ export async function loadBuildConfig(cliOptions = {}, { cwd = process.cwd() } =
|
|
|
85
86
|
options: {}, // This seems to be deprecated
|
|
86
87
|
openapiFile: config.client.openapi_abs,
|
|
87
88
|
assets: assetSpecs,
|
|
88
|
-
clientExclude: clientExcludeSpecs
|
|
89
|
+
clientExclude: clientExcludeSpecs,
|
|
90
|
+
watch: config.client.watch // Preserve watch property
|
|
89
91
|
} : null,
|
|
90
92
|
assets: assetSpecs,
|
|
91
93
|
vite: {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { loadConfig, findConfigFile, loadConfigFromEnv } from './config-loader.js';
|
|
3
|
+
import {setContext} from "../client";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Loads and applies the resolved project configuration to the application's runtime context.
|
|
10
|
+
*
|
|
11
|
+
* @param {(config:any) => any} getAppFunction - A function that returns the application instance (e.g., Express app).
|
|
12
|
+
* @returns {Promise<object>} - The fully resolved and normalized configuration object.
|
|
13
|
+
*/
|
|
14
|
+
export async function buildConfig(getAppFunction) {
|
|
15
|
+
// Retrieve the application instance from the provided function.
|
|
16
|
+
/** @type {any} */
|
|
17
|
+
const app = getAppFunction();
|
|
18
|
+
// Load and resolve the configuration via getConfig (auto-detects config file or env).
|
|
19
|
+
const { config } = await getConfig();
|
|
20
|
+
// Attach the loaded config to the runtime context for downstream usage.
|
|
21
|
+
setContext(app, config);
|
|
22
|
+
// Return the config for consumers (generates env and internal path invariants).
|
|
23
|
+
return {config,app};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Automatically load configuration from environment variable or config file.
|
|
28
|
+
*
|
|
29
|
+
* This function:
|
|
30
|
+
* 1. First checks NOEGO_CONFIGURATION env var (uses loadConfigFromEnv if present)
|
|
31
|
+
* 2. Otherwise searches for config file starting from rootDir (or process.cwd())
|
|
32
|
+
* and walks up directories until a config file is found
|
|
33
|
+
* 3. Sets all environment variables automatically
|
|
34
|
+
* 4. Returns the fully resolved config object
|
|
35
|
+
*
|
|
36
|
+
* @param {string} [rootDir] - Optional starting directory for config file search. Defaults to process.cwd()
|
|
37
|
+
* @returns {Promise<{root: string, config: object, configFilePath: string|null}>}
|
|
38
|
+
*/
|
|
39
|
+
export async function getConfig(rootDir) {
|
|
40
|
+
// First priority: check NOEGO_CONFIGURATION env var
|
|
41
|
+
if (process.env.NOEGO_CONFIGURATION) {
|
|
42
|
+
return loadConfigFromEnv();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Second priority: find and load config file
|
|
46
|
+
const startDir = rootDir ? path.resolve(rootDir) : process.cwd();
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
// Walk up directories to find config file
|
|
50
|
+
let currentDir = startDir;
|
|
51
|
+
let lastDir = '';
|
|
52
|
+
|
|
53
|
+
while (currentDir !== lastDir) {
|
|
54
|
+
try {
|
|
55
|
+
const configFilePath = await findConfigFile(currentDir);
|
|
56
|
+
// Found config file, load it
|
|
57
|
+
// Pass rootDir if provided, otherwise let loadConfig determine root from config file
|
|
58
|
+
return await loadConfig(configFilePath, rootDir);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Config file not found in this directory, try parent
|
|
61
|
+
lastDir = currentDir;
|
|
62
|
+
currentDir = path.dirname(currentDir);
|
|
63
|
+
|
|
64
|
+
// Stop if we've reached filesystem root
|
|
65
|
+
if (currentDir === lastDir) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`No configuration file found starting from ${startDir}.\n` +
|
|
68
|
+
`Please create hammer.config.yml (or a similar named file) in your project root.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// Should never reach here, but just in case
|
|
77
|
+
throw new Error(
|
|
78
|
+
`No configuration file found starting from ${startDir}.\n` +
|
|
79
|
+
`Please create hammer.config.yml (or a similar named file) in your project root.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result returned by buildConfig
|
|
5
|
+
*/
|
|
6
|
+
export interface BuildConfigResult {
|
|
7
|
+
config: Record<string, unknown>;
|
|
8
|
+
app: Express;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loads and applies the resolved project configuration to the application's runtime context.
|
|
13
|
+
*
|
|
14
|
+
* This function:
|
|
15
|
+
* 1. Retrieves the application instance from the provided function
|
|
16
|
+
* 2. Loads configuration via getConfig (auto-detects config file or env)
|
|
17
|
+
* 3. Attaches the loaded config to the runtime context via setContext
|
|
18
|
+
* 4. Returns both the config object and app instance for consumers
|
|
19
|
+
*
|
|
20
|
+
* @param getAppFunction - A function that returns the application instance (e.g., Express app)
|
|
21
|
+
* @returns Promise resolving to an object containing both the config and app
|
|
22
|
+
*/
|
|
23
|
+
export function buildConfig(getAppFunction: (config:any) => Express): Promise<BuildConfigResult>;
|
|
File without changes
|