@zenithbuild/cli 0.6.6 → 0.6.9

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.
Files changed (43) hide show
  1. package/dist/build.d.ts +32 -0
  2. package/dist/build.js +193 -548
  3. package/dist/compiler-bridge-runner.d.ts +5 -0
  4. package/dist/compiler-bridge-runner.js +70 -0
  5. package/dist/component-instance-ir.d.ts +6 -0
  6. package/dist/component-instance-ir.js +0 -20
  7. package/dist/component-occurrences.d.ts +6 -0
  8. package/dist/component-occurrences.js +6 -28
  9. package/dist/dev-server.d.ts +18 -0
  10. package/dist/dev-server.js +65 -114
  11. package/dist/dev-watch.d.ts +1 -0
  12. package/dist/dev-watch.js +2 -2
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.js +6 -28
  15. package/dist/manifest.d.ts +23 -0
  16. package/dist/manifest.js +22 -48
  17. package/dist/preview.d.ts +100 -0
  18. package/dist/preview.js +418 -488
  19. package/dist/resolve-components.d.ts +39 -0
  20. package/dist/resolve-components.js +30 -104
  21. package/dist/server/resolve-request-route.d.ts +39 -0
  22. package/dist/server/resolve-request-route.js +104 -113
  23. package/dist/server-contract.d.ts +39 -0
  24. package/dist/server-contract.js +15 -67
  25. package/dist/toolchain-paths.d.ts +23 -0
  26. package/dist/toolchain-paths.js +139 -39
  27. package/dist/toolchain-runner.d.ts +33 -0
  28. package/dist/toolchain-runner.js +194 -0
  29. package/dist/types/generate-env-dts.d.ts +5 -0
  30. package/dist/types/generate-env-dts.js +4 -2
  31. package/dist/types/generate-routes-dts.d.ts +8 -0
  32. package/dist/types/generate-routes-dts.js +7 -5
  33. package/dist/types/index.d.ts +14 -0
  34. package/dist/types/index.js +16 -7
  35. package/dist/ui/env.d.ts +18 -0
  36. package/dist/ui/env.js +0 -12
  37. package/dist/ui/format.d.ts +33 -0
  38. package/dist/ui/format.js +8 -46
  39. package/dist/ui/logger.d.ts +59 -0
  40. package/dist/ui/logger.js +3 -32
  41. package/dist/version-check.d.ts +54 -0
  42. package/dist/version-check.js +41 -98
  43. package/package.json +6 -4
package/dist/index.js CHANGED
@@ -9,27 +9,24 @@
9
9
  //
10
10
  // Minimal arg parsing. No heavy dependencies.
11
11
  // ---------------------------------------------------------------------------
12
-
13
12
  import { resolve, join, dirname } from 'node:path';
14
13
  import { existsSync, readFileSync } from 'node:fs';
15
14
  import { fileURLToPath } from 'node:url';
16
15
  import { createZenithLogger } from './ui/logger.js';
17
-
18
16
  const COMMANDS = ['dev', 'build', 'preview'];
19
17
  const DEFAULT_VERSION = '0.0.0';
20
18
  const __filename = fileURLToPath(import.meta.url);
21
19
  const __dirname = dirname(__filename);
22
-
23
20
  function getCliVersion() {
24
21
  try {
25
22
  const pkgPath = join(__dirname, '..', 'package.json');
26
23
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
27
24
  return typeof pkg.version === 'string' ? pkg.version : DEFAULT_VERSION;
28
- } catch {
25
+ }
26
+ catch {
29
27
  return DEFAULT_VERSION;
30
28
  }
31
29
  }
