create-zenbu-app 0.0.1 → 0.0.3

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 ADDED
@@ -0,0 +1,11 @@
1
+ All Rights Reserved
2
+
3
+ Copyright (c) 2026 Zenbu Labs Inc.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
8
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
9
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
10
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
11
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # create-zenbu-app
2
+
3
+ Scaffold a new [Zenbu](https://github.com/zenbu-labs/zenbu.js) app.
4
+
5
+ ```bash
6
+ pnpm create zenbu-app my-app
7
+ cd my-app
8
+ pnpm install
9
+ pnpm dev
10
+ ```
11
+
12
+ Zenbu currently requires pnpm 10+. The bundled .app re-installs from the
13
+ pnpm lockfile at first launch, so the project's lockfile must be a pnpm one.
package/dist/index.mjs ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { spawnSync } from "node:child_process";
6
+ //#region src/index.ts
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const TEMPLATE_DIR = path.resolve(__dirname, "..", "template");
9
+ const argv = process.argv.slice(2);
10
+ const flags = new Set(argv.filter((a) => a.startsWith("-")));
11
+ const positional = argv.filter((a) => !a.startsWith("-"));
12
+ const yes = flags.has("--yes") || flags.has("-y");
13
+ const ZENBU_LOCAL_CORE = process.env.ZENBU_LOCAL_CORE;
14
+ function renderTemplate(value, projectName) {
15
+ return value.replace(/\{\{projectName\}\}/g, projectName);
16
+ }
17
+ function copyDirSync(src, dest, projectName) {
18
+ fs.mkdirSync(dest, { recursive: true });
19
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
20
+ const srcPath = path.join(src, entry.name);
21
+ const destName = entry.name.endsWith(".tmpl") ? entry.name.slice(0, -5) : entry.name;
22
+ const destPath = path.join(dest, destName);
23
+ if (entry.isDirectory()) copyDirSync(srcPath, destPath, projectName);
24
+ else {
25
+ const content = fs.readFileSync(srcPath, "utf8");
26
+ fs.writeFileSync(destPath, renderTemplate(content, projectName));
27
+ }
28
+ }
29
+ }
30
+ function rewireToLocalCore(projectDir, corePath) {
31
+ const pkgPath = path.join(projectDir, "package.json");
32
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
33
+ pkg.dependencies = pkg.dependencies ?? {};
34
+ pkg.dependencies["@zenbujs/core"] = `link:${corePath}`;
35
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
36
+ }
37
+ function gitInitWithInitialCommit(projectDir) {
38
+ if (spawnSync("git", [
39
+ "init",
40
+ "-b",
41
+ "main"
42
+ ], {
43
+ cwd: projectDir,
44
+ stdio: "ignore"
45
+ }).status !== 0) return;
46
+ spawnSync("git", ["add", "-A"], {
47
+ cwd: projectDir,
48
+ stdio: "ignore"
49
+ });
50
+ spawnSync("git", [
51
+ "commit",
52
+ "-m",
53
+ "chore: scaffold via create-zenbu-app"
54
+ ], {
55
+ cwd: projectDir,
56
+ stdio: "ignore",
57
+ env: {
58
+ ...process.env,
59
+ GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || "create-zenbu-app",
60
+ GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL || "create-zenbu-app@local",
61
+ GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || "create-zenbu-app",
62
+ GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || "create-zenbu-app@local"
63
+ }
64
+ });
65
+ }
66
+ function main() {
67
+ const projectName = positional[0] ?? ".";
68
+ if (projectName === "." && !yes) {
69
+ console.error("Usage: npm create zenbu-app <project-name>\n (use --yes to scaffold into the current directory)");
70
+ process.exit(1);
71
+ }
72
+ const projectDir = path.resolve(process.cwd(), projectName);
73
+ const displayName = projectName === "." ? path.basename(projectDir) : projectName;
74
+ if (fs.existsSync(projectDir)) {
75
+ const entries = fs.readdirSync(projectDir).filter((e) => e !== ".git");
76
+ const allowedExisting = projectName === "." && fs.existsSync(path.join(projectDir, "package.json"));
77
+ if (entries.length > 0 && !allowedExisting) {
78
+ console.error(`Error: directory "${projectName}" already exists and is not empty.`);
79
+ process.exit(1);
80
+ }
81
+ }
82
+ console.log(`\nScaffolding Zenbu app in "${displayName}"...\n`);
83
+ copyDirSync(TEMPLATE_DIR, projectDir, displayName);
84
+ const gi = path.join(projectDir, "_gitignore");
85
+ if (fs.existsSync(gi)) fs.renameSync(gi, path.join(projectDir, ".gitignore"));
86
+ if (ZENBU_LOCAL_CORE) {
87
+ const corePath = path.resolve(ZENBU_LOCAL_CORE);
88
+ rewireToLocalCore(projectDir, corePath);
89
+ console.log(` → linked @zenbujs/core -> ${corePath}`);
90
+ }
91
+ if (!fs.existsSync(path.join(projectDir, ".git"))) gitInitWithInitialCommit(projectDir);
92
+ const cdHint = projectName === "." ? "" : `cd ${displayName} && `;
93
+ console.log(`Done. Next:\n\n ${cdHint}pnpm install\n pnpm dev\n`);
94
+ console.log(`Note: Zenbu currently requires pnpm 10+.\n`);
95
+ }
96
+ try {
97
+ main();
98
+ } catch (err) {
99
+ console.error("\nError:", err?.message ?? err);
100
+ process.exit(1);
101
+ }
102
+ //#endregion
103
+ export {};
package/package.json CHANGED
@@ -1,20 +1,36 @@
1
1
  {
2
2
  "name": "create-zenbu-app",
3
- "version": "0.0.1",
4
- "description": "Create a new Zenbu application",
3
+ "version": "0.0.3",
4
+ "description": "Scaffold a new Zenbu app",
5
5
  "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
6
9
  "bin": {
7
- "create-zenbu-app": "./bin/create-zenbu-app.mjs"
10
+ "create-zenbu-app": "./dist/index.mjs"
8
11
  },
9
12
  "files": [
10
- "bin/",
11
- "lib/",
13
+ "dist/",
12
14
  "template/"
13
15
  ],
14
- "keywords": ["zenbu", "framework", "scaffold"],
16
+ "keywords": [
17
+ "zenbu",
18
+ "framework",
19
+ "scaffold",
20
+ "create-app"
21
+ ],
15
22
  "license": "MIT",
16
23
  "repository": {
17
24
  "type": "git",
18
25
  "url": "https://github.com/zenbu-labs/zenbu.js.git"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.0.0",
29
+ "tsdown": "^0.21.10",
30
+ "typescript": "^5.0.0"
31
+ },
32
+ "scripts": {
33
+ "build": "tsdown",
34
+ "typecheck": "tsc --noEmit -p tsconfig.json"
19
35
  }
20
- }
36
+ }
@@ -1,3 +1,5 @@
1
1
  node_modules/
