@zenithbuild/core 0.1.0 → 0.3.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.
- package/LICENSE +1 -1
- package/README.md +24 -40
- 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/cli/commands/add.ts +37 -0
- package/cli/commands/build.ts +37 -0
- package/cli/commands/create.ts +702 -0
- package/cli/commands/dev.ts +197 -0
- package/cli/commands/index.ts +112 -0
- package/cli/commands/preview.ts +62 -0
- package/cli/commands/remove.ts +33 -0
- package/cli/index.ts +10 -0
- package/cli/main.ts +101 -0
- package/cli/utils/branding.ts +153 -0
- package/cli/utils/logger.ts +40 -0
- package/cli/utils/plugin-manager.ts +114 -0
- package/cli/utils/project.ts +71 -0
- package/compiler/build-analyzer.ts +122 -0
- package/compiler/discovery/layouts.ts +61 -0
- package/compiler/index.ts +40 -24
- package/compiler/ir/types.ts +1 -0
- package/compiler/parse/parseScript.ts +29 -5
- package/compiler/parse/parseTemplate.ts +96 -58
- package/compiler/parse/scriptAnalysis.ts +77 -0
- package/compiler/runtime/dataExposure.ts +49 -31
- package/compiler/runtime/generateDOM.ts +18 -17
- package/compiler/runtime/generateHydrationBundle.ts +24 -5
- package/compiler/runtime/transformIR.ts +140 -49
- package/compiler/runtime/wrapExpressionWithLoop.ts +11 -11
- package/compiler/spa-build.ts +70 -153
- package/compiler/ssg-build.ts +412 -0
- package/compiler/transform/layoutProcessor.ts +132 -0
- package/compiler/transform/transformNode.ts +19 -19
- package/dist/cli.js +11648 -0
- package/dist/zen-build.js +11659 -0
- package/dist/zen-dev.js +11659 -0
- package/dist/zen-preview.js +11659 -0
- package/dist/zenith.js +11659 -0
- package/package.json +22 -2
- package/runtime/bundle-generator.ts +416 -0
- package/runtime/client-runtime.ts +532 -0
- package/.eslintignore +0 -15
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +0 -25
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +0 -34
- package/.github/pull_request_template.md +0 -15
- package/.github/workflows/discord-changelog.yml +0 -141
- package/.github/workflows/discord-notify.yml +0 -242
- package/.github/workflows/discord-version.yml +0 -195
- package/.prettierignore +0 -13
- package/.prettierrc +0 -21
- package/.zen.d.ts +0 -15
- package/app/components/Button.zen +0 -46
- package/app/components/Link.zen +0 -11
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +0 -59
- package/app/pages/about.zen +0 -23
- package/app/pages/blog/[id].zen +0 -53
- package/app/pages/blog/index.zen +0 -32
- package/app/pages/dynamic-dx.zen +0 -712
- package/app/pages/dynamic-primitives.zen +0 -453
- package/app/pages/index.zen +0 -154
- package/app/pages/navigation-demo.zen +0 -229
- package/app/pages/posts/[...slug].zen +0 -61
- package/app/pages/primitives-demo.zen +0 -273
- package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
- package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
- package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +0 -601
- package/assets/logos/README.md +0 -54
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +0 -39
- package/compiler/legacy/binding.ts +0 -254
- package/compiler/legacy/bindings.ts +0 -338
- package/compiler/legacy/component-process.ts +0 -1208
- package/compiler/legacy/component.ts +0 -301
- package/compiler/legacy/event.ts +0 -50
- package/compiler/legacy/expression.ts +0 -1149
- package/compiler/legacy/mutation.ts +0 -280
- package/compiler/legacy/parse.ts +0 -299
- package/compiler/legacy/split.ts +0 -608
- package/compiler/legacy/types.ts +0 -32
- package/docs/COMMENTS.md +0 -111
- package/docs/COMMITS.md +0 -36
- package/docs/CONTRIBUTING.md +0 -116
- package/docs/STYLEGUIDE.md +0 -62
- package/scripts/webhook-proxy.ts +0 -213
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenith/cli - Dev Command
|
|
3
|
+
*
|
|
4
|
+
* Starts the development server with in-memory compilation and hot reload
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import fs from 'fs'
|
|
9
|
+
import { serve } from 'bun'
|
|
10
|
+
import { requireProject } from '../utils/project'
|
|
11
|
+
import * as logger from '../utils/logger'
|
|
12
|
+
import { compileZenSource } from '../../compiler/index'
|
|
13
|
+
import { discoverLayouts } from '../../compiler/discovery/layouts'
|
|
14
|
+
import { processLayout } from '../../compiler/transform/layoutProcessor'
|
|
15
|
+
import { generateRouteDefinition } from '../../router/manifest'
|
|
16
|
+
import { generateBundleJS } from '../../runtime/bundle-generator'
|
|
17
|
+
|
|
18
|
+
export interface DevOptions {
|
|
19
|
+
port?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface CompiledPage {
|
|
23
|
+
html: string
|
|
24
|
+
script: string
|
|
25
|
+
styles: string[]
|
|
26
|
+
route: string
|
|
27
|
+
lastModified: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const pageCache = new Map<string, CompiledPage>()
|
|
31
|
+
|
|
32
|
+
export async function dev(options: DevOptions = {}): Promise<void> {
|
|
33
|
+
const project = requireProject()
|
|
34
|
+
const port = options.port || parseInt(process.env.PORT || '3000', 10)
|
|
35
|
+
|
|
36
|
+
// Support both app/ and src/ directory structures
|
|
37
|
+
const appDir = project.root
|
|
38
|
+
const pagesDir = project.pagesDir
|
|
39
|
+
|
|
40
|
+
logger.header('Zenith Dev Server')
|
|
41
|
+
logger.log(`Project: ${project.root}`)
|
|
42
|
+
logger.log(`Pages: ${project.pagesDir}`)
|
|
43
|
+
|
|
44
|
+
// File extensions that should be served as static assets
|
|
45
|
+
const STATIC_EXTENSIONS = new Set([
|
|
46
|
+
'.js', '.css', '.ico', '.png', '.jpg', '.jpeg', '.gif', '.svg',
|
|
47
|
+
'.webp', '.woff', '.woff2', '.ttf', '.eot', '.json', '.map'
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate the shared runtime JavaScript
|
|
52
|
+
*/
|
|
53
|
+
function generateRuntimeJS(): string {
|
|
54
|
+
return generateBundleJS()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Compile a .zen page in memory
|
|
59
|
+
*/
|
|
60
|
+
function compilePageInMemory(pagePath: string): CompiledPage | null {
|
|
61
|
+
try {
|
|
62
|
+
const layoutsDir = path.join(pagesDir, '../layouts')
|
|
63
|
+
const layouts = discoverLayouts(layoutsDir)
|
|
64
|
+
|
|
65
|
+
const source = fs.readFileSync(pagePath, 'utf-8')
|
|
66
|
+
|
|
67
|
+
// Find suitable layout
|
|
68
|
+
let processedSource = source
|
|
69
|
+
let layoutToUse = layouts.get('DefaultLayout')
|
|
70
|
+
|
|
71
|
+
if (layoutToUse) {
|
|
72
|
+
processedSource = processLayout(source, layoutToUse)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const result = compileZenSource(processedSource, pagePath)
|
|
76
|
+
|
|
77
|
+
if (!result.finalized) {
|
|
78
|
+
throw new Error('Compilation failed: No finalized output')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const routeDef = generateRouteDefinition(pagePath, pagesDir)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
html: result.finalized.html,
|
|
85
|
+
script: result.finalized.js,
|
|
86
|
+
styles: result.finalized.styles,
|
|
87
|
+
route: routeDef.path,
|
|
88
|
+
lastModified: Date.now()
|
|
89
|
+
}
|
|
90
|
+
} catch (error: any) {
|
|
91
|
+
logger.error(`Compilation error for ${pagePath}: ${error.message}`)
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate full HTML page from compiled output
|
|
98
|
+
*/
|
|
99
|
+
function generateDevHTML(page: CompiledPage): string {
|
|
100
|
+
const runtimeTag = `<script src="/runtime.js"></script>`
|
|
101
|
+
const scriptTag = `<script>\n${page.script}\n</script>`
|
|
102
|
+
const allScripts = `${runtimeTag}\n${scriptTag}`
|
|
103
|
+
|
|
104
|
+
if (page.html.includes('</body>')) {
|
|
105
|
+
return page.html.replace('</body>', `${allScripts}\n</body>`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return `${page.html}\n${allScripts}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find .zen page file for a given route
|
|
113
|
+
*/
|
|
114
|
+
function findPageForRoute(route: string): string | null {
|
|
115
|
+
const exactPath = path.join(pagesDir, route === '/' ? 'index.zen' : `${route.slice(1)}.zen`)
|
|
116
|
+
if (fs.existsSync(exactPath)) return exactPath
|
|
117
|
+
|
|
118
|
+
const indexPath = path.join(pagesDir, route === '/' ? 'index.zen' : `${route.slice(1)}/index.zen`)
|
|
119
|
+
if (fs.existsSync(indexPath)) return indexPath
|
|
120
|
+
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const cachedRuntimeJS = generateRuntimeJS()
|
|
125
|
+
|
|
126
|
+
const server = serve({
|
|
127
|
+
port,
|
|
128
|
+
async fetch(req) {
|
|
129
|
+
const url = new URL(req.url)
|
|
130
|
+
const pathname = url.pathname
|
|
131
|
+
const ext = path.extname(pathname).toLowerCase()
|
|
132
|
+
|
|
133
|
+
if (pathname === '/runtime.js' || pathname === '/assets/bundle.js') {
|
|
134
|
+
return new Response(cachedRuntimeJS, {
|
|
135
|
+
headers: {
|
|
136
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
137
|
+
'Cache-Control': 'no-cache'
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (pathname === '/assets/styles.css' || pathname === '/styles/global.css' || pathname === '/app/styles/global.css') {
|
|
143
|
+
const globalCssPath = path.join(pagesDir, '../styles/global.css')
|
|
144
|
+
if (fs.existsSync(globalCssPath)) {
|
|
145
|
+
const css = fs.readFileSync(globalCssPath, 'utf-8')
|
|
146
|
+
return new Response(css, {
|
|
147
|
+
headers: { 'Content-Type': 'text/css; charset=utf-8' }
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (STATIC_EXTENSIONS.has(ext)) {
|
|
153
|
+
const publicPath = path.join(pagesDir, '../public', pathname)
|
|
154
|
+
const distPath = path.join(pagesDir, '../dist', pathname)
|
|
155
|
+
const appRelativePath = path.join(pagesDir, '..', pathname)
|
|
156
|
+
|
|
157
|
+
for (const filePath of [publicPath, distPath, appRelativePath]) {
|
|
158
|
+
const file = Bun.file(filePath)
|
|
159
|
+
if (await file.exists()) {
|
|
160
|
+
return new Response(file)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return new Response('Not found', { status: 404 })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const pagePath = findPageForRoute(pathname)
|
|
167
|
+
if (pagePath) {
|
|
168
|
+
let cached = pageCache.get(pagePath)
|
|
169
|
+
const stat = fs.statSync(pagePath)
|
|
170
|
+
|
|
171
|
+
if (!cached || stat.mtimeMs > cached.lastModified) {
|
|
172
|
+
const compiled = compilePageInMemory(pagePath)
|
|
173
|
+
if (compiled) {
|
|
174
|
+
pageCache.set(pagePath, compiled)
|
|
175
|
+
cached = compiled
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (cached) {
|
|
180
|
+
const html = generateDevHTML(cached)
|
|
181
|
+
return new Response(html, {
|
|
182
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return new Response('Not Found', { status: 404 })
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
logger.success(`Server running at http://localhost:${server.port}`)
|
|
192
|
+
logger.info('• In-memory compilation active')
|
|
193
|
+
logger.info('• Auto-recompile on file changes')
|
|
194
|
+
logger.info('Press Ctrl+C to stop')
|
|
195
|
+
|
|
196
|
+
await new Promise(() => { })
|
|
197
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenith/cli - Command Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry for all CLI commands
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { dev, type DevOptions } from './dev'
|
|
8
|
+
import { preview, type PreviewOptions } from './preview'
|
|
9
|
+
import { build, type BuildOptions } from './build'
|
|
10
|
+
import { add, type AddOptions } from './add'
|
|
11
|
+
import { remove } from './remove'
|
|
12
|
+
import { create } from './create'
|
|
13
|
+
import * as logger from '../utils/logger'
|
|
14
|
+
|
|
15
|
+
export interface Command {
|
|
16
|
+
name: string
|
|
17
|
+
description: string
|
|
18
|
+
usage: string
|
|
19
|
+
run: (args: string[], options: Record<string, string>) => Promise<void>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const commands: Command[] = [
|
|
23
|
+
{
|
|
24
|
+
name: 'create',
|
|
25
|
+
description: 'Create a new Zenith project',
|
|
26
|
+
usage: 'zenith create [project-name]',
|
|
27
|
+
async run(args) {
|
|
28
|
+
const projectName = args[0]
|
|
29
|
+
await create(projectName)
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'dev',
|
|
34
|
+
description: 'Start development server',
|
|
35
|
+
usage: 'zenith dev [--port <port>]',
|
|
36
|
+
async run(args, options) {
|
|
37
|
+
const opts: DevOptions = {}
|
|
38
|
+
if (options.port) opts.port = parseInt(options.port, 10)
|
|
39
|
+
await dev(opts)
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'preview',
|
|
44
|
+
description: 'Preview production build',
|
|
45
|
+
usage: 'zenith preview [--port <port>]',
|
|
46
|
+
async run(args, options) {
|
|
47
|
+
const opts: PreviewOptions = {}
|
|
48
|
+
if (options.port) opts.port = parseInt(options.port, 10)
|
|
49
|
+
await preview(opts)
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'build',
|
|
54
|
+
description: 'Build for production',
|
|
55
|
+
usage: 'zenith build [--outDir <dir>]',
|
|
56
|
+
async run(args, options) {
|
|
57
|
+
const opts: BuildOptions = {}
|
|
58
|
+
if (options.outDir) opts.outDir = options.outDir
|
|
59
|
+
await build(opts)
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'add',
|
|
64
|
+
description: 'Add a plugin',
|
|
65
|
+
usage: 'zenith add <plugin>',
|
|
66
|
+
async run(args) {
|
|
67
|
+
const pluginName = args[0]
|
|
68
|
+
if (!pluginName) {
|
|
69
|
+
logger.error('Plugin name required')
|
|
70
|
+
process.exit(1)
|
|
71
|
+
}
|
|
72
|
+
await add(pluginName)
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'remove',
|
|
77
|
+
description: 'Remove a plugin',
|
|
78
|
+
usage: 'zenith remove <plugin>',
|
|
79
|
+
async run(args) {
|
|
80
|
+
const pluginName = args[0]
|
|
81
|
+
if (!pluginName) {
|
|
82
|
+
logger.error('Plugin name required')
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}
|
|
85
|
+
await remove(pluginName)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
// Placeholder commands for future expansion
|
|
91
|
+
export const placeholderCommands = ['test', 'export', 'deploy']
|
|
92
|
+
|
|
93
|
+
export function getCommand(name: string): Command | undefined {
|
|
94
|
+
return commands.find(c => c.name === name)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function showHelp(): void {
|
|
98
|
+
logger.header('Zenith CLI')
|
|
99
|
+
console.log('Usage: zenith <command> [options]\n')
|
|
100
|
+
console.log('Commands:')
|
|
101
|
+
|
|
102
|
+
for (const cmd of commands) {
|
|
103
|
+
console.log(` ${cmd.name.padEnd(12)} ${cmd.description}`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log('\nComing soon:')
|
|
107
|
+
for (const cmd of placeholderCommands) {
|
|
108
|
+
console.log(` ${cmd.padEnd(12)} (not yet implemented)`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log('\nRun `zenith <command> --help` for command-specific help.')
|
|
112
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenith/cli - Preview Command
|
|
3
|
+
*
|
|
4
|
+
* Serves the production build from the distribution directory.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import { serve } from 'bun'
|
|
9
|
+
import { requireProject } from '../utils/project'
|
|
10
|
+
import * as logger from '../utils/logger'
|
|
11
|
+
|
|
12
|
+
export interface PreviewOptions {
|
|
13
|
+
port?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function preview(options: PreviewOptions = {}): Promise<void> {
|
|
17
|
+
const project = requireProject()
|
|
18
|
+
const distDir = project.distDir
|
|
19
|
+
const port = options.port || parseInt(process.env.PORT || '4173', 10)
|
|
20
|
+
|
|
21
|
+
logger.header('Zenith Preview Server')
|
|
22
|
+
logger.log(`Serving: ${distDir}`)
|
|
23
|
+
|
|
24
|
+
// File extensions that should be served as static assets
|
|
25
|
+
const STATIC_EXTENSIONS = new Set([
|
|
26
|
+
'.js', '.css', '.ico', '.png', '.jpg', '.jpeg', '.gif', '.svg',
|
|
27
|
+
'.webp', '.woff', '.woff2', '.ttf', '.eot', '.json', '.map'
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
const server = serve({
|
|
31
|
+
port,
|
|
32
|
+
async fetch(req) {
|
|
33
|
+
const url = new URL(req.url)
|
|
34
|
+
const pathname = url.pathname
|
|
35
|
+
const ext = path.extname(pathname).toLowerCase()
|
|
36
|
+
|
|
37
|
+
if (STATIC_EXTENSIONS.has(ext)) {
|
|
38
|
+
const filePath = path.join(distDir, pathname)
|
|
39
|
+
const file = Bun.file(filePath)
|
|
40
|
+
if (await file.exists()) {
|
|
41
|
+
return new Response(file)
|
|
42
|
+
}
|
|
43
|
+
return new Response('Not found', { status: 404 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const indexPath = path.join(distDir, 'index.html')
|
|
47
|
+
const indexFile = Bun.file(indexPath)
|
|
48
|
+
if (await indexFile.exists()) {
|
|
49
|
+
return new Response(indexFile, {
|
|
50
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new Response('No production build found. Run `zenith build` first.', { status: 500 })
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
logger.success(`Preview server running at http://localhost:${server.port}`)
|
|
59
|
+
logger.info('Press Ctrl+C to stop')
|
|
60
|
+
|
|
61
|
+
await new Promise(() => { })
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenith/cli - Remove Command
|
|
3
|
+
*
|
|
4
|
+
* Removes a plugin from the project registry
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { requireProject } from '../utils/project'
|
|
8
|
+
import { removePlugin, hasPlugin } from '../utils/plugin-manager'
|
|
9
|
+
import * as logger from '../utils/logger'
|
|
10
|
+
|
|
11
|
+
export async function remove(pluginName: string): Promise<void> {
|
|
12
|
+
requireProject()
|
|
13
|
+
|
|
14
|
+
logger.header('Remove Plugin')
|
|
15
|
+
|
|
16
|
+
if (!pluginName) {
|
|
17
|
+
logger.error('Plugin name required. Usage: zenith remove <plugin>')
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!hasPlugin(pluginName)) {
|
|
22
|
+
logger.warn(`Plugin "${pluginName}" is not registered`)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const success = removePlugin(pluginName)
|
|
27
|
+
|
|
28
|
+
if (success) {
|
|
29
|
+
logger.info(`Plugin "${pluginName}" has been unregistered.`)
|
|
30
|
+
logger.info('Note: You may want to remove the package manually:')
|
|
31
|
+
logger.log(` bun remove @zenith/plugin-${pluginName}`)
|
|
32
|
+
}
|
|
33
|
+
}
|
package/cli/index.ts
ADDED
package/cli/main.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
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
|
+
}
|