@zenithbuild/core 0.4.6 → 0.5.0-beta.2.1

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 (109) hide show
  1. package/CORE_CONTRACT.md +143 -0
  2. package/README.md +11 -31
  3. package/bin/zenith.js +68 -0
  4. package/package.json +40 -52
  5. package/src/config.js +134 -0
  6. package/src/core-template.js +30 -0
  7. package/src/errors.js +54 -0
  8. package/src/guards.js +61 -0
  9. package/src/hash.js +52 -0
  10. package/src/index.js +26 -0
  11. package/src/ir/index.js +1 -0
  12. package/src/order.js +69 -0
  13. package/src/path.js +131 -0
  14. package/src/schema.js +28 -0
  15. package/src/version.js +67 -0
  16. package/bin/zen-build.ts +0 -2
  17. package/bin/zen-dev.ts +0 -2
  18. package/bin/zen-preview.ts +0 -2
  19. package/bin/zenith.ts +0 -2
  20. package/cli/commands/add.ts +0 -37
  21. package/cli/commands/build.ts +0 -37
  22. package/cli/commands/create.ts +0 -702
  23. package/cli/commands/dev.ts +0 -335
  24. package/cli/commands/index.ts +0 -112
  25. package/cli/commands/preview.ts +0 -62
  26. package/cli/commands/remove.ts +0 -33
  27. package/cli/index.ts +0 -10
  28. package/cli/main.ts +0 -101
  29. package/cli/utils/branding.ts +0 -178
  30. package/cli/utils/content.ts +0 -112
  31. package/cli/utils/logger.ts +0 -46
  32. package/cli/utils/plugin-manager.ts +0 -114
  33. package/cli/utils/project.ts +0 -77
  34. package/compiler/README.md +0 -380
  35. package/compiler/build-analyzer.ts +0 -122
  36. package/compiler/css/index.ts +0 -317
  37. package/compiler/discovery/componentDiscovery.ts +0 -174
  38. package/compiler/discovery/layouts.ts +0 -61
  39. package/compiler/errors/compilerError.ts +0 -56
  40. package/compiler/finalize/finalizeOutput.ts +0 -192
  41. package/compiler/finalize/generateFinalBundle.ts +0 -82
  42. package/compiler/index.ts +0 -81
  43. package/compiler/ir/types.ts +0 -150
  44. package/compiler/output/types.ts +0 -34
  45. package/compiler/parse/detectMapExpressions.ts +0 -102
  46. package/compiler/parse/parseScript.ts +0 -46
  47. package/compiler/parse/parseTemplate.ts +0 -591
  48. package/compiler/parse/parseZenFile.ts +0 -66
  49. package/compiler/parse/scriptAnalysis.ts +0 -83
  50. package/compiler/parse/trackLoopContext.ts +0 -82
  51. package/compiler/runtime/dataExposure.ts +0 -317
  52. package/compiler/runtime/generateDOM.ts +0 -246
  53. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  54. package/compiler/runtime/hydration.ts +0 -309
  55. package/compiler/runtime/navigation.ts +0 -432
  56. package/compiler/runtime/thinRuntime.ts +0 -160
  57. package/compiler/runtime/transformIR.ts +0 -343
  58. package/compiler/runtime/wrapExpression.ts +0 -95
  59. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  60. package/compiler/spa-build.ts +0 -917
  61. package/compiler/ssg-build.ts +0 -422
  62. package/compiler/test/validate-test.ts +0 -104
  63. package/compiler/transform/classifyExpression.ts +0 -444
  64. package/compiler/transform/componentResolver.ts +0 -289
  65. package/compiler/transform/expressionTransformer.ts +0 -385
  66. package/compiler/transform/fragmentLowering.ts +0 -634
  67. package/compiler/transform/generateBindings.ts +0 -47
  68. package/compiler/transform/generateHTML.ts +0 -28
  69. package/compiler/transform/layoutProcessor.ts +0 -132
  70. package/compiler/transform/slotResolver.ts +0 -292
  71. package/compiler/transform/transformNode.ts +0 -126
  72. package/compiler/transform/transformTemplate.ts +0 -38
  73. package/compiler/validate/invariants.ts +0 -292
  74. package/compiler/validate/validateExpressions.ts +0 -168
  75. package/core/config/index.ts +0 -16
  76. package/core/config/loader.ts +0 -69
  77. package/core/config/types.ts +0 -89
  78. package/core/index.ts +0 -135
  79. package/core/lifecycle/index.ts +0 -49
  80. package/core/lifecycle/zen-mount.ts +0 -182
  81. package/core/lifecycle/zen-unmount.ts +0 -88
  82. package/core/plugins/index.ts +0 -7
  83. package/core/plugins/registry.ts +0 -81
  84. package/core/reactivity/index.ts +0 -54
  85. package/core/reactivity/tracking.ts +0 -167
  86. package/core/reactivity/zen-batch.ts +0 -57
  87. package/core/reactivity/zen-effect.ts +0 -139
  88. package/core/reactivity/zen-memo.ts +0 -146
  89. package/core/reactivity/zen-ref.ts +0 -52
  90. package/core/reactivity/zen-signal.ts +0 -121
  91. package/core/reactivity/zen-state.ts +0 -180
  92. package/core/reactivity/zen-untrack.ts +0 -44
  93. package/dist/cli.js +0 -11653
  94. package/dist/zen-build.js +0 -15388
  95. package/dist/zen-dev.js +0 -15388
  96. package/dist/zen-preview.js +0 -15388
  97. package/dist/zenith.js +0 -15388
  98. package/router/index.ts +0 -76
  99. package/router/manifest.ts +0 -314
  100. package/router/navigation/ZenLink.zen +0 -231
  101. package/router/navigation/index.ts +0 -78
  102. package/router/navigation/zen-link.ts +0 -584
  103. package/router/runtime.ts +0 -458
  104. package/router/types.ts +0 -168
  105. package/runtime/build.ts +0 -17
  106. package/runtime/bundle-generator.ts +0 -800
  107. package/runtime/client-runtime.ts +0 -549
  108. package/runtime/serve.ts +0 -93
  109. package/tsconfig.json +0 -28
