Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: CI

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
moon:
name: Moon check and test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install MoonBit CLI
run: |
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"

- name: Moon update
run: moon update

- name: Moon check (js)
run: moon check --target js

- name: Moon test (js)
run: moon test --target js

e2e:
name: Playwright e2e
runs-on: ubuntu-latest
needs: moon
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- name: Install MoonBit CLI
run: |
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"

- name: Moon update
run: moon update

- name: Install Node dependencies
run: pnpm install --frozen-lockfile

- name: Install Playwright browser
run: pnpm exec playwright install --with-deps chromium

- name: Run Playwright tests
run: pnpm test:e2e

bench:
name: Moon benchmark
runs-on: ubuntu-latest
needs: moon
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install MoonBit CLI
run: |
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
echo "$HOME/.moon/bin" >> "$GITHUB_PATH"

- name: Moon update
run: moon update

- name: Run viewer benchmark (js)
run: moon bench -p bit-vcs/bithub/cmd/bithub --target js -f bench_viewer_test.mbt
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
_build/
target/
.mooncakes/
.DS_Store
node_modules/
playwright-report/
test-results/
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
# bithub

`bit` と連携する GitHub-like UI のための実験リポジトリです。

## Current Direction

- まずは `mars` で実装する
- `sol` へ引き上げられるように分解点を固定する
- Cloudflare Workers (JS target) 前提で進める

分解方針は `/Users/mz/ghq/github.com/bit-vcs/bithub/docs/mars-sol-boundary.md` を参照。

## Check

```bash
moon check --target js
moon test --target js
```

## E2E (Playwright)

```bash
pnpm install
pnpm test:e2e
```

## Benchmark

```bash
moon bench -p bit-vcs/bithub/cmd/bithub --target js -f bench_viewer_test.mbt
pnpm bench
moon run src/cmd/bithub_bench --target js -- . 20
```

- `moon bench`: 標準ベンチハーネス
- `pnpm bench`: 手早いサマリ表示(デフォルト設定)
- `moon run ... -- <repo> <iterations>`: 対象リポジトリと反復回数を明示指定

## Local Viewer (`bithub .`)

現在のリポジトリを GitHub 風に閲覧する最小 UI を起動できます。

```bash
./bithub . # port 8787
./bithub . 9000 # custom port
```

- `/` で `README.md` を優先表示
- `/blob/<path>` でファイル表示
- `/issues` で `bit hub` の Issue 一覧表示
- UI は `mizchi/luna/x/components` ベースの最小構成

## Cloudflare Entrypoint

`/Users/mz/ghq/github.com/bit-vcs/bithub/src/cmd/main/main.mbt` に
`fetch(request, env, exec_ctx)` を公開し、`@mars.Server::to_handler_with_env` へ委譲する。
GitHub-like UI interface for bit
5 changes: 5 additions & 0 deletions bithub
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")"
moon run src/cmd/bithub --target js -- "$@"
28 changes: 28 additions & 0 deletions docs/mars-sol-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Mars/Sol Boundary Notes (bithub)

## Goal

`bithub` は当面 `mars` で機能を作り、`sol` への引き上げ可能性を維持する。

## Design Rule

- `core`:
- 画面/機能の契約と純粋ロジック
- `mars` / `sol` 依存を入れない
- `adapters/mars_http`:
- `core` を `mars.Server` へ接続する層
- `cmd/main`:
- Cloudflare 向け `fetch` エントリ公開のみ
- `@mars.Server::to_handler_with_env` に委譲する

## Migration Intention

将来 `sol` へ寄せる場合は、`core` を温存したまま `adapters/sol_*` を追加し、`mars_http` との差し替えで移行する。

## Current Minimal Contract

- `core.mars_route_specs()`
- `core.home_text()`
- `core.healthz_text()`

`mars_http` はこの契約だけに依存する。
73 changes: 73 additions & 0 deletions e2e/file-viewer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { test, expect } from '@playwright/test';

