Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/workflows/build-engine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ jobs:
build:
name: relocate WebKitGTK (ubuntu)
runs-on: ubuntu-latest
# The engine tooling lives in the framework package; run all steps from there.
defaults:
run:
working-directory: packages/bunmaska
steps:
- uses: actions/checkout@v4

Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/deploy-web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: deploy-web

# Builds apps/web and deploys it to Cloudflare Pages (project "bunmaska",
# which serves bunmaska.org) on pushes that touch the site. Repo secrets:
# CLOUDFLARE_API_TOKEN — scoped token (Pages Edit + DNS Edit + Zone Read)
# CLOUDFLARE_ACCOUNT_ID — 33b4bb12637807398ca2edac6c77fe8c
on:
push:
branches: [main]
paths: ['apps/web/**', '.github/workflows/deploy-web.yml']
workflow_dispatch:

permissions:
contents: read
deployments: write

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version-file: .bun-version

- name: Install (workspace)
run: bun install --frozen-lockfile

- name: Build the site
working-directory: apps/web
run: bun run build

- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/web
command: pages deploy dist --project-name=bunmaska --branch=main
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ name: validate
on:
push:
branches: [main]
paths: ['packages/bunmaska/**', 'package.json', 'bun.lock', '.bun-version', '.github/workflows/validate.yml']
pull_request:
branches: [main]
paths: ['packages/bunmaska/**', 'package.json', 'bun.lock', '.bun-version', '.github/workflows/validate.yml']