2
2
  dist/
3
3
  .zenbu/
4
+ node_modules/.zenbujs/
5
+ tsconfig.local.json
@@ -0,0 +1,12 @@
1
+ {
2
+ "appId": "dev.zenbu.{{projectName}}",
3
+ "productName": "{{projectName}}",
4
+ "asar": false,
5
+ "directories": {
6
+ "output": "dist"
7
+ },
8
+ "mac": {
9
+ "category": "public.app-category.developer-tools",
10
+ "target": ["zip"]
11
+ }
12
+ }
@@ -2,10 +2,44 @@
2
2
  "name": "{{projectName}}",
3
3
  "private": true,
4
4
  "type": "module",
5
+ "main": "node_modules/@zenbujs/core/dist/setup-gate.mjs",
5
6
  "scripts": {
6
- "setup": "zenbu/packages/init/setup.ts"
7
+ "dev": "zen dev",
8
+ "build:source": "zen build:source",
9
+ "build:electron": "zen build:electron",
10
+ "publish:source": "zen publish:source",
11
+ "link": "zen link",
12
+ "db:generate": "zen db generate"
7
13
  },
8
14
  "dependencies": {
9
- "zenbu": "file:./zenbu"
15
+ "@zenbujs/core": "0.0.5",
16
+ "@vitejs/plugin-react": "^5.0.0",
17
+ "react": "^19.0.0",
18
+ "react-dom": "^19.0.0",
19
+ "vite": "^6.0.0",
20
+ "zod": "^4.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.0.0",
24
+ "@types/react": "^19.0.0",
25
+ "@types/react-dom": "^19.0.0",
26
+ "electron": "^42.0.0",
27
+ "electron-builder": "^26.0.0"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": ""
32
+ },
33
+ "zenbu": {
34
+ "host": ">=0.0.0 <0.1.0"
35
+ },
36
+ "pnpm": {
37
+ "onlyBuiltDependencies": [
38
+ "@parcel/watcher",
39
+ "dugite",
40
+ "electron",
41
+ "electron-winstaller",
42
+ "esbuild"
43
+ ]
10
44
  }
