@zenithbuild/cli 0.7.0 → 0.7.2
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/README.md +59 -1
- package/dist/adapters/adapter-netlify-static.d.ts +5 -0
- package/dist/adapters/adapter-netlify-static.js +39 -0
- package/dist/adapters/adapter-netlify.d.ts +5 -0
- package/dist/adapters/adapter-netlify.js +129 -0
- package/dist/adapters/adapter-node.d.ts +5 -0
- package/dist/adapters/adapter-node.js +121 -0
- package/dist/adapters/adapter-static.d.ts +5 -0
- package/dist/adapters/adapter-static.js +20 -0
- package/dist/adapters/adapter-types.d.ts +44 -0
- package/dist/adapters/adapter-types.js +65 -0
- package/dist/adapters/adapter-vercel-static.d.ts +5 -0
- package/dist/adapters/adapter-vercel-static.js +36 -0
- package/dist/adapters/adapter-vercel.d.ts +5 -0
- package/dist/adapters/adapter-vercel.js +99 -0
- package/dist/adapters/resolve-adapter.d.ts +5 -0
- package/dist/adapters/resolve-adapter.js +84 -0
- package/dist/adapters/route-rules.d.ts +7 -0
- package/dist/adapters/route-rules.js +88 -0
- package/dist/base-path-html.d.ts +2 -0
- package/dist/base-path-html.js +42 -0
- package/dist/base-path.d.ts +8 -0
- package/dist/base-path.js +74 -0
- package/dist/build/compiler-runtime.d.ts +2 -1
- package/dist/build/compiler-runtime.js +4 -1
- package/dist/build/page-loop.d.ts +2 -2
- package/dist/build/page-loop.js +3 -3
- package/dist/build-output-manifest.d.ts +28 -0
- package/dist/build-output-manifest.js +100 -0
- package/dist/build.js +42 -11
- package/dist/config.d.ts +10 -46
- package/dist/config.js +162 -28
- package/dist/dev-build-session.d.ts +1 -0
- package/dist/dev-build-session.js +4 -5
- package/dist/framework-components/Image.zen +31 -9
- package/dist/images/payload.d.ts +2 -1
- package/dist/images/payload.js +3 -2
- package/dist/images/runtime.js +6 -5
- package/dist/images/service.js +2 -2
- package/dist/images/shared.d.ts +4 -2
- package/dist/images/shared.js +8 -3
- package/dist/index.js +36 -15
- package/dist/manifest.d.ts +14 -2
- package/dist/manifest.js +49 -6
- package/dist/preview.js +61 -25
- package/dist/server-output.d.ts +26 -0
- package/dist/server-output.js +297 -0
- package/dist/server-runtime/node-server.d.ts +2 -0
- package/dist/server-runtime/node-server.js +354 -0
- package/dist/server-runtime/route-render.d.ts +64 -0
- package/dist/server-runtime/route-render.js +273 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ The command-line interface for developing and building Zenith applications.
|
|
|
8
8
|
## Canonical Docs
|
|
9
9
|
|
|
10
10
|
- CLI contract: `../zenith-docs/documentation/cli-contract.md`
|
|
11
|
+
- Deployment targets guide: `/Users/judahsullivan/Personal/zenithbuild-monorepo/docs/documentation/guides/deployment-targets.md`
|
|
11
12
|
- Script server/data contract: `../zenith-docs/documentation/contracts/server-data.md`
|
|
13
|
+
- Server output contract: `./SERVER_OUTPUT_CONTRACT.md`
|
|
12
14
|
|
|
13
15
|
## Overview
|
|
14
16
|
|
|
@@ -21,6 +23,62 @@ The command-line interface for developing and building Zenith applications.
|
|
|
21
23
|
- **Plugin Management**: Easily add and remove Zenith plugins.
|
|
22
24
|
- **Preview**: Test your production builds locally.
|
|
23
25
|
|
|
26
|
+
## Config Baseline
|
|
27
|
+
|
|
28
|
+
Current top-level `zenith.config.js` keys:
|
|
29
|
+
|
|
30
|
+
- `router`
|
|
31
|
+
- `embeddedMarkupExpressions`
|
|
32
|
+
- `typescriptDefault`
|
|
33
|
+
- `outDir`
|
|
34
|
+
- `pagesDir`
|
|
35
|
+
- `basePath`
|
|
36
|
+
- `target`
|
|
37
|
+
- `adapter`
|
|
38
|
+
- `strictDomLints`
|
|
39
|
+
- `images`
|
|
40
|
+
|
|
41
|
+
There is no separate `assetPrefix` config. Public framework asset URLs follow `basePath`.
|
|
42
|
+
|
|
43
|
+
`pagesDir` resolution:
|
|
44
|
+
|
|
45
|
+
- If `pagesDir` is set, the CLI uses that path relative to the project root.
|
|
46
|
+
- If `pagesDir` is not set, the CLI checks `pages/` first, then `src/pages/`, then falls back to the default `pages` path.
|
|
47
|
+
|
|
48
|
+
`basePath` behavior:
|
|
49
|
+
|
|
50
|
+
- `basePath` defaults to `/`.
|
|
51
|
+
- Canonical route paths stay base-path free in manifests and route classification.
|
|
52
|
+
- Public app URLs, bundled asset URLs, router URLs, `/_zenith/image`, and `/__zenith/route-check` are prefixed with `basePath`.
|
|
53
|
+
- Static emitted files stay adapter-neutral; adapters map the public base path to those canonical outputs.
|
|
54
|
+
|
|
55
|
+
`router` behavior:
|
|
56
|
+
|
|
57
|
+
- `router: true` enables client router bootstrap/runtime injection.
|
|
58
|
+
- `router: false` disables client router bootstrap/runtime injection.
|
|
59
|
+
- `assets/router-manifest.json` may still be emitted as an internal preview artifact. Its presence does not mean client router mode is enabled.
|
|
60
|
+
|
|
61
|
+
`target` / `adapter` behavior:
|
|
62
|
+
|
|
63
|
+
- `target` is the shorthand deployment target. Phase 1 defaults loaded config to `target: 'static'`.
|
|
64
|
+
- `adapter` is the explicit adapter object form and is mutually exclusive with `target`.
|
|
65
|
+
- `vercel-static` emits a Vercel Build Output API layout rooted at `outDir`.
|
|
66
|
+
- `netlify-static` emits a Netlify publish directory rooted at `outDir`, including generated `_redirects` rewrites for dynamic prerendered routes.
|
|
67
|
+
- `vercel` emits a Vercel Build Output API layout with packaged route functions for server-classified routes and static rewrites for prerendered dynamic routes.
|
|
68
|
+
- `netlify` emits a deploy root with `publish/`, `functions/`, `netlify.toml`, and generated `_redirects` that force server-classified routes through packaged functions.
|
|
69
|
+
- `node` emits a standalone Node artifact rooted at `outDir` with `index.js`, `package.json`, `manifest.json`, `static/`, and `server/`.
|
|
70
|
+
|
|
71
|
+
Server-capable target contract:
|
|
72
|
+
|
|
73
|
+
- Route classification stays upstream in the manifest and server package layers.
|
|
74
|
+
- `.zenith-output/server` is the canonical packaged server contract consumed by adapters.
|
|
75
|
+
- Adapters package classified output into host layouts; they do not reinterpret route meaning.
|
|
76
|
+
|
|
77
|
+
Current limitations:
|
|
78
|
+
|
|
79
|
+
- There is no separate `assetPrefix` knob. Assets intentionally follow `basePath`.
|
|
80
|
+
- `vercel` and `netlify` do not yet emit a deployed `/_zenith/image` endpoint. The `node` target does.
|
|
81
|
+
|
|
24
82
|
## Commands
|
|
25
83
|
|
|
26
84
|
### `zenith dev`
|
|
@@ -30,7 +88,7 @@ Starts the development server on `localhost:3000`.
|
|
|
30
88
|
Compiles and bundles your application for production.
|
|
31
89
|
|
|
32
90
|
### `zenith preview`
|
|
33
|
-
|
|
91
|
+
Previews the locally built target contract for verification. Static targets serve built files; `target: 'node'` boots the built Node artifact.
|
|
34
92
|
|
|
35
93
|
### `zenith add <plugin>`
|
|
36
94
|
Installs and configures a Zenith plugin.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createNetlifyBasePathAssetRules, createNetlifyRewriteRules } from './route-rules.js';
|
|
4
|
+
function createRedirectsFile(manifest) {
|
|
5
|
+
const lines = [
|
|
6
|
+
'# Generated by Zenith netlify-static adapter',
|
|
7
|
+
...createNetlifyBasePathAssetRules(manifest.base_path)
|
|
8
|
+
];
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
for (const route of manifest.routes) {
|
|
11
|
+
for (const line of createNetlifyRewriteRules(route, manifest.base_path)) {
|
|
12
|
+
if (seen.has(line)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
seen.add(line);
|
|
16
|
+
lines.push(line);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return `${lines.join('\n')}\n`;
|
|
20
|
+
}
|
|
21
|
+
export const netlifyStaticAdapter = {
|
|
22
|
+
name: 'netlify-static',
|
|
23
|
+
validateRoutes(manifest) {
|
|
24
|
+
const serverRoutes = manifest.filter((entry) => entry.render_mode === 'server');
|
|
25
|
+
if (serverRoutes.length === 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const first = serverRoutes[0];
|
|
29
|
+
throw new Error(`[Zenith:Build] target "netlify-static" cannot emit server-rendered routes. ` +
|
|
30
|
+
`Route "${first.path}" (${first.file}) requires render_mode="server".`);
|
|
31
|
+
},
|
|
32
|
+
async adapt(options) {
|
|
33
|
+
const staticDir = join(options.coreOutput, 'static');
|
|
34
|
+
await rm(options.outDir, { recursive: true, force: true });
|
|
35
|
+
await mkdir(options.outDir, { recursive: true });
|
|
36
|
+
await cp(staticDir, options.outDir, { recursive: true, force: true });
|
|
37
|
+
await writeFile(join(options.outDir, '_redirects'), createRedirectsFile(options.manifest), 'utf8');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { prependBasePath } from '../base-path.js';
|
|
4
|
+
import { compareRouteSpecificity } from '../server/resolve-request-route.js';
|
|
5
|
+
import { createNetlifyBasePathAssetRules, createNetlifyRewriteRules } from './route-rules.js';
|
|
6
|
+
function buildNetlifyServerRules(route, basePath = '/') {
|
|
7
|
+
const destination = `/.netlify/functions/__zenith_${route.name}`;
|
|
8
|
+
if (!Array.isArray(route.params) || route.params.length === 0) {
|
|
9
|
+
return [`${prependBasePath(basePath, route.path === '/' ? '/' : route.path)} ${destination} 200!`];
|
|
10
|
+
}
|
|
11
|
+
const segments = route.path.split('/').filter(Boolean);
|
|
12
|
+
const terminal = segments[segments.length - 1];
|
|
13
|
+
const prefix = segments.slice(0, -1).join('/');
|
|
14
|
+
const prefixPath = prefix ? `/${prefix}` : '';
|
|
15
|
+
if (terminal.startsWith('*') && terminal.endsWith('?')) {
|
|
16
|
+
const key = terminal.slice(1, -1);
|
|
17
|
+
const exactPath = prefixPath || '/';
|
|
18
|
+
const splatPath = prefixPath ? `${prefixPath}/*` : '/*';
|
|
19
|
+
return [
|
|
20
|
+
`${prependBasePath(basePath, exactPath)} ${destination}?__zenith_param_${key}= 200!`,
|
|
21
|
+
`${prependBasePath(basePath, splatPath)} ${destination}?__zenith_param_${key}=:splat 200!`
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
if (terminal.startsWith('*')) {
|
|
25
|
+
const key = terminal.slice(1);
|
|
26
|
+
const splatPath = prefixPath ? `${prefixPath}/*` : '/*';
|
|
27
|
+
return [`${prependBasePath(basePath, splatPath)} ${destination}?__zenith_param_${key}=:splat 200!`];
|
|
28
|
+
}
|
|
29
|
+
const sourcePath = `/${segments.map((segment) => segment.startsWith(':') ? segment : segment).join('/')}`;
|
|
30
|
+
const query = route.params.map((param) => `__zenith_param_${param}=:${param}`).join('&');
|
|
31
|
+
return [`${prependBasePath(basePath, sourcePath)} ${destination}?${query} 200!`];
|
|
32
|
+
}
|
|
33
|
+
function createFunctionSource(route) {
|
|
34
|
+
return [
|
|
35
|
+
"import { fileURLToPath } from 'node:url';",
|
|
36
|
+
"import { dirname, join } from 'node:path';",
|
|
37
|
+
"import { renderRouteRequest, extractInternalParams } from './_zenith/runtime/route-render.js';",
|
|
38
|
+
'',
|
|
39
|
+
'const __dirname = dirname(fileURLToPath(import.meta.url));',
|
|
40
|
+
`const route = ${JSON.stringify(route, null, 2)};`,
|
|
41
|
+
'',
|
|
42
|
+
'export default async function(request) {',
|
|
43
|
+
' const params = extractInternalParams(request.url, route);',
|
|
44
|
+
' return renderRouteRequest({',
|
|
45
|
+
' request,',
|
|
46
|
+
' route,',
|
|
47
|
+
' params,',
|
|
48
|
+
` routeModulePath: join(__dirname, '_zenith', 'routes', ${JSON.stringify(route.name)}, 'route', 'entry.js'),`,
|
|
49
|
+
` shellHtmlPath: join(__dirname, '_zenith', 'routes', ${JSON.stringify(route.name)}, 'route', 'page.html'),`,
|
|
50
|
+
` pageAssetPath: ${route.page_asset_file ? "join(__dirname, '_zenith', 'routes', " + JSON.stringify(route.name) + ", 'route', " + JSON.stringify(route.page_asset_file) + ')' : 'null'},`,
|
|
51
|
+
` imageManifestPath: ${route.image_manifest_file ? "join(__dirname, '_zenith', 'routes', " + JSON.stringify(route.name) + ", 'route', " + JSON.stringify(route.image_manifest_file) + ')' : 'null'},`,
|
|
52
|
+
` imageConfig: ${JSON.stringify(route.image_config || {}, null, 2)}`,
|
|
53
|
+
' });',
|
|
54
|
+
'}',
|
|
55
|
+
''
|
|
56
|
+
].join('\n');
|
|
57
|
+
}
|
|
58
|
+
async function loadServerManifest(coreOutput) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(await readFile(join(coreOutput, 'server', 'manifest.json'), 'utf8'));
|
|
61
|
+
return Array.isArray(parsed.routes) ? parsed.routes : [];
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function buildRedirectsFile(buildManifest, serverRoutes) {
|
|
68
|
+
const lines = [
|
|
69
|
+
'# Generated by Zenith netlify adapter',
|
|
70
|
+
...createNetlifyBasePathAssetRules(buildManifest.base_path)
|
|
71
|
+
];
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
for (const route of [...serverRoutes].sort((left, right) => compareRouteSpecificity(left.path, right.path))) {
|
|
74
|
+
for (const line of buildNetlifyServerRules(route, buildManifest.base_path)) {
|
|
75
|
+
if (seen.has(line)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
seen.add(line);
|
|
79
|
+
lines.push(line);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const route of buildManifest.routes.filter((entry) => entry.render_mode === 'prerender' && entry.path_kind === 'dynamic')) {
|
|
83
|
+
for (const line of createNetlifyRewriteRules(route, buildManifest.base_path)) {
|
|
84
|
+
if (seen.has(line)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
seen.add(line);
|
|
88
|
+
lines.push(line);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return `${lines.join('\n')}\n`;
|
|
92
|
+
}
|
|
93
|
+
export const netlifyAdapter = {
|
|
94
|
+
name: 'netlify',
|
|
95
|
+
validateRoutes() { },
|
|
96
|
+
async adapt(options) {
|
|
97
|
+
const publishDir = join(options.outDir, 'publish');
|
|
98
|
+
const functionsDir = join(options.outDir, 'functions');
|
|
99
|
+
const staticDir = join(options.coreOutput, 'static');
|
|
100
|
+
// Route meaning is fixed upstream in the manifest/server package.
|
|
101
|
+
// The adapter only maps already-classified output into Netlify's layout.
|
|
102
|
+
const serverRoutes = await loadServerManifest(options.coreOutput);
|
|
103
|
+
await rm(options.outDir, { recursive: true, force: true });
|
|
104
|
+
await mkdir(publishDir, { recursive: true });
|
|
105
|
+
await mkdir(functionsDir, { recursive: true });
|
|
106
|
+
await cp(staticDir, publishDir, { recursive: true, force: true });
|
|
107
|
+
await writeFile(join(functionsDir, 'package.json'), '{\n "type": "module"\n}\n', 'utf8');
|
|
108
|
+
if (serverRoutes.length > 0) {
|
|
109
|
+
await cp(join(options.coreOutput, 'server', 'runtime'), join(functionsDir, '_zenith', 'runtime'), { recursive: true, force: true });
|
|
110
|
+
await cp(join(options.coreOutput, 'server', 'images'), join(functionsDir, '_zenith', 'images'), { recursive: true, force: true });
|
|
111
|
+
await cp(join(options.coreOutput, 'server', 'base-path.js'), join(functionsDir, '_zenith', 'base-path.js'), { force: true });
|
|
112
|
+
await cp(join(options.coreOutput, 'server', 'server-contract.js'), join(functionsDir, '_zenith', 'server-contract.js'), { force: true });
|
|
113
|
+
for (const route of serverRoutes) {
|
|
114
|
+
await cp(join(options.coreOutput, 'server', 'routes', route.name), join(functionsDir, '_zenith', 'routes', route.name), { recursive: true, force: true });
|
|
115
|
+
await writeFile(join(functionsDir, `__zenith_${route.name}.mjs`), createFunctionSource(route), 'utf8');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await writeFile(join(publishDir, '_redirects'), buildRedirectsFile(options.manifest, serverRoutes), 'utf8');
|
|
119
|
+
await writeFile(join(options.outDir, 'netlify.toml'), [
|
|
120
|
+
'[build]',
|
|
121
|
+
'publish = "publish"',
|
|
122
|
+
'',
|
|
123
|
+
'[functions]',
|
|
124
|
+
'directory = "functions"',
|
|
125
|
+
'node_bundler = "esbuild"',
|
|
126
|
+
''
|
|
127
|
+
].join('\n'), 'utf8');
|
|
128
|
+
}
|
|
129
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
const PACKAGE_REQUIRE = createRequire(import.meta.url);
|
|
6
|
+
const NODE_RUNTIME_FILES = [
|
|
7
|
+
{
|
|
8
|
+
from: new URL('../server-runtime/node-server.js', import.meta.url),
|
|
9
|
+
to: 'runtime/node-server.js'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
from: new URL('../server/resolve-request-route.js', import.meta.url),
|
|
13
|
+
to: 'runtime/resolve-request-route.js'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
from: new URL('../images/service.js', import.meta.url),
|
|
17
|
+
to: 'images/service.js'
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
function createNodeEntrySource() {
|
|
21
|
+
return [
|
|
22
|
+
"import { fileURLToPath } from 'node:url';",
|
|
23
|
+
"import { dirname, resolve } from 'node:path';",
|
|
24
|
+
"import { createNodeServer as createZenithNodeServer, createRequestHandler as createZenithRequestHandler } from './server/runtime/node-server.js';",
|
|
25
|
+
'',
|
|
26
|
+
'const __filename = fileURLToPath(import.meta.url);',
|
|
27
|
+
'const __dirname = dirname(__filename);',
|
|
28
|
+
'',
|
|
29
|
+
'export function createRequestHandler(options = {}) {',
|
|
30
|
+
' return createZenithRequestHandler({',
|
|
31
|
+
' distDir: __dirname,',
|
|
32
|
+
' ...options',
|
|
33
|
+
' });',
|
|
34
|
+
'}',
|
|
35
|
+
'',
|
|
36
|
+
'export function createNodeServer(options = {}) {',
|
|
37
|
+
' return createZenithNodeServer({',
|
|
38
|
+
' distDir: __dirname,',
|
|
39
|
+
' ...options',
|
|
40
|
+
' });',
|
|
41
|
+
'}',
|
|
42
|
+
'',
|
|
43
|
+
'const isDirectRun = process.argv[1] && resolve(process.argv[1]) === __filename;',
|
|
44
|
+
'',
|
|
45
|
+
'if (isDirectRun) {',
|
|
46
|
+
' const port = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3000;',
|
|
47
|
+
" const host = process.env.HOST || '0.0.0.0';",
|
|
48
|
+
' createNodeServer({ port, host }).then(({ port: actualPort }) => {',
|
|
49
|
+
" const displayHost = host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host;",
|
|
50
|
+
' console.log(`http://${displayHost}:${actualPort}`);',
|
|
51
|
+
' }).catch((error) => {',
|
|
52
|
+
' console.error(error);',
|
|
53
|
+
' process.exit(1);',
|
|
54
|
+
' });',
|
|
55
|
+
'}',
|
|
56
|
+
''
|
|
57
|
+
].join('\n');
|
|
58
|
+
}
|
|
59
|
+
function createNodePackageJson() {
|
|
60
|
+
return {
|
|
61
|
+
private: true,
|
|
62
|
+
type: 'module',
|
|
63
|
+
main: './index.js'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createSharpRuntimeSource() {
|
|
67
|
+
const sharpPath = PACKAGE_REQUIRE.resolve('sharp');
|
|
68
|
+
const fallbackUrl = pathToFileURL(sharpPath).href;
|
|
69
|
+
return [
|
|
70
|
+
'async function loadSharp() {',
|
|
71
|
+
' try {',
|
|
72
|
+
" const mod = await import('sharp');",
|
|
73
|
+
' return mod.default || mod;',
|
|
74
|
+
' } catch {',
|
|
75
|
+
` const mod = await import(${JSON.stringify(fallbackUrl)});`,
|
|
76
|
+
' return mod.default || mod;',
|
|
77
|
+
' }',
|
|
78
|
+
'}',
|
|
79
|
+
'',
|
|
80
|
+
'const sharp = await loadSharp();',
|
|
81
|
+
'export default sharp;',
|
|
82
|
+
''
|
|
83
|
+
].join('\n');
|
|
84
|
+
}
|
|
85
|
+
async function copyNodeRuntimeFiles(serverDir) {
|
|
86
|
+
for (const file of NODE_RUNTIME_FILES) {
|
|
87
|
+
const targetPath = join(serverDir, file.to);
|
|
88
|
+
await mkdir(join(targetPath, '..'), { recursive: true });
|
|
89
|
+
await cp(file.from, targetPath, { force: true });
|
|
90
|
+
}
|
|
91
|
+
const imageServicePath = join(serverDir, 'images', 'service.js');
|
|
92
|
+
const imageServiceSource = await readFile(imageServicePath, 'utf8');
|
|
93
|
+
await writeFile(imageServicePath, imageServiceSource.replace("import sharp from 'sharp';", "import sharp from './sharp-runtime.js';"), 'utf8');
|
|
94
|
+
await writeFile(join(serverDir, 'images', 'sharp-runtime.js'), createSharpRuntimeSource(), 'utf8');
|
|
95
|
+
}
|
|
96
|
+
export const nodeAdapter = {
|
|
97
|
+
name: 'node',
|
|
98
|
+
validateRoutes() { },
|
|
99
|
+
async adapt(options) {
|
|
100
|
+
const staticDir = join(options.coreOutput, 'static');
|
|
101
|
+
const packagedServerDir = join(options.coreOutput, 'server');
|
|
102
|
+
const targetServerDir = join(options.outDir, 'server');
|
|
103
|
+
// Route meaning is fixed upstream in the manifest/server package.
|
|
104
|
+
// The adapter only maps already-classified output into a Node runtime layout.
|
|
105
|
+
await rm(options.outDir, { recursive: true, force: true });
|
|
106
|
+
await mkdir(options.outDir, { recursive: true });
|
|
107
|
+
await cp(staticDir, join(options.outDir, 'static'), { recursive: true, force: true });
|
|
108
|
+
await cp(packagedServerDir, targetServerDir, { recursive: true, force: true });
|
|
109
|
+
await copyNodeRuntimeFiles(targetServerDir);
|
|
110
|
+
await writeFile(join(options.outDir, 'manifest.json'), `${JSON.stringify(options.manifest, null, 2)}\n`, 'utf8');
|
|
111
|
+
await writeFile(join(targetServerDir, 'config.json'), `${JSON.stringify({
|
|
112
|
+
target: 'node',
|
|
113
|
+
base_path: options.manifest.base_path || '/',
|
|
114
|
+
static_dir: '../static',
|
|
115
|
+
build_manifest: '../manifest.json',
|
|
116
|
+
images: options.config?.images || {}
|
|
117
|
+
}, null, 2)}\n`, 'utf8');
|
|
118
|
+
await writeFile(join(options.outDir, 'index.js'), createNodeEntrySource(), 'utf8');
|
|
119
|
+
await writeFile(join(options.outDir, 'package.json'), `${JSON.stringify(createNodePackageJson(), null, 2)}\n`, 'utf8');
|
|
120
|
+
}
|
|
121
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cp, mkdir, rm } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const staticAdapter = {
|
|
4
|
+
name: 'static',
|
|
5
|
+
validateRoutes(manifest) {
|
|
6
|
+
const serverRoutes = manifest.filter((entry) => entry.render_mode === 'server');
|
|
7
|
+
if (serverRoutes.length === 0) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const first = serverRoutes[0];
|
|
11
|
+
throw new Error(`[Zenith:Build] target "static" cannot emit server-rendered routes. ` +
|
|
12
|
+
`Route "${first.path}" (${first.file}) requires render_mode="server".`);
|
|
13
|
+
},
|
|
14
|
+
async adapt(options) {
|
|
15
|
+
const staticDir = join(options.coreOutput, 'static');
|
|
16
|
+
await rm(options.outDir, { recursive: true, force: true });
|
|
17
|
+
await mkdir(options.outDir, { recursive: true });
|
|
18
|
+
await cp(staticDir, options.outDir, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const KNOWN_TARGETS: string[];
|
|
2
|
+
export type ZenithTarget = "static" | "vercel-static" | "netlify-static" | "vercel" | "netlify" | "node";
|
|
3
|
+
export type ZenithRenderMode = "prerender" | "server";
|
|
4
|
+
export type ZenithPathKind = "static" | "dynamic";
|
|
5
|
+
export type RouteManifestEntry = {
|
|
6
|
+
path: string;
|
|
7
|
+
file: string;
|
|
8
|
+
path_kind: ZenithPathKind;
|
|
9
|
+
render_mode: ZenithRenderMode;
|
|
10
|
+
params: string[];
|
|
11
|
+
};
|
|
12
|
+
export type BuildManifest = {
|
|
13
|
+
schema_version: number;
|
|
14
|
+
zenith_version: string;
|
|
15
|
+
target: string;
|
|
16
|
+
base_path: string;
|
|
17
|
+
content_hash: string;
|
|
18
|
+
routes: Array<{
|
|
19
|
+
path: string;
|
|
20
|
+
file: string;
|
|
21
|
+
path_kind: ZenithPathKind;
|
|
22
|
+
render_mode: ZenithRenderMode;
|
|
23
|
+
requires_hydration: boolean;
|
|
24
|
+
params: string[];
|
|
25
|
+
html: string;
|
|
26
|
+
assets: string[];
|
|
27
|
+
}>;
|
|
28
|
+
assets: {
|
|
29
|
+
js: string[];
|
|
30
|
+
css: string[];
|
|
31
|
+
vendor: string | null;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export type AdaptOptions = {
|
|
35
|
+
coreOutput: string;
|
|
36
|
+
outDir: string;
|
|
37
|
+
manifest: BuildManifest;
|
|
38
|
+
config: object;
|
|
39
|
+
};
|
|
40
|
+
export type ZenithAdapter = {
|
|
41
|
+
name: string;
|
|
42
|
+
validateRoutes: (manifest: RouteManifestEntry[]) => void;
|
|
43
|
+
adapt: (options: AdaptOptions) => Promise<void>;
|
|
44
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const KNOWN_TARGETS = [
|
|
2
|
+
'static',
|
|
3
|
+
'vercel-static',
|
|
4
|
+
'netlify-static',
|
|
5
|
+
'vercel',
|
|
6
|
+
'netlify',
|
|
7
|
+
'node'
|
|
8
|
+
];
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {'static' | 'vercel-static' | 'netlify-static' | 'vercel' | 'netlify' | 'node'} ZenithTarget
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {'prerender' | 'server'} ZenithRenderMode
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {'static' | 'dynamic'} ZenithPathKind
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* path: string,
|
|
21
|
+
* file: string,
|
|
22
|
+
* path_kind: ZenithPathKind,
|
|
23
|
+
* render_mode: ZenithRenderMode,
|
|
24
|
+
* params: string[]
|
|
25
|
+
* }} RouteManifestEntry
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {{
|
|
29
|
+
* schema_version: number,
|
|
30
|
+
* zenith_version: string,
|
|
31
|
+
* target: string,
|
|
32
|
+
* base_path: string,
|
|
33
|
+
* content_hash: string,
|
|
34
|
+
* routes: Array<{
|
|
35
|
+
* path: string,
|
|
36
|
+
* file: string,
|
|
37
|
+
* path_kind: ZenithPathKind,
|
|
38
|
+
* render_mode: ZenithRenderMode,
|
|
39
|
+
* requires_hydration: boolean,
|
|
40
|
+
* params: string[],
|
|
41
|
+
* html: string,
|
|
42
|
+
* assets: string[]
|
|
43
|
+
* }>,
|
|
44
|
+
* assets: {
|
|
45
|
+
* js: string[],
|
|
46
|
+
* css: string[],
|
|
47
|
+
* vendor: string | null
|
|
48
|
+
* }
|
|
49
|
+
* }} BuildManifest
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {{
|
|
53
|
+
* coreOutput: string,
|
|
54
|
+
* outDir: string,
|
|
55
|
+
* manifest: BuildManifest,
|
|
56
|
+
* config: object
|
|
57
|
+
* }} AdaptOptions
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {{
|
|
61
|
+
* name: string,
|
|
62
|
+
* validateRoutes: (manifest: RouteManifestEntry[]) => void,
|
|
63
|
+
* adapt: (options: AdaptOptions) => Promise<void>
|
|
64
|
+
* }} ZenithAdapter
|
|
65
|
+
*/
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createVercelBasePathAssetRoutes, createVercelRouteSource } from './route-rules.js';
|
|
4
|
+
function createConfig(manifest) {
|
|
5
|
+
return {
|
|
6
|
+
version: 3,
|
|
7
|
+
routes: [
|
|
8
|
+
...createVercelBasePathAssetRoutes(manifest.base_path),
|
|
9
|
+
{ handle: 'filesystem' },
|
|
10
|
+
...manifest.routes.map((route) => ({
|
|
11
|
+
src: createVercelRouteSource(route.path, manifest.base_path),
|
|
12
|
+
dest: route.html
|
|
13
|
+
}))
|
|
14
|
+
]
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export const vercelStaticAdapter = {
|
|
18
|
+
name: 'vercel-static',
|
|
19
|
+
validateRoutes(manifest) {
|
|
20
|
+
const serverRoutes = manifest.filter((entry) => entry.render_mode === 'server');
|
|
21
|
+
if (serverRoutes.length === 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const first = serverRoutes[0];
|
|
25
|
+
throw new Error(`[Zenith:Build] target "vercel-static" cannot emit server-rendered routes. ` +
|
|
26
|
+
`Route "${first.path}" (${first.file}) requires render_mode="server".`);
|
|
27
|
+
},
|
|
28
|
+
async adapt(options) {
|
|
29
|
+
const staticDir = join(options.coreOutput, 'static');
|
|
30
|
+
const vercelStaticDir = join(options.outDir, 'static');
|
|
31
|
+
await rm(options.outDir, { recursive: true, force: true });
|
|
32
|
+
await mkdir(vercelStaticDir, { recursive: true });
|
|
33
|
+
await cp(staticDir, vercelStaticDir, { recursive: true, force: true });
|
|
34
|
+
await writeFile(join(options.outDir, 'config.json'), `${JSON.stringify(createConfig(options.manifest), null, 2)}\n`, 'utf8');
|
|
35
|
+
}
|
|
36
|
+
};
|