@vebui/bun 0.0.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 (2) hide show
  1. package/cli.ts +266 -0
  2. package/package.json +36 -0
package/cli.ts ADDED
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env bun
2
+ import { watch, mkdirSync } from "fs"
3
+ import { readFile, writeFile } from "fs/promises"
4
+ import { Glob } from "bun"
5
+ import { resolve, dirname } from "path"
6
+ import { classToDeclaration, drainCSS } from "@vebui/core"
7
+ import * as VebUI from "@vebui/core"
8
+
9
+ // ─── Args ─────────────────────────────────────────────────────────────────────
10
+
11
+ const [,, command, appFile] = process.argv
12
+
13
+ if (command !== "dev" && command !== "build") {
14
+ console.error("Usage: vebui <dev|build> <file.ts>")
15
+ process.exit(1)
16
+ }
17
+
18
+ const VERBOSE = Bun.env.VEBUI_VERBOSE === "true"
19
+
20
+ function resolveCorePackage(): string {
21
+ const workspacePath = resolve(import.meta.dir, "../../core")
22
+ try {
23
+ const stat = Bun.file(workspacePath + "/index.ts")
24
+ if (stat.size > 0) {
25
+ if (VERBOSE) console.log(`[vebui] using workspace core: ${workspacePath}`)
26
+ return workspacePath
27
+ }
28
+ } catch { }
29
+
30
+ try {
31
+ const corePath = import.meta.resolveSync("@vebui/core")
32
+ if (VERBOSE) console.log(`[vebui] resolved core via import.meta: ${corePath}`)
33
+ if (corePath.includes("/dist/")) return dirname(dirname(corePath))
34
+ return dirname(corePath)
35
+ } catch {
36
+ console.error("[vebui] Error: Cannot resolve @vebui/core package")
37
+ process.exit(1)
38
+ }
39
+ }
40
+
41
+ const corePackageRoot = resolveCorePackage()
42
+ if (VERBOSE) console.log(`[vebui] core package root: ${corePackageRoot}`)
43
+
44
+ const userPatterns = Bun.env.VEBUI_CONTENT?.split(",").map(s => s.trim()) ?? []
45
+ const patterns = [
46
+ `${corePackageRoot}/**/*.ts`,
47
+ ...userPatterns,
48
+ ]
49
+
50
+ const OUT_CSS = Bun.env.VEBUI_CSS_OUT ?? "./assets/vebui/vebui.css"
51
+ const OUT_JS = Bun.env.VEBUI_JS_OUT ?? "./assets/vebui/vebui.client.js"
52
+
53
+ // ─── Extractor ────────────────────────────────────────────────────────────────
54
+
55
+ const CHAIN_RE = /\b(VStack|HStack|Block|Text|Button|Input|Body|Head)(?:(?:\s*\.\s*[a-zA-Z0-9]+\s*\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\))|(?:\s*\.\s*(?:mobile|tablet|desktop|hover|active|focus|focusWithin|before|after)))+/g
56
+ const IMPORT_CSS_RE = /import\s+["'](.+\.css)["']/g
57
+
58
+ async function extractCSSFromSource(source: string, allClasses: Set<string>, discoveredCSS: Set<string>, currentFile: string) {
59
+ for (const match of source.matchAll(IMPORT_CSS_RE)) {
60
+ const importPath = match[1]
61
+ if (!importPath) continue
62
+ const resolvedPath = importPath.startsWith(".")
63
+ ? resolve(dirname(currentFile), importPath)
64
+ : importPath
65
+ discoveredCSS.add(resolvedPath)
66
+ }
67
+
68
+ const MODIFIER_RE = /\.([a-zA-Z0-9]+)\((?:"([^"]+)"|([\d.]+))\)/g
69
+ const MODIFIER_TO_CLS: Record<string, string> = {
70
+ margin: "m", marginT: "mt", marginB: "mb", marginL: "ml", marginR: "mr", marginX: "mx", marginY: "my",
71
+ m: "m", mt: "mt", mb: "mb", ml: "ml", mr: "mr", mx: "mx", my: "my",
72
+ padding: "p", paddingT: "pt", paddingB: "pb", paddingL: "pl", paddingR: "pr", paddingX: "px", paddingY: "py",
73
+ p: "p", pt: "pt", pb: "pb", pl: "pl", pr: "pr", px: "px", py: "py",
74
+ gap: "gap", gapX: "gapx", gapY: "gapy", spaceX: "gapx", spaceY: "gapy",
75
+ radius: "radius", size: "text",
76
+ width: "w", height: "h", background: "bg",
77
+ }
78
+ const INLINE_MODIFIERS = new Set(["color", "weight", "align", "justify", "border", "opacity", "fontWeight", "fontSize", "flex"])
79
+
80
+ for (const match of source.matchAll(MODIFIER_RE)) {
81
+ const [, modifier, strVal, numVal] = match
82
+ const value = strVal ?? numVal
83
+ if (!modifier || value === undefined || value === null) continue
84
+ if (INLINE_MODIFIERS.has(modifier)) continue
85
+ const prefix = MODIFIER_TO_CLS[modifier]
86
+ if (prefix) allClasses.add(`${prefix}-${String(value).replace(/[^a-zA-Z0-9]/g, "_")}`)
87
+ }
88
+
89
+ const mockProxy: any = new Proxy(() => mockProxy, {
90
+ get: (_, key) => {
91
+ if (key === Symbol.toPrimitive) return (hint: string) => hint === "string" ? "MOCK" : 0
92
+ if (key === "key") return "mock-sig"
93
+ return mockProxy
94
+ },
95
+ apply: () => mockProxy,
96
+ })
97
+
98
+ const context: any = { ...VebUI }
99
+
100
+ for (const match of source.matchAll(CHAIN_RE)) {
101
+ const chain = match[0]
102
+ try {
103
+ const proxy = new Proxy(context, {
104
+ get: (target, key) => {
105
+ if (key === Symbol.unscopables) return undefined
106
+ if (key in target) return target[key]
107
+ return mockProxy
108
+ },
109
+ has: () => true,
110
+ })
111
+ const result = new Function("with(this) { return " + chain + " }").call(proxy)
112
+ if (typeof result === "function") {
113
+ const element = result("DUMMY")
114
+ if (element && typeof element.render === "function") {
115
+ const html = element.render()
116
+ for (const classMatch of html.matchAll(/class="([^"]+)"/g)) {
117
+ classMatch[1].split(" ").forEach((cls: string) => {
118
+ if (cls && !cls.startsWith("w-")) allClasses.add(cls)
119
+ })
120
+ }
121
+ }
122
+ }
123
+ } catch (e) {
124
+ if (VERBOSE) console.error(`[vebui] eval failed for ${chain.slice(0, 50).trim()}:`, e)
125
+ }
126
+ }
127
+ }
128
+
129
+ // ─── Bundler ──────────────────────────────────────────────────────────────────
130
+
131
+ async function bundleClient() {
132
+ const clientEntry = resolve(corePackageRoot, "client/index.ts")
133
+ mkdirSync(dirname(OUT_JS), { recursive: true })
134
+
135
+ const result = await Bun.build({
136
+ entrypoints: [clientEntry],
137
+ minify: true,
138
+ })
139
+
140
+ if (!result.success) {
141
+ console.error("[vebui] client bundle failed:", result.logs)
142
+ return
143
+ }
144
+
145
+ const out = result.outputs[0]
146
+ if (out) {
147
+ const code = await out.text()
148
+ await writeFile(OUT_JS, code, "utf-8")
149
+ console.log(`[vebui] bundled client → ${OUT_JS} (${code.length} bytes)`)
150
+ }
151
+ }
152
+
153
+ // ─── CSS Generator ────────────────────────────────────────────────────────────
154
+
155
+ async function resolveFiles(globs: string[]): Promise<string[]> {
156
+ const files = new Set<string>()
157
+ for (const pattern of globs) {
158
+ const glob = new Glob(pattern)
159
+ for await (const file of glob.scan({ cwd: process.cwd(), absolute: true })) {
160
+ if (
161
+ file.includes("clis/") ||
162
+ file.endsWith(".css") ||
163
+ file.endsWith(".js") ||
164
+ file.includes("/client/") ||
165
+ file.includes("\\client\\")
166
+ ) continue
167
+ files.add(file)
168
+ }
169
+ }
170
+ if (VERBOSE) console.log(`[vebui] resolved ${files.size} files`)
171
+ return [...files]
172
+ }
173
+
174
+ async function generateCSS() {
175
+ const files = await resolveFiles(patterns)
176
+ const allClasses = new Set<string>()
177
+ const discoveredCSS = new Set<string>()
178
+
179
+ mkdirSync(dirname(OUT_CSS), { recursive: true })
180
+ drainCSS()
181
+
182
+ for (const file of files) {
183
+ const source = await readFile(file, "utf-8")
184
+ await extractCSSFromSource(source, allClasses, discoveredCSS, file)
185
+ }
186
+
187
+ const hashedCSS = drainCSS()
188
+ const utilityRules: string[] = []
189
+ for (const cls of allClasses) {
190
+ const decl = classToDeclaration(cls)
191
+ if (decl) utilityRules.push(`.${cls} { ${decl} }`)
192
+ }
193
+
194
+ const parts: string[] = ["/* Generated by vebui — do not edit */"]
195
+
196
+ try {
197
+ const tokens = await readFile(resolve(corePackageRoot, "tokens.css"), "utf-8")
198
+ parts.push("\n/* Design Tokens & Reset */")
199
+ parts.push(tokens)
200
+ } catch {
201
+ console.warn("[vebui] Warning: Could not find core/tokens.css")
202
+ }
203
+
204
+ for (const cssFile of discoveredCSS) {
205
+ try {
206
+ const content = await readFile(cssFile, "utf-8")
207
+ parts.push(`\n/* User CSS: ${cssFile} */`)
208
+ parts.push(content)
209
+ } catch {
210
+ console.warn(`[vebui] Warning: Could not read CSS file: ${cssFile}`)
211
+ }
212
+ }
213
+
214
+ parts.push("\n/* Utility Classes */")
215
+ parts.push(...utilityRules)
216
+
217
+ if (hashedCSS.trim()) {
218
+ parts.push("\n/* Hashed Pseudo-classes */")
219
+ parts.push(hashedCSS)
220
+ }
221
+
222
+ const css = parts.join("\n")
223
+ await writeFile(OUT_CSS, css, "utf-8")
224
+ console.log(`[vebui] generated css → ${OUT_CSS} (${files.length} files, ${utilityRules.length} utilities)`)
225
+ }
226
+
227
+ async function buildAll() {
228
+ await Promise.all([generateCSS(), bundleClient()])
229
+ }
230
+
231
+ // ─── Entry ────────────────────────────────────────────────────────────────────
232
+
233
+ await buildAll()
234
+
235
+ if (command === "build") process.exit(0)
236
+
237
+ // dev: spawn the app and watch for changes
238
+
239
+ if (appFile) {
240
+ Bun.spawn(["bun", appFile], {
241
+ stdout: "inherit",
242
+ stderr: "inherit",
243
+ env: process.env as Record<string, string>,
244
+ })
245
+ }
246
+
247
+ const watchDirs = new Set(
248
+ patterns.map(p => {
249
+ const full = p.startsWith("/") ? p : resolve(process.cwd(), p)
250
+ return p.includes("*") ? full.replace(/\/\*.*$/, "") : dirname(full)
251
+ })
252
+ )
253
+
254
+ for (const dir of watchDirs) {
255
+ watch(dir, { recursive: true }, async (_, filename) => {
256
+ if (filename && !filename.endsWith(".ts")) return
257
+ if (!filename || (!filename.includes("core/client") && !filename.includes("src/client"))) {
258
+ await generateCSS()
259
+ }
260
+ if (filename && (filename.includes("core/client") || filename.includes("src/client") || filename.includes("signal-rpc.ts"))) {
261
+ await bundleClient()
262
+ }
263
+ })
264
+ }
265
+
266
+ if (VERBOSE) console.log(`[vebui] watching: ${[...watchDirs].join(", ")}`)
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@vebui/bun",
3
+ "version": "0.0.1",
4
+ "description": "Bun CLI for vebui — dev server and build tool",
5
+ "type": "module",
6
+ "bin": {
7
+ "vebui": "./cli.ts"
8
+ },
9
+ "exports": {
10
+ ".": "./cli.ts"
11
+ },
12
+ "files": [
13
+ "cli.ts",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "keywords": [
18
+ "bun",
19
+ "cli",
20
+ "css",
21
+ "atomic-css",
22
+ "build-tool",
23
+ "vebui"
24
+ ],
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@vebui/core": "^0.1.0"
28
+ },
29
+ "peerDependencies": {
30
+ "bun": ">=1.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/bun": "latest",
34
+ "typescript": "^5"
35
+ }
36
+ }