11
45
  }
@@ -0,0 +1,14 @@
1
+ import { runtime, serviceWithDeps } from "@zenbujs/core/runtime"
2
+ import { WindowService } from "@zenbujs/core/services"
3
+
4
+ export class AppService extends serviceWithDeps({
5
+ window: WindowService,
6
+ }) {
7
+ static key = "app"
8
+
9
+ async evaluate() {
10
+ await this.ctx.window.openView({ scope: "app" })
11
+ }
12
+ }
13
+
14
+ runtime.register(AppService, import.meta)
@@ -0,0 +1,43 @@
1
+ function Titlebar() {
2
+ return (
3
+ <div
4
+ style={{
5
+ height: 40,
6
+ display: "flex",
7
+ alignItems: "center",
8
+ justifyContent: "flex-end",
9
+ padding: "0 12px 0 72px",
10
+ // @ts-expect-error webkit property
11
+ WebkitAppRegion: "drag",
12
+ flexShrink: 0,
13
+ }}
14
+ />
15
+ )
16
+ }
17
+
18
+ function Home() {
19
+ return (
20
+ <main
21
+ style={{
22
+ flex: 1,
23
+ padding: "0 32px 32px",
24
+ fontFamily: "system-ui, -apple-system, sans-serif",
25
+ color: "var(--foreground, #e5e5e5)",
26
+ }}
27
+ >
28
+ <h1>Welcome to Zenbu</h1>
29
+ <p style={{ color: "var(--muted-foreground, #999)", marginTop: 8 }}>
30
+ Edit <code>src/renderer/App.tsx</code> to get started.
31
+ </p>
32
+ </main>
33
+ )
34
+ }
35
+
36
+ export function App() {
37
+ return (
38
+ <div style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
39
+ <Titlebar />
40
+ <Home />
41
+ </div>
42
+ )
43
+ }
@@ -0,0 +1,8 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ background: #111;
8
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react"
2
+ import { createRoot } from "react-dom/client"
3
+ import { App } from "./App"
4
+ import "./app.css"
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,51 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="zenbu-bg" content="#F4F4F4">
6
+ <style>
7
+ * { margin: 0; padding: 0; box-sizing: border-box; }
8
+ html, body { height: 100%; overflow: hidden; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
11
+ background: #F4F4F4;
12
+ -webkit-app-region: drag;
13
+ user-select: none;
14
+ padding: 52px 48px 32px;
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 16px;
18
+ }
19
+ @media (prefers-color-scheme: dark) {
20
+ body { background: #1a1a1a; }
21
+ .skeleton { background: #2a2a2a; }
22
+ .skeleton::after { background: linear-gradient(90deg, transparent, rgba(255,255,255,0.03), transparent); }
23
+ }
24
+ .skeleton {
25
+ background: #e5e5e5;
26
+ border-radius: 6px;
27
+ position: relative;
28
+ overflow: hidden;
29
+ }
30
+ .skeleton::after {
31
+ content: "";
32
+ position: absolute;
33
+ inset: 0;
34
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
35
+ animation: shimmer 1.5s infinite;
36
+ }
37
+ @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
38
+ .title { height: 28px; width: 35%; }
39
+ .line { height: 14px; }
40
+ .line:nth-child(2) { width: 90%; }
41
+ .line:nth-child(3) { width: 70%; }
42
+ .line:nth-child(4) { width: 80%; }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <div class="skeleton title"></div>
47
+ <div class="skeleton line"></div>
48
+ <div class="skeleton line"></div>
49
+ <div class="skeleton line"></div>
50
+ </body>
51
+ </html>
@@ -10,8 +10,9 @@
10
10
  "outDir": "dist",
11
11
  "rootDir": ".",
12
12
  "paths": {
13
- "@/*": ["./src/renderer/*"]
13
+ "@/*": ["./src/renderer/*"],
14
+ "#registry/*": ["./types/*"]
14
15
  }
15
16
  },
16
- "include": ["src"]
17
+ "include": ["src", "types"]
17
18
  }