@@ -1,178 +0,0 @@
1
- /**
2
- * Zenith CLI Branding
3
- *
4
- * ASCII art logo, colors, animations, and styled output
5
- */
6
-
7
- import pc from 'picocolors'
8
-
9
- // Brand colors
10
- export const colors = {
11
- primary: pc.blue,
12
- secondary: pc.cyan,
13
- success: pc.green,
14
- warning: pc.yellow,
15
- error: pc.red,
16
- muted: pc.gray,
17
- bold: pc.bold,
18
- dim: pc.dim
19
- }
20
-
21
- // ASCII Zenith logo
22
- export const LOGO = `
23
- ${pc.cyan('╔═══════════════════════════════════════════════════════════╗')}
24
- ${pc.cyan('║')} ${pc.cyan('║')}
25
- ${pc.cyan('║')} ${pc.bold(pc.blue('███████╗'))}${pc.bold(pc.cyan('███████╗'))}${pc.bold(pc.blue('███╗ ██╗'))}${pc.bold(pc.cyan('██╗'))}${pc.bold(pc.blue('████████╗'))}${pc.bold(pc.cyan('██╗ ██╗'))} ${pc.cyan('║')}
26
- ${pc.cyan('║')} ${pc.bold(pc.blue('╚══███╔╝'))}${pc.bold(pc.cyan('██╔════╝'))}${pc.bold(pc.blue('████╗ ██║'))}${pc.bold(pc.cyan('██║'))}${pc.bold(pc.blue('╚══██╔══╝'))}${pc.bold(pc.cyan('██║ ██║'))} ${pc.cyan('║')}
27
- ${pc.cyan('║')} ${pc.bold(pc.blue(' ███╔╝ '))}${pc.bold(pc.cyan('█████╗ '))}${pc.bold(pc.blue('██╔██╗ ██║'))}${pc.bold(pc.cyan('██║'))}${pc.bold(pc.blue(' ██║ '))}${pc.bold(pc.cyan('███████║'))} ${pc.cyan('║')}
28
- ${pc.cyan('║')} ${pc.bold(pc.blue(' ███╔╝ '))}${pc.bold(pc.cyan('██╔══╝ '))}${pc.bold(pc.blue('██║╚██╗██║'))}${pc.bold(pc.cyan('██║'))}${pc.bold(pc.blue(' ██║ '))}${pc.bold(pc.cyan('██╔══██║'))} ${pc.cyan('║')}
29
- ${pc.cyan('║')} ${pc.bold(pc.blue('███████╗'))}${pc.bold(pc.cyan('███████╗'))}${pc.bold(pc.blue('██║ ╚████║'))}${pc.bold(pc.cyan('██║'))}${pc.bold(pc.blue(' ██║ '))}${pc.bold(pc.cyan('██║ ██║'))} ${pc.cyan('║')}
30
- ${pc.cyan('║')} ${pc.bold(pc.blue('╚══════╝'))}${pc.bold(pc.cyan('╚══════╝'))}${pc.bold(pc.blue('╚═╝ ╚═══╝'))}${pc.bold(pc.cyan('╚═╝'))}${pc.bold(pc.blue(' ╚═╝ '))}${pc.bold(pc.cyan('╚═╝ ╚═╝'))} ${pc.cyan('║')}
31
- ${pc.cyan('║')} ${pc.cyan('║')}
32
- ${pc.cyan('║')} ${pc.dim('The Modern Reactive Web Framework')} ${pc.cyan('║')}
33
- ${pc.cyan('║')} ${pc.cyan('║')}
34
- ${pc.cyan('╚═══════════════════════════════════════════════════════════╝')}
35
- `
36
-
37
- // Compact logo for smaller spaces
38
- export const LOGO_COMPACT = `
39
- ${pc.bold(pc.blue('⚡'))} ${pc.bold(pc.cyan('ZENITH'))} ${pc.dim('- Modern Reactive Framework')}
40
- `
41
-
42
- // Spinner frames for animations
43
- const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
44
-
45
- export class Spinner {
46
- private interval: ReturnType<typeof setInterval> | null = null
47
- private frameIndex = 0
48
- private message: string
49
-
50
- constructor(message: string) {
51
- this.message = message
52
- }
53
-
54
- start() {
55
- this.interval = setInterval(() => {
56
- process.stdout.write(`\r${pc.cyan(spinnerFrames[this.frameIndex])} ${this.message}`)
57
- this.frameIndex = (this.frameIndex + 1) % spinnerFrames.length
58
- }, 80)
59
- }
60
-
61
- stop(finalMessage?: string) {
62
- if (this.interval) {
63
- clearInterval(this.interval)
64
- this.interval = null
65
- }
66
- process.stdout.write('\r' + ' '.repeat(this.message.length + 5) + '\r')
67
- if (finalMessage) {
68
- console.log(finalMessage)
69
- }
70
- }
71
-
72
- succeed(message: string) {
73
- this.stop(`${pc.green('✓')} ${message}`)
74
- }
75
-
76
- fail(message: string) {
77
- this.stop(`${pc.red('✗')} ${message}`)
78
- }
79
- }
80
-
81
- // Styled output functions
82
- export function showLogo() {
83
- console.log(LOGO)
84
- }
85
-
86
- export function showCompactLogo() {
87
- console.log(LOGO_COMPACT)
88
- }
89
-
90
- export function header(text: string) {
91
- console.log(`\n${pc.bold(pc.cyan('▸'))} ${pc.bold(text)}\n`)
92
- }
93
-
94
- export function success(text: string) {
95
- console.log(`${pc.green('✓')} ${text}`)
96
- }
97
-
98
- export function error(text: string) {
99
- console.log(`${pc.red('✗')} ${text}`)
100
- }
101
-
102
- export function warn(text: string) {
103
- console.log(`${pc.yellow('⚠')} ${text}`)
104
- }
105
-
106
- export function info(text: string) {
107
- console.log(`${pc.blue('ℹ')} ${text}`)
108
- }
109
-
110
- export function step(num: number, text: string) {
111
- console.log(`${pc.dim(`[${num}]`)} ${text}`)
112
- }
113
-
114
- export function highlight(text: string): string {
115
- return pc.cyan(text)
116
- }
117
-
118
- export function dim(text: string): string {
119
- return pc.dim(text)
120
- }
121
-
122
- export function bold(text: string): string {
123
- return pc.bold(text)
124
- }
125
-
126
- // Animated intro (optional)
127
- export async function showIntro() {
128
- console.clear()
129
- showLogo()
130
- await sleep(300)
131
- }
132
-
133
- function sleep(ms: number): Promise<void> {
134
- return new Promise(resolve => setTimeout(resolve, ms))
135
- }
136
-
137
- // Next steps box
138
- export function showNextSteps(projectName: string) {
139
- console.log(`
140
- ${pc.cyan('┌─────────────────────────────────────────────────────────┐')}
141
- ${pc.cyan('│')} ${pc.cyan('│')}
142
- ${pc.cyan('│')} ${pc.green('✨')} ${pc.bold('Your Zenith app is ready!')} ${pc.cyan('│')}
143
- ${pc.cyan('│')} ${pc.cyan('│')}
144
- ${pc.cyan('│')} ${pc.dim('Next steps:')} ${pc.cyan('│')}
145
- ${pc.cyan('│')} ${pc.cyan('│')}
146
- ${pc.cyan('│')} ${pc.cyan('$')} ${pc.bold(`cd ${projectName}`)}${' '.repeat(Math.max(0, 40 - projectName.length))}${pc.cyan('│')}
147
- ${pc.cyan('│')} ${pc.cyan('$')} ${pc.bold('bun run dev')} ${pc.cyan('│')}
148
- ${pc.cyan('│')} ${pc.cyan('│')}
149
- ${pc.cyan('│')} ${pc.dim('Then open')} ${pc.underline(pc.blue('http://localhost:3000'))} ${pc.cyan('│')}
150
- ${pc.cyan('│')} ${pc.cyan('│')}
151
- ${pc.cyan('└─────────────────────────────────────────────────────────┘')}
152
- `)
153
- }
154
-
155
- /**
156
- * Show dev server startup panel
157
- */
158
- export function showServerPanel(options: {
159
- project: string,
160
- pages: string,
161
- url: string,
162
- hmr: boolean,
163
- mode: string
164
- }) {
165
- console.clear()
166
- console.log(LOGO_COMPACT)
167
- console.log(`${pc.cyan('────────────────────────────────────────────────────────────')}`)
168
- console.log(` ${pc.magenta('🟣 Zenith Dev Server')}`)
169
- console.log(`${pc.cyan('────────────────────────────────────────────────────────────')}`)
170
- console.log(` ${pc.bold('Project:')} ${pc.dim(options.project)}`)
171
- console.log(` ${pc.bold('Pages:')} ${pc.dim(options.pages)}`)
172
- console.log(` ${pc.bold('Mode:')} ${pc.cyan(options.mode)} ${pc.dim(`(${options.hmr ? 'HMR enabled' : 'HMR disabled'})`)}`)
173
- console.log(`${pc.cyan('────────────────────────────────────────────────────────────')}`)
174
- console.log(` ${pc.bold('Server:')} ${pc.cyan(pc.underline(options.url))} ${pc.dim('(clickable)')}`)
175
- console.log(` ${pc.bold('Hot Reload:')} ${options.hmr ? pc.green('Enabled ✅') : pc.red('Disabled ✗')}`)
176
- console.log(`${pc.cyan('────────────────────────────────────────────────────────────')}`)
177
- console.log(` ${pc.dim('Press Ctrl+C to stop')}\n`)
178
- }
@@ -1,112 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { marked } from 'marked';
4
-
5
- export interface ContentItem {
6
- id?: string | number;
7
- slug?: string | null;
8
- collection?: string | null;
9
- content?: string | null;
10
- [key: string]: any | null;
11
- }
12
-
13
- /**
14
- * Load all content from the content directory
15
- */
16
- export function loadContent(contentDir: string): Record<string, ContentItem[]> {
17
- if (!fs.existsSync(contentDir)) {
18
- return {};
19
- }
20
-
21
- const collections: Record<string, ContentItem[]> = {};
22
- const files = getAllFiles(contentDir);
23
-
24
- for (const filePath of files) {
25
- const ext = path.extname(filePath).toLowerCase();
26
- const relativePath = path.relative(contentDir, filePath);
27
- const collection = relativePath.split(path.sep)[0];
28
- if (!collection) continue;
29
-
30
- const slug = relativePath.replace(/\.(md|mdx|json)$/, '').replace(/\\/g, '/');
31
- const id = slug;
32
-
33
- const rawContent = fs.readFileSync(filePath, 'utf-8');
34
-
35
- if (!collections[collection]) {
36
- collections[collection] = [];
37
- }
38
-
39
- if (ext === '.json') {
40
- try {
41
- const data = JSON.parse(rawContent);
42
- collections[collection].push({
43
- id,
44
- slug,
45
- collection,
46
- content: '',
47
- ...data
48
- });
49
- } catch (e) {
50
- console.error(`Error parsing JSON file ${filePath}:`, e);
51
- }
52
- } else if (ext === '.md' || ext === '.mdx') {
53
- const { metadata, content } = parseMarkdown(rawContent);
54
- collections[collection].push({
55
- id,
56
- slug,
57
- collection,
58
- content,
59
- ...metadata
60
- });
61
- }
62
- }
63
-
64
- return collections;
65
- }
66
-
67
- function getAllFiles(dir: string, fileList: string[] = []): string[] {
68
- const files = fs.readdirSync(dir);
69
- files.forEach((file: string) => {
70
- const name = path.join(dir, file);
71
- if (fs.statSync(name).isDirectory()) {
72
- getAllFiles(name, fileList);
73
- } else {
74
- fileList.push(name);
75
- }
76
- });
77
- return fileList;
78
- }
79
-
80
- function parseMarkdown(content: string): { metadata: Record<string, any>, content: string } {
81
- const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
82
- const match = content.match(frontmatterRegex);
83
-
84
- if (!match) {
85
- return { metadata: {}, content: content.trim() };
86
- }
87
-
88
- const [, yamlStr, body] = match;
89
- const metadata: Record<string, any> = {};
90
-
91
- if (yamlStr) {
92
- yamlStr.split('\n').forEach(line => {
93
- const [key, ...values] = line.split(':');
94
- if (key && values.length > 0) {
95
- const value = values.join(':').trim();
96
- // Basic type conversion
97
- if (value === 'true') metadata[key.trim()] = true;
98
- else if (value === 'false') metadata[key.trim()] = false;
99
- else if (!isNaN(Number(value))) metadata[key.trim()] = Number(value);
100
- else if (value.startsWith('[') && value.endsWith(']')) {
101
- metadata[key.trim()] = value.slice(1, -1).split(',').map(v => v.trim().replace(/^['"]|['"]$/g, ''));
102
- }
103
- else metadata[key.trim()] = value.replace(/^['"]|['"]$/g, '');
104
- }
105
- });
106
- }
107
-
108
- return {
109
- metadata,
110
- content: marked.parse((body || '').trim()) as string
111
- };
112
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * @zenith/cli - Logger Utility
3
- *
4
- * Colored console output for CLI feedback
5
- */
6
-
7
- import pc from 'picocolors'
8
-
9
- export function log(message: string): void {
10
- console.log(`${pc.cyan('[zenith]')} ${message}`)
11
- }
12
-
13
- export function success(message: string): void {
14
- console.log(`${pc.green('✓')} ${message}`)
15
- }
16
-
17
- export function warn(message: string): void {
18
- console.log(`${pc.yellow('⚠')} ${message}`)
19
- }
20
-
21
- export function error(message: string): void {
22
- console.error(`${pc.red('✗')} ${message}`)
23
- }
24
-
25
- export function info(message: string): void {
26
- console.log(`${pc.blue('ℹ')} ${message}`)
27
- }
28
-
29
- export function header(title: string): void {
30
- console.log(`\n${pc.bold(pc.cyan(title))}\n`)
31
- }
32
-
33
- export function hmr(type: 'CSS' | 'Page' | 'Layout' | 'Content', path: string): void {
34
- console.log(`${pc.magenta('[HMR]')} ${pc.bold(type)} updated: ${pc.dim(path)}`)
35
- }
36
-
37
- export function route(method: string, path: string, status: number, totalMs: number, compileMs: number, renderMs: number): void {
38
- const statusColor = status < 400 ? pc.green : pc.red
39
- const timeColor = totalMs > 1000 ? pc.yellow : pc.gray
40
-
41
- console.log(
42
- `${pc.bold(method)} ${pc.cyan(path.padEnd(15))} ` +
43
- `${statusColor(status)} ${pc.dim('in')} ${timeColor(`${totalMs}ms`)} ` +
44
- `${pc.dim(`(compile: ${compileMs}ms, render: ${renderMs}ms)`)}`
45
- )
46
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * @zenith/cli - Plugin Manager
3
- *
4
- * Manages zenith.plugins.json for plugin registration
5
- */
6
-
7
- import fs from 'fs'
8
- import path from 'path'
9
- import { findProjectRoot } from './project'
10
- import * as logger from './logger'
11
-
12
- export interface PluginConfig {
13
- name: string
14
- installedAt: string
15
- options?: Record<string, unknown>
16
- }
17
-
18
- export interface PluginsFile {
19
- plugins: PluginConfig[]
20
- }
21
-
22
- const PLUGINS_FILE = 'zenith.plugins.json'
23
-
24
- /**
25
- * Get path to plugins file
26
- */
27
- function getPluginsPath(): string {
28
- const root = findProjectRoot()
29
- if (!root) {
30
- throw new Error('Not in a Zenith project')
31
- }
32
- return path.join(root, PLUGINS_FILE)
33
- }
34
-
35
- /**
36
- * Read plugins file
37
- */
38
- export function readPlugins(): PluginsFile {
39
- const pluginsPath = getPluginsPath()
40
-
41
- if (!fs.existsSync(pluginsPath)) {
42
- return { plugins: [] }
43
- }
44
-
45
- try {
46
- return JSON.parse(fs.readFileSync(pluginsPath, 'utf-8'))
47
- } catch {
48
- return { plugins: [] }
49
- }
50
- }
51
-
52
- /**
53
- * Write plugins file
54
- */
55
- function writePlugins(data: PluginsFile): void {
56
- const pluginsPath = getPluginsPath()
57
- fs.writeFileSync(pluginsPath, JSON.stringify(data, null, 2))
58
- }
59
-
60
- /**
61
- * Add a plugin to the registry
62
- */
63
- export function addPlugin(name: string, options?: Record<string, unknown>): boolean {
64
- const data = readPlugins()
65
-
66
- // Check if already installed
67
- if (data.plugins.some(p => p.name === name)) {
68
- logger.warn(`Plugin "${name}" is already registered`)
69
- return false
70
- }
71
-
72
- data.plugins.push({
73
- name,
74
- installedAt: new Date().toISOString(),
75
- options
76
- })
77
-
78
- writePlugins(data)
79
- logger.success(`Added plugin "${name}"`)
80
- return true
81
- }
82
-
83
- /**
84
- * Remove a plugin from the registry
85
- */
86
- export function removePlugin(name: string): boolean {
87
- const data = readPlugins()
88
- const initialLength = data.plugins.length
89
-
90
- data.plugins = data.plugins.filter(p => p.name !== name)
91
-
92
- if (data.plugins.length === initialLength) {
93
- logger.warn(`Plugin "${name}" is not registered`)
94
- return false
95
- }
96
-
97
- writePlugins(data)
98
- logger.success(`Removed plugin "${name}"`)
99
- return true
100
- }
101
-
102
- /**
103
- * List all registered plugins
104
- */
105
- export function listPlugins(): PluginConfig[] {
106
- return readPlugins().plugins
107
- }
108
-
109
- /**
110
- * Check if a plugin is registered
111
- */
112
- export function hasPlugin(name: string): boolean {
113
- return readPlugins().plugins.some(p => p.name === name)
114
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * @zenith/cli - Project Utility
3
- *
4
- * Detects Zenith project root and configuration
5
- */
6
-
7
- import fs from 'fs'
8
- import path from 'path'
9
-
10
- export interface ZenithProject {
11
- root: string
12
- pagesDir: string
13
- distDir: string
14
- hasZenithDeps: boolean
15
- }
16
-
17
- /**
18
- * Find the project root by looking for package.json with @zenith dependencies
19
- */
20
- export function findProjectRoot(startDir: string = process.cwd()): string | null {
21
- let current = startDir
22
-
23
- while (current !== path.dirname(current)) {
24
- const pkgPath = path.join(current, 'package.json')
25
-
26
- if (fs.existsSync(pkgPath)) {
27
- try {
28
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
29
- const deps = { ...pkg.dependencies, ...pkg.devDependencies }
30
-
31
- // Check for any @zenith/* or @zenithbuild/* dependency
32
- const hasZenith = Object.keys(deps).some(d => d.startsWith('@zenith/') || d.startsWith('@zenithbuild/'))
33
- if (hasZenith) {
34
- return current
35
- }
36
- } catch {
37
- // Invalid JSON, skip
38
- }
39
- }
40
-
41
- current = path.dirname(current)
42
- }
43
-
44
- return null
45
- }
46
-
47
- /**
48
- * Get project configuration
49
- */
50
- export function getProject(cwd: string = process.cwd()): ZenithProject | null {
51
- const root = findProjectRoot(cwd)
52
- if (!root) return null
53
-
54
- // Support both app/ and src/ directory structures
55
- let appDir = path.join(root, 'app')
56
- if (!fs.existsSync(appDir)) {
57
- appDir = path.join(root, 'src')
58
- }
59
-
60
- return {
61
- root,
62
- pagesDir: path.join(appDir, 'pages'),
63
- distDir: path.join(appDir, 'dist'),
64
- hasZenithDeps: true
65
- }
66
- }
67
-
68
- /**
69
- * Ensure we're in a Zenith project
70
- */
71
- export function requireProject(cwd: string = process.cwd()): ZenithProject {
72
- const project = getProject(cwd)
73
- if (!project) {
74
- throw new Error('Not in a Zenith project. Run this command from a directory with @zenith/* dependencies.')
75
- }
76
- return project
77
- }