32
-
33
30
  function printUsage(logger) {
34
31
  logger.heading('V0');
35
32
  logger.print('Usage:');
@@ -42,12 +39,10 @@ function printUsage(logger) {
42
39
  logger.print(' -v, --version Print Zenith CLI version');
43
40
  logger.print('');
44
41
  }
45
-
46
42
  function resolvePort(args, fallback) {
47
43
  if (!Array.isArray(args) || args.length === 0) {
48
44
  return fallback;
49
45
  }
50
-
51
46
  const flagIndex = args.findIndex((arg) => arg === '--port' || arg === '-p');
52
47
  if (flagIndex >= 0 && args[flagIndex + 1]) {
53
48
  const parsed = Number.parseInt(args[flagIndex + 1], 10);
@@ -55,7 +50,6 @@ function resolvePort(args, fallback) {
55
50
  return parsed;
56
51
  }
57
52
  }
58
-
59
53
  const positional = args.find((arg) => /^[0-9]+$/.test(arg));
60
54
  if (positional) {
61
55
  const parsed = Number.parseInt(positional, 10);
@@ -63,10 +57,8 @@ function resolvePort(args, fallback) {
63
57
  return parsed;
64
58
  }
65
59
  }
66
-
67
60
  return fallback;
68
61
  }
69
-
70
62
  /**
71
63
  * Load zenith.config.js from project root.
72
64
  *
@@ -78,11 +70,11 @@ async function loadConfig(projectRoot) {
78
70
  try {
79
71
  const mod = await import(configPath);
80
72
  return mod.default || {};
81
- } catch {
73
+ }
74
+ catch {
82
75
  return {};
83
76
  }
84
77
  }
85
-
86
78
  /**
87
79
  * CLI entry point.
88
80
  *
@@ -93,29 +85,24 @@ export async function cli(args, cwd) {
93
85
  const logger = createZenithLogger(process);
94
86
  const command = args[0];
95
87
  const cliVersion = getCliVersion();
96
-
97
88
  if (args.includes('--version') || args.includes('-v')) {
98
89
  logger.print(`zenith ${cliVersion}`);
99
90
  process.exit(0);
100
91
  }
101
-
102
92
  if (args.includes('--help') || args.includes('-h')) {
103
93
  printUsage(logger);
104
94
  process.exit(0);
105
95
  }
106
-
107
96
  if (!command || !COMMANDS.includes(command)) {
108
97
  printUsage(logger);
109
98
  process.exit(command ? 1 : 0);
110
99
  }
111
-
112
100
  const projectRoot = resolve(cwd || process.cwd());
113
101
  const rootPagesDir = join(projectRoot, 'pages');
114
102
  const srcPagesDir = join(projectRoot, 'src', 'pages');
115
103
  const pagesDir = existsSync(rootPagesDir) ? rootPagesDir : srcPagesDir;
116
104
  const outDir = join(projectRoot, 'dist');
117
105
  const config = await loadConfig(projectRoot);
118
-
119
106
  if (command === 'build' || command === 'dev') {
120
107
  const { maybeWarnAboutZenithVersionMismatch } = await import('./version-check.js');
121
108
  await maybeWarnAboutZenithVersionMismatch({
@@ -124,7 +111,6 @@ export async function cli(args, cwd) {
124
111
  command
125
112
  });
126
113
  }
127
-
128
114
  if (command === 'build') {
129
115
  const { build } = await import('./build.js');
130
116
  logger.build('Building…');
@@ -132,7 +118,6 @@ export async function cli(args, cwd) {
132
118
  logger.ok(`Built ${result.pages} page(s), ${result.assets.length} asset(s)`);
133
119
  logger.summary([{ label: 'Output', value: './dist' }], 'BUILD');
134
120
  }
135
-
136
121
  if (command === 'dev') {
137
122
  const { createDevServer } = await import('./dev-server.js');
138
123
  const port = process.env.ZENITH_DEV_PORT
@@ -142,7 +127,6 @@ export async function cli(args, cwd) {
142
127
  logger.dev('Starting dev server…');
143
128
  const dev = await createDevServer({ pagesDir, outDir, port, host, config, logger });
144
129
  logger.ok(`http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${dev.port}`);
145
-
146
130
  // Graceful shutdown
147
131
  process.on('SIGINT', () => {
148
132
  dev.close();
@@ -153,7 +137,6 @@ export async function cli(args, cwd) {
153
137
  process.exit(0);
154
138
  });
155
139
  }
156
-
157
140
  if (command === 'preview') {
158
141
  const { createPreviewServer } = await import('./preview.js');
159
142
  const port = resolvePort(args.slice(1), 4000);
@@ -161,7 +144,6 @@ export async function cli(args, cwd) {
161
144
  logger.dev('Starting preview server…');
162
145
  const preview = await createPreviewServer({ distDir: outDir, port, host, logger });
163
146
  logger.ok(`http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${preview.port}`);
164
-
165
147
  process.on('SIGINT', () => {
166
148
  preview.close();
167
149
  process.exit(0);
@@ -172,13 +154,9 @@ export async function cli(args, cwd) {
172
154
  });
173
155
  }
174
156
  }
175
-
176
157
  // Auto-run if called directly
177
- const isDirectRun = process.argv[1] && (
178
- process.argv[1].endsWith('/index.js') ||
179
- process.argv[1].endsWith('/zenith')
180
- );
181
-
158
+ const isDirectRun = process.argv[1] && (process.argv[1].endsWith('/index.js') ||
159
+ process.argv[1].endsWith('/zenith'));
182
160
  if (isDirectRun) {
183
161
  cli(process.argv.slice(2)).catch((error) => {
184
162
  const logger = createZenithLogger(process);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @typedef {{ path: string, file: string }} ManifestEntry
3
+ */
4
+ /**
5
+ * Scan a pages directory and produce a deterministic RouteManifest.
6
+ *
7
+ * @param {string} pagesDir - Absolute path to /pages directory
8
+ * @param {string} [extension='.zen'] - File extension to scan for
9
+ * @returns {Promise<ManifestEntry[]>}
10
+ */
11
+ export function generateManifest(pagesDir: string, extension?: string): Promise<ManifestEntry[]>;
12
+ /**
13
+ * Generate a JavaScript module string from manifest entries.
14
+ * Used for writing the manifest file to disk.
15
+ *
16
+ * @param {ManifestEntry[]} entries
17
+ * @returns {string}
18
+ */
19
+ export function serializeManifest(entries: ManifestEntry[]): string;
20
+ export type ManifestEntry = {
21
+ path: string;
22
+ file: string;
23
+ };
package/dist/manifest.js CHANGED
@@ -14,14 +14,11 @@
14
14
  // - Deterministic precedence: static > :param > *catchall
15
15
  // - Tie-breaker: lexicographic route path
16
16
  // ---------------------------------------------------------------------------
17
-
18
17
  import { readdir, stat } from 'node:fs/promises';
19
18
  import { join, relative, sep, basename, extname, dirname } from 'node:path';
20
-
21
19
  /**
22
20
  * @typedef {{ path: string, file: string }} ManifestEntry
23
21
  */
24
-
25
22
  /**
26
23
  * Scan a pages directory and produce a deterministic RouteManifest.
27
24
  *
@@ -31,16 +28,13 @@ import { join, relative, sep, basename, extname, dirname } from 'node:path';
31
28
  */
32
29
  export async function generateManifest(pagesDir, extension = '.zen') {
33
30
  const entries = await _scanDir(pagesDir, pagesDir, extension);
34
-
35
31
  // Validate: no repeated param names in any single route
36
32
  for (const entry of entries) {
37
33
  _validateParams(entry.path);
38
34
  }
39
-
40
35
  // Sort: static first, dynamic after, alpha within each category
41
36
  return _sortEntries(entries);
42
37
  }
43
-
44
38
  /**
45
39
  * Recursively scan a directory for page files.
46
40
  *
@@ -52,33 +46,29 @@ export async function generateManifest(pagesDir, extension = '.zen') {
52
46
  async function _scanDir(dir, root, ext) {
53
47
  /** @type {ManifestEntry[]} */
54
48
  const entries = [];
55
-
56
49
  let items;
57
50
  try {
58
51
  items = await readdir(dir);
59
- } catch {
52
+ }
53
+ catch {
60
54
  return entries;
61
55
  }
62
-
63
56
  // Sort items for deterministic traversal
64
57
  items.sort();
65
-
66
58
  for (const item of items) {
67
59
  const fullPath = join(dir, item);
68
60
  const info = await stat(fullPath);
69
-
70
61
  if (info.isDirectory()) {
71
62
  const nested = await _scanDir(fullPath, root, ext);
72
63
  entries.push(...nested);
73
- } else if (item.endsWith(ext)) {
64
+ }
65
+ else if (item.endsWith(ext)) {
74
66
  const routePath = _fileToRoute(fullPath, root, ext);
75
67
  entries.push({ path: routePath, file: relative(root, fullPath) });
76
68
  }
77
69
  }
78
-
79
70
  return entries;
80
71
  }
