create-zenbu-app 0.0.1 → 0.0.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 +11 -0
- package/README.md +13 -0
- package/dist/index.mjs +103 -0
- package/package.json +23 -7
- package/template/_gitignore +2 -0
- package/template/config.json +6 -0
- package/template/electron-builder.json +12 -0
- package/template/loading.html +50 -0
- package/template/package.json +36 -2
- package/template/src/main/services/app.ts.tmpl +14 -0
- package/template/src/renderer/App.tsx.tmpl +43 -0
- package/template/src/renderer/app.css +8 -0
- package/template/src/renderer/main.tsx.tmpl +10 -0
- package/template/{tsconfig.json → tsconfig.json.tmpl} +3 -2
- package/template/vite.config.ts.tmpl +16 -0
- package/template/zenbu.build.ts.tmpl +43 -0
- package/template/zenbu.plugin.json +1 -0
- package/bin/create-zenbu-app.mjs +0 -88
- package/lib/bootstrap.mjs +0 -147
- package/template/src/main/services/.gitkeep +0 -0
- package/template/src/renderer/App.tsx +0 -8
- package/template/src/renderer/main.tsx +0 -7
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.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Scaffold a new Zenbu app",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
6
9
|
"bin": {
|
|
7
|
-
"create-zenbu-app": "./
|
|
10
|
+
"create-zenbu-app": "./dist/index.mjs"
|
|
8
11
|
},
|
|
9
12
|
"files": [
|
|
10
|
-
"
|
|
11
|
-
"lib/",
|
|
13
|
+
"dist/",
|
|
12
14
|
"template/"
|
|
13
15
|
],
|
|
14
|
-
"keywords": [
|
|
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
|
+
}
|
package/template/_gitignore
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<style>
|
|
6
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7
|
+
html, body { height: 100%; overflow: hidden; }
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
10
|
+
background: #F4F4F4;
|
|
11
|
+
-webkit-app-region: drag;
|
|
12
|
+
user-select: none;
|
|
13
|
+
padding: 52px 48px 32px;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: 16px;
|
|
17
|
+
}
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
body { background: #1a1a1a; }
|
|
20
|
+
.skeleton { background: #2a2a2a; }
|
|
21
|
+
.skeleton::after { background: linear-gradient(90deg, transparent, rgba(255,255,255,0.03), transparent); }
|
|
22
|
+
}
|
|
23
|
+
.skeleton {
|
|
24
|
+
background: #e5e5e5;
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
position: relative;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
}
|
|
29
|
+
.skeleton::after {
|
|
30
|
+
content: "";
|
|
31
|
+
position: absolute;
|
|
32
|
+
inset: 0;
|
|
33
|
+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
|
34
|
+
animation: shimmer 1.5s infinite;
|
|
35
|
+
}
|
|
36
|
+
@keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
|
|
37
|
+
.title { height: 28px; width: 35%; }
|
|
38
|
+
.line { height: 14px; }
|
|
39
|
+
.line:nth-child(2) { width: 90%; }
|
|
40
|
+
.line:nth-child(3) { width: 70%; }
|
|
41
|
+
.line:nth-child(4) { width: 80%; }
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div class="skeleton title"></div>
|
|
46
|
+
<div class="skeleton line"></div>
|
|
47
|
+
<div class="skeleton line"></div>
|
|
48
|
+
<div class="skeleton line"></div>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
package/template/package.json
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
15
|
+
"@zenbujs/core": "^0.0.1",
|
|
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,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,43 @@
|
|
|
1
|
+
import { defineBuildConfig, stripIfDisabled, dropFiles } from "@zenbujs/core/build";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build pipeline for `zen build:source` and `zen build:electron`.
|
|
5
|
+
*
|
|
6
|
+
* Edit `include`/`ignore` to choose what gets shipped to the mirror repo and
|
|
7
|
+
* seeded inside the .app. Add transforms to strip feature-flagged code,
|
|
8
|
+
* drop test files, etc.
|
|
9
|
+
*
|
|
10
|
+
* Set `mirror.target` to "<owner>/<repo>" (a GitHub repo) to enable
|
|
11
|
+
* `zen publish:source init` / `zen publish:source push`.
|
|
12
|
+
*/
|
|
13
|
+
export default defineBuildConfig({
|
|
14
|
+
source: ".",
|
|
15
|
+
out: ".zenbu/build/seed",
|
|
16
|
+
|
|
17
|
+
include: [
|
|
18
|
+
"src/**/*",
|
|
19
|
+
"package.json",
|
|
20
|
+
"pnpm-lock.yaml",
|
|
21
|
+
"tsconfig.json",
|
|
22
|
+
"zenbu.plugin.json",
|
|
23
|
+
"config.json",
|
|
24
|
+
"db/**",
|
|
25
|
+
"db.config.ts",
|
|
26
|
+
"vite.config.ts",
|
|
27
|
+
"loading.html",
|
|
28
|
+
],
|
|
29
|
+
ignore: [
|
|
30
|
+
"src/**/*.test.ts",
|
|
31
|
+
"src/**/*.test.tsx",
|
|
32
|
+
"src/**/*.spec.ts",
|
|
33
|
+
"src/**/*.spec.tsx",
|
|
34
|
+
"src/dev-only/**",
|
|
35
|
+
],
|
|
36
|
+
|
|
37
|
+
transforms: [
|
|
38
|
+
stripIfDisabled({ FLAG_BETA: false }),
|
|
39
|
+
dropFiles(/\.stories\.tsx?$/),
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
// mirror: { target: "{{owner}}/{{repo}}", branch: "main" },
|
|
43
|
+
});
|
package/bin/create-zenbu-app.mjs
DELETED
|
@@ -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
|