@zenithbuild/core 0.6.17 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.d.ts CHANGED
@@ -7,6 +7,26 @@ export interface ZenithConfig {
7
7
  pagesDir: string;
8
8
  experimental: Record<string, never>;
9
9
  strictDomLints: boolean;
10
+ images: ZenithImageConfig;
11
+ }
12
+ export interface ZenithImageRemotePattern {
13
+ protocol: string;
14
+ hostname: string;
15
+ port?: string;
16
+ pathname?: string;
17
+ search?: string;
18
+ }
19
+ export interface ZenithImageConfig {
20
+ formats: string[];
21
+ quality: number;
22
+ deviceSizes: number[];
23
+ imageSizes: number[];
24
+ remotePatterns: ZenithImageRemotePattern[];
25
+ allowSvg: boolean;
26
+ maxRemoteBytes: number;
27
+ maxPixels: number;
28
+ minimumCacheTTL: number;
29
+ dangerouslyAllowLocalNetwork: boolean;
10
30
  }
11
31
  type ConfigInput = Partial<ZenithConfig> | null | undefined;
12
32
  export declare function validateConfig(config: ConfigInput): ZenithConfig;
package/dist/config.js CHANGED
@@ -1,5 +1,17 @@
1
1
  import { join } from 'node:path';
2
2
  import { pathToFileURL } from 'node:url';