81
-
82
72
  /**
83
73
  * Convert a file path to a route path.
84
74
  *
@@ -97,10 +87,8 @@ async function _scanDir(dir, root, ext) {
97
87
  function _fileToRoute(filePath, root, ext) {
98
88
  const rel = relative(root, filePath);
99
89
  const withoutExt = rel.slice(0, -ext.length);
100
-
101
90
  // Normalize path separators
102
91
  const segments = withoutExt.split(sep).filter(Boolean);
103
-
104
92
  // Convert segments
105
93
  const routeSegments = segments.map((seg) => {
106
94
  // [[...param]] → *param? (optional catch-all)
@@ -108,13 +96,11 @@ function _fileToRoute(filePath, root, ext) {
108
96
  if (optionalCatchAllMatch) {
109
97
  return '*' + optionalCatchAllMatch[1] + '?';
110
98
  }
111
-
112
99
  // [...param] → *param (required catch-all)
113
100
  const catchAllMatch = seg.match(/^\[\.\.\.([a-zA-Z_][a-zA-Z0-9_]*)\]$/);
114
101
  if (catchAllMatch) {
115
102
  return '*' + catchAllMatch[1];
116
103
  }
117
-
118
104
  // [param] → :param
119
105
  const paramMatch = seg.match(/^\[([a-zA-Z_][a-zA-Z0-9_]*)\]$/);
120
106
  if (paramMatch) {
@@ -122,16 +108,13 @@ function _fileToRoute(filePath, root, ext) {
122
108
  }
123
109
  return seg;
124
110
  });
125
-
126
111
  // Remove trailing 'index'
127
112
  if (routeSegments.length > 0 && routeSegments[routeSegments.length - 1] === 'index') {
128
113
  routeSegments.pop();
129
114
  }
130
-
131
115
  const route = '/' + routeSegments.join('/');
132
116
  return route;
133
117
  }
134
-
135
118
  /**
136
119
  * Validate that a route path has no repeated param names.
137
120
  *
@@ -141,7 +124,6 @@ function _fileToRoute(filePath, root, ext) {
141
124
  function _validateParams(routePath) {
142
125
  const segments = routePath.split('/').filter(Boolean);
143
126
  const paramNames = new Set();
144
-
145
127
  for (let i = 0; i < segments.length; i++) {
146
128
  const seg = segments[i];
147
129
  if (seg.startsWith(':') || seg.startsWith('*')) {
@@ -151,20 +133,15 @@ function _validateParams(routePath) {
151
133
  const name = optionalCatchAll ? rawName.slice(0, -1) : rawName;
152
134
  const label = isCatchAll ? `*${rawName}` : `:${name}`;
153
135
  if (paramNames.has(name)) {
154
- throw new Error(
155
- `[Zenith CLI] Repeated param name '${label}' in route '${routePath}'`
156
- );
136
+ throw new Error(`[Zenith CLI] Repeated param name '${label}' in route '${routePath}'`);
157
137
  }
158
138
  if (isCatchAll && i !== segments.length - 1) {
159
- throw new Error(
160
- `[Zenith CLI] Catch-all segment '${label}' must be the last segment in route '${routePath}'`
161
- );
139
+ throw new Error(`[Zenith CLI] Catch-all segment '${label}' must be the last segment in route '${routePath}'`);
162
140
  }
163
141
  paramNames.add(name);
164
142
  }
165
143
  }
166
144
  }
167
-
168
145
  /**
169
146
  * Check if a route contains any dynamic segments.
170
147
  *
@@ -174,7 +151,6 @@ function _validateParams(routePath) {
174
151
  function _isDynamic(routePath) {
175
152
  return routePath.split('/').some((seg) => seg.startsWith(':') || seg.startsWith('*'));
176
153
  }
177
-
178
154
  /**
179
155
  * Sort manifest entries by deterministic route precedence.
180
156
  *
@@ -184,7 +160,6 @@ function _isDynamic(routePath) {
184
160
  function _sortEntries(entries) {
185
161
  return [...entries].sort((a, b) => compareRouteSpecificity(a.path, b.path));
186
162
  }
187
-
188
163
  /**
189
164
  * Deterministic route precedence:
190
165
  * static segment > param segment > catch-all segment.
@@ -195,9 +170,10 @@ function _sortEntries(entries) {
195
170
  * @returns {number}
196
171
  */
