@zenithbuild/core 0.6.2 → 0.6.4

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 (112) hide show
  1. package/CORE_CONTRACT.md +145 -0
  2. package/README.md +14 -29
  3. package/bin/zenith.js +89 -0
  4. package/package.json +39 -56
  5. package/src/config.js +136 -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 -388
  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 -178
  38. package/compiler/discovery/layouts.ts +0 -70
  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 -83
  43. package/compiler/ir/types.ts +0 -174
  44. package/compiler/output/types.ts +0 -34
  45. package/compiler/parse/detectMapExpressions.ts +0 -102
  46. package/compiler/parse/importTypes.ts +0 -78
  47. package/compiler/parse/parseImports.ts +0 -309
  48. package/compiler/parse/parseScript.ts +0 -46
  49. package/compiler/parse/parseTemplate.ts +0 -599
  50. package/compiler/parse/parseZenFile.ts +0 -66
  51. package/compiler/parse/scriptAnalysis.ts +0 -91
  52. package/compiler/parse/trackLoopContext.ts +0 -82
  53. package/compiler/runtime/dataExposure.ts +0 -317
  54. package/compiler/runtime/generateDOM.ts +0 -246
  55. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  56. package/compiler/runtime/hydration.ts +0 -309
  57. package/compiler/runtime/navigation.ts +0 -432
  58. package/compiler/runtime/thinRuntime.ts +0 -160
  59. package/compiler/runtime/transformIR.ts +0 -370
  60. package/compiler/runtime/wrapExpression.ts +0 -95
  61. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  62. package/compiler/spa-build.ts +0 -917
  63. package/compiler/ssg-build.ts +0 -422
  64. package/compiler/test/validate-test.ts +0 -104
  65. package/compiler/transform/classifyExpression.ts +0 -444
  66. package/compiler/transform/componentResolver.ts +0 -312
  67. package/compiler/transform/componentScriptTransformer.ts +0 -303
  68. package/compiler/transform/expressionTransformer.ts +0 -385
  69. package/compiler/transform/fragmentLowering.ts +0 -634
  70. package/compiler/transform/generateBindings.ts +0 -47
  71. package/compiler/transform/generateHTML.ts +0 -28
  72. package/compiler/transform/layoutProcessor.ts +0 -132
  73. package/compiler/transform/slotResolver.ts +0 -292
  74. package/compiler/transform/transformNode.ts +0 -126
  75. package/compiler/transform/transformTemplate.ts +0 -38
  76. package/compiler/validate/invariants.ts +0 -292
  77. package/compiler/validate/validateExpressions.ts +0 -168
  78. package/core/config/index.ts +0 -16
  79. package/core/config/loader.ts +0 -69
  80. package/core/config/types.ts +0 -89
  81. package/core/index.ts +0 -135
  82. package/core/lifecycle/index.ts +0 -49
  83. package/core/lifecycle/zen-mount.ts +0 -182
  84. package/core/lifecycle/zen-unmount.ts +0 -88
  85. package/core/plugins/index.ts +0 -7
  86. package/core/plugins/registry.ts +0 -81
  87. package/core/reactivity/index.ts +0 -54
  88. package/core/reactivity/tracking.ts +0 -167
  89. package/core/reactivity/zen-batch.ts +0 -57
  90. package/core/reactivity/zen-effect.ts +0 -139
  91. package/core/reactivity/zen-memo.ts +0 -146
  92. package/core/reactivity/zen-ref.ts +0 -52
  93. package/core/reactivity/zen-signal.ts +0 -121
  94. package/core/reactivity/zen-state.ts +0 -180
  95. package/core/reactivity/zen-untrack.ts +0 -44
  96. package/dist/cli.js +0 -11665
  97. package/dist/zen-build.js +0 -21172
  98. package/dist/zen-dev.js +0 -21172
  99. package/dist/zen-preview.js +0 -21172
  100. package/dist/zenith.js +0 -21172
  101. package/router/index.ts +0 -28
  102. package/router/manifest.ts +0 -314
  103. package/router/navigation/ZenLink.zen +0 -231
  104. package/router/navigation/index.ts +0 -78
  105. package/router/navigation/zen-link.ts +0 -584
  106. package/router/runtime.ts +0 -458
  107. package/router/types.ts +0 -168
  108. package/runtime/build.ts +0 -17
  109. package/runtime/bundle-generator.ts +0 -1247
  110. package/runtime/client-runtime.ts +0 -549
  111. package/runtime/serve.ts +0 -93
  112. package/tsconfig.json +0 -28
