@noego/app 0.0.1
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/AGENTS.md +457 -0
- package/bin/app.js +5 -0
- package/docs/design.md +107 -0
- package/package.json +24 -0
- package/src/args.js +180 -0
- package/src/build/bootstrap.js +43 -0
- package/src/build/client-modules.js +9 -0
- package/src/build/client.js +206 -0
- package/src/build/context.js +16 -0
- package/src/build/fix-imports.js +99 -0
- package/src/build/helpers.js +29 -0
- package/src/build/html.js +83 -0
- package/src/build/openapi.js +249 -0
- package/src/build/plugins/client-exclude.js +90 -0
- package/src/build/runtime-manifest.js +64 -0
- package/src/build/server.js +294 -0
- package/src/build/ssr.js +257 -0
- package/src/build/ui-common.js +188 -0
- package/src/build/vite.js +45 -0
- package/src/cli.js +72 -0
- package/src/commands/build.js +59 -0
- package/src/commands/preview.js +33 -0
- package/src/commands/serve.js +213 -0
- package/src/config.js +584 -0
- package/src/logger.js +16 -0
- package/src/utils/command.js +23 -0
package/src/build/ssr.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { buildWithVite } from './vite.js';
|
|
6
|
+
import { createUiBuildConfig } from './ui-common.js';
|
|
7
|
+
import { resolveClientEntry } from './client.js';
|
|
8
|
+
|
|
9
|
+
export async function resolveSsrBuildConfig(context, discovery) {
|
|
10
|
+
const { config } = context;
|
|
11
|
+
const { entryAbsolute } = await resolveClientEntry(context);
|
|
12
|
+
const shared = await createUiBuildConfig(context, {
|
|
13
|
+
discovery,
|
|
14
|
+
entryAbsolute,
|
|
15
|
+
outDir: config.layout.ssrOutDir,
|
|
16
|
+
ssr: true,
|
|
17
|
+
configFileOption: config.vite.ssr.configFile,
|
|
18
|
+
overrideOption: config.vite.ssr.override,
|
|
19
|
+
emptyOutDir: true,
|
|
20
|
+
manifest: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!shared.config) {
|
|
24
|
+
return { config: null, components: [] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
config: shared.config,
|
|
29
|
+
components: shared.components,
|
|
30
|
+
missing: shared.missing
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function buildSsr(context, discovery) {
|
|
35
|
+
const { logger, config } = context;
|
|
36
|
+
|
|
37
|
+
const resolution = await resolveSsrBuildConfig(context, discovery);
|
|
38
|
+
|
|
39
|
+
if (!resolution.config) {
|
|
40
|
+
logger.info('No .svelte components found; skipping SSR build.');
|
|
41
|
+
return { manifestPath: null, manifest: { routes: {}, fallback: null } };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
logger.info(`Precompiling ${resolution.components.length} Svelte component(s) for SSR`);
|
|
45
|
+
|
|
46
|
+
await buildWithVite(context, resolution.config);
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(resolution.missing) && resolution.missing.length > 0) {
|
|
49
|
+
resolution.missing.forEach((component) => {
|
|
50
|
+
logger.warn(`Skipping missing UI component during SSR build: ${component}`);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await buildForgeRecursiveRenderer(context);
|
|
55
|
+
|
|
56
|
+
const manifest = buildSsrManifest(config, discovery);
|
|
57
|
+
const manifestPath = path.join(config.layout.ssrOutDir, 'manifest.json');
|
|
58
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
59
|
+
|
|
60
|
+
// Overlay SSR compiled modules into the mirrored UI root so runtime imports
|
|
61
|
+
// like component_path.replace('.svelte', '.js') resolve without loaders.
|
|
62
|
+
try {
|
|
63
|
+
await fs.mkdir(config.layout.uiOutDir, { recursive: true });
|
|
64
|
+
await fs.cp(config.layout.ssrOutDir, config.layout.uiOutDir, { recursive: true, force: true });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
// non-fatal; report via console for visibility
|
|
67
|
+
console.warn('Failed to overlay SSR output into UI root:', err?.message || err);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await writeSsrEntryModule(context, resolution.components).catch((err) => {
|
|
71
|
+
logger.warn(`Failed to generate SSR entry module: ${err?.message || err}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
manifestPath,
|
|
76
|
+
manifest
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function resolveRecursiveRendererBuildConfig(context) {
|
|
81
|
+
const { config, requireFromRoot } = context;
|
|
82
|
+
const forgeEntry = requireFromRoot.resolve('@noego/forge');
|
|
83
|
+
const forgeRoot = await findPackageRoot(forgeEntry, '@noego/forge');
|
|
84
|
+
const entryPath = path.join(forgeRoot, 'src', 'components', 'RecursiveRender.svelte');
|
|
85
|
+
|
|
86
|
+
const pluginModule = await loadSveltePluginModule(context, [config.rootDir, forgeRoot]);
|
|
87
|
+
const createSveltePlugin = pluginModule?.svelte ?? pluginModule?.default;
|
|
88
|
+
if (typeof createSveltePlugin !== 'function') {
|
|
89
|
+
throw new Error('Unable to load @sveltejs/vite-plugin-svelte');
|
|
90
|
+
}
|
|
91
|
+
const sveltePlugin = createSveltePlugin();
|
|
92
|
+
|
|
93
|
+
const baseConfig = {
|
|
94
|
+
configFile: false,
|
|
95
|
+
mode: config.mode,
|
|
96
|
+
root: forgeRoot,
|
|
97
|
+
plugins: [sveltePlugin],
|
|
98
|
+
build: {
|
|
99
|
+
ssr: true,
|
|
100
|
+
outDir: config.layout.ssrOutDir,
|
|
101
|
+
emptyOutDir: false,
|
|
102
|
+
rollupOptions: {
|
|
103
|
+
input: entryPath,
|
|
104
|
+
preserveEntrySignatures: 'strict',
|
|
105
|
+
output: {
|
|
106
|
+
entryFileNames: 'RecursiveRender.js',
|
|
107
|
+
chunkFileNames: 'chunks/[name]-[hash].js',
|
|
108
|
+
assetFileNames: 'chunks/[name]-[hash][extname]'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const printableConfig = {
|
|
115
|
+
...baseConfig,
|
|
116
|
+
plugins: baseConfig.plugins.map((plugin) => plugin?.name || 'anonymous')
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
config: baseConfig,
|
|
121
|
+
printableConfig
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function buildForgeRecursiveRenderer(context) {
|
|
126
|
+
const { logger } = context;
|
|
127
|
+
try {
|
|
128
|
+
const resolution = await resolveRecursiveRendererBuildConfig(context);
|
|
129
|
+
await buildWithVite(context, resolution.config);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
logger.warn(`Skipping Forge RecursiveRender precompile: ${e?.message || e}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function writeSsrEntryModule(context, componentList = []) {
|
|
136
|
+
const { config } = context;
|
|
137
|
+
const uiOutDir = config.layout.uiOutDir;
|
|
138
|
+
const entryPath = path.join(uiOutDir, 'entry-ssr.js');
|
|
139
|
+
|
|
140
|
+
const uniqueComponents = Array.from(
|
|
141
|
+
new Set(
|
|
142
|
+
componentList
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
.filter((component) => typeof component === 'string' && component.endsWith('.svelte'))
|
|
145
|
+
)
|
|
146
|
+
).sort();
|
|
147
|
+
|
|
148
|
+
const importLines = [];
|
|
149
|
+
const mapLines = [];
|
|
150
|
+
let index = 0;
|
|
151
|
+
|
|
152
|
+
for (const component of uniqueComponents) {
|
|
153
|
+
const jsPathRelative = toPosix(component.replace(/\.svelte$/, '.js'));
|
|
154
|
+
const absoluteJsPath = path.join(uiOutDir, jsPathRelative);
|
|
155
|
+
try {
|
|
156
|
+
await fs.access(absoluteJsPath);
|
|
157
|
+
} catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const identifier = `mod${index++}`;
|
|
161
|
+
const importPath = `./${jsPathRelative}`;
|
|
162
|
+
importLines.push(`import * as ${identifier} from ${JSON.stringify(importPath)};`);
|
|
163
|
+
const key = toPosix(component);
|
|
164
|
+
mapLines.push(` ${JSON.stringify(key)}: ${identifier}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const contents = [
|
|
168
|
+
'// Generated by Hammer: maps Svelte components to precompiled SSR modules.',
|
|
169
|
+
...importLines,
|
|
170
|
+
'',
|
|
171
|
+
'export const components = {',
|
|
172
|
+
mapLines.join(',\n'),
|
|
173
|
+
'};',
|
|
174
|
+
'',
|
|
175
|
+
'export default components;',
|
|
176
|
+
''
|
|
177
|
+
].join('\n');
|
|
178
|
+
|
|
179
|
+
await fs.mkdir(path.dirname(entryPath), { recursive: true });
|
|
180
|
+
await fs.writeFile(entryPath, contents, 'utf8');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function toPosix(value) {
|
|
184
|
+
return value.split(path.sep).join('/');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function loadSveltePluginModule(context, searchRoots = []) {
|
|
188
|
+
const attempted = new Set();
|
|
189
|
+
for (const root of searchRoots.filter(Boolean)) {
|
|
190
|
+
const candidate = path.join(root, 'node_modules', '@sveltejs', 'vite-plugin-svelte', 'src', 'index.js');
|
|
191
|
+
if (attempted.has(candidate)) continue;
|
|
192
|
+
attempted.add(candidate);
|
|
193
|
+
try {
|
|
194
|
+
return await import(pathToFileURL(candidate).href);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (err?.code !== 'ERR_MODULE_NOT_FOUND' && err?.code !== 'ENOENT') {
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return import('@sveltejs/vite-plugin-svelte');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function findPackageRoot(entryPath, packageName) {
|
|
205
|
+
let current = path.dirname(entryPath);
|
|
206
|
+
const filesystemRoot = path.parse(current).root;
|
|
207
|
+
while (current && current !== filesystemRoot) {
|
|
208
|
+
const pkgPath = path.join(current, 'package.json');
|
|
209
|
+
try {
|
|
210
|
+
const content = await fs.readFile(pkgPath, 'utf8');
|
|
211
|
+
const pkg = JSON.parse(content);
|
|
212
|
+
if (pkg.name === packageName) return current;
|
|
213
|
+
} catch {}
|
|
214
|
+
current = path.dirname(current);
|
|
215
|
+
}
|
|
216
|
+
throw new Error(`Unable to locate package root for ${packageName}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildSsrManifest(config, discovery) {
|
|
220
|
+
const routesManifest = {};
|
|
221
|
+
for (const route of discovery.ui.routes) {
|
|
222
|
+
routesManifest[route.path] = {
|
|
223
|
+
method: route.method,
|
|
224
|
+
view: route.view ? componentToSsrPath(route.view, config) : null,
|
|
225
|
+
layouts: route.layout.map((item) => componentToSsrPath(item, config)),
|
|
226
|
+
middleware: route.middleware
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const fallback = discovery.ui.fallback;
|
|
231
|
+
const normalizedFallback = fallback
|
|
232
|
+
? {
|
|
233
|
+
view: fallback.view ? componentToSsrPath(fallback.view, config) : null,
|
|
234
|
+
layouts: fallback.layouts.map((item) => componentToSsrPath(item, config)),
|
|
235
|
+
middleware: fallback.middleware
|
|
236
|
+
}
|
|
237
|
+
: null;
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
routes: routesManifest,
|
|
241
|
+
fallback: normalizedFallback
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function componentToSsrPath(componentPath, config) {
|
|
246
|
+
const relative = componentPath.startsWith('.')
|
|
247
|
+
? path.normalize(componentPath)
|
|
248
|
+
: componentPath;
|
|
249
|
+
const trimmed = relative.startsWith(config.ui.rootDir)
|
|
250
|
+
? path.relative(config.ui.rootDir, relative)
|
|
251
|
+
: relative;
|
|
252
|
+
// Mirror SSR outputs under the same relative UI root within dist
|
|
253
|
+
const uiRelRoot = path.relative(config.rootDir, config.ui.rootDir);
|
|
254
|
+
const withoutExt = trimmed.replace(/\.svelte$/, '.js');
|
|
255
|
+
const joined = path.join(uiRelRoot, withoutExt);
|
|
256
|
+
return joined.split(path.sep).join('/');
|
|
257
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { clientExcludePlugin } from './plugins/client-exclude.js';
|
|
4
|
+
import { resolveConfigFile, fileExists } from './helpers.js';
|
|
5
|
+
import { mergeViteConfig } from './vite.js';
|
|
6
|
+
|
|
7
|
+
export async function collectUiComponentEntries(context, discovery) {
|
|
8
|
+
const { config, requireFromRoot } = context;
|
|
9
|
+
const components = new Set([
|
|
10
|
+
...(discovery?.ui?.views || []),
|
|
11
|
+
...(discovery?.ui?.layouts || [])
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const fallback = discovery?.ui?.fallback;
|
|
15
|
+
if (fallback) {
|
|
16
|
+
if (fallback.view) components.add(fallback.view);
|
|
17
|
+
(fallback.layouts || []).forEach((layout) => components.add(layout));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const globModule = requireFromRoot('glob');
|
|
22
|
+
const glob = typeof globModule === 'function'
|
|
23
|
+
? globModule
|
|
24
|
+
: globModule.glob.bind(globModule);
|
|
25
|
+
const discovered = await glob('**/*.svelte', {
|
|
26
|
+
cwd: config.ui.rootDir,
|
|
27
|
+
absolute: false,
|
|
28
|
+
nodir: true,
|
|
29
|
+
dot: false
|
|
30
|
+
});
|
|
31
|
+
discovered.forEach((rel) => components.add(rel));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
// glob is optional; ignore if not available
|
|
34
|
+
if (err && err.code !== 'MODULE_NOT_FOUND') {
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Array.from(components).sort();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function createUiBuildConfig(context, options) {
|
|
43
|
+
const {
|
|
44
|
+
discovery,
|
|
45
|
+
entryAbsolute,
|
|
46
|
+
outDir,
|
|
47
|
+
ssr,
|
|
48
|
+
configFileOption,
|
|
49
|
+
overrideOption,
|
|
50
|
+
emptyOutDir = true,
|
|
51
|
+
manifest = true
|
|
52
|
+
} = options;
|
|
53
|
+
|
|
54
|
+
const { config } = context;
|
|
55
|
+
const entryResolved = entryAbsolute ? path.resolve(entryAbsolute) : null;
|
|
56
|
+
|
|
57
|
+
const componentRelatives = await collectUiComponentEntries(context, discovery);
|
|
58
|
+
const presentComponents = [];
|
|
59
|
+
const missingComponents = [];
|
|
60
|
+
const inputSet = new Set();
|
|
61
|
+
|
|
62
|
+
if (entryResolved) inputSet.add(entryResolved);
|
|
63
|
+
|
|
64
|
+
for (const relative of componentRelatives) {
|
|
65
|
+
const absolute = path.resolve(config.ui.rootDir, relative);
|
|
66
|
+
// eslint-disable-next-line no-await-in-loop
|
|
67
|
+
if (await fileExists(absolute)) {
|
|
68
|
+
presentComponents.push(relative);
|
|
69
|
+
inputSet.add(absolute);
|
|
70
|
+
} else {
|
|
71
|
+
missingComponents.push(relative);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const inputs = Array.from(inputSet).sort();
|
|
76
|
+
|
|
77
|
+
const configFile = await resolveConfigFile(
|
|
78
|
+
configFileOption,
|
|
79
|
+
path.join(config.rootDir, 'vite.config.js')
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const baseConfig = {
|
|
83
|
+
configFile,
|
|
84
|
+
mode: config.mode,
|
|
85
|
+
root: config.ui.rootDir,
|
|
86
|
+
plugins: [
|
|
87
|
+
clientExcludePlugin(config.ui.clientExclude, { projectRoot: config.rootDir })
|
|
88
|
+
],
|
|
89
|
+
build: {
|
|
90
|
+
outDir,
|
|
91
|
+
emptyOutDir,
|
|
92
|
+
manifest,
|
|
93
|
+
assetsDir: '',
|
|
94
|
+
ssr,
|
|
95
|
+
rollupOptions: {
|
|
96
|
+
input: inputs,
|
|
97
|
+
preserveEntrySignatures: 'strict',
|
|
98
|
+
output: {
|
|
99
|
+
entryFileNames: createEntryFileNames(context, entryResolved),
|
|
100
|
+
chunkFileNames: 'chunks/[name]-[hash].js',
|
|
101
|
+
assetFileNames: 'chunks/[name]-[hash][extname]'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
let finalConfig = baseConfig;
|
|
108
|
+
if (overrideOption) {
|
|
109
|
+
finalConfig = await mergeViteConfig(context, baseConfig, overrideOption);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
finalConfig.configFile = configFile;
|
|
113
|
+
finalConfig.mode = config.mode;
|
|
114
|
+
finalConfig.root = config.ui.rootDir;
|
|
115
|
+
finalConfig.plugins = ensureExcludePlugin(finalConfig.plugins, config);
|
|
116
|
+
|
|
117
|
+
finalConfig.build = finalConfig.build || {};
|
|
118
|
+
finalConfig.build.outDir = outDir;
|
|
119
|
+
finalConfig.build.emptyOutDir = emptyOutDir;
|
|
120
|
+
finalConfig.build.manifest = manifest;
|
|
121
|
+
finalConfig.build.assetsDir = '';
|
|
122
|
+
finalConfig.build.ssr = ssr;
|
|
123
|
+
|
|
124
|
+
finalConfig.build.rollupOptions = finalConfig.build.rollupOptions || {};
|
|
125
|
+
finalConfig.build.rollupOptions.input = inputs;
|
|
126
|
+
finalConfig.build.rollupOptions.preserveEntrySignatures = 'strict';
|
|
127
|
+
|
|
128
|
+
const output = finalConfig.build.rollupOptions.output || {};
|
|
129
|
+
output.entryFileNames = createEntryFileNames(context, entryResolved);
|
|
130
|
+
output.chunkFileNames = 'chunks/[name]-[hash].js';
|
|
131
|
+
output.assetFileNames = 'chunks/[name]-[hash][extname]';
|
|
132
|
+
finalConfig.build.rollupOptions.output = output;
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
config: finalConfig,
|
|
136
|
+
entryAbsolute: entryResolved,
|
|
137
|
+
inputs,
|
|
138
|
+
components: presentComponents,
|
|
139
|
+
missing: missingComponents
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ensureExcludePlugin(plugins, config) {
|
|
144
|
+
let list = Array.isArray(plugins) ? [...plugins] : (plugins ? [plugins] : []);
|
|
145
|
+
const hasPlugin = list.some((plugin) => plugin?.name === 'hammer-client-exclude');
|
|
146
|
+
if (!hasPlugin) {
|
|
147
|
+
list.unshift(
|
|
148
|
+
clientExcludePlugin(config.ui.clientExclude, { projectRoot: config.rootDir })
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return list;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createEntryFileNames(context, entryResolved) {
|
|
155
|
+
const { config } = context;
|
|
156
|
+
const entryPath = entryResolved ? path.resolve(entryResolved) : null;
|
|
157
|
+
return (chunkInfo) => {
|
|
158
|
+
const facade = chunkInfo.facadeModuleId
|
|
159
|
+
? path.resolve(chunkInfo.facadeModuleId)
|
|
160
|
+
: null;
|
|
161
|
+
if (facade && entryPath && facade === entryPath) {
|
|
162
|
+
return `${chunkInfo.name}-[hash].js`;
|
|
163
|
+
}
|
|
164
|
+
if (facade) {
|
|
165
|
+
const relToUi = path.relative(config.ui.rootDir, facade);
|
|
166
|
+
if (!relToUi.startsWith('..')) {
|
|
167
|
+
return toOutputPath(relToUi);
|
|
168
|
+
}
|
|
169
|
+
const relToRoot = path.relative(config.rootDir, facade);
|
|
170
|
+
if (!relToRoot.startsWith('..')) {
|
|
171
|
+
return toOutputPath(relToRoot);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return `chunks/${chunkInfo.name}-[hash].js`;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function toOutputPath(relativePath) {
|
|
179
|
+
const normalized = toPosix(relativePath);
|
|
180
|
+
if (/\.(svelte|ts|tsx|js|mjs)$/.test(normalized)) {
|
|
181
|
+
return normalized.replace(/\.(svelte|ts|tsx|js|mjs)$/, '.js');
|
|
182
|
+
}
|
|
183
|
+
return normalized;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function toPosix(value) {
|
|
187
|
+
return value.split(path.sep).join('/');
|
|
188
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
|
|
4
|
+
let cachedModule = null;
|
|
5
|
+
|
|
6
|
+
async function getViteModule(context) {
|
|
7
|
+
if (cachedModule) {
|
|
8
|
+
return cachedModule;
|
|
9
|
+
}
|
|
10
|
+
const resolved = context.requireFromRoot.resolve('vite');
|
|
11
|
+
cachedModule = await import(pathToFileURL(resolved).href);
|
|
12
|
+
return cachedModule;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function buildWithVite(context, inlineConfig) {
|
|
16
|
+
const vite = await getViteModule(context);
|
|
17
|
+
return vite.build(inlineConfig);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function mergeViteConfig(context, baseConfig, overrideConfig) {
|
|
21
|
+
if (!overrideConfig) return baseConfig;
|
|
22
|
+
const vite = await getViteModule(context);
|
|
23
|
+
return vite.mergeConfig(baseConfig, overrideConfig);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function loadUserViteConfig(context, configFile, command) {
|
|
27
|
+
if (!configFile) return null;
|
|
28
|
+
const vite = await getViteModule(context);
|
|
29
|
+
const result = await vite.loadConfigFromFile(
|
|
30
|
+
{
|
|
31
|
+
command,
|
|
32
|
+
mode: context.config.mode
|
|
33
|
+
},
|
|
34
|
+
configFile,
|
|
35
|
+
context.config.rootDir
|
|
36
|
+
);
|
|
37
|
+
return result?.config ?? null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function toRelativeInput(filePath, rootDir) {
|
|
41
|
+
if (!filePath) return null;
|
|
42
|
+
const absolute = path.resolve(filePath);
|
|
43
|
+
const relative = path.relative(rootDir, absolute);
|
|
44
|
+
return relative.split(path.sep).join('/');
|
|
45
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
|
|
5
|
+
import { parseCliArgs, printHelpAndExit } from './args.js';
|
|
6
|
+
import { loadBuildConfig } from './config.js';
|
|
7
|
+
import { runBuild } from './commands/build.js';
|
|
8
|
+
import { runServe } from './commands/serve.js';
|
|
9
|
+
import { runPreview } from './commands/preview.js';
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
try {
|
|
13
|
+
const { command, options } = parseCliArgs(process.argv.slice(2));
|
|
14
|
+
|
|
15
|
+
if (options.help || !command) {
|
|
16
|
+
printHelpAndExit();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (options.version) {
|
|
20
|
+
const packageJson = await loadOwnPackage();
|
|
21
|
+
process.stdout.write(`${packageJson.version}\n`);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
switch (command) {
|
|
26
|
+
case 'build': {
|
|
27
|
+
const config = await loadBuildConfig(options, { cwd: process.cwd() });
|
|
28
|
+
await runBuild(config);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case 'serve': {
|
|
32
|
+
const config = await loadBuildConfig(options, { cwd: process.cwd() });
|
|
33
|
+
await runServe(config);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'preview': {
|
|
37
|
+
const config = await loadBuildConfig(options, { cwd: process.cwd() });
|
|
38
|
+
await runPreview(config);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
default:
|
|
42
|
+
throw cliError(`Unknown command "${command}"`);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error?.code === 'CLI_USAGE') {
|
|
46
|
+
process.stderr.write(`app: ${error.message}\n`);
|
|
47
|
+
process.stderr.write(`Run "app --help" for usage.\n`);
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
process.stderr.write(`[app] ${error?.message || error}\n`);
|
|
53
|
+
if (process.env.APP_DEBUG || process.env.HAMMER_DEBUG) {
|
|
54
|
+
console.error(error);
|
|
55
|
+
}
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function loadOwnPackage() {
|
|
61
|
+
const pkgPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'package.json');
|
|
62
|
+
const content = await readFile(pkgPath, 'utf8');
|
|
63
|
+
return JSON.parse(content);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cliError(message) {
|
|
67
|
+
const error = new Error(message);
|
|
68
|
+
error.code = 'CLI_USAGE';
|
|
69
|
+
return error;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await main();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { createBuildContext } from '../build/context.js';
|
|
5
|
+
import { buildServer } from '../build/server.js';
|
|
6
|
+
import { discoverProject } from '../build/openapi.js';
|
|
7
|
+
import { buildClient } from '../build/client.js';
|
|
8
|
+
import { buildClientModules } from '../build/client-modules.js';
|
|
9
|
+
import { buildSsr } from '../build/ssr.js';
|
|
10
|
+
import { rewriteHtmlTemplate } from '../build/html.js';
|
|
11
|
+
import { writeRuntimeManifest } from '../build/runtime-manifest.js';
|
|
12
|
+
import { generateBootstrap } from '../build/bootstrap.js';
|
|
13
|
+
|
|
14
|
+
export async function runBuild(config) {
|
|
15
|
+
const context = createBuildContext(config);
|
|
16
|
+
const { logger } = context;
|
|
17
|
+
|
|
18
|
+
await prepareWorkspace(context);
|
|
19
|
+
const discovery = await discoverProject(context);
|
|
20
|
+
const serverArtifacts = await buildServer(context, discovery);
|
|
21
|
+
const clientArtifacts = await buildClient(context, discovery);
|
|
22
|
+
await buildClientModules(context, discovery);
|
|
23
|
+
await rewriteHtmlTemplate(context, clientArtifacts);
|
|
24
|
+
const ssrArtifacts = await buildSsr(context, discovery);
|
|
25
|
+
const runtimeManifestPath = await writeRuntimeManifest(context, {
|
|
26
|
+
discovery,
|
|
27
|
+
server: serverArtifacts || {},
|
|
28
|
+
client: clientArtifacts,
|
|
29
|
+
ssr: ssrArtifacts
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
logger.info(`Runtime manifest: ${path.relative(config.rootDir, runtimeManifestPath)}`);
|
|
33
|
+
logger.info(`Client manifest: ${path.relative(config.outDir, clientArtifacts.manifestPath)}`);
|
|
34
|
+
if (ssrArtifacts.manifestPath) {
|
|
35
|
+
logger.info(`SSR manifest: ${path.relative(config.outDir, ssrArtifacts.manifestPath)}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await generateBootstrap(context);
|
|
39
|
+
|
|
40
|
+
logger.info('Build complete');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function prepareWorkspace(context) {
|
|
44
|
+
const { config, logger } = context;
|
|
45
|
+
const { outDir, layout, rootDir } = config;
|
|
46
|
+
|
|
47
|
+
if (path.resolve(outDir) === path.resolve(rootDir)) {
|
|
48
|
+
throw new Error('Refusing to clean output: --out resolves to project root');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
logger.info(`Cleaning output directory ${outDir}`);
|
|
52
|
+
await fs.rm(outDir, { recursive: true, force: true });
|
|
53
|
+
await fs.mkdir(layout.tempDir, { recursive: true });
|
|
54
|
+
await fs.mkdir(layout.serverOutDir, { recursive: true });
|
|
55
|
+
await fs.mkdir(layout.middlewareOutDir, { recursive: true });
|
|
56
|
+
await fs.mkdir(layout.clientOutDir, { recursive: true });
|
|
57
|
+
await fs.mkdir(layout.uiOutDir, { recursive: true });
|
|
58
|
+
await fs.mkdir(layout.ssrOutDir, { recursive: true });
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import util from 'node:util';
|
|
2
|
+
|
|
3
|
+
import { createBuildContext } from '../build/context.js';
|
|
4
|
+
import { discoverProject } from '../build/openapi.js';
|
|
5
|
+
import { resolveClientBuildConfig } from '../build/client.js';
|
|
6
|
+
import { resolveClientModulesConfig } from '../build/client-modules.js';
|
|
7
|
+
import {
|
|
8
|
+
resolveSsrBuildConfig,
|
|
9
|
+
resolveRecursiveRendererBuildConfig
|
|
10
|
+
} from '../build/ssr.js';
|
|
11
|
+
|
|
12
|
+
const INSPECT_OPTIONS = { depth: null, colors: false };
|
|
13
|
+
|
|
14
|
+
function printSection(heading, payload) {
|
|
15
|
+
console.log(`========== ${heading} ==========`);
|
|
16
|
+
console.log(util.inspect(payload, INSPECT_OPTIONS));
|
|
17
|
+
console.log();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runPreview(config) {
|
|
21
|
+
const context = createBuildContext(config);
|
|
22
|
+
const discovery = await discoverProject(context);
|
|
23
|
+
|
|
24
|
+
const client = await resolveClientBuildConfig(context, discovery);
|
|
25
|
+
const clientModules = await resolveClientModulesConfig(context, discovery);
|
|
26
|
+
const ssr = await resolveSsrBuildConfig(context, discovery);
|
|
27
|
+
const recursive = await resolveRecursiveRendererBuildConfig(context);
|
|
28
|
+
|
|
29
|
+
printSection('UI CSR', client.config);
|
|
30
|
+
printSection('UI SSR', ssr.config);
|
|
31
|
+
printSection('Recursive Renderer CSR', clientModules.config);
|
|
32
|
+
printSection('Recursive Renderer SSR', recursive?.printableConfig ?? recursive?.config ?? recursive);
|
|
33
|
+
}
|