@zenithbuild/cli 0.4.2
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/LICENSE +21 -0
- package/README.md +40 -0
- package/bin/zen-build.ts +2 -0
- package/bin/zen-dev.ts +2 -0
- package/bin/zen-preview.ts +2 -0
- package/bin/zenith.ts +2 -0
- package/package.json +63 -0
- package/src/commands/add.ts +37 -0
- package/src/commands/build.ts +36 -0
- package/src/commands/create.ts +702 -0
- package/src/commands/dev.ts +467 -0
- package/src/commands/index.ts +112 -0
- package/src/commands/preview.ts +62 -0
- package/src/commands/remove.ts +33 -0
- package/src/index.ts +10 -0
- package/src/main.ts +101 -0
- package/src/utils/branding.ts +178 -0
- package/src/utils/logger.ts +46 -0
- package/src/utils/plugin-manager.ts +114 -0
- package/src/utils/project.ts +77 -0
package/src/main.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenithbuild/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
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenithbuild/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
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenithbuild/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
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenithbuild/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 @zenithbuild/* or @zenithbuild/* dependency
|
|
32
|
+
const hasZenith = Object.keys(deps).some(d => d.startsWith('@zenithbuild/') || 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 @zenithbuild/* dependencies.')
|
|
75
|
+
}
|
|
76
|
+
return project
|
|
77
|
+
}
|