package/cli/main.ts DELETED
@@ -1,101 +0,0 @@
1
- /**
2
- * @zenith/cli - Shared CLI Execution Logic
3
- */
4
-
5
- import process from 'node:process'
6
- import { getCommand, showHelp, placeholderCommands } from './commands/index'
7
- import * as logger from './utils/logger'
8
- import { execSync } from 'node:child_process'
9
-
10
- /**
11
- * Check if Bun is available in the environment
12
- */
13
- function checkBun() {
14
- try {
15
- execSync('bun --version', { stdio: 'pipe' })
16
- return true
17
- } catch {
18
- return false
19
- }
20
- }
21
-
22
- export interface CLIOptions {
23
- defaultCommand?: string
24
- }
25
-
26
- /**
27
- * Main CLI execution entry point
28
- */
29
- export async function runCLI(options: CLIOptions = {}) {
30
- // 1. Check for Bun
31
- if (!checkBun()) {
32
- logger.error('Bun is required to run Zenith.')
33
- logger.info('Please install Bun: https://bun.sh/install')
34
- process.exit(1)
35
- }
36
-
37
- const args = process.argv.slice(2)
38
- const VERSION = '0.3.0'
39
-
40
- // 2. Handle global version flag
41
- if (args.includes('--version') || args.includes('-v')) {
42
- console.log(`Zenith CLI v${VERSION}`)
43
- process.exit(0)
44
- }
45
-
46
- // Determine command name: either from args or default (for aliases)
47
- let commandName = args[0]
48
- let commandArgs = args.slice(1)
49
-
50
- if (options.defaultCommand) {
51
- if (!commandName || commandName.startsWith('-')) {
52
- commandName = options.defaultCommand
53
- commandArgs = args
54
- }
55
- }
56
-
57
- // Handle help
58
- if (!commandName || ((commandName === '--help' || commandName === '-h') && !options.defaultCommand)) {
59
- showHelp()
60
- process.exit(0)
61
- }
62
-
63
- // Parse options (--key value format) for internal use if needed
64
- const cliOptions: Record<string, string> = {}
65
- for (let i = 0; i < commandArgs.length; i++) {
66
- const arg = commandArgs[i]!
67
- if (arg.startsWith('--')) {
68
- const key = arg.slice(2)
69
- const value = commandArgs[i + 1]
70
- if (value && !value.startsWith('--')) {
71
- cliOptions[key] = value
72
- i++
73
- } else {
74
- cliOptions[key] = 'true'
75
- }
76
- }
77
- }
78
-
79
- // Check for placeholder commands
80
- if (placeholderCommands.includes(commandName)) {
81
- logger.warn(`Command "${commandName}" is not yet implemented.`)
82
- logger.info('This feature is planned for a future release.')
83
- process.exit(0)
84
- }
85
-
86
- const command = getCommand(commandName)
87
-
88
- if (!command) {
89
- logger.error(`Unknown command: ${commandName}`)
90
- showHelp()
91
- process.exit(1)
92
- }
93
-
94
- try {
95
- await command!.run(commandArgs, cliOptions)
96
- } catch (err: unknown) {
97
- const message = err instanceof Error ? err.message : String(err)
98
- logger.error(message)
99
- process.exit(1)
100
- }
101
- }
@@ -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
- }