@noego/app 0.0.2 → 0.0.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.
@@ -0,0 +1,203 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import YAML from 'yaml';
4
+
5
+ const CONFIG_FILENAMES = [
6
+ 'noego.yaml', 'noego.yml', 'noego.config.yaml', 'noego.config.yml',
7
+ 'app.yaml', 'app.yml', 'app.config.yaml', 'app.config.yml',
8
+ 'hammer.yaml', 'hammer.yml', 'hammer.config.yaml', 'hammer.config.yml'
9
+ ];
10
+
11
+ export async function loadConfig(configFilePath, cliRoot) {
12
+ const baseDir = path.dirname(configFilePath);
13
+
14
+ const content = await fs.readFile(configFilePath, 'utf8');
15
+ const config = YAML.parse(content);
16
+
17
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
18
+ throw new Error(`Invalid YAML configuration in ${configFilePath}: expected an object`);
19
+ }
20
+
21
+ // 1. Determine Project Root
22
+ const root = cliRoot
23
+ ? path.resolve(cliRoot)
24
+ : (config.root
25
+ ? path.resolve(baseDir, config.root)
26
+ : baseDir);
27
+ config.root = root;
28
+ process.env.NOEGO_ROOT = root;
29
+
30
+ const resolvedOutDir = path.resolve(root, config.outDir ?? 'dist');
31
+ config.outDir = config.outDir ?? 'dist';
32
+ config.outDir_abs = resolvedOutDir;
33
+
34
+ // 2. Resolve All Paths to Absolute & Validate
35
+ if (config.app?.boot) {
36
+ config.app.boot_abs = path.resolve(root, config.app.boot);
37
+ } else {
38
+ throw new Error('Configuration Error: `app.boot` is a required field.');
39
+ }
40
+
41
+ if (config.server) {
42
+ if (!config.server.main) {
43
+ throw new Error('Configuration Error: `server.main` is required when a `server` block is defined.');
44
+ }
45
+ config.server.main_abs = path.resolve(root, config.server.main);
46
+ if (config.server.controllers) {
47
+ config.server.controllers_abs = path.resolve(root, config.server.controllers);
48
+ config.server.controllers_base_path = config.server.controllers_abs;
49
+ process.env.NOEGO_CONTROLLERS_PATH = config.server.controllers_abs;
50
+ }
51
+ if (config.server.middleware) {
52
+ config.server.middleware_abs = path.resolve(root, config.server.middleware);
53
+ config.server.middleware_path = config.server.middleware_abs;
54
+ process.env.NOEGO_MIDDLEWARE_PATH = config.server.middleware_abs;
55
+ }
56
+ if (config.server.openapi) {
57
+ config.server.openapi_abs = path.resolve(root, config.server.openapi);
58
+ config.server.openapi_path = config.server.openapi_abs;
59
+ process.env.NOEGO_OPENAPI_PATH = config.server.openapi_abs;
60
+ }
61
+ }
62
+
63
+ if (config.client) {
64
+ if (!config.client.main) {
65
+ throw new Error('Configuration Error: `client.main` is required when a `client` block is defined.');
66
+ }
67
+ if (!config.client.shell) {
68
+ throw new Error('Configuration Error: `client.shell` is required when a `client` block is defined.');
69
+ }
70
+ config.client.main_abs = path.resolve(root, config.client.main);
71
+ config.client.shell_abs = path.resolve(root, config.client.shell);
72
+ config.client.shell_path = config.client.shell;
73
+ process.env.NOEGO_CLIENT_SHELL = config.client.shell_abs;
74
+ if (config.client.openapi) {
75
+ config.client.openapi_abs = path.resolve(root, config.client.openapi);
76
+ config.client.openapi_path = config.client.openapi;
77
+ process.env.NOEGO_CLIENT_OPENAPI = config.client.openapi_abs;
78
+ }
79
+ const inferredComponentDir = config.client.componentDir
80
+ ? config.client.componentDir
81
+ : path.relative(root, path.dirname(config.client.main_abs));
82
+ config.client.component_dir = inferredComponentDir;
83
+ config.client.componentDir_abs = path.resolve(root, inferredComponentDir);
84
+ }
85
+
86
+ // 3. Set Ports and Environment Variables
87
+ const frontendPort = process.env.PORT ? parseInt(process.env.PORT, 10) : (config.dev?.port || 3000);
88
+
89
+ let backendPort;
90
+ if (process.env.BACKEND_PORT) {
91
+ backendPort = parseInt(process.env.BACKEND_PORT, 10);
92
+ } else if (process.env.PORT) {
93
+ backendPort = frontendPort + 1;
94
+ } else {
95
+ backendPort = config.dev?.backendPort || frontendPort + 1;
96
+ }
97
+
98
+ // Only set environment variables if not already set by parent process
99
+ if (!process.env.NOEGO_PORT) {
100
+ process.env.NOEGO_PORT = String(frontendPort);
101
+ }
102
+ if (!process.env.NOEGO_BACKEND_PORT) {
103
+ process.env.NOEGO_BACKEND_PORT = String(backendPort);
104
+ }
105
+
106
+ if (!config.dev) config.dev = {};
107
+ config.dev.port = frontendPort;
108
+ config.dev.backendPort = backendPort;
109
+
110
+ config.assets = Array.isArray(config.assets) ? config.assets : [];
111
+ if (config.client) {
112
+ config.client.exclude = Array.isArray(config.client.exclude) ? config.client.exclude : [];
113
+ config.client.assetDirs = [];
114
+ const relativeResourcesDir = config.client.resourcesDir ?? path.join(path.dirname(config.client.shell ?? ''), 'resources');
115
+ const resourcesDirAbs = path.resolve(root, relativeResourcesDir);
116
+ config.client.assetDirs.push({ mountPath: '/css', absolutePath: path.join(resourcesDirAbs, 'tailwind') });
117
+ config.client.assetDirs.push({ mountPath: '/images', absolutePath: path.join(resourcesDirAbs, 'images') });
118
+ }
119
+
120
+ process.env.NOEGO_CONFIGURATION = JSON.stringify(config);
121
+
122
+ return { root, config, configFilePath };
123
+ }
124
+
125
+ /**
126
+ * Load configuration from NOEGO_CONFIGURATION environment variable
127
+ * Used when running from a generated bootstrap file (production builds)
128
+ */
129
+ export function loadConfigFromEnv() {
130
+ if (!process.env.NOEGO_CONFIGURATION) {
131
+ throw new Error('NOEGO_CONFIGURATION environment variable is not set');
132
+ }
133
+
134
+ try {
135
+ const config = JSON.parse(process.env.NOEGO_CONFIGURATION);
136
+
137
+ // Validate critical fields
138
+ if (!config?.root) {
139
+ throw new Error('config.root is required in NOEGO_CONFIGURATION');
140
+ }
141
+ if (!config?.app?.boot_abs) {
142
+ throw new Error('config.app.boot_abs is required in NOEGO_CONFIGURATION');
143
+ }
144
+
145
+ // Set individual env vars for backwards compatibility
146
+ process.env.NOEGO_ROOT = config.root;
147
+
148
+ if (config.server?.controllers_abs) {
149
+ process.env.NOEGO_CONTROLLERS_PATH = config.server.controllers_abs;
150
+ }
151
+ if (config.server?.middleware_abs) {
152
+ process.env.NOEGO_MIDDLEWARE_PATH = config.server.middleware_abs;
153
+ }
154
+ if (config.server?.openapi_abs) {
155
+ process.env.NOEGO_OPENAPI_PATH = config.server.openapi_abs;
156
+ }
157
+
158
+ if (config.client?.shell_abs) {
159
+ process.env.NOEGO_CLIENT_SHELL = config.client.shell_abs;
160
+ }
161
+ if (config.client?.openapi_abs) {
162
+ process.env.NOEGO_CLIENT_OPENAPI = config.client.openapi_abs;
163
+ }
164
+
165
+ if (config.dev?.port) {
166
+ process.env.NOEGO_PORT = String(config.dev.port);
167
+ }
168
+ if (config.dev?.backendPort) {
169
+ process.env.NOEGO_BACKEND_PORT = String(config.dev.backendPort);
170
+ }
171
+
172
+ return { root: config.root, config, configFilePath: null };
173
+ } catch (error) {
174
+ throw new Error(`Failed to parse NOEGO_CONFIGURATION: ${error.message}`);
175
+ }
176
+ }
177
+
178
+ export async function findConfigFile(rootDir, explicit) {
179
+ if (explicit) {
180
+ const candidate = path.isAbsolute(explicit) ? explicit : path.resolve(rootDir, explicit);
181
+ try {
182
+ await fs.access(candidate);
183
+ return candidate;
184
+ } catch {
185
+ throw new Error(`Config file not found: ${explicit}`);
186
+ }
187
+ }
188
+
189
+ for (const fileName of CONFIG_FILENAMES) {
190
+ const candidate = path.join(rootDir, fileName);
191
+ try {
192
+ await fs.access(candidate);
193
+ return candidate;
194
+ } catch {
195
+ continue;
196
+ }
197
+ }
198
+
199
+ throw new Error(
200
+ `No configuration file found in ${rootDir}.\nPlease create hammer.config.yml (or a similar named file) in your project root.`
201
+ );
202
+ }
203
+
@@ -0,0 +1,47 @@
1
+ import fs from 'node:fs/promises';
2
+
3
+ export async function parseHtmlScripts(htmlFilePath) {
4
+ const content = await fs.readFile(htmlFilePath, 'utf8');
5
+ const scripts = [];
6
+
7
+ // Match script tags with src attribute
8
+ const scriptRegex = /<script[^>]*src=["']([^"']+)["'][^>]*><\/script>/gi;
9
+ let match;
10
+
11
+ while ((match = scriptRegex.exec(content)) !== null) {
12
+ const src = match[1];
13
+ // Skip Vite dev client
14
+ if (src.includes('/@vite/client')) continue;
15
+ scripts.push(src);
16
+ }
17
+
18
+ return scripts;
19
+ }
20
+
21
+ /**
22
+ * Injects configuration into HTML template as window.__CONFIGURATION__
23
+ *
24
+ * @param {string} htmlFilePath - Path to HTML template
25
+ * @param {object} config - Configuration object to inject
26
+ * @returns {Promise<string>} HTML content with injected configuration
27
+ */
28
+ export async function injectConfiguration(htmlFilePath, config) {
29
+ let html = await fs.readFile(htmlFilePath, 'utf8');
30
+
31
+ // Create the configuration script
32
+ const configScript = `<script>window.__CONFIGURATION__ = ${JSON.stringify(config)};</script>`;
33
+
34
+ // Inject before closing </head> tag, or at the beginning of <body> if no </head>
35
+ if (html.includes('</head>')) {
36
+ html = html.replace('</head>', `${configScript}\n</head>`);
37
+ } else if (html.includes('<body')) {
38
+ // Find the opening body tag and inject after it
39
+ html = html.replace(/(<body[^>]*>)/, `$1\n${configScript}`);
40
+ } else {
41
+ // Fallback: inject at the beginning
42
+ html = configScript + '\n' + html;
43
+ }
44
+
45
+ return html;
46
+ }
47
+
@@ -0,0 +1,4 @@
1
+ export { runRuntime } from './runtime.js';
2
+ export { loadConfig, findConfigFile } from './config-loader.js';
3
+ export { parseHtmlScripts } from './html-parser.js';
4
+