197
172
  function compareRouteSpecificity(a, b) {
198
- if (a === '/' && b !== '/') return -1;
199
- if (b === '/' && a !== '/') return 1;
200
-
173
+ if (a === '/' && b !== '/')
174
+ return -1;
175
+ if (b === '/' && a !== '/')
176
+ return 1;
201
177
  const aSegs = a.split('/').filter(Boolean);
202
178
  const bSegs = b.split('/').filter(Boolean);
203
179
  const aClass = routeClass(aSegs);
@@ -205,9 +181,7 @@ function compareRouteSpecificity(a, b) {
205
181
  if (aClass !== bClass) {
206
182
  return bClass - aClass;
207
183
  }
208
-
209
184
  const max = Math.min(aSegs.length, bSegs.length);
210
-
211
185
  for (let i = 0; i < max; i++) {
212
186
  const aWeight = segmentWeight(aSegs[i]);
213
187
  const bWeight = segmentWeight(bSegs[i]);
@@ -215,14 +189,11 @@ function compareRouteSpecificity(a, b) {
215
189
  return bWeight - aWeight;
216
190
  }
217
191
  }
218
-
219
192
  if (aSegs.length !== bSegs.length) {
220
193
  return bSegs.length - aSegs.length;
221
194
  }
222
-
223
195
  return a.localeCompare(b);
224
196
  }
225
-
226
197
  /**
227
198
  * @param {string[]} segments
228
199
  * @returns {number}
@@ -233,26 +204,30 @@ function routeClass(segments) {
233
204
  for (const segment of segments) {
234
205
  if (segment.startsWith('*')) {
235
206
  hasCatchAll = true;
236
- } else if (segment.startsWith(':')) {
207
+ }
208
+ else if (segment.startsWith(':')) {
237
209
  hasParam = true;
238
210
  }
239
211
  }
240
- if (!hasParam && !hasCatchAll) return 3;
241
- if (hasCatchAll) return 1;
212
+ if (!hasParam && !hasCatchAll)
213
+ return 3;
214
+ if (hasCatchAll)
215
+ return 1;
242
216
  return 2;
243
217
  }
244
-
245
218
  /**
246
219
  * @param {string | undefined} segment
247
220
  * @returns {number}
248
221
  */
249
222
  function segmentWeight(segment) {
250
- if (!segment) return 0;
251
- if (segment.startsWith('*')) return 1;
252
- if (segment.startsWith(':')) return 2;
223
+ if (!segment)
224
+ return 0;
225
+ if (segment.startsWith('*'))
226
+ return 1;
227
+ if (segment.startsWith(':'))
228
+ return 2;
253
229
  return 3;
254
230
  }
255
-
256
231
  /**
257
232
  * Generate a JavaScript module string from manifest entries.
258
233
  * Used for writing the manifest file to disk.
@@ -268,6 +243,5 @@ export function serializeManifest(entries) {
268
243
  : `() => import('./pages/${e.file}')`;
269
244
  return ` { path: '${e.path}', load: ${loader} }`;
270
245
  });
271
-
272
246
  return `export default [\n${lines.join(',\n')}\n];\n`;
273
247
  }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Create and start a preview server.
3
+ *
4
+ * @param {{ distDir: string, port?: number, host?: string, logger?: object | null }} options
5
+ * @returns {Promise<{ server: import('http').Server, port: number, close: () => void }>}
6
+ */
7
+ export function createPreviewServer(options: {
8
+ distDir: string;
9
+ port?: number;
10
+ host?: string;
11
+ logger?: object | null;
12
+ }): Promise<{
13
+ server: import("http").Server;
14
+ port: number;
15
+ close: () => void;
16
+ }>;
17
+ /**
18
+ * @typedef {{
19
+ * path: string;
20
+ * output: string;
21
+ * server_script?: string | null;
22
+ * server_script_path?: string | null;
23
+ * prerender?: boolean;
24
+ * route_id?: string;
25
+ * pattern?: string;
26
+ * params_shape?: Record<string, string>;
27
+ * has_guard?: boolean;
28
+ * has_load?: boolean;
29
+ * guard_module_ref?: string | null;
30
+ * load_module_ref?: string | null;
31
+ * }} PreviewRoute
32
+ */
33
+ /**
34
+ * @param {string} distDir
35
+ * @returns {Promise<PreviewRoute[]>}
36
+ */
37
+ export function loadRouteManifest(distDir: string): Promise<PreviewRoute[]>;
38
+ /**
39
+ * @param {{ source: string, sourcePath: string, params: Record<string, string>, requestUrl?: string, requestMethod?: string, requestHeaders?: Record<string, string | string[] | undefined>, routePattern?: string, routeFile?: string, routeId?: string }} input
40
+ * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, load: string } }>}
41
+ */
42
+ export function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, routePattern, routeFile, routeId, guardOnly }: {
43
+ source: string;
44
+ sourcePath: string;
45
+ params: Record<string, string>;
46
+ requestUrl?: string;
47
+ requestMethod?: string;
48
+ requestHeaders?: Record<string, string | string[] | undefined>;
49
+ routePattern?: string;
50
+ routeFile?: string;
51
+ routeId?: string;
52
+ }): Promise<{
53
+ result: {
54
+ kind: string;
55
+ [key: string]: unknown;
56
+ };
57
+ trace: {
58
+ guard: string;
59
+ load: string;
60
+ };
61
+ }>;
62
+ /**
63
+ * @param {{ source: string, sourcePath: string, params: Record<string, string>, requestUrl?: string, requestMethod?: string, requestHeaders?: Record<string, string | string[] | undefined>, routePattern?: string, routeFile?: string, routeId?: string }} input
64
+ * @returns {Promise<Record<string, unknown> | null>}
65
+ */
66
+ export function executeServerScript(input: {
67
+ source: string;
68
+ sourcePath: string;
69
+ params: Record<string, string>;
70
+ requestUrl?: string;
71
+ requestMethod?: string;
72
+ requestHeaders?: Record<string, string | string[] | undefined>;
73
+ routePattern?: string;
74
+ routeFile?: string;
75
+ routeId?: string;
76
+ }): Promise<Record<string, unknown> | null>;
77
+ /**
78
+ * @param {string} html
79
+ * @param {Record<string, unknown>} payload
80
+ * @returns {string}
81
+ */
82
+ export function injectSsrPayload(html: string, payload: Record<string, unknown>): string;
83
+ export function toStaticFilePath(distDir: any, pathname: any): string | null;
84
+ export function resolveWithinDist(distDir: any, requestPath: any): string | null;
85
+ export const matchRoute: typeof matchManifestRoute;
86
+ export type PreviewRoute = {
87
+ path: string;
88
+ output: string;
89
+ server_script?: string | null;
90
+ server_script_path?: string | null;
91
+ prerender?: boolean;
92
+ route_id?: string;
93
+ pattern?: string;
94
+ params_shape?: Record<string, string>;
95
+ has_guard?: boolean;
96
+ has_load?: boolean;
97
+ guard_module_ref?: string | null;
98
+ load_module_ref?: string | null;
99
+ };
100
+ import { matchRoute as matchManifestRoute } from './server/resolve-request-route.js';