create-zenbu-app 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.
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import { ensureBunBootstrapped, runCommand, BUN_BIN } from "../lib/bootstrap.mjs";
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const TEMPLATE_DIR = path.resolve(__dirname, "..", "template");
10
+ const SUBMODULE_REPO = "https://github.com/zenbu-labs/zenbu.js.git";
11
+
12
+ function copyDirSync(src, dest) {
13
+ fs.mkdirSync(dest, { recursive: true });
14
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
15
+ const srcPath = path.join(src, entry.name);
16
+ const destPath = path.join(dest, entry.name);
17
+ if (entry.isDirectory()) {
18
+ copyDirSync(srcPath, destPath);
19
+ } else {
20
+ fs.copyFileSync(srcPath, destPath);
21
+ }
22
+ }
23
+ }
24
+
25
+ async function main() {
26
+ const projectName = process.argv[2];
27
+ if (!projectName) {
28
+ console.error("Usage: create-zenbu-app <project-name>");
29
+ process.exit(1);
30
+ }
31
+
32
+ const projectDir = path.resolve(process.cwd(), projectName);
33
+ if (fs.existsSync(projectDir)) {
34
+ console.error(`Error: directory "${projectName}" already exists.`);
35
+ process.exit(1);
36
+ }
37
+
38
+ console.log(`\nCreating Zenbu app in ${projectDir}\n`);
39
+
40
+ console.log("→ Copying template files...");
41
+ copyDirSync(TEMPLATE_DIR, projectDir);
42
+
43
+ if (fs.existsSync(path.join(projectDir, "_gitignore"))) {
44
+ fs.renameSync(
45
+ path.join(projectDir, "_gitignore"),
46
+ path.join(projectDir, ".gitignore"),
47
+ );
48
+ }
49
+
50
+ const pkgPath = path.join(projectDir, "package.json");
51
+ const pkgContent = fs.readFileSync(pkgPath, "utf8");
52
+ fs.writeFileSync(pkgPath, pkgContent.replace(/\{\{projectName\}\}/g, projectName));
53
+
54
+ console.log("→ Initializing git repository...");
55
+ await runCommand("git", ["init"], projectDir);
56
+
57
+ console.log("→ Adding zenbu.js submodule...");
58
+ await runCommand(
59
+ "git",
60
+ ["submodule", "add", SUBMODULE_REPO, "zenbu"],
61
+ projectDir,
62
+ );
63
+
64
+ console.log("→ Bootstrapping toolchain...");
65
+ await ensureBunBootstrapped();
66
+
67
+ const setupTs = path.join(projectDir, "zenbu", "packages", "init", "setup.ts");
68
+ if (fs.existsSync(setupTs)) {
69
+ console.log("→ Running framework setup...");
70
+ await runCommand(
71
+ BUN_BIN,
72
+ [setupTs],
73
+ path.join(projectDir, "zenbu"),
74
+ { ZENBU_STANDALONE: "1" },
75
+ );
76
+ } else {
77
+ console.log(" ⚠ setup.ts not found in submodule, skipping framework setup");
78
+ }
79
+
80
+ console.log(`\n✓ Done! Your Zenbu app is ready.\n`);
81
+ console.log(` cd ${projectName}`);
82
+ console.log(` zen open\n`);
83
+ }
84
+
85
+ main().catch((err) => {
86
+ console.error("\nError:", err.message || err);
87
+ process.exit(1);
88
+ });
@@ -0,0 +1,147 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import fs from "node:fs";
4
+ import crypto from "node:crypto";
5
+ import https from "node:https";
6
+ import { execFileSync, spawn } from "node:child_process";
7
+
8
+ const CACHE_ROOT = path.join(os.homedir(), "Library", "Caches", "Zenbu");
9
+ const BIN_DIR = path.join(CACHE_ROOT, "bin");
10
+ const BUN_BIN = path.join(BIN_DIR, "bun");
11
+ const BUN_VERSION_MARKER = path.join(BIN_DIR, ".bun.version");
12
+
13
+ const BOOTSTRAP_BUN = {
14
+ version: "1.3.12",
15
+ targets: {
16
+ "darwin-aarch64": {
17
+ asset: "bun-darwin-aarch64.zip",
18
+ sha256:
19
+ "6c4bb87dd013ed1a8d6a16e357a3d094959fd5530b4d7061f7f3680c3c7cea1c",
20
+ },
21
+ "darwin-x64": {
22
+ asset: "bun-darwin-x64.zip",
23
+ sha256:
24
+ "0f58c53a3e7947f1e626d2f8d285f97c14b7cadcca9c09ebafc0ae9d35b58c3d",
25
+ },
26
+ },
27
+ };
28
+
29
+ function detectBunTarget() {
30
+ const arch = os.arch();
31
+ if (arch === "arm64") return "darwin-aarch64";
32
+ if (arch === "x64") return "darwin-x64";
33
+ throw new Error(`unsupported architecture: ${arch}`);
34
+ }
35
+
36
+ function downloadFile(url, destPath) {
37
+ return new Promise((resolve, reject) => {
38
+ const req = https.get(url, (res) => {
39
+ if (
40
+ res.statusCode &&
41
+ res.statusCode >= 300 &&
42
+ res.statusCode < 400 &&
43
+ res.headers.location
44
+ ) {
45
+ res.resume();
46
+ downloadFile(res.headers.location, destPath).then(resolve, reject);
47
+ return;
48
+ }
49
+ if (res.statusCode !== 200) {
50
+ reject(new Error(`GET ${url} -> ${res.statusCode}`));
51
+ res.resume();
52
+ return;
53
+ }
54
+ const out = fs.createWriteStream(destPath);
55
+ res.pipe(out);
56
+ out.on("finish", () => out.close(resolve));
57
+ out.on("error", reject);
58
+ });
59
+ req.on("error", reject);
60
+ });
61
+ }
62
+
63
+ async function sha256(filePath) {
64
+ const hash = crypto.createHash("sha256");
65
+ await new Promise((resolve, reject) => {
66
+ const stream = fs.createReadStream(filePath);
67
+ stream.on("data", (chunk) => hash.update(chunk));
68
+ stream.on("end", resolve);
69
+ stream.on("error", reject);
70
+ });
71
+ return hash.digest("hex");
72
+ }
73
+
74
+ function findBunBinary(dir) {
75
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
76
+ for (const entry of entries) {
77
+ const full = path.join(dir, entry.name);
78
+ if (entry.isDirectory()) {
79
+ const nested = findBunBinary(full);
80
+ if (nested) return nested;
81
+ } else if (entry.isFile() && entry.name === "bun") {
82
+ return full;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+
88
+ export async function ensureBunBootstrapped() {
89
+ if (fs.existsSync(BUN_BIN)) {
90
+ console.log(` ✓ bun already installed at ${BUN_BIN}`);
91
+ return;
92
+ }
93
+
94
+ const target = detectBunTarget();
95
+ const { asset, sha256: expectedSha } = BOOTSTRAP_BUN.targets[target];
96
+ const tag = `bun-v${BOOTSTRAP_BUN.version}`;
97
+ const url = `https://github.com/oven-sh/bun/releases/download/${tag}/${asset}`;
98
+
99
+ fs.mkdirSync(BIN_DIR, { recursive: true });
100
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "zenbu-bun-"));
101
+ const zipPath = path.join(tmpDir, asset);
102
+
103
+ console.log(` → downloading bun ${BOOTSTRAP_BUN.version}`);
104
+ await downloadFile(url, zipPath);
105
+
106
+ const actualSha = await sha256(zipPath);
107
+ if (actualSha !== expectedSha) {
108
+ throw new Error(
109
+ `bun sha256 mismatch: expected ${expectedSha}, got ${actualSha}`,
110
+ );
111
+ }
112
+
113
+ console.log(` → unpacking`);
114
+ execFileSync("unzip", ["-q", asset], { cwd: tmpDir });
115
+
116
+ const extracted = findBunBinary(tmpDir);
117
+ if (!extracted) {
118
+ throw new Error("could not locate bun binary in downloaded archive");
119
+ }
120
+ fs.copyFileSync(extracted, BUN_BIN);
121
+ fs.chmodSync(BUN_BIN, 0o755);
122
+ fs.writeFileSync(BUN_VERSION_MARKER, BOOTSTRAP_BUN.version);
123
+
124
+ const nodeLink = path.join(BIN_DIR, "node");
125
+ try { fs.unlinkSync(nodeLink); } catch {}
126
+ fs.symlinkSync("bun", nodeLink);
127
+
128
+ fs.rmSync(tmpDir, { recursive: true, force: true });
129
+ console.log(` ✓ bun ${BOOTSTRAP_BUN.version} installed`);
130
+ }
131
+
132
+ export function runCommand(cmd, args, cwd, extraEnv = {}) {
133
+ return new Promise((resolve, reject) => {
134
+ const proc = spawn(cmd, args, {
135
+ cwd,
136
+ stdio: ["ignore", "inherit", "inherit"],
137
+ env: { ...process.env, FORCE_COLOR: "0", ...extraEnv },
138
+ });
139
+ proc.on("close", (code) => {
140
+ if (code === 0) resolve();
141
+ else reject(new Error(`${cmd} exited with code ${code}`));
142
+ });
143
+ proc.on("error", reject);
144
+ });
145
+ }
146
+
147
+ export { BUN_BIN, BIN_DIR, CACHE_ROOT };
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "create-zenbu-app",
3
+ "version": "0.0.1",
4
+ "description": "Create a new Zenbu application",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-zenbu-app": "./bin/create-zenbu-app.mjs"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "lib/",
12
+ "template/"
13
+ ],
14
+ "keywords": ["zenbu", "framework", "scaffold"],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/zenbu-labs/zenbu.js.git"
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ .zenbu/
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "setup": "zenbu/packages/init/setup.ts"
7
+ },
8
+ "dependencies": {
9
+ "zenbu": "file:./zenbu"
10
+ }
11
+ }
File without changes
@@ -0,0 +1,8 @@
1
+ export function App() {
2
+ return (
3
+ <div style={{ padding: 32, fontFamily: "system-ui, sans-serif" }}>
4
+ <h1>Welcome to Zenbu</h1>
5
+ <p>Edit <code>src/renderer/App.tsx</code> to get started.</p>
6
+ </div>
7
+ );
8
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Zenbu App</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,7 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import { App } from "./App";
3
+
4
+ const root = document.getElementById("root");
5
+ if (root) {
6
+ createRoot(root).render(<App />);
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "dist",
11
+ "rootDir": ".",
12
+ "paths": {
13
+ "@/*": ["./src/renderer/*"]
14
+ }
15
+ },
16
+ "include": ["src"]
17
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "app",
3
+ "services": [
4
+ "src/main/services/*.ts"
5
+ ]
6
+ }