3
+ const DEFAULT_IMAGE_CONFIG = {
4
+ formats: ['webp', 'avif'],
5
+ quality: 75,
6
+ deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
7
+ imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
8
+ remotePatterns: [],
9
+ allowSvg: false,
10
+ maxRemoteBytes: 10 * 1024 * 1024,
11
+ maxPixels: 40_000_000,
12
+ minimumCacheTTL: 60,
13
+ dangerouslyAllowLocalNetwork: false
14
+ };
3
15
  const DEFAULTS = {
4
16
  router: false,
5
17
  embeddedMarkupExpressions: false,
@@ -8,7 +20,8 @@ const DEFAULTS = {
8
20
  outDir: 'dist',
9
21
  pagesDir: 'pages',
10
22
  experimental: {},
11
- strictDomLints: false
23
+ strictDomLints: false,
24
+ images: DEFAULT_IMAGE_CONFIG
12
25
  };
13
26
  const SCHEMA = {
14
27
  router: 'boolean',
@@ -18,11 +31,140 @@ const SCHEMA = {
18
31
  outDir: 'string',
19
32
  pagesDir: 'string',
20
33
  experimental: 'object',
21
- strictDomLints: 'boolean'
34
+ strictDomLints: 'boolean',
35
+ images: 'object'
22
36
  };
37
+ function cloneImageConfig() {
38
+ return {
39
+ formats: [...DEFAULT_IMAGE_CONFIG.formats],
40
+ quality: DEFAULT_IMAGE_CONFIG.quality,
41
+ deviceSizes: [...DEFAULT_IMAGE_CONFIG.deviceSizes],
42
+ imageSizes: [...DEFAULT_IMAGE_CONFIG.imageSizes],
43
+ remotePatterns: [],
44
+ allowSvg: DEFAULT_IMAGE_CONFIG.allowSvg,
45
+ maxRemoteBytes: DEFAULT_IMAGE_CONFIG.maxRemoteBytes,
46
+ maxPixels: DEFAULT_IMAGE_CONFIG.maxPixels,
47
+ minimumCacheTTL: DEFAULT_IMAGE_CONFIG.minimumCacheTTL,
48
+ dangerouslyAllowLocalNetwork: DEFAULT_IMAGE_CONFIG.dangerouslyAllowLocalNetwork
49
+ };
50
+ }
51
+ function requirePositiveInt(value, key) {
52
+ if (!Number.isInteger(value) || Number(value) <= 0) {
53
+ throw new Error(`[Zenith:Config] images.${key} must be a positive integer`);
54
+ }
55
+ return Number(value);
56
+ }
57
+ function normalizeStringList(value, key) {
58
+ if (!Array.isArray(value) || value.length === 0) {
59
+ throw new Error(`[Zenith:Config] images.${key} must be a non-empty array`);
60
+ }
61
+ const out = value.map((entry) => {
62
+ if (typeof entry !== 'string' || entry.trim() === '') {
63
+ throw new Error(`[Zenith:Config] images.${key} must contain non-empty strings`);
64
+ }
65
+ return entry.trim().toLowerCase();
66
+ });
67
+ return [...new Set(out)];
68
+ }
69
+ function normalizePositiveIntList(value, key) {
70
+ if (!Array.isArray(value) || value.length === 0) {
71
+ throw new Error(`[Zenith:Config] images.${key} must be a non-empty array`);
72
+ }
73
+ const out = value.map((entry) => requirePositiveInt(entry, key));
74
+ return [...new Set(out)].sort((left, right) => left - right);
75
+ }
76
+ function normalizeRemotePatterns(value) {
77
+ if (!Array.isArray(value)) {
78
+ throw new Error('[Zenith:Config] images.remotePatterns must be an array');
79
+ }
80
+ return value.map((entry) => {
81
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
82
+ throw new Error('[Zenith:Config] images.remotePatterns must contain plain objects');
83
+ }
84
+ const protocol = typeof entry.protocol === 'string' && entry.protocol.trim().length > 0
85
+ ? entry.protocol.trim().replace(/:$/, '').toLowerCase()
86
+ : 'https';
87
+ const hostname = typeof entry.hostname === 'string' ? entry.hostname.trim().toLowerCase() : '';
88
+ if (!hostname) {
89
+ throw new Error('[Zenith:Config] images.remotePatterns[].hostname is required');
90
+ }
91
+ return {
92
+ protocol,
93
+ hostname,
94
+ port: typeof entry.port === 'string' ? entry.port.trim() : '',
95
+ pathname: typeof entry.pathname === 'string' && entry.pathname.trim().length > 0
96
+ ? entry.pathname.trim()
97
+ : '/**',
98
+ search: typeof entry.search === 'string' ? entry.search : ''
99
+ };
100
+ });
101
+ }
102
+ function normalizeImageConfig(input) {
103
+ if (input === undefined || input === null) {
104
+ return cloneImageConfig();
105
+ }
106
+ if (typeof input !== 'object' || Array.isArray(input)) {
107
+ throw new Error('[Zenith:Config] images must be a plain object');
108
+ }
109
+ const allowed = new Set([
110
+ 'formats',
111
+ 'quality',
112
+ 'deviceSizes',
113
+ 'imageSizes',
114
+ 'remotePatterns',
115
+ 'allowSvg',
116
+ 'maxRemoteBytes',
117
+ 'maxPixels',
118
+ 'minimumCacheTTL',
119
+ 'dangerouslyAllowLocalNetwork'
120
+ ]);
121
+ for (const key of Object.keys(input)) {
122
+ if (!allowed.has(key)) {
123
+ throw new Error(`[Zenith:Config] Unknown key: "images.${key}"`);
124
+ }
125
+ }
126
+ const config = cloneImageConfig();
127
+ if ('formats' in input) {
128
+ config.formats = normalizeStringList(input.formats, 'formats');
129
+ }
130
+ if ('quality' in input) {
131
+ config.quality = requirePositiveInt(input.quality, 'quality');
132
+ }
133
+ if ('deviceSizes' in input) {
134
+ config.deviceSizes = normalizePositiveIntList(input.deviceSizes, 'deviceSizes');
135
+ }
136
+ if ('imageSizes' in input) {
137
+ config.imageSizes = normalizePositiveIntList(input.imageSizes, 'imageSizes');
138
+ }
139
+ if ('remotePatterns' in input) {
140
+ config.remotePatterns = normalizeRemotePatterns(input.remotePatterns);
141
+ }
142
+ if ('allowSvg' in input) {
143
+ if (typeof input.allowSvg !== 'boolean') {
144
+ throw new Error('[Zenith:Config] images.allowSvg must be boolean');
145
+ }
146
+ config.allowSvg = Boolean(input.allowSvg);
147
+ }
148
+ if ('maxRemoteBytes' in input) {
149
+ config.maxRemoteBytes = requirePositiveInt(input.maxRemoteBytes, 'maxRemoteBytes');
150
+ }
151
+ if ('maxPixels' in input) {
152
+ config.maxPixels = requirePositiveInt(input.maxPixels, 'maxPixels');
153
+ }
154
+ if ('minimumCacheTTL' in input) {
155
+ config.minimumCacheTTL = requirePositiveInt(input.minimumCacheTTL, 'minimumCacheTTL');
156
+ }
157
+ if ('dangerouslyAllowLocalNetwork' in input) {
158
+ if (typeof input.dangerouslyAllowLocalNetwork !== 'boolean') {
159
+ throw new Error('[Zenith:Config] images.dangerouslyAllowLocalNetwork must be boolean');
160
+ }
161
+ config.dangerouslyAllowLocalNetwork = Boolean(input.dangerouslyAllowLocalNetwork);
162
+ }
163
+ return config;
164
+ }
23
165
  export function validateConfig(config) {
24
166
  if (config === null || config === undefined) {
25
- return { ...DEFAULTS };
167
+ return { ...DEFAULTS, images: cloneImageConfig() };
26
168
  }
27
169
  if (typeof config !== 'object' || Array.isArray(config)) {
28
170
  throw new Error('[Zenith:Config] Config must be a plain object');
@@ -32,7 +174,7 @@ export function validateConfig(config) {
32
174
  throw new Error(`[Zenith:Config] Unknown key: "${key}"`);
33
175
  }
34
176
  }
35
- const result = { ...DEFAULTS };
177
+ const result = { ...DEFAULTS, images: cloneImageConfig() };
36
178
  for (const [key, expectedType] of Object.entries(SCHEMA)) {
37
179
  if (key in config) {
38
180
  const value = config[key];
@@ -49,6 +191,10 @@ export function validateConfig(config) {
49
191
  result[key] = { ...DEFAULTS.experimental };
50
192
  continue;
51
193
  }
194
+ if (key === 'images') {
195
+ result.images = normalizeImageConfig(value);
196
+ continue;
197
+ }
52
198
  result[key] = value;
53
199
  }
54
200
  }
@@ -68,11 +214,11 @@ export async function loadConfig(projectRoot) {
68
214
  error.code === 'ENOENT' ||
69
215
  error.message?.includes('Cannot find module') ||
70
216
  error.message?.includes('ENOENT')) {
71
- return { ...DEFAULTS };
217
+ return { ...DEFAULTS, images: cloneImageConfig() };
72
218
  }
73
219
  throw err;
74
220
  }
75
221
  }
76
222
  export function getDefaults() {
77
- return { ...DEFAULTS };
223
+ return { ...DEFAULTS, images: cloneImageConfig() };
78
224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenithbuild/core",
3
- "version": "0.6.17",
3
+ "version": "0.7.0",
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.6.17",
47
- "@zenithbuild/compiler": "0.6.17",
48
- "@zenithbuild/runtime": "0.6.17",
49
- "@zenithbuild/router": "0.6.17",
50
- "@zenithbuild/bundler": "0.6.17"
46
+ "@zenithbuild/cli": "0.7.0",
47
+ "@zenithbuild/compiler": "0.7.0",
48
+ "@zenithbuild/runtime": "0.7.0",
49
+ "@zenithbuild/router": "0.7.0",
50
+ "@zenithbuild/bundler": "0.7.0"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "rm -rf dist && tsc -p tsconfig.build.json",