@zenithbuild/core 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.
@@ -0,0 +1,38 @@
1
+ export type ZenithTarget = 'static' | 'vercel-static' | 'netlify-static' | 'vercel' | 'netlify' | 'node';
2
+ export type ZenithRenderMode = 'prerender' | 'server';
3
+ export type ZenithPathKind = 'static' | 'dynamic';
4
+ export interface RouteManifestEntry {
5
+ path: string;
6
+ file: string;
7
+ path_kind: ZenithPathKind;
8
+ render_mode: ZenithRenderMode;
9
+ params: string[];
10
+ }
11
+ export interface BuildManifestRoute extends RouteManifestEntry {
12
+ requires_hydration: boolean;
13
+ html: string;
14
+ assets: string[];
15
+ }
16
+ export interface BuildManifest {
17
+ schema_version: number;
18
+ zenith_version: string;
19
+ target: string;
20
+ base_path: string;
21
+ content_hash: string;
22
+ routes: BuildManifestRoute[];
23
+ assets: {
24
+ js: string[];
25
+ css: string[];
26
+ vendor: string | null;
27
+ };
28
+ }
29
+ export interface ZenithAdapter {
30
+ name: string;
31
+ validateRoutes(manifest: RouteManifestEntry[]): void;
32
+ adapt(options: {
33
+ coreOutput: string;
34
+ outDir: string;
35
+ manifest: BuildManifest;
36
+ config: Record<string, unknown>;
37
+ }): Promise<void>;
38
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/config.d.ts CHANGED
@@ -1,14 +1,24 @@
1
+ import type { ZenithAdapter, ZenithTarget } from './config-types.js';
2
+ export type { BuildManifest, BuildManifestRoute, RouteManifestEntry, ZenithAdapter, ZenithPathKind, ZenithRenderMode, ZenithTarget } from './config-types.js';
1
3
  export interface ZenithConfig {
2
4
  router: boolean;
3
5
  embeddedMarkupExpressions: boolean;
4
- types: boolean;
5
6
  typescriptDefault: boolean;
6
7
  outDir: string;
7
8
  pagesDir: string;
8
- experimental: Record<string, never>;
9
+ basePath: string;
10
+ target: ZenithTarget;
11
+ adapter: ZenithAdapter | null;
9
12
  strictDomLints: boolean;
10
13
  images: ZenithImageConfig;
11
14
  }
15
+ export type ZenithConfigInput = Partial<Omit<ZenithConfig, 'target' | 'adapter'>> & ({
16
+ target?: ZenithTarget;
17
+ adapter?: never;
18
+ } | {
19
+ target?: never;
20
+ adapter: ZenithAdapter;
21
+ });
12
22
  export interface ZenithImageRemotePattern {
13
23
  protocol: string;
14
24
  hostname: string;
@@ -28,8 +38,8 @@ export interface ZenithImageConfig {
28
38
  minimumCacheTTL: number;
29
39
  dangerouslyAllowLocalNetwork: boolean;
30
40
  }
31
- type ConfigInput = Partial<ZenithConfig> | null | undefined;
41
+ type ConfigInput = ZenithConfigInput | null | undefined;
42
+ export declare function defineConfig<T extends ZenithConfigInput>(config: T): T;
32
43
  export declare function validateConfig(config: ConfigInput): ZenithConfig;
33
44
  export declare function loadConfig(projectRoot: string): Promise<ZenithConfig>;
34
45
  export declare function getDefaults(): ZenithConfig;
35
- export {};
package/dist/config.js CHANGED
@@ -1,5 +1,18 @@
1
- import { join } from 'node:path';
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
+ const PACKAGE_REQUIRE = createRequire(import.meta.url);
7
+ const CONFIG_FILES = ['zenith.config.ts', 'zenith.config.js'];
8
+ const KNOWN_TARGETS = [
9
+ 'static',
10
+ 'vercel-static',
11
+ 'netlify-static',
12
+ 'vercel',
13
+ 'netlify',
14
+ 'node'
15
+ ];
3
16
  const DEFAULT_IMAGE_CONFIG = {
4
17
  formats: ['webp', 'avif'],
5
18
  quality: 75,
@@ -15,22 +28,24 @@ const DEFAULT_IMAGE_CONFIG = {
15
28
  const DEFAULTS = {
16
29
  router: false,
17
30
  embeddedMarkupExpressions: false,
18
- types: true,
19
31
  typescriptDefault: true,
20
32
  outDir: 'dist',
21
33
  pagesDir: 'pages',
22
- experimental: {},
34
+ basePath: '/',
35
+ target: 'static',
36
+ adapter: null,
23
37
  strictDomLints: false,
24
38
  images: DEFAULT_IMAGE_CONFIG
25
39
  };
26
40
  const SCHEMA = {
27
41
  router: 'boolean',
28
42
  embeddedMarkupExpressions: 'boolean',
29
- types: 'boolean',
30
43
  typescriptDefault: 'boolean',
31
44
  outDir: 'string',
32
45
  pagesDir: 'string',
33
- experimental: 'object',
46
+ basePath: 'string',
47
+ target: 'string',
48
+ adapter: 'object',
34
49
  strictDomLints: 'boolean',
35
50
  images: 'object'
36
51
  };
@@ -162,6 +177,100 @@ function normalizeImageConfig(input) {
162
177
  }
163
178
  return config;
164
179
  }
180
+ function normalizeBasePath(value) {
181
+ const raw = String(value ?? '/').trim();
182
+ if (!raw || raw === '/') {
183
+ return '/';
184
+ }
185
+ if (raw.includes('?') || raw.includes('#')) {
186
+ throw new Error('[Zenith:Config] Key "basePath" must not include query or hash fragments');
187
+ }
188
+ if (!raw.startsWith('/')) {
189
+ throw new Error('[Zenith:Config] Key "basePath" must start with "/"');
190
+ }
191
+ const normalized = raw.replace(/\/{2,}/g, '/').replace(/\/+$/g, '');
192
+ return normalized || '/';
193
+ }
194
+ function validateAdapterValue(value) {
195
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
196
+ throw new Error('[Zenith:Config] Key "adapter" must be a plain object');
197
+ }
198
+ if (typeof value.name !== 'string' || value.name.trim().length === 0) {
199
+ throw new Error('[Zenith:Config] Key "adapter.name" must be a non-empty string');
200
+ }
201
+ if (typeof value.validateRoutes !== 'function') {
202
+ throw new Error('[Zenith:Config] Key "adapter.validateRoutes" must be a function');
203
+ }
204
+ if (typeof value.adapt !== 'function') {
205
+ throw new Error('[Zenith:Config] Key "adapter.adapt" must be a function');
206
+ }
207
+ return value;
208
+ }
209
+ function resolveConfigFile(projectRoot) {
210
+ const matches = CONFIG_FILES
211
+ .map((name) => join(projectRoot, name))
212
+ .filter((candidate) => existsSync(candidate));
213
+ if (matches.length > 1) {
214
+ throw new Error(`[Zenith:Config] Multiple config files found. Keep exactly one of: ${CONFIG_FILES.join(', ')}`);
215
+ }
216
+ return matches[0] || null;
217
+ }
218
+ function resolveTypeScriptApi(projectRoot) {
219
+ try {
220
+ const projectRequire = createRequire(join(projectRoot, '__zenith_config_loader__.js'));
221
+ return projectRequire('typescript');
222
+ }
223
+ catch {
224
+ try {
225
+ return PACKAGE_REQUIRE('typescript');
226
+ }
227
+ catch {
228
+ throw new Error('[Zenith:Config] zenith.config.ts requires the `typescript` package to be installed.');
229
+ }
230
+ }
231
+ }
232
+ function importTypescriptConfig(configPath, projectRoot) {
233
+ return readFile(configPath, 'utf8').then((source) => {
234
+ const ts = resolveTypeScriptApi(projectRoot);
235
+ const transpiled = ts.transpileModule(source, {
236
+ compilerOptions: {
237
+ module: ts.ModuleKind.ESNext,
238
+ target: ts.ScriptTarget.ES2022,
239
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
240
+ esModuleInterop: true,
241
+ allowSyntheticDefaultImports: true
242
+ },
243
+ fileName: configPath
244
+ }).outputText;
245
+ const tempConfigPath = join(projectRoot, `.zenith.config.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.mjs`);
246
+ return writeFile(tempConfigPath, transpiled, 'utf8')
247
+ .then(() => import(`${pathToFileURL(tempConfigPath).href}?t=${Date.now()}`))
248
+ .finally(() => rm(tempConfigPath, { force: true }));
249
+ });
250
+ }
251
+ function importJavascriptConfig(configPath, projectRoot) {
252
+ return readFile(configPath, 'utf8').then((source) => {
253
+ const isCommonJs = /\bmodule\.exports\b|\bexports\./.test(source);
254
+ const tempConfigPath = join(projectRoot, `.zenith.config.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.${isCommonJs ? 'cjs' : 'mjs'}`);
255
+ return writeFile(tempConfigPath, source, 'utf8')
256
+ .then(() => {
257
+ if (isCommonJs) {
258
+ const projectRequire = createRequire(join(projectRoot, '__zenith_config_loader__.js'));
259
+ const resolvedPath = projectRequire.resolve(tempConfigPath);
260
+ const requireCache = projectRequire.cache || PACKAGE_REQUIRE.cache;
261
+ if (requireCache && resolvedPath in requireCache) {
262
+ delete requireCache[resolvedPath];
263
+ }
264
+ return projectRequire(tempConfigPath);
265
+ }
266
+ return import(`${pathToFileURL(tempConfigPath).href}?t=${Date.now()}`);
267
+ })
268
+ .finally(() => rm(tempConfigPath, { force: true }));
269
+ });
270
+ }
271
+ export function defineConfig(config) {
272
+ return config;
273
+ }
165
274
  export function validateConfig(config) {
166
275
  if (config === null || config === undefined) {
167
276
  return { ...DEFAULTS, images: cloneImageConfig() };
@@ -174,50 +283,50 @@ export function validateConfig(config) {
174
283
  throw new Error(`[Zenith:Config] Unknown key: "${key}"`);
175
284
  }
176
285
  }
286
+ if ('target' in config && 'adapter' in config) {
287
+ throw new Error('[Zenith:Config] Keys "target" and "adapter" are mutually exclusive');
288
+ }
177
289
  const result = { ...DEFAULTS, images: cloneImageConfig() };
178
290
  for (const [key, expectedType] of Object.entries(SCHEMA)) {
179
- if (key in config) {
180
- const value = config[key];
181
- if (typeof value !== expectedType) {
182
- throw new Error(`[Zenith:Config] Key "${key}" must be ${expectedType}, got ${typeof value}`);
183
- }
184
- if (expectedType === 'string' && typeof value === 'string' && value.trim() === '') {
185
- throw new Error(`[Zenith:Config] Key "${key}" must be a non-empty string`);
186
- }
187
- if (key === 'experimental' && value) {
188
- if (typeof value !== 'object' || Array.isArray(value)) {
189
- throw new Error('[Zenith:Config] Key "experimental" must be a plain object');
190
- }
191
- result[key] = { ...DEFAULTS.experimental };
192
- continue;
193
- }
194
- if (key === 'images') {
195
- result.images = normalizeImageConfig(value);
196
- continue;
197
- }
198
- result[key] = value;
291
+ if (!(key in config)) {
292
+ continue;
199
293
  }
294
+ const value = config[key];
295
+ if (key === 'images') {
296
+ result.images = normalizeImageConfig(value);
297
+ continue;
298
+ }
299
+ if (key === 'adapter') {
300
+ result.adapter = validateAdapterValue(value);
301
+ continue;
302
+ }
303
+ if (typeof value !== expectedType) {
304
+ throw new Error(`[Zenith:Config] Key "${key}" must be ${expectedType}, got ${typeof value}`);
305
+ }
306
+ if (expectedType === 'string' && typeof value === 'string' && value.trim() === '') {
307
+ throw new Error(`[Zenith:Config] Key "${key}" must be a non-empty string`);
308
+ }
309
+ if (key === 'target' && !KNOWN_TARGETS.includes(value)) {
310
+ throw new Error(`[Zenith:Config] Unsupported target: "${value}"`);
311
+ }
312
+ if (key === 'basePath') {
313
+ result.basePath = normalizeBasePath(value);
314
+ continue;
315
+ }
316
+ result[key] = value;
200
317
  }
201
318
  return result;
202
319
  }
203
320
  export async function loadConfig(projectRoot) {
204
- const configPath = join(projectRoot, 'zenith.config.js');
205
- try {
206
- const url = pathToFileURL(configPath).href;
207
- const mod = await import(url);
208
- const raw = mod.default || mod;
209
- return validateConfig(raw);
210
- }
211
- catch (err) {
212
- const error = err;
213
- if (error.code === 'ERR_MODULE_NOT_FOUND' ||
214
- error.code === 'ENOENT' ||
215
- error.message?.includes('Cannot find module') ||
216
- error.message?.includes('ENOENT')) {
217
- return { ...DEFAULTS, images: cloneImageConfig() };
218
- }
219
- throw err;
321
+ const resolvedProjectRoot = resolve(projectRoot);
322
+ const configPath = resolveConfigFile(resolvedProjectRoot);
323
+ if (!configPath) {
324
+ return { ...DEFAULTS, images: cloneImageConfig() };
220
325
  }
326
+ const mod = configPath.endsWith('.ts')
327
+ ? await importTypescriptConfig(configPath, resolvedProjectRoot)
328
+ : await importJavascriptConfig(configPath, resolvedProjectRoot);
329
+ return validateConfig(mod.default || mod);
221
330
  }
222
331
  export function getDefaults() {
223
332
  return { ...DEFAULTS, images: cloneImageConfig() };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenithbuild/core",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Deterministic utility substrate for the Zenith framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,11 +43,11 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@zenithbuild/cli": "0.7.1",
47
- "@zenithbuild/compiler": "0.7.1",
48
- "@zenithbuild/runtime": "0.7.1",
49
- "@zenithbuild/router": "0.7.1",
50
- "@zenithbuild/bundler": "0.7.1"
46
+ "@zenithbuild/cli": "0.7.3",
47
+ "@zenithbuild/compiler": "0.7.3",
48
+ "@zenithbuild/runtime": "0.7.3",
49
+ "@zenithbuild/router": "0.7.3",
50
+ "@zenithbuild/bundler": "0.7.3"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "rm -rf dist && tsc -p tsconfig.build.json",