@jk2908/solas 0.3.8 → 0.4.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/CHANGELOG.md +20 -0
- package/README.md +66 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -2
- package/dist/internal/browser-router/link.d.ts +1 -1
- package/dist/internal/browser-router/link.js +1 -1
- package/dist/internal/browser-router/router.d.ts +2 -165
- package/dist/internal/browser-router/router.js +3 -99
- package/dist/internal/browser-router/shared.d.ts +169 -0
- package/dist/internal/browser-router/shared.js +71 -0
- package/dist/internal/browser-router/use-router.d.ts +1 -1
- package/dist/internal/codegen/environments.js +5 -4
- package/dist/internal/env/rsc.d.ts +2 -2
- package/dist/internal/env/rsc.js +159 -62
- package/dist/internal/http-router/create-http-router.d.ts +1 -1
- package/dist/internal/http-router/create-http-router.js +4 -2
- package/dist/internal/http-router/router.d.ts +4 -14
- package/dist/internal/http-router/router.js +32 -59
- package/dist/internal/navigation/http-exception.d.ts +4 -4
- package/dist/internal/navigation/http-exception.js +4 -5
- package/dist/internal/navigation/redirect-boundary.js +2 -11
- package/dist/internal/navigation/redirect.d.ts +3 -0
- package/dist/internal/navigation/redirect.js +51 -0
- package/dist/internal/postbuild.d.ts +1 -0
- package/dist/{cli/build.js → internal/postbuild.js} +13 -48
- package/dist/internal/prerender.d.ts +4 -19
- package/dist/internal/prerender.js +8 -98
- package/dist/internal/public-files.d.ts +18 -0
- package/dist/internal/public-files.js +63 -0
- package/dist/internal/resolver.d.ts +23 -23
- package/dist/internal/server/actions.d.ts +2 -5
- package/dist/internal/server/actions.js +4 -35
- package/dist/internal/server/csrf.d.ts +14 -0
- package/dist/internal/server/csrf.js +98 -0
- package/dist/router.d.ts +1 -0
- package/dist/router.js +1 -0
- package/dist/solas.d.ts +12 -1
- package/dist/solas.js +116 -1
- package/dist/types.d.ts +8 -3
- package/dist/utils/base-path.d.ts +14 -0
- package/dist/utils/base-path.js +85 -0
- package/dist/utils/export-reader.js +10 -4
- package/package.json +4 -7
- package/dist/cli/build.d.ts +0 -7
- package/dist/cli/dev.d.ts +0 -4
- package/dist/cli/dev.js +0 -13
- package/dist/cli/preview.d.ts +0 -1
- package/dist/cli/preview.js +0 -47
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -28
package/dist/solas.js
CHANGED
|
@@ -12,12 +12,14 @@ var Solas;
|
|
|
12
12
|
Config.ENTRY_RSC = 'entry.rsc.tsx';
|
|
13
13
|
Config.ENTRY_SSR = 'entry.ssr.tsx';
|
|
14
14
|
Config.ENTRY_BROWSER = 'entry.browser.tsx';
|
|
15
|
-
Config.ASSETS_DIR =
|
|
15
|
+
Config.ASSETS_DIR = `_${Config.SLUG}`;
|
|
16
|
+
Config.PUBLIC_DIR = 'public';
|
|
16
17
|
Config.$ = Symbol(Config.SLUG);
|
|
17
18
|
Config.REQUEST_META_KEY = `__${Config.SLUG.toUpperCase()}__`;
|
|
18
19
|
Config.LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
19
20
|
Config.PRERENDER_MODES = ['full', 'ppr', false];
|
|
20
21
|
Config.TRAILING_SLASH_MODES = ['always', 'never', 'ignore'];
|
|
22
|
+
Config.RUNTIME_MANIFEST = 'runtime-manifest.json';
|
|
21
23
|
const CONFIG_KEYS = new Set([
|
|
22
24
|
'port',
|
|
23
25
|
'logger',
|
|
@@ -25,6 +27,7 @@ var Solas;
|
|
|
25
27
|
'precompress',
|
|
26
28
|
'prerender',
|
|
27
29
|
'sitemap',
|
|
30
|
+
'trustedOrigins',
|
|
28
31
|
'trailingSlash',
|
|
29
32
|
'url',
|
|
30
33
|
]);
|
|
@@ -67,6 +70,33 @@ var Solas;
|
|
|
67
70
|
errors.push('config.precompress must be a boolean');
|
|
68
71
|
}
|
|
69
72
|
}
|
|
73
|
+
if ('trustedOrigins' in input && input.trustedOrigins !== undefined) {
|
|
74
|
+
if (!Array.isArray(input.trustedOrigins)) {
|
|
75
|
+
errors.push('config.trustedOrigins must be an array of origins');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
for (const [index, value] of input.trustedOrigins.entries()) {
|
|
79
|
+
if (typeof value !== 'string') {
|
|
80
|
+
errors.push(`config.trustedOrigins[${index}] must be a string`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const url = new URL(value);
|
|
85
|
+
const canonical = value.replace(/\/$/, '');
|
|
86
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
87
|
+
errors.push(`config.trustedOrigins[${index}] must use http:// or https://`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (canonical !== url.origin) {
|
|
91
|
+
errors.push(`config.trustedOrigins[${index}] must be an origin without a path, query, or hash`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
errors.push(`config.trustedOrigins[${index}] must be a valid URL origin`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
70
100
|
if ('sitemap' in input && input.sitemap !== undefined && input.sitemap !== false) {
|
|
71
101
|
if (typeof input.sitemap !== 'boolean' && typeof input.sitemap !== 'object') {
|
|
72
102
|
errors.push('config.sitemap must be a boolean or an object with a routes function');
|
|
@@ -129,6 +159,91 @@ var Solas;
|
|
|
129
159
|
return value;
|
|
130
160
|
}
|
|
131
161
|
Solas.getVersion = getVersion;
|
|
162
|
+
let Runtime;
|
|
163
|
+
(function (Runtime) {
|
|
164
|
+
const manifestCache = new Map();
|
|
165
|
+
function getManifestPath(outDir) {
|
|
166
|
+
return [outDir, Config.GENERATED_DIR, Config.RUNTIME_MANIFEST]
|
|
167
|
+
.map((part, index) => {
|
|
168
|
+
const normalised = part.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
169
|
+
if (index === 0)
|
|
170
|
+
return normalised.replace(/\/+$/, '');
|
|
171
|
+
return normalised.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
172
|
+
})
|
|
173
|
+
.join('/');
|
|
174
|
+
}
|
|
175
|
+
Runtime.getManifestPath = getManifestPath;
|
|
176
|
+
async function loadManifest(outDir) {
|
|
177
|
+
if (manifestCache.has(outDir)) {
|
|
178
|
+
return manifestCache.get(outDir) ?? null;
|
|
179
|
+
}
|
|
180
|
+
const file = Bun.file(getManifestPath(outDir));
|
|
181
|
+
if (!(await file.exists())) {
|
|
182
|
+
manifestCache.set(outDir, null);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const value = JSON.parse(await file.text());
|
|
187
|
+
if (!isRecord(value)) {
|
|
188
|
+
manifestCache.set(outDir, null);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const artifacts = value.artifacts ?? value.routes;
|
|
192
|
+
const publicFiles = value.publicFiles;
|
|
193
|
+
if (!isRecord(artifacts)) {
|
|
194
|
+
manifestCache.set(outDir, null);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
if (publicFiles !== undefined && !Array.isArray(publicFiles)) {
|
|
198
|
+
manifestCache.set(outDir, null);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
for (const entry of Object.values(artifacts)) {
|
|
202
|
+
if (!isRecord(entry)) {
|
|
203
|
+
manifestCache.set(outDir, null);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const { mode, files } = entry;
|
|
207
|
+
if (mode !== 'full' && mode !== 'ppr') {
|
|
208
|
+
manifestCache.set(outDir, null);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (files !== undefined) {
|
|
212
|
+
if (!Array.isArray(files)) {
|
|
213
|
+
manifestCache.set(outDir, null);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
for (const file of files) {
|
|
217
|
+
if (file !== 'html' &&
|
|
218
|
+
file !== 'prelude' &&
|
|
219
|
+
file !== 'postponed' &&
|
|
220
|
+
file !== 'metadata') {
|
|
221
|
+
manifestCache.set(outDir, null);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const entry of publicFiles ?? []) {
|
|
228
|
+
if (typeof entry !== 'string' || !entry.startsWith('/')) {
|
|
229
|
+
manifestCache.set(outDir, null);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const manifest = {
|
|
234
|
+
artifacts: artifacts,
|
|
235
|
+
publicFiles: new Set(publicFiles ?? []),
|
|
236
|
+
};
|
|
237
|
+
manifestCache.set(outDir, manifest);
|
|
238
|
+
return manifest;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
manifestCache.set(outDir, null);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
Runtime.loadManifest = loadManifest;
|
|
246
|
+
})(Runtime = Solas.Runtime || (Solas.Runtime = {}));
|
|
132
247
|
let Events;
|
|
133
248
|
(function (Events) {
|
|
134
249
|
Events.names = {
|
package/dist/types.d.ts
CHANGED
|
@@ -2,35 +2,38 @@ type BunRequest = Request & {
|
|
|
2
2
|
params?: Record<string, string | string[]>;
|
|
3
3
|
};
|
|
4
4
|
import { ExportReader } from './utils/export-reader.js';
|
|
5
|
+
import type { BrowserRouter } from './internal/browser-router/shared.js';
|
|
5
6
|
import type { Build } from './internal/build.js';
|
|
6
7
|
import type { HttpRouter } from './internal/http-router/router.js';
|
|
7
8
|
import type { Metadata } from './internal/metadata.js';
|
|
8
9
|
import type { HttpException } from './internal/navigation/http-exception.js';
|
|
9
|
-
import { BrowserRouter } from './internal/browser-router/router.js';
|
|
10
10
|
import { Solas } from './solas.js';
|
|
11
11
|
export type LogLevel = (typeof Solas.Config.LOG_LEVELS)[number];
|
|
12
|
+
type Origin = `http://${string}` | `https://${string}`;
|
|
12
13
|
type PluginConfigBase = {
|
|
13
14
|
port?: number;
|
|
14
15
|
precompress?: boolean;
|
|
15
16
|
prerender?: Route.Prerender;
|
|
16
17
|
metadata?: Metadata.Item;
|
|
17
18
|
trailingSlash?: (typeof Solas.Config.TRAILING_SLASH_MODES)[number];
|
|
19
|
+
trustedOrigins?: readonly Origin[];
|
|
18
20
|
readonly logger?: {
|
|
19
21
|
level?: LogLevel;
|
|
20
22
|
};
|
|
21
23
|
};
|
|
22
24
|
export type PluginConfig = PluginConfigBase & ({
|
|
23
|
-
url:
|
|
25
|
+
url: Origin;
|
|
24
26
|
sitemap: true | {
|
|
25
27
|
routes: (existing: string[]) => string[] | Promise<string[]>;
|
|
26
28
|
};
|
|
27
29
|
} | {
|
|
28
|
-
url?:
|
|
30
|
+
url?: Origin;
|
|
29
31
|
sitemap?: false;
|
|
30
32
|
});
|
|
31
33
|
export type RuntimeConfig = PluginConfig & {
|
|
32
34
|
precompress: NonNullable<PluginConfig['precompress']>;
|
|
33
35
|
trailingSlash: NonNullable<PluginConfig['trailingSlash']>;
|
|
36
|
+
trustedOrigins: NonNullable<PluginConfig['trustedOrigins']>;
|
|
34
37
|
};
|
|
35
38
|
export type BuildContext = {
|
|
36
39
|
prerenderRoutes: Set<string>;
|
|
@@ -99,6 +102,8 @@ export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' |
|
|
|
99
102
|
export type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
100
103
|
export type LooseNumber<T extends number> = T | (number & {});
|
|
101
104
|
export type BuildManifest = {
|
|
105
|
+
base: string;
|
|
106
|
+
publicFiles: string[];
|
|
102
107
|
prerenderRoutes: string[];
|
|
103
108
|
sitemapRoutes: string[];
|
|
104
109
|
precompress: boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare namespace BasePath {
|
|
2
|
+
/**
|
|
3
|
+
* Normalise a base path so every check uses the same shape
|
|
4
|
+
*/
|
|
5
|
+
function normalise(value: string | null | undefined): string;
|
|
6
|
+
/**
|
|
7
|
+
* Strip the base path from a request path
|
|
8
|
+
*/
|
|
9
|
+
function strip(pathname: string, base: string | null | undefined): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Add the base path to a path when needed
|
|
12
|
+
*/
|
|
13
|
+
function apply(pathname: string, base: string | null | undefined): string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export { BasePath };
|
|
2
|
+
var BasePath;
|
|
3
|
+
(function (BasePath) {
|
|
4
|
+
/**
|
|
5
|
+
* Normalise a base path so every check uses the same shape
|
|
6
|
+
*/
|
|
7
|
+
function normalise(value) {
|
|
8
|
+
// no base means the app lives at the site root
|
|
9
|
+
if (!value)
|
|
10
|
+
return '/';
|
|
11
|
+
if (value === '/' || value === '.' || value === './')
|
|
12
|
+
return '/';
|
|
13
|
+
let pathname = value.trim();
|
|
14
|
+
if (!pathname)
|
|
15
|
+
return '/';
|
|
16
|
+
// plain path bases are the common case, so keep them cheap
|
|
17
|
+
if (!pathname.startsWith('http://') && !pathname.startsWith('https://')) {
|
|
18
|
+
const hashIndex = pathname.indexOf('#');
|
|
19
|
+
const searchIndex = pathname.indexOf('?');
|
|
20
|
+
const end = hashIndex === -1
|
|
21
|
+
? searchIndex
|
|
22
|
+
: searchIndex === -1
|
|
23
|
+
? hashIndex
|
|
24
|
+
: Math.min(hashIndex, searchIndex);
|
|
25
|
+
if (end >= 0)
|
|
26
|
+
pathname = pathname.slice(0, end);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
try {
|
|
30
|
+
// full urls can still show up here, but we only need the path part
|
|
31
|
+
pathname = new URL(pathname).pathname;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// if parsing fails, fall back to the raw value below
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!pathname || pathname === '.' || pathname === './')
|
|
38
|
+
return '/';
|
|
39
|
+
// keep one stable shape: leading slash, trailing slash
|
|
40
|
+
if (!pathname.startsWith('/'))
|
|
41
|
+
pathname = `/${pathname}`;
|
|
42
|
+
return pathname.endsWith('/') ? pathname : `${pathname}/`;
|
|
43
|
+
}
|
|
44
|
+
BasePath.normalise = normalise;
|
|
45
|
+
/**
|
|
46
|
+
* Strip the base path from a request path
|
|
47
|
+
*/
|
|
48
|
+
function strip(pathname, base) {
|
|
49
|
+
const normalisedBase = normalise(base);
|
|
50
|
+
// root base means there is nothing to strip
|
|
51
|
+
if (normalisedBase === '/')
|
|
52
|
+
return pathname || '/';
|
|
53
|
+
const basePath = normalisedBase.slice(0, -1);
|
|
54
|
+
// treat both '/docs' and '/docs/' as the app root
|
|
55
|
+
if (pathname === basePath || pathname === normalisedBase)
|
|
56
|
+
return '/';
|
|
57
|
+
// paths outside the base do not belong to this app
|
|
58
|
+
if (!pathname.startsWith(`${basePath}/`))
|
|
59
|
+
return null;
|
|
60
|
+
// return the path as the app should see it
|
|
61
|
+
return pathname.slice(basePath.length) || '/';
|
|
62
|
+
}
|
|
63
|
+
BasePath.strip = strip;
|
|
64
|
+
/**
|
|
65
|
+
* Add the base path to a path when needed
|
|
66
|
+
*/
|
|
67
|
+
function apply(pathname, base) {
|
|
68
|
+
const normalisedBase = normalise(base);
|
|
69
|
+
// always work with a path-like value
|
|
70
|
+
const target = pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
71
|
+
// root base means the path can pass through unchanged
|
|
72
|
+
if (normalisedBase === '/')
|
|
73
|
+
return target;
|
|
74
|
+
const basePath = normalisedBase.slice(0, -1);
|
|
75
|
+
// leave it alone if the base is already there
|
|
76
|
+
if (target === basePath || target.startsWith(`${basePath}/`))
|
|
77
|
+
return target;
|
|
78
|
+
// the app root maps to the base path itself
|
|
79
|
+
if (target === '/')
|
|
80
|
+
return normalisedBase;
|
|
81
|
+
// everything else sits underneath the base path
|
|
82
|
+
return `${basePath}${target}`;
|
|
83
|
+
}
|
|
84
|
+
BasePath.apply = apply;
|
|
85
|
+
})(BasePath || (BasePath = {}));
|
|
@@ -84,17 +84,23 @@ export class ExportReader {
|
|
|
84
84
|
* The export must be in the form of `export const|let|var name = <literal>`
|
|
85
85
|
*/
|
|
86
86
|
async literal(filePath, name, validate) {
|
|
87
|
-
|
|
87
|
+
if (!(await this.has(filePath, name)))
|
|
88
|
+
return;
|
|
89
|
+
// transpile first so comments and type-only syntax do not confuse the
|
|
90
|
+
// literal matcher with exports that do not actually exist at runtime
|
|
91
|
+
const code = this.#getTranspiler(filePath).transformSync(await this.raw(filePath));
|
|
88
92
|
// build the matcher from escaped plain-text pieces so arbitrary export names
|
|
89
93
|
// cannot change the regex shape
|
|
90
94
|
const source =
|
|
91
|
-
// match: `export const|let|var `
|
|
92
|
-
'\\
|
|
95
|
+
// match: `export const|let|var ` at statement boundaries
|
|
96
|
+
'(?:^|[;\\n])\\s*export\\s+(?:const|let|var)\\s+' +
|
|
93
97
|
// treat export name as plain text in regex
|
|
94
98
|
name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
|
95
99
|
// capture one supported literal value (string, number, boolean, null)
|
|
96
100
|
'\\s*=\\s*(?<value>(?:"(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\'|\\x60(?:[^\\x60\\\\]|\\\\.)*\\x60|true|false|null|-?\\d+(?:\\.\\d+)?))(?=\\s|;|$)';
|
|
97
|
-
|
|
101
|
+
// multiline mode lets ^ match the start of each transpiled line, so the
|
|
102
|
+
// export regex stays anchored to a real statement boundary instead of the file start
|
|
103
|
+
const text = code.match(new RegExp(source, 'm'))?.groups?.value;
|
|
98
104
|
if (!text)
|
|
99
105
|
return;
|
|
100
106
|
// only support cheap literal parsing here. Anything richer should go through
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jk2908/solas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "A Vite + React meta-framework exploring streaming, Server Components, and partial prerendering. Designed for simplicity and lightness",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
7
7
|
"ppr",
|
|
@@ -17,10 +17,7 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/jk2908/solas.git"
|
|
21
|
-
},
|
|
22
|
-
"bin": {
|
|
23
|
-
"solas": "./dist/cli.js"
|
|
20
|
+
"url": "git+https://github.com/jk2908/solas.git"
|
|
24
21
|
},
|
|
25
22
|
"files": [
|
|
26
23
|
"dist",
|
|
@@ -74,7 +71,7 @@
|
|
|
74
71
|
}
|
|
75
72
|
},
|
|
76
73
|
"scripts": {
|
|
77
|
-
"build": "rm -rf dist && tsgo
|
|
74
|
+
"build": "rm -rf dist && tsgo",
|
|
78
75
|
"lint": "bunx oxlint .",
|
|
79
76
|
"lint:fix": "bunx oxlint --fix .",
|
|
80
77
|
"format": "bunx oxfmt --write .",
|
package/dist/cli/build.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The build command does more than just run vite build - it also handles prerendering and
|
|
3
|
-
* precompressing assets. This is because prerendering needs to run against the built
|
|
4
|
-
* server entry to ensure the same code paths as preview, and precompressing needs
|
|
5
|
-
* to include the prerendered html and json files
|
|
6
|
-
*/
|
|
7
|
-
export declare function build(): Promise<void>;
|
package/dist/cli/dev.d.ts
DELETED
package/dist/cli/dev.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Start the vite development server
|
|
3
|
-
*/
|
|
4
|
-
export async function dev() {
|
|
5
|
-
const proc = Bun.spawn(['bunx', '--bun', 'vite', 'dev'], {
|
|
6
|
-
cwd: process.cwd(),
|
|
7
|
-
stdout: 'inherit',
|
|
8
|
-
stderr: 'inherit',
|
|
9
|
-
stdin: 'inherit',
|
|
10
|
-
env: { ...process.env, NODE_ENV: 'development' },
|
|
11
|
-
});
|
|
12
|
-
await proc.exited;
|
|
13
|
-
}
|
package/dist/cli/preview.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function preview(): Promise<void>;
|
package/dist/cli/preview.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { Logger } from '../utils/logger.js';
|
|
4
|
-
import { Solas } from '../solas.js';
|
|
5
|
-
const logger = new Logger();
|
|
6
|
-
const DEFAULT_PREVIEW_PORT = 4173;
|
|
7
|
-
const [, , , ...args] = process.argv;
|
|
8
|
-
export async function preview() {
|
|
9
|
-
// preview should behave like production, not like vite dev
|
|
10
|
-
process.env.NODE_ENV = 'production';
|
|
11
|
-
const cwd = process.cwd();
|
|
12
|
-
const outDir = path.resolve(cwd, Solas.Config.OUT_DIR);
|
|
13
|
-
const rscDir = path.join(outDir, 'rsc');
|
|
14
|
-
const rscEntry = path.join(rscDir, 'index.js');
|
|
15
|
-
const portFlagIndex = args.findIndex(arg => arg === '--port' || arg === '-p');
|
|
16
|
-
const parsedPort = portFlagIndex >= 0 && args[portFlagIndex + 1]
|
|
17
|
-
? Number(args[portFlagIndex + 1])
|
|
18
|
-
: DEFAULT_PREVIEW_PORT;
|
|
19
|
-
// fail fast if the port is invalid
|
|
20
|
-
if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
|
|
21
|
-
logger.error(`[preview] invalid port: ${args[portFlagIndex + 1] ?? 'undefined'}`);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
// the built server entry handles routing, prerendered html, and ssr here
|
|
25
|
-
try {
|
|
26
|
-
await fs.access(rscEntry);
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
logger.error(`[preview] missing ${path.relative(cwd, rscEntry)} - run \`${Solas.Config.SLUG} build\` from this project directory first`, err);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
const { default: app } = await import(/* @vite-ignore */ rscEntry);
|
|
33
|
-
try {
|
|
34
|
-
// keep the preview server thin and let the app handle requests
|
|
35
|
-
Bun.serve({
|
|
36
|
-
port: parsedPort,
|
|
37
|
-
fetch: app.fetch,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
logger.error(`[preview] failed to start on port ${parsedPort}: ${err}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
logger.info('[preview]', `server running at http://localhost:${parsedPort}`);
|
|
45
|
-
// keep the process running after the server starts
|
|
46
|
-
await new Promise(() => { });
|
|
47
|
-
}
|
package/dist/cli.d.ts
DELETED
package/dist/cli.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { build } from './cli/build.js';
|
|
3
|
-
import { dev } from './cli/dev.js';
|
|
4
|
-
import { preview } from './cli/preview.js';
|
|
5
|
-
import { Solas } from './solas.js';
|
|
6
|
-
// read the subcommand once and dispatch below
|
|
7
|
-
const [, , command] = process.argv;
|
|
8
|
-
switch (command) {
|
|
9
|
-
case 'build':
|
|
10
|
-
await build();
|
|
11
|
-
break;
|
|
12
|
-
case 'dev':
|
|
13
|
-
await dev();
|
|
14
|
-
break;
|
|
15
|
-
case 'preview':
|
|
16
|
-
await preview();
|
|
17
|
-
break;
|
|
18
|
-
default:
|
|
19
|
-
console.log(`
|
|
20
|
-
${Solas.Config.NAME} - cli
|
|
21
|
-
|
|
22
|
-
Commands:
|
|
23
|
-
build Build for production (vite build + prerender + compress)
|
|
24
|
-
dev Start development server
|
|
25
|
-
preview Preview production build (serves prerendered HTML with SSR fallback)
|
|
26
|
-
`);
|
|
27
|
-
process.exit(command ? 1 : 0);
|
|
28
|
-
}
|