test('root opens README.md by default', async ({ page }) => {
await page.goto('/');

await expect(page.getByRole('link', { name: /\[\*\] README\.md/ })).toBeVisible();
await expect(page.getByRole('heading', { level: 1, name: 'bithub' })).toBeVisible();
await expect(page.locator('main')).toContainText('README.md');
});

test('can open a source file from nav', async ({ page }) => {
await page.goto('/');

await page.getByRole('link', { name: 'src/cmd/bithub/main.mbt' }).click();

await expect(page).toHaveURL(/\/blob\/src\/cmd\/bithub\/main\.mbt$/);
await expect(page.locator('main')).toContainText('fn main');
});

test('path traversal is rejected', async ({ page }) => {
const response = await page.goto('/blob/..%2FREADME.md');

expect(response).not.toBeNull();
expect(response!.status()).toBe(400);
await expect(page.locator('main')).toContainText('Invalid path.');
});

test('issues list page is available', async ({ page }) => {
await page.goto('/');

await page.getByRole('link', { name: 'issues' }).click();

await expect(page).toHaveURL(/\/issues$/);
await expect(page.getByRole('heading', { level: 1, name: 'Issues' })).toBeVisible();
});

test('issues route works with query string', async ({ page }) => {
await page.goto('/issues?state=open');

await expect(page.getByRole('heading', { level: 1, name: 'Issues' })).toBeVisible();
});

test('blob route works with query string', async ({ page }) => {
const response = await page.goto('/blob/README.md?raw=1');

expect(response).not.toBeNull();
expect(response!.status()).toBe(200);
await expect(page.locator('main')).toContainText('README.md');
});

test('missing blob returns 404 page', async ({ page }) => {
const response = await page.goto('/blob/not-found-file.txt');

expect(response).not.toBeNull();
expect(response!.status()).toBe(404);
await expect(page.locator('main')).toContainText('File not found');
});

test('unknown route returns 404 page', async ({ page }) => {
const response = await page.goto('/__no_such_route__');

expect(response).not.toBeNull();
expect(response!.status()).toBe(404);
await expect(page.locator('main')).toContainText('Route not found.');
});

test('can navigate back to home from issues page', async ({ page }) => {
await page.goto('/issues');
await page.getByRole('banner').getByRole('link', { name: 'bithub' }).click();

await expect(page).toHaveURL(/\/$/);
await expect(page.locator('main')).toContainText('README.md');
});
25 changes: 25 additions & 0 deletions moon.mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "bit-vcs/bithub",
"version": "0.1.0",
"deps": {
"moonbitlang/async": "0.16.6",
"mizchi/mars": "0.3.7",
"mizchi/bit": "0.21.2",
"mizchi/markdown": "0.4.7",
"mizchi/luna": "0.12.3",
"moonbitlang/x": "0.4.40",
"mizchi/js": "0.10.14"
},
"readme": "README.md",
"repository": "https://github.com/bit-vcs/bithub",
"license": "MIT",
"keywords": [
"bithub",
"bit",
"mars",
"sol"
],
"description": "GitHub-like UI backend for bit",
"source": "src",
"preferred-target": "js"
}
1 change: 1 addition & 0 deletions moon.pkg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "bit-vcs-bithub",
"private": true,
"scripts": {
"bench": "moon run src/cmd/bithub_bench --target js -- . 20",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:ui": "playwright test --ui"
},
"devDependencies": {
"@playwright/test": "^1.52.0"
}
}
21 changes: 21 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig } from '@playwright/test';

const PORT = 4173;

export default defineConfig({
testDir: './e2e',
timeout: 30_000,
expect: {
timeout: 5_000,
},
use: {
baseURL: `http://127.0.0.1:${PORT}`,
trace: 'on-first-retry',
},
webServer: {
command: `./bithub . ${PORT}`,
url: `http://127.0.0.1:${PORT}`,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
52 changes: 52 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/adapters/mars_http/moon.pkg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {
"mizchi/mars" @mars,
"bit-vcs/bithub/core" @core,
}
Loading