@jk2908/solas 0.1.0
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/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +219 -0
- package/dist/error-boundary.d.ts +1 -0
- package/dist/error-boundary.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +235 -0
- package/dist/internal/build.d.ts +104 -0
- package/dist/internal/build.js +633 -0
- package/dist/internal/codegen/config.d.ts +5 -0
- package/dist/internal/codegen/config.js +19 -0
- package/dist/internal/codegen/environments.d.ts +12 -0
- package/dist/internal/codegen/environments.js +42 -0
- package/dist/internal/codegen/manifest.d.ts +5 -0
- package/dist/internal/codegen/manifest.js +15 -0
- package/dist/internal/codegen/maps.d.ts +5 -0
- package/dist/internal/codegen/maps.js +75 -0
- package/dist/internal/codegen/utils.d.ts +1 -0
- package/dist/internal/codegen/utils.js +2 -0
- package/dist/internal/env/browser.d.ts +4 -0
- package/dist/internal/env/browser.js +58 -0
- package/dist/internal/env/request-context.d.ts +19 -0
- package/dist/internal/env/request-context.js +2 -0
- package/dist/internal/env/rsc.d.ts +39 -0
- package/dist/internal/env/rsc.js +368 -0
- package/dist/internal/env/ssr.d.ts +42 -0
- package/dist/internal/env/ssr.js +149 -0
- package/dist/internal/env/utils.d.ts +2 -0
- package/dist/internal/env/utils.js +28 -0
- package/dist/internal/metadata.d.ts +81 -0
- package/dist/internal/metadata.js +185 -0
- package/dist/internal/navigation/http-exception-boundary.d.ts +12 -0
- package/dist/internal/navigation/http-exception-boundary.js +48 -0
- package/dist/internal/navigation/http-exception.d.ts +33 -0
- package/dist/internal/navigation/http-exception.js +45 -0
- package/dist/internal/navigation/link.d.ts +13 -0
- package/dist/internal/navigation/link.js +63 -0
- package/dist/internal/navigation/redirect-boundary.d.ts +12 -0
- package/dist/internal/navigation/redirect-boundary.js +39 -0
- package/dist/internal/navigation/redirect.d.ts +21 -0
- package/dist/internal/navigation/redirect.js +63 -0
- package/dist/internal/navigation/use-search-params.d.ts +1 -0
- package/dist/internal/navigation/use-search-params.js +13 -0
- package/dist/internal/prerender.d.ts +151 -0
- package/dist/internal/prerender.js +422 -0
- package/dist/internal/render/head.d.ts +4 -0
- package/dist/internal/render/head.js +38 -0
- package/dist/internal/render/tree.d.ts +47 -0
- package/dist/internal/render/tree.js +108 -0
- package/dist/internal/router/create-router.d.ts +6 -0
- package/dist/internal/router/create-router.js +95 -0
- package/dist/internal/router/pattern.d.ts +8 -0
- package/dist/internal/router/pattern.js +31 -0
- package/dist/internal/router/prefetcher.d.ts +47 -0
- package/dist/internal/router/prefetcher.js +90 -0
- package/dist/internal/router/resolver.d.ts +174 -0
- package/dist/internal/router/resolver.js +356 -0
- package/dist/internal/router/router-context.d.ts +11 -0
- package/dist/internal/router/router-context.js +7 -0
- package/dist/internal/router/router-provider.d.ts +6 -0
- package/dist/internal/router/router-provider.js +131 -0
- package/dist/internal/router/router.d.ts +79 -0
- package/dist/internal/router/router.js +417 -0
- package/dist/internal/router/use-router.d.ts +5 -0
- package/dist/internal/router/use-router.js +5 -0
- package/dist/internal/server/cookies.d.ts +6 -0
- package/dist/internal/server/cookies.js +17 -0
- package/dist/internal/server/dynamic.d.ts +9 -0
- package/dist/internal/server/dynamic.js +22 -0
- package/dist/internal/server/headers.d.ts +5 -0
- package/dist/internal/server/headers.js +19 -0
- package/dist/internal/server/url.d.ts +5 -0
- package/dist/internal/server/url.js +16 -0
- package/dist/internal/ui/defaults/error.d.ts +4 -0
- package/dist/internal/ui/defaults/error.js +6 -0
- package/dist/internal/ui/error-boundary.d.ts +26 -0
- package/dist/internal/ui/error-boundary.js +41 -0
- package/dist/navigation.d.ts +6 -0
- package/dist/navigation.js +6 -0
- package/dist/prerender.d.ts +1 -0
- package/dist/prerender.js +1 -0
- package/dist/router.d.ts +4 -0
- package/dist/router.js +4 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +4 -0
- package/dist/solas.d.ts +32 -0
- package/dist/solas.js +125 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.js +1 -0
- package/dist/utils/compress.d.ts +11 -0
- package/dist/utils/compress.js +76 -0
- package/dist/utils/context.d.ts +6 -0
- package/dist/utils/context.js +25 -0
- package/dist/utils/cookies.d.ts +3 -0
- package/dist/utils/cookies.js +35 -0
- package/dist/utils/export-reader.d.ts +29 -0
- package/dist/utils/export-reader.js +117 -0
- package/dist/utils/format.d.ts +6 -0
- package/dist/utils/format.js +72 -0
- package/dist/utils/logger.d.ts +52 -0
- package/dist/utils/logger.js +105 -0
- package/dist/utils/time.d.ts +4 -0
- package/dist/utils/time.js +29 -0
- package/package.json +111 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Solas } from '../solas';
|
|
4
|
+
import { Logger } from '../utils/logger';
|
|
5
|
+
import { Prerender } from './prerender';
|
|
6
|
+
export { Build };
|
|
7
|
+
var Build;
|
|
8
|
+
(function (Build) {
|
|
9
|
+
const HTTP_VERBS = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
|
|
10
|
+
Build.EntryKind = {
|
|
11
|
+
SHELL: '$S',
|
|
12
|
+
LAYOUT: '$L',
|
|
13
|
+
PAGE: '$P',
|
|
14
|
+
401: '$401',
|
|
15
|
+
403: '$403',
|
|
16
|
+
404: '$404',
|
|
17
|
+
500: '$500',
|
|
18
|
+
LOADING: '$LOAD',
|
|
19
|
+
MIDDLEWARE: '$MW',
|
|
20
|
+
ENDPOINT: '$E',
|
|
21
|
+
};
|
|
22
|
+
const logger = new Logger();
|
|
23
|
+
/**
|
|
24
|
+
* Finder class to process application routes
|
|
25
|
+
*/
|
|
26
|
+
class Finder {
|
|
27
|
+
buildContext;
|
|
28
|
+
config;
|
|
29
|
+
constructor(buildContext, config) {
|
|
30
|
+
this.buildContext = buildContext;
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extracts dynamic parameter names from a file path
|
|
35
|
+
*/
|
|
36
|
+
static getParams(file) {
|
|
37
|
+
return Array.from(file.matchAll(/\[(?:\.\.\.)?([^\]]+)\]/g), m => m[1]);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the depth of a route based on slashes
|
|
41
|
+
*/
|
|
42
|
+
static getDepth(route) {
|
|
43
|
+
if (route === '/')
|
|
44
|
+
return 0;
|
|
45
|
+
// count slashes to determine depth
|
|
46
|
+
return route.split('/').length - 1;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convert a file path to a canonical route.
|
|
50
|
+
*/
|
|
51
|
+
static toCanonicalRoute(file) {
|
|
52
|
+
const route = file
|
|
53
|
+
.replace(new RegExp(`^${Solas.Config.APP_DIR}`), '')
|
|
54
|
+
.replace(/\/\+page\.(j|t)sx?$/, '')
|
|
55
|
+
.replace(/\/\+endpoint\.(j|t)sx?$/, '')
|
|
56
|
+
.replace(/\[\.\.\..+?\]/g, '*') // wildcard routes
|
|
57
|
+
.replace(/\[(.+?)\]/g, ':$1'); // dynamic routes
|
|
58
|
+
if (!route || route === '')
|
|
59
|
+
return '/';
|
|
60
|
+
return route.startsWith('/') ? route : `/${route}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the import path for a file
|
|
64
|
+
* This finds the relative path from the generated
|
|
65
|
+
* directory to the file, removes the extension and
|
|
66
|
+
* replaces backslashes with forward slashes.
|
|
67
|
+
*/
|
|
68
|
+
static getImportPath(file) {
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const generatedDir = path.join(cwd, Solas.Config.GENERATED_DIR);
|
|
71
|
+
return path
|
|
72
|
+
.relative(generatedDir, path.resolve(cwd, file))
|
|
73
|
+
.replace(/\\/g, '/')
|
|
74
|
+
.replace(/\.(t|j)sx?$/, '');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run the Finder to get the app route and associated data
|
|
78
|
+
* needed for codegen
|
|
79
|
+
*/
|
|
80
|
+
async run() {
|
|
81
|
+
try {
|
|
82
|
+
return await this.process(await this.#scan(Solas.Config.APP_DIR));
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
logger.error('[Build:Finder:run]: failed to build manifest', err);
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Scan the filesystem to get all routes for processing
|
|
91
|
+
*/
|
|
92
|
+
async #scan(dir, res = { segments: [], endpoints: [] }, prev = {
|
|
93
|
+
layouts: [],
|
|
94
|
+
'401s': [],
|
|
95
|
+
'403s': [],
|
|
96
|
+
'404s': [],
|
|
97
|
+
'500s': [],
|
|
98
|
+
loaders: [],
|
|
99
|
+
middlewares: [],
|
|
100
|
+
}) {
|
|
101
|
+
try {
|
|
102
|
+
// define valid route files
|
|
103
|
+
const EXTENSIONS = {
|
|
104
|
+
page: ['tsx', 'jsx'],
|
|
105
|
+
api: ['ts', 'js'],
|
|
106
|
+
};
|
|
107
|
+
// define route file types
|
|
108
|
+
const TYPES = {
|
|
109
|
+
page: '+page',
|
|
110
|
+
'401': '+401',
|
|
111
|
+
'403': '+403',
|
|
112
|
+
'404': '+404',
|
|
113
|
+
'500': '+500',
|
|
114
|
+
layout: '+layout',
|
|
115
|
+
loading: '+loading',
|
|
116
|
+
middleware: '+middleware',
|
|
117
|
+
endpoint: '+endpoint',
|
|
118
|
+
};
|
|
119
|
+
// map of valid files for each type
|
|
120
|
+
const validFiles = {
|
|
121
|
+
[TYPES.page]: new Set(EXTENSIONS.page.map(ext => `${TYPES.page}.${ext}`)),
|
|
122
|
+
[TYPES['401']]: new Set(EXTENSIONS.page.map(ext => `${TYPES['401']}.${ext}`)),
|
|
123
|
+
[TYPES['403']]: new Set(EXTENSIONS.page.map(ext => `${TYPES['403']}.${ext}`)),
|
|
124
|
+
[TYPES['404']]: new Set(EXTENSIONS.page.map(ext => `${TYPES['404']}.${ext}`)),
|
|
125
|
+
[TYPES['500']]: new Set(EXTENSIONS.page.map(ext => `${TYPES['500']}.${ext}`)),
|
|
126
|
+
[TYPES.loading]: new Set(EXTENSIONS.page.map(ext => `${TYPES.loading}.${ext}`)),
|
|
127
|
+
[TYPES.layout]: new Set(EXTENSIONS.page.map(ext => `${TYPES.layout}.${ext}`)),
|
|
128
|
+
[TYPES.middleware]: new Set(EXTENSIONS.api.map(ext => `${TYPES.middleware}.${ext}`)),
|
|
129
|
+
[TYPES.endpoint]: new Set(EXTENSIONS.api.map(ext => `${TYPES.endpoint}.${ext}`)),
|
|
130
|
+
};
|
|
131
|
+
const files = await fs.readdir(dir, { withFileTypes: true });
|
|
132
|
+
// keep a predictable order so layout/loading are picked
|
|
133
|
+
// up before page. Avoids OS dir ordering causing pages
|
|
134
|
+
// to steal layout/loaders first and drop alignment
|
|
135
|
+
files.sort((a, b) => {
|
|
136
|
+
if (a.isFile() && b.isDirectory())
|
|
137
|
+
return -1;
|
|
138
|
+
if (a.isDirectory() && b.isFile())
|
|
139
|
+
return 1;
|
|
140
|
+
if (a.isFile() && b.isFile()) {
|
|
141
|
+
const priority = (d) => {
|
|
142
|
+
const base = path.basename(d.name);
|
|
143
|
+
if (validFiles[TYPES.layout].has(base))
|
|
144
|
+
return 0;
|
|
145
|
+
if (validFiles[TYPES['401']].has(base))
|
|
146
|
+
return 1;
|
|
147
|
+
if (validFiles[TYPES['403']].has(base))
|
|
148
|
+
return 2;
|
|
149
|
+
if (validFiles[TYPES['404']].has(base))
|
|
150
|
+
return 3;
|
|
151
|
+
if (validFiles[TYPES['500']].has(base))
|
|
152
|
+
return 4;
|
|
153
|
+
if (validFiles[TYPES.loading].has(base))
|
|
154
|
+
return 5;
|
|
155
|
+
if (validFiles[TYPES.middleware].has(base))
|
|
156
|
+
return 6;
|
|
157
|
+
if (validFiles[TYPES.page].has(base))
|
|
158
|
+
return 7;
|
|
159
|
+
if (validFiles[TYPES.endpoint].has(base))
|
|
160
|
+
return 8;
|
|
161
|
+
return 8;
|
|
162
|
+
};
|
|
163
|
+
return priority(a) - priority(b);
|
|
164
|
+
}
|
|
165
|
+
return 0;
|
|
166
|
+
});
|
|
167
|
+
// current layout, status boundaries, loader, middleware, and page files for this segment
|
|
168
|
+
let currentLayout;
|
|
169
|
+
let current401;
|
|
170
|
+
let current403;
|
|
171
|
+
let current404;
|
|
172
|
+
let current500;
|
|
173
|
+
let currentLoader;
|
|
174
|
+
let currentMiddleware;
|
|
175
|
+
let currentPage;
|
|
176
|
+
for (const file of files) {
|
|
177
|
+
const route = path.join(dir, file.name);
|
|
178
|
+
if (file.isDirectory()) {
|
|
179
|
+
// before recursing, create segment for current dir if it
|
|
180
|
+
// has a layout (defines a wrapper for child routes)
|
|
181
|
+
if (!currentPage && currentLayout) {
|
|
182
|
+
const layouts = [...prev.layouts, currentLayout];
|
|
183
|
+
const unauthorized = [...prev['401s'], current401 ?? null];
|
|
184
|
+
const forbidden = [...prev['403s'], current403 ?? null];
|
|
185
|
+
const notFounds = [...prev['404s'], current404 ?? null];
|
|
186
|
+
const serverErrors = [...prev['500s'], current500 ?? null];
|
|
187
|
+
const loaders = [...prev.loaders, currentLoader ?? null];
|
|
188
|
+
const middlewares = [...prev.middlewares, currentMiddleware ?? null];
|
|
189
|
+
const shell = layouts[0];
|
|
190
|
+
if (shell) {
|
|
191
|
+
res.segments.push({
|
|
192
|
+
dir,
|
|
193
|
+
page: undefined,
|
|
194
|
+
'401s': unauthorized,
|
|
195
|
+
'403s': forbidden,
|
|
196
|
+
'404s': notFounds,
|
|
197
|
+
'500s': serverErrors,
|
|
198
|
+
loaders,
|
|
199
|
+
middlewares,
|
|
200
|
+
layouts: layouts.length > 1 ? layouts.slice(1) : [],
|
|
201
|
+
shell,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const next = {
|
|
206
|
+
layouts: [...prev.layouts, currentLayout ?? null],
|
|
207
|
+
'401s': [...prev['401s'], current401 ?? null],
|
|
208
|
+
'403s': [...prev['403s'], current403 ?? null],
|
|
209
|
+
'404s': [...prev['404s'], current404 ?? null],
|
|
210
|
+
'500s': [...prev['500s'], current500 ?? null],
|
|
211
|
+
loaders: [...prev.loaders, currentLoader ?? null],
|
|
212
|
+
middlewares: [...prev.middlewares, currentMiddleware ?? null],
|
|
213
|
+
};
|
|
214
|
+
await this.#scan(route, res, next);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
const base = path.basename(file.name);
|
|
218
|
+
const relative = path.relative(process.cwd(), route).replace(/\\/g, '/');
|
|
219
|
+
if (validFiles[TYPES.layout].has(base)) {
|
|
220
|
+
currentLayout = relative;
|
|
221
|
+
}
|
|
222
|
+
else if (validFiles[TYPES['401']].has(base)) {
|
|
223
|
+
current401 = relative;
|
|
224
|
+
}
|
|
225
|
+
else if (validFiles[TYPES['403']].has(base)) {
|
|
226
|
+
current403 = relative;
|
|
227
|
+
}
|
|
228
|
+
else if (validFiles[TYPES['404']].has(base)) {
|
|
229
|
+
current404 = relative;
|
|
230
|
+
}
|
|
231
|
+
else if (validFiles[TYPES['500']].has(base)) {
|
|
232
|
+
current500 = relative;
|
|
233
|
+
}
|
|
234
|
+
else if (validFiles[TYPES.loading].has(base)) {
|
|
235
|
+
currentLoader = relative;
|
|
236
|
+
}
|
|
237
|
+
else if (validFiles[TYPES.middleware].has(base)) {
|
|
238
|
+
currentMiddleware = relative;
|
|
239
|
+
}
|
|
240
|
+
else if (validFiles[TYPES.endpoint].has(base)) {
|
|
241
|
+
res.endpoints.push({
|
|
242
|
+
file: relative,
|
|
243
|
+
middlewares: [...prev.middlewares, currentMiddleware ?? null],
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else if (validFiles[TYPES.page].has(base)) {
|
|
247
|
+
currentPage = relative;
|
|
248
|
+
const layouts = [...prev.layouts, currentLayout ?? null];
|
|
249
|
+
const unauthorized = [...prev['401s'], current401 ?? null];
|
|
250
|
+
const forbidden = [...prev['403s'], current403 ?? null];
|
|
251
|
+
const notFounds = [...prev['404s'], current404 ?? null];
|
|
252
|
+
const serverErrors = [...prev['500s'], current500 ?? null];
|
|
253
|
+
const loaders = [...prev.loaders, currentLoader ?? null];
|
|
254
|
+
const middlewares = [...prev.middlewares, currentMiddleware ?? null];
|
|
255
|
+
const shell = layouts?.[0];
|
|
256
|
+
if (!shell)
|
|
257
|
+
throw new Error('Missing app shell');
|
|
258
|
+
res.segments.push({
|
|
259
|
+
dir,
|
|
260
|
+
page: relative,
|
|
261
|
+
'401s': unauthorized,
|
|
262
|
+
'403s': forbidden,
|
|
263
|
+
'404s': notFounds,
|
|
264
|
+
'500s': serverErrors,
|
|
265
|
+
loaders,
|
|
266
|
+
middlewares,
|
|
267
|
+
layouts: layouts.length > 1 ? layouts.slice(1) : [],
|
|
268
|
+
shell,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// warn if segment has status boundaries/loading but no page or layout
|
|
274
|
+
if (!currentPage &&
|
|
275
|
+
!currentLayout &&
|
|
276
|
+
(current401 || current403 || current404 || current500 || currentLoader)) {
|
|
277
|
+
logger.warn(`[Build:Finder:#scan]: ${dir} has status route files or +loading but no +page or +layout. This path will not be routable (404), but these files will still be inherited by child routes`);
|
|
278
|
+
}
|
|
279
|
+
// create segment if we have a layout but no page and
|
|
280
|
+
// haven't created one yet (no subdirectories triggered it)
|
|
281
|
+
if (!currentPage && currentLayout && !res.segments.some(s => s.dir === dir)) {
|
|
282
|
+
const layouts = [...prev.layouts, currentLayout];
|
|
283
|
+
const unauthorized = [...prev['401s'], current401 ?? null];
|
|
284
|
+
const forbidden = [...prev['403s'], current403 ?? null];
|
|
285
|
+
const notFounds = [...prev['404s'], current404 ?? null];
|
|
286
|
+
const serverErrors = [...prev['500s'], current500 ?? null];
|
|
287
|
+
const loaders = [...prev.loaders, currentLoader ?? null];
|
|
288
|
+
const middlewares = [...prev.middlewares, currentMiddleware ?? null];
|
|
289
|
+
const shell = layouts[0];
|
|
290
|
+
if (shell) {
|
|
291
|
+
res.segments.push({
|
|
292
|
+
dir,
|
|
293
|
+
page: undefined,
|
|
294
|
+
'401s': unauthorized,
|
|
295
|
+
'403s': forbidden,
|
|
296
|
+
'404s': notFounds,
|
|
297
|
+
'500s': serverErrors,
|
|
298
|
+
loaders,
|
|
299
|
+
middlewares,
|
|
300
|
+
layouts: layouts.length > 1 ? layouts.slice(1) : [],
|
|
301
|
+
shell,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return res;
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
logger.error(`[Build:Finder:#scan]: Failed to compose manifest from ${dir}`, err);
|
|
309
|
+
return {
|
|
310
|
+
segments: [],
|
|
311
|
+
endpoints: [],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Process the scanned route data
|
|
317
|
+
*/
|
|
318
|
+
async process(res) {
|
|
319
|
+
const processed = new Set();
|
|
320
|
+
const prerenderedRoutes = new Set();
|
|
321
|
+
const manifest = {};
|
|
322
|
+
// imports for endpoints and components
|
|
323
|
+
const imports = {
|
|
324
|
+
endpoints: { static: new Map() },
|
|
325
|
+
components: { static: new Map(), dynamic: new Map() },
|
|
326
|
+
middlewares: { static: new Map() },
|
|
327
|
+
};
|
|
328
|
+
const modules = {};
|
|
329
|
+
const prerenderCache = new Map();
|
|
330
|
+
for (const segment of res.segments) {
|
|
331
|
+
try {
|
|
332
|
+
if (!this.buildContext || !this.config)
|
|
333
|
+
continue;
|
|
334
|
+
const { shell: shellPath, layouts: layoutPaths, '401s': unauthorizedPaths, '403s': forbiddenPaths, page: pagePath, '404s': notFoundPaths, '500s': serverErrorPaths, loaders: loaderPaths, middlewares: middlewarePaths, dir, } = segment;
|
|
335
|
+
// route is derived from dir path, not page
|
|
336
|
+
const route = Finder.toCanonicalRoute(pagePath ?? `${dir.replace(/\\/g, '/')}/+page.tsx`);
|
|
337
|
+
const params = Finder.getParams(dir);
|
|
338
|
+
const depth = Finder.getDepth(route);
|
|
339
|
+
const isDynamic = route.includes(':');
|
|
340
|
+
const isWildcard = route.includes('*');
|
|
341
|
+
// effective mode for this segment; start from global config then
|
|
342
|
+
// apply shell/layout/page overrides
|
|
343
|
+
let currentPrerenderMode = this.config?.prerender ?? false;
|
|
344
|
+
/**
|
|
345
|
+
* Apply explicit prerender mode overrides in inheritance order
|
|
346
|
+
*/
|
|
347
|
+
function applyPrerenderMode(flag) {
|
|
348
|
+
if (flag === undefined)
|
|
349
|
+
return;
|
|
350
|
+
currentPrerenderMode = flag;
|
|
351
|
+
}
|
|
352
|
+
const shellImport = Finder.getImportPath(shellPath);
|
|
353
|
+
const shellId = `${Build.EntryKind.SHELL}${Bun.hash(shellImport)}`;
|
|
354
|
+
const layoutIds = [];
|
|
355
|
+
const unauthorizedIds = [];
|
|
356
|
+
const forbiddenIds = [];
|
|
357
|
+
const notFoundIds = [];
|
|
358
|
+
const serverErrorIds = [];
|
|
359
|
+
const loadingIds = [];
|
|
360
|
+
const middlewareIds = [];
|
|
361
|
+
// check shell prerender
|
|
362
|
+
if (!processed.has(shellPath)) {
|
|
363
|
+
prerenderCache.set(shellPath, await Prerender.Build.getStaticFlag(shellPath, this.buildContext));
|
|
364
|
+
imports.components.static.set(shellId, shellImport);
|
|
365
|
+
processed.add(shellPath);
|
|
366
|
+
}
|
|
367
|
+
applyPrerenderMode(prerenderCache.get(shellPath));
|
|
368
|
+
for (const layoutPath of layoutPaths) {
|
|
369
|
+
if (!layoutPath) {
|
|
370
|
+
layoutIds.push(null);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const layoutImport = Finder.getImportPath(layoutPath);
|
|
374
|
+
const layoutId = `${Build.EntryKind.LAYOUT}${Bun.hash(layoutImport)}`;
|
|
375
|
+
if (!processed.has(layoutPath)) {
|
|
376
|
+
prerenderCache.set(layoutPath, await Prerender.Build.getStaticFlag(layoutPath, this.buildContext));
|
|
377
|
+
imports.components.dynamic.set(layoutId, layoutImport);
|
|
378
|
+
processed.add(layoutPath);
|
|
379
|
+
}
|
|
380
|
+
applyPrerenderMode(prerenderCache.get(layoutPath));
|
|
381
|
+
layoutIds.push(layoutId);
|
|
382
|
+
}
|
|
383
|
+
for (const unauthorizedPath of unauthorizedPaths) {
|
|
384
|
+
if (!unauthorizedPath) {
|
|
385
|
+
unauthorizedIds.push(null);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const unauthorizedImport = Finder.getImportPath(unauthorizedPath);
|
|
389
|
+
const unauthorizedId = `${Build.EntryKind['401']}${Bun.hash(unauthorizedImport)}`;
|
|
390
|
+
unauthorizedIds.push(unauthorizedId);
|
|
391
|
+
if (!processed.has(unauthorizedPath)) {
|
|
392
|
+
imports.components.dynamic.set(unauthorizedId, unauthorizedImport);
|
|
393
|
+
processed.add(unauthorizedPath);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
for (const forbiddenPath of forbiddenPaths) {
|
|
397
|
+
if (!forbiddenPath) {
|
|
398
|
+
forbiddenIds.push(null);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const forbiddenImport = Finder.getImportPath(forbiddenPath);
|
|
402
|
+
const forbiddenId = `${Build.EntryKind['403']}${Bun.hash(forbiddenImport)}`;
|
|
403
|
+
forbiddenIds.push(forbiddenId);
|
|
404
|
+
if (!processed.has(forbiddenPath)) {
|
|
405
|
+
imports.components.dynamic.set(forbiddenId, forbiddenImport);
|
|
406
|
+
processed.add(forbiddenPath);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const notFoundPath of notFoundPaths) {
|
|
410
|
+
// hole if level does not declare a 404 boundary.
|
|
411
|
+
// Keep slot so indices match layouts
|
|
412
|
+
if (!notFoundPath) {
|
|
413
|
+
notFoundIds.push(null);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const notFoundImport = Finder.getImportPath(notFoundPath);
|
|
417
|
+
const notFoundId = `${Build.EntryKind['404']}${Bun.hash(notFoundImport)}`;
|
|
418
|
+
notFoundIds.push(notFoundId);
|
|
419
|
+
// dedupe imports but still assign the slot for this route
|
|
420
|
+
if (!processed.has(notFoundPath)) {
|
|
421
|
+
imports.components.dynamic.set(notFoundId, notFoundImport);
|
|
422
|
+
processed.add(notFoundPath);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
for (const serverErrorPath of serverErrorPaths) {
|
|
426
|
+
if (!serverErrorPath) {
|
|
427
|
+
serverErrorIds.push(null);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const serverErrorImport = Finder.getImportPath(serverErrorPath);
|
|
431
|
+
const serverErrorId = `${Build.EntryKind['500']}${Bun.hash(serverErrorImport)}`;
|
|
432
|
+
serverErrorIds.push(serverErrorId);
|
|
433
|
+
if (!processed.has(serverErrorPath)) {
|
|
434
|
+
imports.components.dynamic.set(serverErrorId, serverErrorImport);
|
|
435
|
+
processed.add(serverErrorPath);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
for (const loaderPath of loaderPaths) {
|
|
439
|
+
// hole if level does not declare a loader.
|
|
440
|
+
// Keep slot so indices match layouts
|
|
441
|
+
if (!loaderPath) {
|
|
442
|
+
loadingIds.push(null);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const loaderImport = Finder.getImportPath(loaderPath);
|
|
446
|
+
const loaderId = `${Build.EntryKind.LOADING}${Bun.hash(loaderImport)}`;
|
|
447
|
+
loadingIds.push(loaderId);
|
|
448
|
+
// dedupe imports but still assign the slot for this route
|
|
449
|
+
if (!processed.has(loaderPath)) {
|
|
450
|
+
imports.components.dynamic.set(loaderId, loaderImport);
|
|
451
|
+
processed.add(loaderPath);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (const middlewarePath of middlewarePaths) {
|
|
455
|
+
if (!middlewarePath) {
|
|
456
|
+
middlewareIds.push(null);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const middlewareImport = Finder.getImportPath(middlewarePath);
|
|
460
|
+
const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Bun.hash(middlewareImport)}`;
|
|
461
|
+
middlewareIds.push(middlewareId);
|
|
462
|
+
if (!processed.has(middlewarePath)) {
|
|
463
|
+
// route scanning only tells us this is a +middleware file path so
|
|
464
|
+
// we still validate that the module actually exports middleware
|
|
465
|
+
if (!(await this.buildContext.exportReader.has(middlewarePath, 'middleware'))) {
|
|
466
|
+
throw new Error(`Missing middleware export in ${middlewarePath}`);
|
|
467
|
+
}
|
|
468
|
+
imports.middlewares.static.set(middlewareId, middlewareImport);
|
|
469
|
+
processed.add(middlewarePath);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// generate entry id based on page if exists, otherwise dir
|
|
473
|
+
const entryId = pagePath
|
|
474
|
+
? `${Build.EntryKind.PAGE}${Bun.hash(Finder.getImportPath(pagePath))}`
|
|
475
|
+
: `${Build.EntryKind.PAGE}${Bun.hash(route)}`;
|
|
476
|
+
if (pagePath) {
|
|
477
|
+
const pagePrerender = await Prerender.Build.getStaticFlag(pagePath, this.buildContext);
|
|
478
|
+
applyPrerenderMode(pagePrerender);
|
|
479
|
+
imports.components.dynamic.set(entryId, Finder.getImportPath(pagePath));
|
|
480
|
+
processed.add(pagePath);
|
|
481
|
+
}
|
|
482
|
+
const shouldPrerender = currentPrerenderMode !== false;
|
|
483
|
+
const prerenderMode = shouldPrerender
|
|
484
|
+
? currentPrerenderMode
|
|
485
|
+
: false;
|
|
486
|
+
if (shouldPrerender) {
|
|
487
|
+
if (!isDynamic && !isWildcard) {
|
|
488
|
+
prerenderedRoutes.add(route);
|
|
489
|
+
}
|
|
490
|
+
else if (pagePath) {
|
|
491
|
+
const staticParams = await Prerender.Build.getStaticParams(pagePath, this.buildContext);
|
|
492
|
+
for (const r of Prerender.Build.getDynamicRouteList(route, params, staticParams)) {
|
|
493
|
+
prerenderedRoutes.add(r);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const entry = {
|
|
498
|
+
__id: entryId,
|
|
499
|
+
__path: route,
|
|
500
|
+
__params: params,
|
|
501
|
+
__kind: Build.EntryKind.PAGE,
|
|
502
|
+
__depth: depth,
|
|
503
|
+
method: 'get',
|
|
504
|
+
paths: {
|
|
505
|
+
layouts: [shellPath, ...layoutPaths].map(layout => layout ? Finder.getImportPath(layout) : null),
|
|
506
|
+
'401s': unauthorizedPaths.map(unauthorized => unauthorized ? Finder.getImportPath(unauthorized) : null),
|
|
507
|
+
'403s': forbiddenPaths.map(forbidden => forbidden ? Finder.getImportPath(forbidden) : null),
|
|
508
|
+
'404s': notFoundPaths.map(notFound => notFound ? Finder.getImportPath(notFound) : null),
|
|
509
|
+
'500s': serverErrorPaths.map(serverError => serverError ? Finder.getImportPath(serverError) : null),
|
|
510
|
+
loaders: loaderPaths.map(loader => loader ? Finder.getImportPath(loader) : null),
|
|
511
|
+
middlewares: middlewarePaths.map(middleware => middleware ? Finder.getImportPath(middleware) : null),
|
|
512
|
+
page: pagePath ? Finder.getImportPath(pagePath) : null,
|
|
513
|
+
},
|
|
514
|
+
prerender: prerenderMode,
|
|
515
|
+
dynamic: isDynamic,
|
|
516
|
+
wildcard: isWildcard,
|
|
517
|
+
};
|
|
518
|
+
if (manifest[route]) {
|
|
519
|
+
if (Array.isArray(manifest[route])) {
|
|
520
|
+
manifest[route].push(entry);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
manifest[route] = [manifest[route], entry];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
manifest[route] = entry;
|
|
528
|
+
}
|
|
529
|
+
modules[entryId] = {
|
|
530
|
+
shellId,
|
|
531
|
+
layoutIds,
|
|
532
|
+
pageId: pagePath ? entryId : undefined,
|
|
533
|
+
'401Ids': unauthorizedIds,
|
|
534
|
+
'403Ids': forbiddenIds,
|
|
535
|
+
'404Ids': notFoundIds,
|
|
536
|
+
'500Ids': serverErrorIds,
|
|
537
|
+
loadingIds,
|
|
538
|
+
middlewareIds,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
if (err instanceof Error &&
|
|
543
|
+
err.message.startsWith('Missing middleware export')) {
|
|
544
|
+
throw err;
|
|
545
|
+
}
|
|
546
|
+
logger.error('[Build:Finder:process]: failed to process segment', err);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
for (const endpoint of res.endpoints) {
|
|
550
|
+
try {
|
|
551
|
+
const endpointFilePath = endpoint.file;
|
|
552
|
+
const endpointMiddlewarePaths = endpoint.middlewares;
|
|
553
|
+
if (!this.buildContext || processed.has(endpointFilePath))
|
|
554
|
+
continue;
|
|
555
|
+
const route = Finder.toCanonicalRoute(endpointFilePath);
|
|
556
|
+
const params = Finder.getParams(endpointFilePath);
|
|
557
|
+
const endpointExports = await this.buildContext.exportReader.exports(endpointFilePath);
|
|
558
|
+
const group = [];
|
|
559
|
+
for (const method of endpointExports) {
|
|
560
|
+
if (!HTTP_VERBS.includes(method)) {
|
|
561
|
+
logger.warn('[Build:Finder:process]', `Ignoring unsupported HTTP verb: ${method} in ${endpointFilePath}`);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const m = method.toLowerCase();
|
|
565
|
+
const endpointId = `${Build.EntryKind.ENDPOINT}${Bun.hash(Finder.getImportPath(endpointFilePath))}_${m}`;
|
|
566
|
+
const middlewareIds = await Promise.all(endpointMiddlewarePaths.map(async middlewarePath => {
|
|
567
|
+
if (!middlewarePath)
|
|
568
|
+
return null;
|
|
569
|
+
const middlewareImport = Finder.getImportPath(middlewarePath);
|
|
570
|
+
const middlewareId = `${Build.EntryKind.MIDDLEWARE}${Bun.hash(middlewareImport)}`;
|
|
571
|
+
if (!processed.has(middlewarePath)) {
|
|
572
|
+
// endpoint middleware discovery gives us file paths, not proof of the export
|
|
573
|
+
// so check the module shape before we register the import
|
|
574
|
+
if (!(await this.buildContext.exportReader.has(middlewarePath, 'middleware'))) {
|
|
575
|
+
throw new Error(`Missing middleware export in ${middlewarePath}`);
|
|
576
|
+
}
|
|
577
|
+
imports.middlewares.static.set(middlewareId, middlewareImport);
|
|
578
|
+
processed.add(middlewarePath);
|
|
579
|
+
}
|
|
580
|
+
return middlewareId;
|
|
581
|
+
}));
|
|
582
|
+
group.push({
|
|
583
|
+
__id: endpointId,
|
|
584
|
+
__path: route,
|
|
585
|
+
__params: params,
|
|
586
|
+
__kind: Build.EntryKind.ENDPOINT,
|
|
587
|
+
method: m,
|
|
588
|
+
middlewares: endpointMiddlewarePaths.map(middlewarePath => middlewarePath ? Finder.getImportPath(middlewarePath) : null),
|
|
589
|
+
});
|
|
590
|
+
imports.endpoints.static.set(endpointId, Finder.getImportPath(endpointFilePath));
|
|
591
|
+
modules[endpointId] = { endpointId, middlewareIds };
|
|
592
|
+
processed.add(endpointFilePath);
|
|
593
|
+
}
|
|
594
|
+
const entry = group.length === 1 ? group[0] : group;
|
|
595
|
+
if (endpointMiddlewarePaths.length) {
|
|
596
|
+
modules[route] = {
|
|
597
|
+
...(modules[route] ?? {}),
|
|
598
|
+
middlewareIds: endpointMiddlewarePaths.map(middlewarePath => middlewarePath
|
|
599
|
+
? `${Build.EntryKind.MIDDLEWARE}${Bun.hash(Finder.getImportPath(middlewarePath))}`
|
|
600
|
+
: null),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (manifest[route]) {
|
|
604
|
+
if (Array.isArray(manifest[route])) {
|
|
605
|
+
manifest[route] = [
|
|
606
|
+
...manifest[route],
|
|
607
|
+
...(Array.isArray(entry) ? entry : [entry]),
|
|
608
|
+
];
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
manifest[route] = [
|
|
612
|
+
manifest[route],
|
|
613
|
+
...(Array.isArray(entry) ? entry : [entry]),
|
|
614
|
+
];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
manifest[route] = entry;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
if (err instanceof Error &&
|
|
623
|
+
err.message.startsWith('Missing middleware export')) {
|
|
624
|
+
throw err;
|
|
625
|
+
}
|
|
626
|
+
logger.error('[Build:Finder:process]: failed to process route', err);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return { manifest, imports, modules, prerenderedRoutes };
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
Build.Finder = Finder;
|
|
633
|
+
})(Build || (Build = {}));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Solas } from '../../solas';
|
|
2
|
+
import { AUTOGEN_MSG } from './utils';
|
|
3
|
+
/**
|
|
4
|
+
* Generates the code to create an exported config object
|
|
5
|
+
*/
|
|
6
|
+
export function writeConfig(config) {
|
|
7
|
+
return `
|
|
8
|
+
${AUTOGEN_MSG}
|
|
9
|
+
|
|
10
|
+
import type { PluginConfig } from '${Solas.Config.PKG_NAME}'
|
|
11
|
+
import { Logger } from '${Solas.Config.PKG_NAME}/utils/logger'
|
|
12
|
+
|
|
13
|
+
const config = ${JSON.stringify(config, null, 2)} as const satisfies PluginConfig
|
|
14
|
+
|
|
15
|
+
if (config.logger?.level) Logger.defaultLevel = config.logger.level
|
|
16
|
+
|
|
17
|
+
export { config }
|
|
18
|
+
`.trim();
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the RSC entry code
|
|
3
|
+
*/
|
|
4
|
+
export declare function writeRSCEntry(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Generates the SSR entry code
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeSSREntry(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generates the browser entry code
|
|
11
|
+
*/
|
|
12
|
+
export declare function writeBrowserEntry(): string;
|