@@ -0,0 +1,16 @@
1
+ import path from "node:path"
2
+ import { fileURLToPath } from "node:url"
3
+ import { defineConfig } from "vite"
4
+ import react from "@vitejs/plugin-react"
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+
8
+ export default defineConfig({
9
+ root: path.resolve(__dirname, "src", "renderer"),
10
+ plugins: [react()],
11
+ resolve: {
12
+ alias: {
13
+ "@": path.resolve(__dirname, "src", "renderer"),
14
+ },
15
+ },
16
+ })
@@ -0,0 +1,52 @@
1
+ import {
2
+ defineConfig,
3
+ definePlugin,
4
+ defineBuildConfig,
5
+ stripIfDisabled,
6
+ dropFiles,
7
+ } from "@zenbujs/core/config";
8
+
9
+ export default defineConfig({
10
+ // Where the kyju database lives (relative to this file).
11
+ db: "./.zenbu/db",
12
+
13
+ // Boot-window HTML. The single ui entrypoint for the whole app.
14
+ uiEntrypoint: "./src/renderer",
15
+
16
+ // Plugins are pure main-process: services + optional schema/preload/events.
17
+ // The "host plugin" is just the first entry by convention.
18
+ plugins: [
19
+ definePlugin({
20
+ name: "app",
21
+ services: ["./src/main/services/*.ts"],
22
+ }),
23
+ ],
24
+
25
+ // Build pipeline for `zen build:source` (mirror staging) and
26
+ // `zen build:electron` (signed .app via electron-builder). Set
27
+ // `mirror.target` to "<owner>/<repo>" before shipping.
28
+ build: defineBuildConfig({
29
+ source: ".",
30
+ out: ".zenbu/build/source",
31
+ include: [
32
+ "src/**/*",
33
+ "package.json",
34
+ "pnpm-lock.yaml",
35
+ "tsconfig.json",
36
+ "zenbu.config.ts",
37
+ "vite.config.ts",
38
+ ],
39
+ ignore: [
40
+ "src/**/*.test.ts",
41
+ "src/**/*.test.tsx",
42
+ "src/**/*.spec.ts",
43
+ "src/**/*.spec.tsx",
44
+ "src/dev-only/**",
45
+ ],
46
+ transforms: [
47
+ stripIfDisabled({ FLAG_BETA: false }),
48
+ dropFiles(/\.stories\.tsx?$/),
49
+ ],
50
+ // mirror: { target: "{{owner}}/{{repo}}", branch: "main" },
51
+ }),
52
+ });
@@ -1,88 +0,0 @@
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
- });
package/lib/bootstrap.mjs DELETED
@@ -1,147 +0,0 @@
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 };
File without changes
@@ -1,8 +0,0 @@
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
- }
@@ -1,7 +0,0 @@
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
- }
@@ -1,6 +0,0 @@
1
- {
2
- "name": "app",
3
- "services": [
4
- "src/main/services/*.ts"
5
- ]
6
- }