concurrency:
group: validate-${{ github.ref }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ node_modules/
# Build output
dist/
build/
.astro/
.wrangler/
*.tsbuildinfo

# Native build artifacts
Expand Down
141 changes: 15 additions & 126 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,138 +1,27 @@
# Bun Maska
# Bunmaska — monorepo

> The bread and butter of desktop apps: Electron's familiar APIs on Bun and your operating system's own WebKit. No bundled Chromium — because shipping 150 MB of browser with every app is one of those ideas that made sense in 2013 and has been quietly ruining laptop fans ever since.
The home of [Bunmaska](./packages/bunmaska) and everything built around it. A Bun workspace; no extra task runner.

## What
## What's in here

A **drop-in replacement for Electron** that refuses to bundle an entire browser engine just so you can put a web page in a native window.
| Path | What it is |
|---|---|
| [`packages/bunmaska`](./packages/bunmaska) | **The framework** — a drop-in Electron replacement on Bun + WebKit. Published to npm as `bunmaska`. |
| [`apps/web`](./apps/web) | **The website + docs** — [bunmaska.org](https://bunmaska.org). Astro, deployed to Cloudflare Pages. |

You keep writing against the APIs you already know (`app`, `BrowserWindow`, `ipcMain`, `ipcRenderer`, `Menu`, `dialog`, `clipboard`, `webContents.printToPDF`, `capturePage`, etc.). We swap the heavy parts: runtime becomes Bun instead of Node, renderer becomes whatever WebKit your operating system already ships (WKWebView on macOS, WebKitGTK 6 on Linux). The result is dramatically smaller apps, zero compiled native code in your dependency tree, and a native module system that is just TypeScript calling `dlopen`.
Future neighbours slot in without rework: `apps/chai` (the app store), `examples/*` (starter boilerplates), more `packages/*`.

## The part worth paying attention to

Most "lighter Electron" projects are just better at gzipping. Bun Maska does something structurally different.

**A native module is a `.ts` file.**

Need to talk to a serial port, USB device, system keychain, IOKit, a custom sensor, or anything else the OS exposes? In Electron this usually means `node-gyp`, N-API, `electron-rebuild`, and hoping the prebuilts match your exact Electron version forever. In Bun Maska you write a small TypeScript file that `dlopen`s `libSystem`, `libc`, or `IOKit` and calls it directly. No compiler. No build step. No ABI compatibility matrix. No Python summoning ritual when you upgrade.

This is not a roadmap item. It is how Bun Maska itself is built — thirty-plus system libraries wired with **zero** `cc` calls anywhere in the tree.

Add the fact that we ship **no browser engine at all**, and your updates stop being hostage situations, your users stop getting Chromium CVEs that have nothing to do with your code, and your laptop fan gets a long-overdue vacation.

That combination — buildless native extensibility + no engine tax — is the thing no amount of tree-shaking inside a bundled Chromium can ever give you.

## Why (the non-marketing reasons)

Your laptop fan has strong opinions about RAM. Your users have data caps and limited patience. And we are allergic to lying in README files.

Honest measured numbers: a packaged Bun Maska app is roughly a **16–23 MB download** and lands at about **61 MB on disk** once Bun unpacks itself. The equivalent minimal Electron app starts north of 150 MB and only grows from there. Updates are tiny because there is no 150 MB engine to re-download. Your OS already patches WebKit. We do not make you participate in the Chromium CVE re-shipping treadmill.

## "But then I can't pin the WebKit version"

Fair. By default you render on whatever WebKit the machine already has — that's the whole reason the apps are small. When you'd rather ship the exact build you tested, Bun Maska has a content-addressed engine store: pin an engine in `bunmaska.config`, and at launch the app resolves *that* build from `~/.bunmaska/webkit/<id>/` — downloaded once, shared by every app, so they stay tiny. Different apps can pin different versions and run side by side; there is no global "switch the version for everything" footgun. If the pinned engine isn't there, the app quietly falls back to the system WebKit and tells you so.

```sh
bunmaska engine list # what's installed, side by side
bunmaska engine which # the engine this project resolves
bunmaska engine install … # a local build, or a signature- and hash-verified URL
bunmaska doctor # runtime, store, and the resolved pin
```

Where this honestly is: the resolve → store → load path is built and **proven on Linux** — an app loads its pinned WebKit from the store instead of the system one, verified in CI. The part still in progress is *publishing* the prebuilt engine binaries to install from. macOS pinning is designed and feasible (it just means shipping our own signed `WebKit.framework`); Windows is its own saga (below). So the plumbing is real; the hosted engines are the next milestone, not a finished promise.

## Status

**Alpha.** Held together with optimism, `strict: true`, and roughly 1,400 tests that were green the last time CI ran.

It works properly on both **macOS** and **Linux** today. You get real native windows, system WebKit rendering, full Electron-style IPC with context isolation, application and context menus, tray icons, dialogs, clipboard (text + HTML + images), `nativeImage`, `safeStorage`, `powerMonitor`, `printToPDF`, `capturePage`, and a growing list of other modules — all implemented with pure `bun:ffi` and zero compiled code in the framework.

There is also a CLI that scaffolds projects, runs them with hot reload, and packages real distributables (`.app`/`.dmg` on macOS, AppDir + `.deb` on Linux) with optional auto-update support.

If you are already running this in production, we admire your courage and decline all responsibility. If you are a large company doing an evaluation, please read the word "alpha" three more times before proceeding. If it is still 2027 and this file still opens with the word "alpha", feel free to open an issue titled "are you OK".

## The honest trade-offs

We are not going to sell you a fantasy.

- **Single process.** No Chromium sandbox. No per-window crash isolation. A nasty WebKit or JavaScriptCore crash takes the whole app with it. This is the architectural price of the lightness.
- **No Windows support yet.** Windows ships no system WebKit, so doing it our way means bringing our own — WinCairo WebKit, not WebView2 (that's Chromium with extra steps). It's deferred, not abandoned, and it lands behind macOS + Linux. We are aware this is a hill. We are comfortable dying on it.
- **~70–80% weighted API parity** for the things most real apps actually use. The long tail (`BrowserView`, sync IPC, Web Serial/WebHID/WebUSB from the renderer, deeply Chromium-internal surfaces) is either out of scope by design or will throw a clear error so you know immediately what is missing.

## Platforms

| OS | Status |
|---------|------------------------------------------------------------------------|
| macOS | Actively developed — AppKit + WKWebView via `objc_msgSend` and hand-built ObjC blocks |
| Linux | Actively developed — GTK 4 + WebKitGTK 6 via `dlopen` |
| Windows | Deferred — will bring WinCairo WebKit, never WebView2/Chromium |

## Install

```sh
bun add bunmaska # or: npm i bunmaska
```

Genuinely alpha — so pin your version and keep your expectations friendly. You need [Bun](https://bun.sh) ≥ 1.3 (yes, that is the entire point). Want to hack on it directly instead? Clone it:

```sh
git clone https://github.com/ipfizz/bunmaska.git
cd bunmaska && bun install
```

## Quick start

```ts
import { app, BrowserWindow } from 'bunmaska';

app.whenReady().then(() => {
const win = new BrowserWindow({
width: 1024,
height: 768,
title: 'Hello Bun Maska'
});
win.loadURL('https://example.com');
});
```

Run it with `bun run main.ts` (or `bunmaska run main.ts` once you have the CLI).

## The CLI
## Working in here

```sh
bunmaska init my-app # scaffold a full starter (main + preload + renderer + config)
bunmaska dev # run with file watching and auto-restart
bunmaska run main.ts # just run it
bunmaska build # produce distributables for the current OS
bunmaska build --update # also emit the auto-update feed (update.json + .tar.zst)
bunmaska engine list # manage the pinned-WebKit engine store (install/which/prune/…)
bunmaska doctor # report the runtime, the store, and the resolved engine
```

## Migrating from Electron

Change your imports:

```ts
// Before
import { app, BrowserWindow, ipcMain } from 'electron';

// After
import { app, BrowserWindow, ipcMain } from 'bunmaska';
// or use the explicit shim that throws helpful errors on unimplemented modules:
import { app, BrowserWindow } from 'bunmaska/electron';
bun install # install every workspace
bun run validate # lint + type-check + test (the framework)
bun run build # build the website
bun run dev # run the website locally
```

Most core modules behave the same. Anything not yet implemented throws an actionable error naming the exact missing module instead of failing mysteriously at 2 a.m.

One migration detail worth knowing up front: Web Serial, WebHID, and WebUSB are Chromium-only. System WebKit does not expose them. If your app uses them from the renderer, that code needs to move to the main process and cross IPC (which, conveniently, is exactly where Bun Maska makes talking to hardware via FFI much more pleasant than the old `node-gyp` dance).

## Contributing

You somehow found this repo before we told anyone. Hello.

Open an issue. Keep your expectations realistic. Try not to be a jerk. A proper contributing guide will exist once the project is less "held together with optimism."
Target one workspace with `--filter`, e.g. `bun run --filter bunmaska test` or `bun run --filter @bunmaska/web dev`.

## License

[MIT](./LICENSE). Go wild.
[MIT](./LICENSE).
24 changes: 24 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store

# jetbrains setting folder
.idea/
43 changes: 43 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# bunmaska-site

The landing page **and** docs for [Bunmaska](https://github.com/ipfizz/bunmaska) — one
app, statically rendered. Apple-clean, warm "Maska Gold" accent, hairline borders,
zero drop shadows. Tone: funny, dry, allergic to lying (matches the Bunmaska README).

## Stack

- **Astro 6** (`output: 'static'` — every page is server-rendered to HTML at build time)
- **React 19** islands (theme toggle, install tabs — surgical, near-zero JS)
- **Tailwind v4** (`@tailwindcss/vite`, CSS-first `@theme` tokens — no `tailwind.config.js`)
- **MDX** content collection for docs
- **Self-hosted fonts**: Inter Variable (UI), Instrument Serif (display), Geist Mono (code)
- Runs on **Bun**.

## Develop

```sh
bun install
bun run dev # http://localhost:4321
bun run build # static render → ./dist
bun run preview # serve ./dist
```

## Where things live

- `src/styles/global.css` — the entire design system (tokens, light/dark, no-shadow rules).
- `src/pages/index.astro` — the landing page (all sections + copy).
- `src/content/docs/**` — the docs corpus (markdown). Add a file, add it to `src/nav.ts`.
- `src/nav.ts` — single source of truth for the docs sidebar order + prev/next.
- `src/layouts/` — `Base.astro` (shell + theme) and `Doc.astro` (3-column docs).
- `src/components/` — Nav, Footer, Sidebar, Toc, PrevNext, CodeBlock, Callout, and the
two React islands (`ThemeToggle.tsx`, `InstallTabs.tsx`).

## Adding a docs page

1. Drop a `.md`/`.mdx` file in `src/content/docs/` (frontmatter: `title`, `description`).
Don't write an `# H1` — the title comes from frontmatter.
2. Add its slug + label to the right group in `src/nav.ts`.

## Not yet wired (v1)

Pagefind ⌘K search, an OG image, the real logo, and the deeper API-reference pages.
28 changes: 28 additions & 0 deletions apps/web/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @ts-check
import { defineConfig } from 'astro/config';

import react from '@astrojs/react';
import mdx from '@astrojs/mdx';
import icon from 'astro-icon';
import sitemap from '@astrojs/sitemap';
import tailwindcss from '@tailwindcss/vite';

import rehypeCopyButton from './src/rehype-copy-button.mjs';

// https://astro.build/config
export default defineConfig({
site: 'https://bunmaska.org',
output: 'static',
integrations: [react(), mdx(), icon(), sitemap()],
redirects: {
'/docs': '/docs/introduction',
'/docs/roadmap': '/roadmap',
},
markdown: {
shikiConfig: { themes: { light: 'github-light', dark: 'github-dark' }, wrap: false },
rehypePlugins: [rehypeCopyButton],
},
vite: {
plugins: [tailwindcss()],
},
});
35 changes: 35 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@bunmaska/web",
"type": "module",
"private": true,
"version": "0.0.1",
"engines": {
"node": ">=22.12.0"
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^6.0.3",
"@astrojs/react": "^5.0.7",
"@astrojs/sitemap": "^3.7.3",
"@fontsource-variable/geist-mono": "^5.2.8",
"@fontsource-variable/inter": "^5.2.8",
"@fontsource/instrument-serif": "^5.2.8",
"@iconify-json/lucide": "^1.2.112",
"@tailwindcss/vite": "^4.3.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"astro": "^6.4.6",
"astro-icon": "^1.1.5",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tailwindcss": "^4.3.1"
},
"devDependencies": {
"@types/bun": "^1.3.14"
}
}
Binary file added apps/web/public/favicon.ico
Binary file not shown.
4 changes: 4 additions & 0 deletions apps/web/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions apps/web/public/og.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions apps/web/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
User-agent: *
Allow: /

Sitemap: https://bunmaska.org/sitemap-index.xml
Loading
Loading