@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/.claude/settings.local.json +3 -11
- package/package.json +15 -1
- package/src/args.js +1 -0
- package/src/build/bootstrap.js +114 -8
- package/src/build/context.js +2 -2
- package/src/build/helpers.js +10 -0
- package/src/build/openapi.js +9 -4
- package/src/build/runtime-manifest.js +22 -30
- package/src/build/server.js +34 -4
- package/src/cli.js +10 -5
- package/src/client.js +141 -0
- package/src/commands/build.js +66 -21
- package/src/commands/dev.js +624 -0
- package/src/commands/runtime-entry.ts +16 -0
- package/src/config.js +73 -553
- package/src/index.js +7 -0
- package/src/runtime/config-loader.js +203 -0
- package/src/runtime/html-parser.js +47 -0
- package/src/runtime/index.js +4 -0
- package/src/runtime/runtime.js +749 -0
- package/types/client.d.ts +23 -0
|
@@ -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
|
+
|