@zenithbuild/cli 0.4.11 → 0.5.0-beta.2.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,52 @@
1
+ import { writeFile, mkdir } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+
4
+ export async function generateEnvDts(projectRoot) {
5
+ const content = `// .zenith/zenith-env.d.ts
6
+ // Auto-generated by Zenith. Do not edit.
7
+
8
+ export {};
9
+
10
+ declare global {
11
+ namespace Zenith {
12
+ type Params = Record<string, string>;
13
+
14
+ interface LoadContext {
15
+ params: Params;
16
+ url: URL;
17
+ request: Request;
18
+ route: {
19
+ id: string;
20
+ file: string;
21
+ pattern: string;
22
+ };
23
+ }
24
+
25
+ interface ErrorState {
26
+ status?: number;
27
+ code?: string;
28
+ message: string;
29
+ }
30
+
31
+ type PageData = Record<string, unknown> & {
32
+ __zenith_error?: ErrorState;
33
+ };
34
+
35
+ type Load<T extends PageData = PageData> = (ctx: LoadContext) => Promise<T> | T;
36
+
37
+ type Renderable =
38
+ | string
39
+ | number
40
+ | boolean
41
+ | null
42
+ | undefined
43
+ | Renderable[]
44
+ | { __zenith_fragment: true };
45
+ }
46
+ }
47
+ `;
48
+
49
+ const outPath = join(projectRoot, '.zenith', 'zenith-env.d.ts');
50
+ await mkdir(dirname(outPath), { recursive: true });
51
+ await writeFile(outPath, content, 'utf8');
52
+ }
@@ -0,0 +1,22 @@
1
+ import { writeFile, mkdir } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+
4
+ export async function generateRoutesDts(projectRoot, manifest) {
5
+ const routes = (manifest || []).map(r => r.path).filter(Boolean);
6
+ const typeDef = routes.length > 0
7
+ ? routes.map(r => '"' + r + '"').join(' | ')
8
+ : 'string';
9
+
10
+ const content = '// .zenith/zenith-routes.d.ts\\n' +
11
+ '// Auto-generated by Zenith. Do not edit.\\n\\n' +
12
+ 'export {};\\n\\n' +
13
+ 'declare global {\\n' +
14
+ ' namespace Zenith {\\n' +
15
+ ' type RoutePattern = ' + typeDef + ';\\n' +
16
+ ' }\\n' +
17
+ '}\\n';
18
+
19
+ const outPath = join(projectRoot, '.zenith', 'zenith-routes.d.ts');
20
+ await mkdir(dirname(outPath), { recursive: true });
21
+ await writeFile(outPath, content, 'utf8');
22
+ }
@@ -0,0 +1,34 @@
1
+ import { generateEnvDts } from './generate-env-dts.js';
2
+ import { generateRoutesDts } from './generate-routes-dts.js';
3
+ import { join } from 'node:path';
4
+ import { access, constants } from 'node:fs/promises';
5
+
6
+ export async function ensureZenithTypes(projectRoot, manifest) {
7
+ try {
8
+ await generateEnvDts(projectRoot);
9
+ if (manifest) {
10
+ await generateRoutesDts(projectRoot, manifest);
11
+ }
12
+
13
+ // Check if tsconfig.json exists, if it does, check if .zenith is included
14
+ const tsconfigPath = join(projectRoot, 'tsconfig.json');
15
+ let hasTsConfig = false;
16
+ try {
17
+ await access(tsconfigPath, constants.F_OK);
18
+ hasTsConfig = true;
19
+ } catch {
20
+ hasTsConfig = false;
21
+ }
22
+
23
+ if (hasTsConfig) {
24
+ // In a real implementation this would parse the JSON and check "include".
25
+ // For now, we simply inform the user to include it if they haven't.
26
+ if (!globalThis.__zenithTypesWarned) {
27
+ console.warn('\\x1b[33m[zenith]\\x1b[0m For the best TypeScript experience, ensure ".zenith/**/*.d.ts" is in your tsconfig.json "include" array.');
28
+ globalThis.__zenithTypesWarned = true;
29
+ }
30
+ }
31
+ } catch (err) {
32
+ console.error('[zenith] Failed to generate type definitions:', err);
33
+ }
34
+ }
package/dist/ui/env.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * UI environment mode detection for deterministic CLI output.
3
+ */
4
+
5
+ function flagEnabled(value) {
6
+ if (value === undefined || value === null) {
7
+ return false;
8
+ }
9
+ const normalized = String(value).trim().toLowerCase();
10
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
11
+ }
12
+
13
+ /**
14
+ * @param {{ env?: Record<string, string | undefined>, stdout?: { isTTY?: boolean } }} runtime
15
+ */
16
+ export function getUiMode(runtime = process) {
17
+ const env = runtime.env || {};
18
+ const tty = Boolean(runtime.stdout?.isTTY);
19
+ const ci = flagEnabled(env.CI);
20
+ const noUi = flagEnabled(env.ZENITH_NO_UI);
21
+ const noColor = env.NO_COLOR !== undefined && String(env.NO_COLOR).length >= 0;
22
+ const forceColor = flagEnabled(env.FORCE_COLOR);
23
+ const debug = flagEnabled(env.ZENITH_DEBUG);
24
+
25
+ const plain = noUi || ci || !tty;
26
+ const color = !plain && !noColor && (forceColor || tty);
27
+ const spinner = tty && !plain && !ci;
28
+
29
+ return {
30
+ plain,
31
+ color,
32
+ tty,
33
+ ci,
34
+ spinner,
35
+ debug
36
+ };
37
+ }
38
+
39
+ export function isUiPlain(runtime = process) {
40
+ return getUiMode(runtime).plain;
41
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Deterministic text formatters for CLI UX.
3
+ */
4
+
5
+ import { relative, sep } from 'node:path';
6
+
7
+ const ANSI = {
8
+ reset: '\x1b[0m',
9
+ bold: '\x1b[1m',
10
+ dim: '\x1b[2m',
11
+ red: '\x1b[31m',
12
+ yellow: '\x1b[33m',
13
+ green: '\x1b[32m',
14
+ cyan: '\x1b[36m'
15
+ };
16
+
17
+ function colorize(mode, token, text) {
18
+ if (!mode.color) {
19
+ return text;
20
+ }
21
+ return `${ANSI[token]}${text}${ANSI.reset}`;
22
+ }
23
+
24
+ export function formatHeading(mode, text) {
25
+ const label = mode.plain ? 'ZENITH CLI' : colorize(mode, 'bold', 'Zenith CLI');
26
+ return `${label} ${text}`.trim();
27
+ }
28
+
29
+ export function formatStep(mode, text) {
30
+ if (mode.plain) {
31
+ return `[zenith] INFO: ${text}`;
32
+ }
33
+ const bullet = colorize(mode, 'cyan', '•');
34
+ return `[zenith] ${bullet} ${text}`;
35
+ }
36
+
37
+ export function formatSummaryTable(mode, rows) {
38
+ if (!Array.isArray(rows) || rows.length === 0) {
39
+ return '';
40
+ }
41
+ const maxLabel = rows.reduce((acc, row) => Math.max(acc, String(row.label || '').length), 0);
42
+ return rows
43
+ .map((row) => {
44
+ const label = String(row.label || '').padEnd(maxLabel, ' ');
45
+ const value = String(row.value || '');
46
+ return `[zenith] ${label} : ${value}`;
47
+ })
48
+ .join('\n');
49
+ }
50
+
51
+ export function sanitizeErrorMessage(input) {
52
+ return String(input ?? '')
53
+ .replace(/\r/g, '')
54
+ .trim();
55
+ }
56
+
57
+ function normalizeFileLinePath(line) {
58
+ const match = line.match(/^(\s*File:\s+)(.+)$/);
59
+ if (!match) {
60
+ return line;
61
+ }
62
+
63
+ const prefix = match[1];
64
+ const filePath = match[2].trim();
65
+ if (!filePath.startsWith('/') && !/^[A-Za-z]:\\/.test(filePath)) {
66
+ return line;
67
+ }
68
+
69
+ const cwd = process.cwd();
70
+ const cwdWithSep = cwd.endsWith(sep) ? cwd : `${cwd}${sep}`;
71
+ if (filePath === cwd) {
72
+ return `${prefix}.`;
73
+ }
74
+ if (filePath.startsWith(cwdWithSep)) {
75
+ const relativePath = relative(cwd, filePath).replaceAll('\\', '/');
76
+ return `${prefix}${relativePath || '.'}`;
77
+ }
78
+
79
+ return line;
80
+ }
81
+
82
+ export function normalizeErrorMessagePaths(message) {
83
+ return String(message || '')
84
+ .split('\n')
85
+ .map((line) => normalizeFileLinePath(line))
86
+ .join('\n');
87
+ }
88
+
89
+ /**
90
+ * @param {unknown} err
91
+ */
92
+ export function normalizeError(err) {
93
+ if (err instanceof Error) {
94
+ return err;
95
+ }
96
+ return new Error(sanitizeErrorMessage(err));
97
+ }
98
+
99
+ /**
100
+ * @param {unknown} err
101
+ * @param {{ plain: boolean, color: boolean, debug: boolean }} mode
102
+ */
103
+ export function formatErrorBlock(err, mode) {
104
+ const normalized = normalizeError(err);
105
+ const maybe = /** @type {{ code?: unknown, phase?: unknown, kind?: unknown }} */ (normalized);
106
+ const kind = sanitizeErrorMessage(maybe.kind || maybe.code || 'CLI_ERROR');
107
+ const phase = maybe.phase ? sanitizeErrorMessage(maybe.phase) : '';
108
+ const code = maybe.code ? sanitizeErrorMessage(maybe.code) : '';
109
+ const rawMessage = sanitizeErrorMessage(normalized.message || String(normalized));
110
+ const message = normalizeErrorMessagePaths(rawMessage);
111
+
112
+ const lines = [];
113
+ lines.push('[zenith] ERROR: Command failed');
114
+ lines.push(`[zenith] Error Kind: ${kind}`);
115
+ if (phase) {
116
+ lines.push(`[zenith] Phase: ${phase}`);
117
+ }
118
+ if (code) {
119
+ lines.push(`[zenith] Code: ${code}`);
120
+ }
121
+ lines.push(`[zenith] Message: ${message}`);
122
+
123
+ if (mode.debug && normalized.stack) {
124
+ lines.push('[zenith] Stack:');
125
+ lines.push(...String(normalized.stack).split('\n').slice(0, 20));
126
+ }
127
+
128
+ return lines.join('\n');
129
+ }
130
+
131
+ export function containsAnsi(value) {
132
+ return /\x1b\[[0-9;]*m/.test(String(value || ''));
133
+ }
@@ -0,0 +1,105 @@
1
+ import { formatErrorBlock, formatHeading, formatStep, formatSummaryTable } from './format.js';
2
+ import { getUiMode } from './env.js';
3
+
4
+ const SPINNER_FRAMES = ['-', '\\', '|', '/'];
5
+
6
+ function write(out, text) {
7
+ out.write(`${text}\n`);
8
+ }
9
+
10
+ function createSpinner(mode, stderr) {
11
+ if (!mode.spinner) {
12
+ return {
13
+ start() { },
14
+ update() { },
15
+ stop() { },
16
+ succeed() { },
17
+ fail() { }
18
+ };
19
+ }
20
+
21
+ let interval = null;
22
+ let frame = 0;
23
+ let message = '';
24
+
25
+ const paint = () => {
26
+ const current = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
27
+ stderr.write(`\r[zenith] ${current} ${message}`);
28
+ frame += 1;
29
+ };
30
+
31
+ const clear = () => {
32
+ stderr.write('\r');
33
+ stderr.write(' '.repeat(message.length + 12));
34
+ stderr.write('\r');
35
+ };
36
+
37
+ return {
38
+ start(nextMessage) {
39
+ message = String(nextMessage || '');
40
+ clearInterval(interval);
41
+ frame = 0;
42
+ paint();
43
+ interval = setInterval(paint, 80);
44
+ },
45
+ update(nextMessage) {
46
+ message = String(nextMessage || '');
47
+ },
48
+ stop() {
49
+ clearInterval(interval);
50
+ interval = null;
51
+ clear();
52
+ },
53
+ succeed(nextMessage) {
54
+ this.stop();
55
+ write(stderr, `[zenith] OK: ${nextMessage}`);
56
+ },
57
+ fail(nextMessage) {
58
+ this.stop();
59
+ write(stderr, `[zenith] ERROR: ${nextMessage}`);
60
+ }
61
+ };
62
+ }
63
+
64
+ /**
65
+ * @param {NodeJS.Process} runtime
66
+ */
67
+ export function createLogger(runtime = process) {
68
+ const mode = getUiMode(runtime);
69
+ const stdout = runtime.stdout;
70
+ const stderr = runtime.stderr;
71
+ const spinner = createSpinner(mode, stderr);
72
+
73
+ return {
74
+ mode,
75
+ spinner,
76
+ heading(text) {
77
+ write(stdout, formatHeading(mode, text));
78
+ },
79
+ info(text) {
80
+ if (mode.plain) {
81
+ write(stdout, `[zenith] INFO: ${text}`);
82
+ return;
83
+ }
84
+ write(stdout, formatStep(mode, text));
85
+ },
86
+ success(text) {
87
+ write(stdout, `[zenith] OK: ${text}`);
88
+ },
89
+ warn(text) {
90
+ write(stderr, `[zenith] WARN: ${text}`);
91
+ },
92
+ error(err) {
93
+ write(stderr, formatErrorBlock(err, mode));
94
+ },
95
+ summary(rows) {
96
+ const table = formatSummaryTable(mode, rows);
97
+ if (table) {
98
+ write(stdout, table);
99
+ }
100
+ },
101
+ print(text) {
102
+ write(stdout, String(text));
103
+ }
104
+ };
105
+ }
package/package.json CHANGED
@@ -1,63 +1,35 @@
1
1
  {
2
2
  "name": "@zenithbuild/cli",
3
- "version": "0.4.11",
4
- "description": "CLI for Zenith framework - dev server, build tools, and plugin management",
3
+ "version": "0.5.0-beta.2.3",
4
+ "description": "Deterministic project orchestrator for Zenith framework",
5
+ "license": "MIT",
5
6
  "type": "module",
6
- "bin": {
7
- "zenith": "./dist/zenith.js",
8
- "zen-dev": "./dist/zen-dev.js",
9
- "zen-build": "./dist/zen-build.js",
10
- "zen-preview": "./dist/zen-preview.js"
11
- },
12
- "main": "./src/index.ts",
13
- "types": "./src/index.ts",
7
+ "main": "./dist/index.js",
14
8
  "exports": {
15
- ".": {
16
- "types": "./src/index.ts",
17
- "import": "./src/index.ts"
18
- }
9
+ ".": "./dist/index.js"
19
10
  },
20
11
  "files": [
21
- "bin",
22
- "src"
23
- ],
24
- "scripts": {
25
- "dev": "bun run build && bin/zenith.ts dev",
26
- "build": "bun build bin/zenith.ts bin/zen-dev.ts bin/zen-build.ts bin/zen-preview.ts --outdir dist --target bun --bundle && for f in dist/*.js; do echo '#!/usr/bin/env bun' | cat - \"$f\" > \"$f.tmp\" && mv \"$f.tmp\" \"$f\" && chmod +x \"$f\"; done",
27
- "start": "bun run build && bun run dev",
28
- "test": "bun test",
29
- "release": "bun run scripts/release.ts",
30
- "release:dry": "bun run scripts/release.ts --dry-run",
31
- "release:patch": "bun run scripts/release.ts --bump=patch",
32
- "release:minor": "bun run scripts/release.ts --bump=minor",
33
- "release:major": "bun run scripts/release.ts --bump=major"
34
- },
35
- "keywords": [
36
- "zenith",
37
- "cli",
38
- "framework",
39
- "ssg",
40
- "ssr"
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE",
15
+ "dist/**",
16
+ "package.json"
41
17
  ],
42
- "author": "Zenith Team",
43
- "license": "MIT",
44
- "repository": {
45
- "type": "git",
46
- "url": "git@github.com:zenithbuild/zenith-cli.git"
47
- },
48
18
  "publishConfig": {
49
19
  "access": "public"
50
20
  },
51
- "private": false,
52
- "peerDependencies": {
53
- "@zenithbuild/core": "^1.2.15"
21
+ "scripts": {
22
+ "build": "mkdir -p dist && cp -a src/* dist/",
23
+ "test": "node --experimental-vm-modules $(node -e \"const path=require('node:path');const pkg=require.resolve('jest/package.json');process.stdout.write(path.join(path.dirname(pkg),'bin/jest.js'))\") --config jest.config.js --forceExit --runInBand",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "dependencies": {
27
+ "@zenithbuild/compiler": "0.5.0-beta.2.3"
54
28
  },
55
29
  "devDependencies": {
56
- "@types/bun": "latest"
30
+ "@jest/globals": "^30.2.0",
31
+ "jest": "^30.2.0",
32
+ "jest-environment-jsdom": "^30.2.0"
57
33
  },
58
- "dependencies": {
59
- "@zenithbuild/compiler": "^1.0.16",
60
- "@zenithbuild/router": "^1.0.8",
61
- "picocolors": "^1.0.0"
62
- }
63
- }
34
+ "private": false
35
+ }
package/bin/zen-build.ts DELETED
@@ -1,2 +0,0 @@
1
- import { runCLI } from '../src/main'
2
- runCLI({ defaultCommand: 'build' })
package/bin/zen-dev.ts DELETED
@@ -1,2 +0,0 @@
1
- import { runCLI } from '../src/main'
2
- runCLI({ defaultCommand: 'dev' })
@@ -1,2 +0,0 @@
1
- import { runCLI } from '../src/main'
2
- runCLI({ defaultCommand: 'preview' })
package/bin/zenith.ts DELETED
@@ -1,2 +0,0 @@
1
- import { runCLI } from '../src/main'
2
- runCLI()