@zenithbuild/cli 0.7.1 → 0.7.3
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 +4 -2
package/dist/build.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { resolveBuildAdapter } from './adapters/resolve-adapter.js';
|
|
4
|
+
import { normalizeBasePath } from './base-path.js';
|
|
5
|
+
import { rewriteSoftNavigationHrefBasePathInHtmlFiles } from './base-path-html.js';
|
|
6
|
+
import { writeBuildOutputManifest } from './build-output-manifest.js';
|
|
2
7
|
import { generateManifest } from './manifest.js';
|
|
3
8
|
import { buildComponentRegistry } from './resolve-components.js';
|
|
4
9
|
import { collectAssets, createCompilerWarningEmitter, runBundler } from './build/compiler-runtime.js';
|
|
@@ -8,6 +13,7 @@ import { materializeImageMarkupInHtmlFiles } from './images/materialize.js';
|
|
|
8
13
|
import { buildImageArtifacts } from './images/service.js';
|
|
9
14
|
import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } from './images/payload.js';
|
|
10
15
|
import { createStartupProfiler } from './startup-profile.js';
|
|
16
|
+
import { writeServerOutput } from './server-output.js';
|
|
11
17
|
import { resolveBundlerBin } from './toolchain-paths.js';
|
|
12
18
|
import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate } from './toolchain-runner.js';
|
|
13
19
|
import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
|
|
@@ -41,15 +47,18 @@ export async function build(options) {
|
|
|
41
47
|
const { pagesDir, outDir, config = {}, logger = null, showBundlerInfo = true } = options;
|
|
42
48
|
const startupProfile = createStartupProfiler('cli-build');
|
|
43
49
|
const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
|
|
50
|
+
const coreOutputDir = join(projectRoot, '.zenith-output');
|
|
51
|
+
const staticOutputDir = join(coreOutputDir, 'static');
|
|
44
52
|
const srcDir = resolve(pagesDir, '..');
|
|
45
53
|
const compilerBin = createCompilerToolchain({ projectRoot, logger });
|
|
46
54
|
const bundlerBin = createBundlerToolchain({ projectRoot, logger });
|
|
47
55
|
const compilerTotals = createCompilerTotals();
|
|
48
|
-
const
|
|
56
|
+
const { target, adapter, mode } = resolveBuildAdapter(config);
|
|
57
|
+
const basePath = normalizeBasePath(config.basePath || '/');
|
|
58
|
+
const routerEnabled = config.router === true;
|
|
49
59
|
const compilerOpts = {
|
|
50
60
|
typescriptDefault: config.typescriptDefault === true,
|
|
51
|
-
experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true
|
|
52
|
-
|| config.experimental?.embeddedMarkupExpressions === true,
|
|
61
|
+
experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true,
|
|
53
62
|
strictDomLints: config.strictDomLints === true
|
|
54
63
|
};
|
|
55
64
|
ensureToolchainCompatibility(bundlerBin);
|
|
@@ -64,11 +73,16 @@ export async function build(options) {
|
|
|
64
73
|
}
|
|
65
74
|
const registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
|
|
66
75
|
void RUNTIME_MARKUP_BINDING;
|
|
67
|
-
const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir));
|
|
76
|
+
const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir, '.zen', { compilerOpts }));
|
|
77
|
+
if (mode !== 'legacy') {
|
|
78
|
+
adapter.validateRoutes(manifest);
|
|
79
|
+
}
|
|
68
80
|
await startupProfile.measureAsync('ensure_zenith_type_declarations', () => ensureZenithTypeDeclarations({
|
|
69
81
|
manifest,
|
|
70
82
|
pagesDir
|
|
71
83
|
}));
|
|
84
|
+
await startupProfile.measureAsync('reset_core_output', () => rm(coreOutputDir, { recursive: true, force: true }));
|
|
85
|
+
await startupProfile.measureAsync('prepare_core_output', () => mkdir(staticOutputDir, { recursive: true }));
|
|
72
86
|
const emitCompilerWarning = createCompilerWarningEmitter((line) => {
|
|
73
87
|
if (logger && typeof logger.warn === 'function') {
|
|
74
88
|
logger.warn(line, { onceKey: `compiler-warning:${line}` });
|
|
@@ -83,29 +97,46 @@ export async function build(options) {
|
|
|
83
97
|
registry,
|
|
84
98
|
compilerOpts,
|
|
85
99
|
compilerBin,
|
|
86
|
-
|
|
100
|
+
routerEnabled,
|
|
87
101
|
startupProfile,
|
|
88
102
|
compilerTotals,
|
|
89
103
|
emitCompilerWarning
|
|
90
104
|
});
|
|
91
105
|
if (envelopes.length > 0) {
|
|
92
|
-
await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes,
|
|
106
|
+
await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, staticOutputDir, projectRoot, logger, showBundlerInfo, bundlerBin, { basePath }), { envelopes: envelopes.length });
|
|
93
107
|
}
|
|
108
|
+
await startupProfile.measureAsync('rewrite_soft_navigation_base_path', () => rewriteSoftNavigationHrefBasePathInHtmlFiles(staticOutputDir, basePath));
|
|
94
109
|
const { manifest: imageManifest } = await startupProfile.measureAsync('build_image_artifacts', () => buildImageArtifacts({
|
|
95
110
|
projectRoot,
|
|
96
|
-
outDir,
|
|
111
|
+
outDir: staticOutputDir,
|
|
97
112
|
config: config.images
|
|
98
113
|
}));
|
|
99
|
-
const imageRuntimePayload = createImageRuntimePayload(config.images, imageManifest, 'passthrough');
|
|
114
|
+
const imageRuntimePayload = createImageRuntimePayload(config.images, imageManifest, 'passthrough', basePath);
|
|
100
115
|
await startupProfile.measureAsync('materialize_image_markup', () => materializeImageMarkupInHtmlFiles({
|
|
101
|
-
distDir:
|
|
116
|
+
distDir: staticOutputDir,
|
|
102
117
|
payload: imageRuntimePayload
|
|
103
118
|
}));
|
|
104
|
-
await startupProfile.measureAsync('inject_image_runtime_payload', () => injectImageRuntimePayloadIntoHtmlFiles(
|
|
119
|
+
await startupProfile.measureAsync('inject_image_runtime_payload', () => injectImageRuntimePayloadIntoHtmlFiles(staticOutputDir, imageRuntimePayload));
|
|
120
|
+
const buildManifest = await startupProfile.measureAsync('write_core_manifest', () => writeBuildOutputManifest({
|
|
121
|
+
coreOutputDir,
|
|
122
|
+
staticDir: staticOutputDir,
|
|
123
|
+
target,
|
|
124
|
+
routeManifest: manifest,
|
|
125
|
+
basePath
|
|
126
|
+
}));
|
|
127
|
+
await startupProfile.measureAsync('write_server_output', () => writeServerOutput({
|
|
128
|
+
coreOutputDir,
|
|
129
|
+
staticDir: staticOutputDir,
|
|
130
|
+
projectRoot,
|
|
131
|
+
config,
|
|
132
|
+
basePath
|
|
133
|
+
}));
|
|
134
|
+
await startupProfile.measureAsync('adapt_output', () => adapter.adapt({ coreOutput: coreOutputDir, outDir, manifest: buildManifest, config }));
|
|
105
135
|
const assets = await startupProfile.measureAsync('collect_assets', () => collectAssets(outDir));
|
|
106
136
|
startupProfile.emit('build_complete', {
|
|
107
137
|
pages: manifest.length,
|
|
108
138
|
assets: assets.length,
|
|
139
|
+
target,
|
|
109
140
|
compilerTotals,
|
|
110
141
|
expressionRewriteMetrics
|
|
111
142
|
});
|
package/dist/config.d.ts
CHANGED
|
@@ -1,55 +1,19 @@
|
|
|
1
|
-
export function
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
allowSvg: boolean;
|
|
9
|
-
maxRemoteBytes: number;
|
|
10
|
-
maxPixels: number;
|
|
11
|
-
minimumCacheTTL: number;
|
|
12
|
-
dangerouslyAllowLocalNetwork: boolean;
|
|
13
|
-
};
|
|
14
|
-
router: boolean;
|
|
15
|
-
embeddedMarkupExpressions: boolean;
|
|
16
|
-
types: boolean;
|
|
17
|
-
typescriptDefault: boolean;
|
|
18
|
-
outDir: string;
|
|
19
|
-
pagesDir: string;
|
|
20
|
-
experimental: {};
|
|
21
|
-
strictDomLints: boolean;
|
|
22
|
-
};
|
|
23
|
-
export function loadConfig(projectRoot: any): Promise<{
|
|
24
|
-
images: {
|
|
25
|
-
formats: string[];
|
|
26
|
-
deviceSizes: number[];
|
|
27
|
-
imageSizes: number[];
|
|
28
|
-
remotePatterns: any[];
|
|
29
|
-
quality: number;
|
|
30
|
-
allowSvg: boolean;
|
|
31
|
-
maxRemoteBytes: number;
|
|
32
|
-
maxPixels: number;
|
|
33
|
-
minimumCacheTTL: number;
|
|
34
|
-
dangerouslyAllowLocalNetwork: boolean;
|
|
35
|
-
};
|
|
36
|
-
router: boolean;
|
|
37
|
-
embeddedMarkupExpressions: boolean;
|
|
38
|
-
types: boolean;
|
|
39
|
-
typescriptDefault: boolean;
|
|
40
|
-
outDir: string;
|
|
41
|
-
pagesDir: string;
|
|
42
|
-
experimental: {};
|
|
43
|
-
strictDomLints: boolean;
|
|
44
|
-
}>;
|
|
1
|
+
export function hasExplicitConfigKey(config: any, key: any): boolean;
|
|
2
|
+
export function isLoadedConfig(config: any): boolean;
|
|
3
|
+
export function isConfigKeyExplicit(config: any, key: any): boolean;
|
|
4
|
+
export function resolveConfigPagesDir(projectRoot: any, config: any): string;
|
|
5
|
+
export function resolveConfigOutDir(projectRoot: any, config: any): string;
|
|
6
|
+
export function validateConfig(config: any): any;
|
|
7
|
+
export function loadConfig(projectRoot: any): Promise<any>;
|
|
45
8
|
export namespace DEFAULT_CONFIG {
|
|
46
9
|
let router: boolean;
|
|
47
10
|
let embeddedMarkupExpressions: boolean;
|
|
48
|
-
let types: boolean;
|
|
49
11
|
let typescriptDefault: boolean;
|
|
50
12
|
let outDir: string;
|
|
51
13
|
let pagesDir: string;
|
|
52
|
-
let
|
|
14
|
+
let basePath: string;
|
|
15
|
+
let target: string;
|
|
16
|
+
let adapter: null;
|
|
53
17
|
let strictDomLints: boolean;
|
|
54
18
|
let images: {
|
|
55
19
|
formats: string[];
|
package/dist/config.js
CHANGED
|
@@ -1,31 +1,166 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
2
5
|
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { KNOWN_TARGETS } from './adapters/adapter-types.js';
|
|
7
|
+
import { normalizeBasePath } from './base-path.js';
|
|
3
8
|
import { normalizeImageConfig } from './images/shared.js';
|
|
9
|
+
const PACKAGE_REQUIRE = createRequire(import.meta.url);
|
|
10
|
+
const CONFIG_FILES = ['zenith.config.ts', 'zenith.config.js'];
|
|
11
|
+
const CONFIG_META = Symbol('zenith.config.meta');
|
|
4
12
|
export const DEFAULT_CONFIG = {
|
|
5
13
|
router: false,
|
|
6
14
|
embeddedMarkupExpressions: false,
|
|
7
|
-
types: true,
|
|
8
15
|
typescriptDefault: true,
|
|
9
16
|
outDir: 'dist',
|
|
10
17
|
pagesDir: 'pages',
|
|
11
|
-
|
|
18
|
+
basePath: '/',
|
|
19
|
+
target: 'static',
|
|
20
|
+
adapter: null,
|
|
12
21
|
strictDomLints: false,
|
|
13
22
|
images: normalizeImageConfig()
|
|
14
23
|
};
|
|
15
24
|
const TOP_LEVEL_SCHEMA = {
|
|
16
25
|
router: 'boolean',
|
|
17
26
|
embeddedMarkupExpressions: 'boolean',
|
|
18
|
-
types: 'boolean',
|
|
19
27
|
typescriptDefault: 'boolean',
|
|
20
28
|
outDir: 'string',
|
|
21
29
|
pagesDir: 'string',
|
|
22
|
-
|
|
30
|
+
basePath: 'string',
|
|
31
|
+
target: 'string',
|
|
32
|
+
adapter: 'object',
|
|
23
33
|
strictDomLints: 'boolean',
|
|
24
34
|
images: 'object'
|
|
25
35
|
};
|
|
36
|
+
function attachConfigMeta(config, explicitKeys) {
|
|
37
|
+
Object.defineProperty(config, CONFIG_META, {
|
|
38
|
+
value: { explicitKeys: new Set(explicitKeys), loaded: false },
|
|
39
|
+
enumerable: false,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true
|
|
42
|
+
});
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
function markLoaded(config) {
|
|
46
|
+
if (config?.[CONFIG_META]) {
|
|
47
|
+
config[CONFIG_META].loaded = true;
|
|
48
|
+
}
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
function validateAdapterValue(value) {
|
|
52
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
53
|
+
throw new Error('[Zenith:Config] Key "adapter" must be a plain object');
|
|
54
|
+
}
|
|
55
|
+
if (typeof value.name !== 'string' || value.name.trim().length === 0) {
|
|
56
|
+
throw new Error('[Zenith:Config] Key "adapter.name" must be a non-empty string');
|
|
57
|
+
}
|
|
58
|
+
if (typeof value.validateRoutes !== 'function') {
|
|
59
|
+
throw new Error('[Zenith:Config] Key "adapter.validateRoutes" must be a function');
|
|
60
|
+
}
|
|
61
|
+
if (typeof value.adapt !== 'function') {
|
|
62
|
+
throw new Error('[Zenith:Config] Key "adapter.adapt" must be a function');
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function resolveConfigFile(projectRoot) {
|
|
67
|
+
const matches = CONFIG_FILES
|
|
68
|
+
.map((name) => join(projectRoot, name))
|
|
69
|
+
.filter((candidate) => existsSync(candidate));
|
|
70
|
+
if (matches.length > 1) {
|
|
71
|
+
throw new Error(`[Zenith:Config] Multiple config files found. Keep exactly one of: ${CONFIG_FILES.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
return matches[0] || null;
|
|
74
|
+
}
|
|
75
|
+
function resolveTypeScriptApi(projectRoot) {
|
|
76
|
+
try {
|
|
77
|
+
const projectRequire = createRequire(join(projectRoot, '__zenith_config_loader__.js'));
|
|
78
|
+
return projectRequire('typescript');
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
try {
|
|
82
|
+
return PACKAGE_REQUIRE('typescript');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new Error('[Zenith:Config] zenith.config.ts requires the `typescript` package to be installed.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function importTypescriptConfig(configPath, projectRoot) {
|
|
90
|
+
const source = await readFile(configPath, 'utf8');
|
|
91
|
+
const ts = resolveTypeScriptApi(projectRoot);
|
|
92
|
+
const transpiled = ts.transpileModule(source, {
|
|
93
|
+
compilerOptions: {
|
|
94
|
+
module: ts.ModuleKind.ESNext,
|
|
95
|
+
target: ts.ScriptTarget.ES2022,
|
|
96
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
97
|
+
esModuleInterop: true,
|
|
98
|
+
allowSyntheticDefaultImports: true
|
|
99
|
+
},
|
|
100
|
+
fileName: configPath
|
|
101
|
+
}).outputText;
|
|
102
|
+
const tempConfigPath = join(projectRoot, `.zenith.config.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.mjs`);
|
|
103
|
+
await writeFile(tempConfigPath, transpiled, 'utf8');
|
|
104
|
+
try {
|
|
105
|
+
return await import(`${pathToFileURL(tempConfigPath).href}?t=${Date.now()}`);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await rm(tempConfigPath, { force: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function importJavascriptConfig(configPath, projectRoot) {
|
|
112
|
+
const source = await readFile(configPath, 'utf8');
|
|
113
|
+
const isCommonJs = /\bmodule\.exports\b|\bexports\./.test(source);
|
|
114
|
+
const tempConfigPath = join(projectRoot, `.zenith.config.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.${isCommonJs ? 'cjs' : 'mjs'}`);
|
|
115
|
+
await writeFile(tempConfigPath, source, 'utf8');
|
|
116
|
+
try {
|
|
117
|
+
if (isCommonJs) {
|
|
118
|
+
const projectRequire = createRequire(join(projectRoot, '__zenith_config_loader__.js'));
|
|
119
|
+
const resolvedPath = projectRequire.resolve(tempConfigPath);
|
|
120
|
+
const requireCache = projectRequire.cache || PACKAGE_REQUIRE.cache;
|
|
121
|
+
if (requireCache && resolvedPath in requireCache) {
|
|
122
|
+
delete requireCache[resolvedPath];
|
|
123
|
+
}
|
|
124
|
+
return projectRequire(tempConfigPath);
|
|
125
|
+
}
|
|
126
|
+
return await import(`${pathToFileURL(tempConfigPath).href}?t=${Date.now()}`);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
await rm(tempConfigPath, { force: true });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export function hasExplicitConfigKey(config, key) {
|
|
133
|
+
return Boolean(config?.[CONFIG_META]?.explicitKeys?.has(key));
|
|
134
|
+
}
|
|
135
|
+
export function isLoadedConfig(config) {
|
|
136
|
+
return Boolean(config?.[CONFIG_META]?.loaded === true);
|
|
137
|
+
}
|
|
138
|
+
export function isConfigKeyExplicit(config, key) {
|
|
139
|
+
if (config && typeof config === 'object' && config[CONFIG_META]) {
|
|
140
|
+
return hasExplicitConfigKey(config, key);
|
|
141
|
+
}
|
|
142
|
+
return Boolean(config && Object.prototype.hasOwnProperty.call(config, key));
|
|
143
|
+
}
|
|
144
|
+
export function resolveConfigPagesDir(projectRoot, config) {
|
|
145
|
+
if (isConfigKeyExplicit(config, 'pagesDir')) {
|
|
146
|
+
return resolve(projectRoot, config.pagesDir);
|
|
147
|
+
}
|
|
148
|
+
const rootPagesDir = join(projectRoot, 'pages');
|
|
149
|
+
if (existsSync(rootPagesDir)) {
|
|
150
|
+
return rootPagesDir;
|
|
151
|
+
}
|
|
152
|
+
const srcPagesDir = join(projectRoot, 'src', 'pages');
|
|
153
|
+
if (existsSync(srcPagesDir)) {
|
|
154
|
+
return srcPagesDir;
|
|
155
|
+
}
|
|
156
|
+
return resolve(projectRoot, config?.pagesDir || DEFAULT_CONFIG.pagesDir);
|
|
157
|
+
}
|
|
158
|
+
export function resolveConfigOutDir(projectRoot, config) {
|
|
159
|
+
return resolve(projectRoot, config?.outDir || DEFAULT_CONFIG.outDir);
|
|
160
|
+
}
|
|
26
161
|
export function validateConfig(config) {
|
|
27
162
|
if (config === null || config === undefined) {
|
|
28
|
-
return { ...DEFAULT_CONFIG, images: normalizeImageConfig() };
|
|
163
|
+
return attachConfigMeta({ ...DEFAULT_CONFIG, images: normalizeImageConfig() }, []);
|
|
29
164
|
}
|
|
30
165
|
if (typeof config !== 'object' || Array.isArray(config)) {
|
|
31
166
|
throw new Error('[Zenith:Config] Config must be a plain object');
|
|
@@ -35,6 +170,9 @@ export function validateConfig(config) {
|
|
|
35
170
|
throw new Error(`[Zenith:Config] Unknown key: "${key}"`);
|
|
36
171
|
}
|
|
37
172
|
}
|
|
173
|
+
if ('target' in config && 'adapter' in config) {
|
|
174
|
+
throw new Error('[Zenith:Config] Keys "target" and "adapter" are mutually exclusive');
|
|
175
|
+
}
|
|
38
176
|
const result = {
|
|
39
177
|
...DEFAULT_CONFIG,
|
|
40
178
|
images: normalizeImageConfig(DEFAULT_CONFIG.images)
|
|
@@ -48,11 +186,8 @@ export function validateConfig(config) {
|
|
|
48
186
|
result.images = normalizeImageConfig(value);
|
|
49
187
|
continue;
|
|
50
188
|
}
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
throw new Error(`[Zenith:Config] Key "${key}" must be a plain object`);
|
|
54
|
-
}
|
|
55
|
-
result[key] = { ...value };
|
|
189
|
+
if (key === 'adapter') {
|
|
190
|
+
result.adapter = validateAdapterValue(value);
|
|
56
191
|
continue;
|
|
57
192
|
}
|
|
58
193
|
if (typeof value !== expectedType) {
|
|
@@ -61,26 +196,25 @@ export function validateConfig(config) {
|
|
|
61
196
|
if (expectedType === 'string' && value.trim().length === 0) {
|
|
62
197
|
throw new Error(`[Zenith:Config] Key "${key}" must be a non-empty string`);
|
|
63
198
|
}
|
|
199
|
+
if (key === 'target' && !KNOWN_TARGETS.includes(value)) {
|
|
200
|
+
throw new Error(`[Zenith:Config] Unsupported target: "${value}"`);
|
|
201
|
+
}
|
|
202
|
+
if (key === 'basePath') {
|
|
203
|
+
result.basePath = normalizeBasePath(value);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
64
206
|
result[key] = value;
|
|
65
207
|
}
|
|
66
|
-
return result;
|
|
208
|
+
return attachConfigMeta(result, Object.keys(config));
|
|
67
209
|
}
|
|
68
210
|
export async function loadConfig(projectRoot) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return validateConfig(mod.default || mod);
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
const code = typeof error?.code === 'string' ? error.code : '';
|
|
77
|
-
const message = typeof error?.message === 'string' ? error.message : '';
|
|
78
|
-
if (code === 'ERR_MODULE_NOT_FOUND'
|
|
79
|
-
|| code === 'ENOENT'
|
|
80
|
-
|| message.includes('Cannot find module')
|
|
81
|
-
|| message.includes('ENOENT')) {
|
|
82
|
-
return validateConfig(null);
|
|
83
|
-
}
|
|
84
|
-
throw error;
|
|
211
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
212
|
+
const configPath = resolveConfigFile(resolvedProjectRoot);
|
|
213
|
+
if (!configPath) {
|
|
214
|
+
return markLoaded(validateConfig(null));
|
|
85
215
|
}
|
|
216
|
+
const mod = configPath.endsWith('.ts')
|
|
217
|
+
? await importTypescriptConfig(configPath, resolvedProjectRoot)
|
|
218
|
+
: await importJavascriptConfig(configPath, resolvedProjectRoot);
|
|
219
|
+
return markLoaded(validateConfig(mod.default || mod));
|
|
86
220
|
}
|
|
@@ -237,11 +237,10 @@ export function createDevBuildSession(options) {
|
|
|
237
237
|
const srcDir = resolve(resolvedPagesDir, '..');
|
|
238
238
|
const compilerBin = createCompilerToolchain({ projectRoot, logger });
|
|
239
239
|
const bundlerBin = createBundlerToolchain({ projectRoot, logger });
|
|
240
|
-
const
|
|
240
|
+
const routerEnabled = config.router === true;
|
|
241
241
|
const compilerOpts = {
|
|
242
242
|
typescriptDefault: config.typescriptDefault === true,
|
|
243
|
-
experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true
|
|
244
|
-
|| config.experimental?.embeddedMarkupExpressions === true,
|
|
243
|
+
experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true,
|
|
245
244
|
strictDomLints: config.strictDomLints === true
|
|
246
245
|
};
|
|
247
246
|
ensureToolchainCompatibility(bundlerBin);
|
|
@@ -308,7 +307,7 @@ export function createDevBuildSession(options) {
|
|
|
308
307
|
registry: state.registry,
|
|
309
308
|
compilerOpts,
|
|
310
309
|
compilerBin,
|
|
311
|
-
|
|
310
|
+
routerEnabled,
|
|
312
311
|
startupProfile,
|
|
313
312
|
compilerTotals,
|
|
314
313
|
emitCompilerWarning,
|
|
@@ -356,7 +355,7 @@ export function createDevBuildSession(options) {
|
|
|
356
355
|
registry: state.registry,
|
|
357
356
|
compilerOpts,
|
|
358
357
|
compilerBin,
|
|
359
|
-
|
|
358
|
+
routerEnabled,
|
|
360
359
|
startupProfile,
|
|
361
360
|
compilerTotals,
|
|
362
361
|
emitCompilerWarning,
|
|
@@ -32,6 +32,24 @@ function normalizeFormat(value: unknown): string {
|
|
|
32
32
|
return String(value || "").trim().toLowerCase().replace(/^\./, "");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function normalizeBasePath(value: unknown): string {
|
|
36
|
+
const raw = safeString(value);
|
|
37
|
+
if (!raw || raw === "/") return "/";
|
|
38
|
+
if (!raw.startsWith("/")) return "/";
|
|
39
|
+
const normalized = raw.replace(/\/{2,}/g, "/").replace(/\/+$/g, "");
|
|
40
|
+
return normalized || "/";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function prependBasePath(basePath: string, pathname: string): string {
|
|
44
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
45
|
+
if (normalizedBasePath === "/") return pathname;
|
|
46
|
+
if (pathname === "/") return normalizedBasePath;
|
|
47
|
+
if (pathname === normalizedBasePath || pathname.startsWith(normalizedBasePath + "/")) {
|
|
48
|
+
return pathname;
|
|
49
|
+
}
|
|
50
|
+
return `${normalizedBasePath}${pathname}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
function isRemoteUrl(value: string): boolean {
|
|
36
54
|
return /^https?:\/\//i.test(value);
|
|
37
55
|
}
|
|
@@ -71,17 +89,20 @@ function buildLocalImageKey(publicPath: string): string {
|
|
|
71
89
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
72
90
|
}
|
|
73
91
|
|
|
74
|
-
function buildLocalVariantPath(publicPath: string, width: number, quality: number, format: string): string {
|
|
75
|
-
return
|
|
92
|
+
function buildLocalVariantPath(publicPath: string, width: number, quality: number, format: string, basePath: string): string {
|
|
93
|
+
return prependBasePath(
|
|
94
|
+
basePath,
|
|
95
|
+
`/_zenith/image/local/${buildLocalImageKey(publicPath)}/w${width}-q${quality}.${normalizeFormat(format)}`
|
|
96
|
+
);
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
function buildRemoteVariantPath(remoteUrl: string, width: number, quality: number, format: string): string {
|
|
99
|
+
function buildRemoteVariantPath(remoteUrl: string, width: number, quality: number, format: string, basePath: string): string {
|
|
79
100
|
const query = new URLSearchParams();
|
|
80
101
|
query.set("url", remoteUrl);
|
|
81
102
|
query.set("w", String(width));
|
|
82
103
|
query.set("q", String(quality));
|
|
83
104
|
if (format) query.set("f", normalizeFormat(format));
|
|
84
|
-
return
|
|
105
|
+
return `${prependBasePath(basePath, "/_zenith/image")}?${query.toString()}`;
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
function escapeRegex(value: string): string {
|
|
@@ -227,6 +248,7 @@ function renderImage(): string {
|
|
|
227
248
|
if (!source || !alt) return "";
|
|
228
249
|
|
|
229
250
|
const config = isPlainObject(payload.config) ? payload.config : {};
|
|
251
|
+
const basePath = normalizeBasePath((payload as any).basePath);
|
|
230
252
|
const className = safeString(rawProps.class);
|
|
231
253
|
const style = mergeStyle(rawProps.style, rawProps.fit, rawProps.position);
|
|
232
254
|
const loading = rawProps.priority === true ? "eager" : safeString(rawProps.loading) || "lazy";
|
|
@@ -248,8 +270,8 @@ function renderImage(): string {
|
|
|
248
270
|
const fallbackWidth = widths.length > 0 ? widths[widths.length - 1] : (width || manifestEntry?.width || 0);
|
|
249
271
|
model = {
|
|
250
272
|
src: rawProps.unoptimized === true
|
|
251
|
-
? source.path
|
|
252
|
-
: buildLocalVariantPath(source.path, fallbackWidth, quality, fallbackFormat),
|
|
273
|
+
? prependBasePath(basePath, source.path)
|
|
274
|
+
: buildLocalVariantPath(source.path, fallbackWidth, quality, fallbackFormat, basePath),
|
|
253
275
|
width,
|
|
254
276
|
height,
|
|
255
277
|
sizes,
|
|
@@ -258,7 +280,7 @@ function renderImage(): string {
|
|
|
258
280
|
: sourceFormats.map((format) => ({
|
|
259
281
|
type: mimeTypeForFormat(format),
|
|
260
282
|
sizes,
|
|
261
|
-
srcset: widths.map((candidate) => `${buildLocalVariantPath(source.path, candidate, quality, format)} ${candidate}w`).join(", ")
|
|
283
|
+
srcset: widths.map((candidate) => `${buildLocalVariantPath(source.path, candidate, quality, format, basePath)} ${candidate}w`).join(", ")
|
|
262
284
|
})).filter((entry) => entry.type && entry.srcset)
|
|
263
285
|
};
|
|
264
286
|
} else {
|
|
@@ -272,14 +294,14 @@ function renderImage(): string {
|
|
|
272
294
|
model = { src: source.url, width, height, sizes, sources: [] };
|
|
273
295
|
} else {
|
|
274
296
|
model = {
|
|
275
|
-
src: buildRemoteVariantPath(source.url, widths[widths.length - 1] || width, quality, ""),
|
|
297
|
+
src: buildRemoteVariantPath(source.url, widths[widths.length - 1] || width, quality, "", basePath),
|
|
276
298
|
width,
|
|
277
299
|
height,
|
|
278
300
|
sizes,
|
|
279
301
|
sources: ((config.formats as string[]) || []).map((format) => ({
|
|
280
302
|
type: mimeTypeForFormat(format),
|
|
281
303
|
sizes,
|
|
282
|
-
srcset: widths.map((candidate) => `${buildRemoteVariantPath(source.url, candidate, quality, format)} ${candidate}w`).join(", ")
|
|
304
|
+
srcset: widths.map((candidate) => `${buildRemoteVariantPath(source.url, candidate, quality, format, basePath)} ${candidate}w`).join(", ")
|
|
283
305
|
})).filter((entry) => entry.type && entry.srcset)
|
|
284
306
|
};
|
|
285
307
|
}
|
package/dist/images/payload.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export function createImageRuntimePayload(config: any, localImages: any, mode?: string): {
|
|
1
|
+
export function createImageRuntimePayload(config: any, localImages: any, mode?: string, basePath?: string): {
|
|
2
2
|
mode: string;
|
|
3
|
+
basePath: string;
|
|
3
4
|
config: {
|
|
4
5
|
formats: string[];
|
|
5
6
|
deviceSizes: number[];
|
package/dist/images/payload.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { imageRuntimeGlobalName, normalizeImageConfig, normalizeImageRuntimePayload } from './shared.js';
|
|
2
2
|
import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
export function createImageRuntimePayload(config, localImages, mode = 'passthrough') {
|
|
4
|
+
export function createImageRuntimePayload(config, localImages, mode = 'passthrough', basePath = '/') {
|
|
5
5
|
return normalizeImageRuntimePayload({
|
|
6
6
|
mode,
|
|
7
|
+
basePath,
|
|
7
8
|
config: normalizeImageConfig(config),
|
|
8
9
|
localImages: localImages && typeof localImages === 'object' ? localImages : {}
|
|
9
10
|
});
|
|
@@ -17,7 +18,7 @@ function serializeInlineScriptJson(payload) {
|
|
|
17
18
|
.replace(/\u2029/g, '\\u2029');
|
|
18
19
|
}
|
|
19
20
|
export function injectImageRuntimePayload(html, payload) {
|
|
20
|
-
const safePayload = createImageRuntimePayload(payload?.config || {}, payload?.localImages || {}, payload?.mode || 'passthrough');
|
|
21
|
+
const safePayload = createImageRuntimePayload(payload?.config || {}, payload?.localImages || {}, payload?.mode || 'passthrough', payload?.basePath || '/');
|
|
21
22
|
const globalName = imageRuntimeGlobalName();
|
|
22
23
|
const serialized = serializeInlineScriptJson(safePayload);
|
|
23
24
|
const scriptTag = `<script id="zenith-image-runtime">window.${globalName} = ${serialized};</script>`;
|
package/dist/images/runtime.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prependBasePath } from '../base-path.js';
|
|
1
2
|
import { buildLocalVariantPath, buildRemoteVariantPath, imageRuntimeGlobalName, matchRemotePattern, normalizeImageRuntimePayload, normalizeImageSource, resolveWidthCandidates } from './shared.js';
|
|
2
3
|
function safeString(value) {
|
|
3
4
|
if (typeof value === 'string') {
|
|
@@ -128,14 +129,14 @@ function buildLocalImageModel(props, payload, source) {
|
|
|
128
129
|
: true);
|
|
129
130
|
const fallbackWidth = widths.length > 0 ? widths[widths.length - 1] : width;
|
|
130
131
|
const imgSrc = props.unoptimized === true
|
|
131
|
-
? source.path
|
|
132
|
-
: buildLocalVariantPath(source.path, fallbackWidth || width || manifestEntry?.width || 0, quality, fallbackFormat);
|
|
132
|
+
? prependBasePath(payload?.basePath || '/', source.path)
|
|
133
|
+
: buildLocalVariantPath(source.path, fallbackWidth || width || manifestEntry?.width || 0, quality, fallbackFormat, payload?.basePath || '/');
|
|
133
134
|
const sources = props.unoptimized === true
|
|
134
135
|
? []
|
|
135
136
|
: sourceFormats.map((format) => ({
|
|
136
137
|
type: mimeTypeForFormat(format),
|
|
137
138
|
sizes,
|
|
138
|
-
srcset: widths.map((candidate) => `${buildLocalVariantPath(source.path, candidate, quality, format)} ${candidate}w`).join(', ')
|
|
139
|
+
srcset: widths.map((candidate) => `${buildLocalVariantPath(source.path, candidate, quality, format, payload?.basePath || '/')} ${candidate}w`).join(', ')
|
|
139
140
|
})).filter((entry) => entry.type && entry.srcset);
|
|
140
141
|
return {
|
|
141
142
|
src: imgSrc,
|
|
@@ -168,10 +169,10 @@ function buildRemoteImageModel(props, payload, source) {
|
|
|
168
169
|
const sources = (config.formats || []).map((format) => ({
|
|
169
170
|
type: mimeTypeForFormat(format),
|
|
170
171
|
sizes,
|
|
171
|
-
srcset: widths.map((candidate) => `${buildRemoteVariantPath(source.url, candidate, quality, format)} ${candidate}w`).join(', ')
|
|
172
|
+
srcset: widths.map((candidate) => `${buildRemoteVariantPath(source.url, candidate, quality, format, payload?.basePath || '/')} ${candidate}w`).join(', ')
|
|
172
173
|
})).filter((entry) => entry.type && entry.srcset);
|
|
173
174
|
return {
|
|
174
|
-
src: buildRemoteVariantPath(source.url, widths[widths.length - 1] || width, quality, ''),
|
|
175
|
+
src: buildRemoteVariantPath(source.url, widths[widths.length - 1] || width, quality, '', payload?.basePath || '/'),
|
|
175
176
|
width,
|
|
176
177
|
height,
|
|
177
178
|
sizes,
|
package/dist/images/service.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { mkdir, readFile, stat, writeFile, readdir } from 'node:fs/promises';
|
|
4
4
|
import { dirname, extname, join, relative, resolve } from 'node:path';
|
|
5
5
|
import sharp from 'sharp';
|
|
6
|
-
import { buildLocalImageKey,
|
|
6
|
+
import { buildLocalImageKey, buildLocalVariantAssetPath, matchRemotePattern, normalizeImageConfig, normalizeImageFormat } from './shared.js';
|
|
7
7
|
const RASTER_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.avif']);
|
|
8
8
|
const MIME_BY_FORMAT = {
|
|
9
9
|
avif: 'image/avif',
|
|
@@ -95,7 +95,7 @@ async function writeIfStale(sourcePath, targetPath, buffer) {
|
|
|
95
95
|
await writeFile(targetPath, buffer);
|
|
96
96
|
}
|
|
97
97
|
function variantRelativePath(publicPath, width, quality, format) {
|
|
98
|
-
return
|
|
98
|
+
return buildLocalVariantAssetPath(publicPath, width, quality, format).replace(/^\//, '');
|
|
99
99
|
}
|
|
100
100
|
function createRemoteCacheKey(url, width, quality, format) {
|
|
101
101
|
return buildLocalImageKey(`${url}|${width}|${quality}|${format || 'original'}`);
|
package/dist/images/shared.d.ts
CHANGED
|
@@ -15,12 +15,14 @@ export function isRemoteImageUrl(value: any): boolean;
|
|
|
15
15
|
export function normalizeImageSource(input: any): any;
|
|
16
16
|
export function normalizeImageFormat(value: any): string;
|
|
17
17
|
export function buildLocalImageKey(publicPath: any): string;
|
|
18
|
-
export function
|
|
19
|
-
export function
|
|
18
|
+
export function buildLocalVariantAssetPath(publicPath: any, width: any, quality: any, format: any): string;
|
|
19
|
+
export function buildLocalVariantPath(publicPath: any, width: any, quality: any, format: any, basePath?: string): string;
|
|
20
|
+
export function buildRemoteVariantPath(remoteUrl: any, width: any, quality: any, format: any, basePath?: string): string;
|
|
20
21
|
export function resolveWidthCandidates(width: any, sizes: any, config: any, manifestEntry: any): any[];
|
|
21
22
|
export function imageRuntimeGlobalName(): string;
|
|
22
23
|
export function normalizeImageRuntimePayload(payload: any): {
|
|
23
24
|
mode: string;
|
|
25
|
+
basePath: string;
|
|
24
26
|
config: {
|
|
25
27
|
formats: string[];
|
|
26
28
|
deviceSizes: number[];
|