chore(docker,ci): Dockerfile 与 CI 切换 pnpm workspace,docs 同步

Dockerfile:
- Node 22 安装段追加 corepack enable + pnpm 11.8.0
- 缓存层从'只 tiptap 的 package.json'扩展为整个 workspace
  (libs/package.json + pnpm-workspace.yaml + pnpm-lock.yaml + 4 lib manifest),
  npm ci 改为 pnpm install --frozen-lockfile,覆盖全部 4 个 lib 的 Docker 层缓存
- 构建步骤 make build-editor 改为 make build-libs

Gitea CI:
- check job 的 Install Node 步骤加装 pnpm,新增 Biome lint 步骤
  (cd libs && pnpm install --frozen-lockfile && pnpm exec biome check .)
- build job 同步装 pnpm,make build 不变(Makefile 已改用 workspace)

.dockerignore: 补齐其余 3 个 lib 的 public 产物 + workspace node_modules 模式

AGENTS.md: Development Commands 删 make clippy、新增 make lint/make fix 说明;
Prerequisites 补 Biome;libs 段改为 pnpm workspace 模型;修正文档漂移
(tt-reveal→tt-expand,eval→Reflect::get)
This commit is contained in:
xfy 2026-07-02 18:10:11 +08:00
parent a47e1e6027
commit 2a7f7be738
4 changed files with 45 additions and 26 deletions

View File

@ -3,12 +3,16 @@ target/
dist/ dist/
.dioxus/ .dioxus/
node_modules/ node_modules/
libs/tiptap-editor/node_modules/ libs/node_modules/
libs/*/node_modules/
# Generated assets are rebuilt inside the container. # Generated assets are rebuilt inside the container.
public/style.css public/style.css
public/highlight.css public/highlight.css
public/tiptap/ public/tiptap/
public/codemirror/
public/lightbox/
public/yggdrasil-core/
# Uploads directory is created at runtime; only keep the gitkeep marker. # Uploads directory is created at runtime; only keep the gitkeep marker.
uploads/* uploads/*

View File

@ -35,10 +35,11 @@ jobs:
done done
echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Node.js - name: Install Node.js & pnpm
run: | run: |
apt-get update apt-get update
apt-get install -y nodejs npm apt-get install -y nodejs npm
npm install -g pnpm
- name: Install Rust target - name: Install Rust target
run: rustup target add wasm32-unknown-unknown run: rustup target add wasm32-unknown-unknown
@ -55,6 +56,9 @@ jobs:
- name: Run Clippy - name: Run Clippy
run: cargo clippy --all-targets --all-features -- -D warnings run: cargo clippy --all-targets --all-features -- -D warnings
- name: Biome lint (libs)
run: cd libs && pnpm install --frozen-lockfile && pnpm exec biome check .
- name: Run tests - name: Run tests
run: cargo test --all-features run: cargo test --all-features
@ -89,10 +93,11 @@ jobs:
done done
echo "$HOME/.cargo/bin" >> $GITHUB_PATH echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Node.js - name: Install Node.js & pnpm
run: | run: |
apt-get update apt-get update
apt-get install -y nodejs npm apt-get install -y nodejs npm
npm install -g pnpm
- name: Install Rust target - name: Install Rust target
run: rustup target add wasm32-unknown-unknown run: rustup target add wasm32-unknown-unknown

View File

@ -14,35 +14,37 @@
## Development Commands ## Development Commands
```bash ```bash
make dev # 增量构建 4 个 libs + tailwindcss watch + dx serve (needs PostgreSQL, SSR_CACHE_SECS=0) make dev # 增量构建 4 个 libs (pnpm -r run build) + tailwindcss watch + dx serve (needs PostgreSQL, SSR_CACHE_SECS=0)
make build # build-editor → build-lightbox → build-core → build-codemirror → highlight-css → tailwindcss → doc → dx build --release → restore-webp make build # pnpm install → build-libs → highlight-css → tailwindcss → doc → dx build --release → restore-webp
make build-linux # 客户端 + 服务端分离构建,target x86_64-unknown-linux-musl make build-linux # 客户端 + 服务端分离构建,target x86_64-unknown-linux-musl
make build-freebsd # cross-compile FreeBSD x86_64 server binary (clang + lld + sysroot, via cargo, not dx) make build-freebsd # cross-compile FreeBSD x86_64 server binary (clang + lld + sysroot, via cargo, not dx)
make freebsd-sysroot # download/extract FreeBSD base.txz → .freebsd-sysroot/ (idempotent) make freebsd-sysroot # download/extract FreeBSD base.txz → .freebsd-sysroot/ (idempotent)
make css # one-shot Tailwind build make css # one-shot Tailwind build
make css-watch # Tailwind watch mode make css-watch # Tailwind watch mode
make test # cargo test + pnpm test in all 4 libs (tiptap-editor / lightbox / yggdrasil-core / codemirror-editor) make test # cargo test + pnpm -r run test (all 4 libs: tiptap-editor / lightbox / yggdrasil-core / codemirror-editor)
make doc # cargo doc (ayu 主题) → 拷贝到 public/doc/,随 build 发布 make doc # cargo doc (ayu 主题) → 拷贝到 public/doc/,随 build 发布
make doc-open # 同 doc生成后自动用浏览器打开本地预览不拷贝 make doc-open # 同 doc生成后自动用浏览器打开本地预览不拷贝
make clippy # cargo clippy --all-targets --all-features -- -D warnings (严格模式,warning 即失败) make lint # Biome check (libs) + cargo clippy (严格模式,warning 即失败)
make fix # cargo fix --allow-dirty make fix # biome format --write (libs) + cargo fix --allow-dirty
make clean # cargo clean + rm public/{style.css,highlight.css,doc} + rm -rf uploads/.cache make clean # cargo clean + rm public/{style.css,highlight.css,doc} + rm -rf uploads/.cache + rm -rf libs/node_modules libs/*/node_modules
``` ```
**Build order matters**: `make build` runs all 4 lib builds`highlight-css` (`cargo run --bin generate_highlight_css`) → `tailwindcss --minify``doc``dx build --release``restore-webp`. Do not run `dx build --release` alone. **Build order matters**: `make build` runs `pnpm install --frozen-lockfile` (in `libs/`) → `build-libs` (`pnpm -r run build`, all 4 libs in parallel)`highlight-css` (`cargo run --bin generate_highlight_css`) → `tailwindcss --minify``doc``dx build --release``restore-webp`. Do not run `dx build --release` alone.
**`restore-webp` workaround**: dx build 0.7.9 re-encodes `public/*.webp` into VP8L lossless stills (drops animation frames, 7-8× larger), contradicting the "verbatim copy" promise. `restore-webp` overwrites `.webp` in `target/dx/**/web/public/` from source `public/`. SVG/ICO are unaffected. Remove once upstream fixes it. **`restore-webp` workaround**: dx build 0.7.9 re-encodes `public/*.webp` into VP8L lossless stills (drops animation frames, 7-8× larger), contradicting the "verbatim copy" promise. `restore-webp` overwrites `.webp` in `target/dx/**/web/public/` from source `public/`. SVG/ICO are unaffected. Remove once upstream fixes it.
**Libs use pnpm, not npm**: every lib has `pnpm-lock.yaml`; Makefile recipes use `pnpm ci`/`pnpm install`/`pnpm run`. The `*-incremental` targets skip install (assume `node_modules` present) for faster `make dev`. **JS workspace lives under `libs/`**: `libs/` is a pnpm workspace — root `libs/package.json` hoists the 4 shared devDeps (`happy-dom`/`typescript`/`vite`/`vitest`) + Biome; `libs/pnpm-workspace.yaml` lists `libs/*` packages; `libs/pnpm-lock.yaml` is the single lockfile. First-time setup: `cd libs && pnpm install`. All Makefile recipes `cd libs && pnpm ...`. Build a single lib: `make build-editor` (≈ `cd libs && pnpm --filter @yggdrasil/tiptap-editor run build`).
## Prerequisites ## Prerequisites
- Rust 1.95+ with `wasm32-unknown-unknown` target - Rust 1.95+ with `wasm32-unknown-unknown` target
- `dx` CLI (`cargo install dioxus-cli`) - `dx` CLI (`cargo install dioxus-cli`)
- `tailwindcss` CLI v4 — install via `npm install -g @tailwindcss/cli` (v4 splits the CLI into its own package; the `tailwindcss` core package has no `bin`), or use the standalone binary - `tailwindcss` CLI v4 — install via `npm install -g @tailwindcss/cli` (v4 splits the CLI into its own package; the `tailwindcss` core package has no `bin`), or use the standalone binary
- `pnpm` (all 4 `libs/` subprojects use it) - `pnpm` 11+ (`libs/` is a pnpm workspace; vendored as a `devDependency` via corepack, but a global install is convenient)
- PostgreSQL running locally - PostgreSQL running locally
**Biome** (linter + formatter for `libs/`) is a `devDependency` in `libs/package.json` — no separate install. `make lint` runs `biome check` + `cargo clippy`; `make fix` runs `biome format --write` + `cargo fix`. Config in `libs/biome.json`.
## Environment ## Environment
Create `.env` (not committed): Create `.env` (not committed):
@ -152,16 +154,16 @@ src/codemirror_bridge.rs — wasm-bindgen bindings for CodeMirror editor (mirror
## Frontend Lib Subprojects ## Frontend Lib Subprojects
Four Vite-built IIFE libraries under `libs/`, each with `pnpm-lock.yaml`. Built artifacts go to `public/<name>/`**do not edit `public/<name>/` files; they are build artifacts**. Each `build` script is `tsc --noEmit && vite build` (type-check before bundle). Output is IIFE because Dioxus `[web.resource] script` injects bare `<script src>` without `type="module"` support. Registered globally in `Dioxus.toml` `script`/`style` arrays. Four Vite-built IIFE libraries under `libs/`, managed as a **pnpm workspace** (single `libs/pnpm-lock.yaml`, shared `libs/tsconfig.base.json` + `libs/biome.json`, hoisted devDeps). Built artifacts go to `public/<name>/`**do not edit `public/<name>/` files; they are build artifacts**. Each `build` script is `tsc --noEmit && vite build` (type-check before bundle). Output is IIFE because Dioxus `[web.resource] script` injects bare `<script src>` without `type="module"` support. Registered globally in `Dioxus.toml` `script`/`style` arrays.
| Lib | Output dir | Exposes | Wiring | | Lib | Output dir | Exposes | Wiring |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `libs/tiptap-editor/` | `public/tiptap/` (`editor.js`/`.css`/`.map`) | `window.TiptapEditor` | wasm-bindgen via `src/tiptap_bridge.rs` — injects `Closure` callbacks (`onUpdate`/`onReady`/`onUploadEvent`/`onImageUpload`) into `TiptapEditor.create`, holds instance + closures in `EditorHandle` (Drop → `destroy()`). No `js_sys::eval`, no `window` globals, no polling. | | `libs/tiptap-editor/` | `public/tiptap/` (`editor.js`/`.css`/`.map`) | `window.TiptapEditor` | wasm-bindgen via `src/tiptap_bridge.rs` — injects `Closure` callbacks (`onUpdate`/`onReady`/`onUploadEvent`/`onImageUpload`) into `TiptapEditor.create`, holds instance + closures in `EditorHandle` (Drop → `destroy()`). No `js_sys::eval`, no `window` globals, no polling. |
| `libs/codemirror-editor/` | `public/codemirror/` (`editor.js`/`.map`, **no CSS**) | `window.CodeMirrorEditor` (object literal `{ create }`) + `window.EditorOptions` (class, survives TS erasure) | `src/codemirror_bridge.rs` mirrors tiptap — `get_module()` uses `Reflect::get` + `unchecked_into` (object literal, NOT a constructor extern). Themes are JS `Extension`s from `@catppuccin/codemirror` (Latte/Mocha), hot-swapped via `Compartment.reconfigure`. | | `libs/codemirror-editor/` | `public/codemirror/` (`editor.js`/`.map`, **no CSS**) | `window.CodeMirrorEditor` (object literal `{ create }`) + `window.EditorOptions` (class, survives TS erasure) | `src/codemirror_bridge.rs` mirrors tiptap — `get_module()` uses `Reflect::get` + `unchecked_into` (object literal, NOT a constructor extern). Themes are JS `Extension`s from `@catppuccin/codemirror` (Latte/Mocha), hot-swapped via `Compartment.reconfigure`. |
| `libs/lightbox/` | `public/lightbox/` (`lightbox.js`/`.css`/`.map`) | self-initializing IIFE | **Not** wasm-bindgen. `src/components/post/post_content.rs` sets `window.__lightboxSelectors` before load; IIFE tail reads it and self-initializes. Direct fallback call if already loaded. | | `libs/lightbox/` | `public/lightbox/` (`lightbox.js`/`.css`/`.map`) | self-initializing IIFE | **Not** wasm-bindgen. `src/components/post/post_content.rs` sets `window.__lightboxSelectors` before load; IIFE tail reads it and self-initializes. Direct fallback call if already loaded. |
| `libs/yggdrasil-core/` | `public/yggdrasil-core/` (`yggdrasil-core.js`/`.css`/`.map`) | `window.__initPostContent`, `window.__startThemeTransition` | Designated home for all new core JS — add here, not to `public/js/`. Rust calls entry points via `js_sys::eval("window.__xxx(...)")` guarded by `if (window.__xxx)`. Theme reveal uses View Transitions API (`startViewTransition` + `@keyframes tt-reveal` `clip-path` expand); falls back to instant switch when VT / `prefers-reduced-motion`. | | `libs/yggdrasil-core/` | `public/yggdrasil-core/` (`yggdrasil-core.js`/`.css`/`.map`) | `window.__initPostContent`, `window.__startThemeTransition` | Designated home for all new core JS — add here, not to `public/js/`. Rust calls entry points via `js_sys::Reflect::get` + `Function::apply` (no `js_sys::eval`), silently no-oping if undefined. Theme reveal uses View Transitions API (`startViewTransition` + `@keyframes tt-expand` `clip-path` expand); falls back to instant switch when VT / `prefers-reduced-motion`. |
Run a single lib's tests: `cd libs/<name> && pnpm test` (Vitest + happy-dom). Watch mode: `pnpm test:watch`. Run a single lib's tests: `cd libs && pnpm --filter @yggdrasil/<name> test` (Vitest + happy-dom). Watch mode: append `-- test:watch`.
## Database Management (`/admin/system`) ## Database Management (`/admin/system`)
@ -195,9 +197,9 @@ Admin area at `/admin/system` (menu "系统") with 5 tabs: 数据库状态 / 服
## Testing ## Testing
```bash ```bash
make test # cargo test (Rust) + pnpm test in all 4 libs make test # cargo test (Rust) + pnpm -r run test in all 4 libs
make clippy # cargo clippy --all-targets --all-features -- -D warnings make lint # Biome check (libs) + cargo clippy --all-targets --all-features -- -D warnings
dx check # Dioxus type-check (catches component/Router issues) dx check # Dioxus type-check (catches component/Router issues)
``` ```
**Must verify both targets**: This is a fullstack project — the server binary and the WASM frontend are two separate compilation targets with different features (`--all-features` enables `web`+`server`; the WASM bundle uses `--no-default-features --features web`). A change that compiles on one target can fail on the other. **`cargo build --all-features` alone is NOT sufficient** — it only builds the server binary. Before considering work done, compile the WASM target too: **Must verify both targets**: This is a fullstack project — the server binary and the WASM frontend are two separate compilation targets with different features (`--all-features` enables `web`+`server`; the WASM bundle uses `--no-default-features --features web`). A change that compiles on one target can fail on the other. **`cargo build --all-features` alone is NOT sufficient** — it only builds the server binary. Before considering work done, compile the WASM target too:
@ -230,7 +232,7 @@ Most Rust tests use `#[cfg(all(test, feature = "server"))]` — they only run wh
- `public/{tiptap,codemirror,lightbox,yggdrasil-core}/` — Vite build outputs - `public/{tiptap,codemirror,lightbox,yggdrasil-core}/` — Vite build outputs
- `public/doc/` — cargo doc output (copied by `make doc`) - `public/doc/` — cargo doc output (copied by `make doc`)
- `/dist`, `/.dioxus`, `/target`, `/static` - `/dist`, `/.dioxus`, `/target`, `/static`
- `node_modules` (inside each `libs/` subproject) - `libs/node_modules/` + `libs/*/node_modules/` — pnpm workspace store + symlinks
- `uploads/.cache/` — image processing disk cache - `uploads/.cache/` — image processing disk cache
- `backups/` — admin DB backup files - `backups/` — admin DB backup files
- `.freebsd-sysroot/` — FreeBSD cross-compile sysroot (machine-local) - `.freebsd-sysroot/` — FreeBSD cross-compile sysroot (machine-local)

View File

@ -24,7 +24,7 @@ RUN apt-get update \
git \ git \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Install Node.js 22 (required by Tailwind CSS v4 and the Tiptap editor build). # Install Node.js 22 (required by Tailwind CSS v4 and the JS libs build) + pnpm.
RUN mkdir -p /etc/apt/keyrings \ RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
@ -32,7 +32,9 @@ RUN mkdir -p /etc/apt/keyrings \
> /etc/apt/sources.list.d/nodesource.list \ > /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \ && apt-get update \
&& apt-get install -y --no-install-recommends nodejs \ && apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/* \
&& corepack enable \
&& corepack prepare pnpm@11.8.0 --activate
# Add the targets used by Dioxus fullstack builds. # Add the targets used by Dioxus fullstack builds.
RUN rustup target add wasm32-unknown-unknown x86_64-unknown-linux-musl RUN rustup target add wasm32-unknown-unknown x86_64-unknown-linux-musl
@ -48,16 +50,22 @@ RUN curl -fsSL "https://github.com/tailwindlabs/tailwindcss/releases/download/v$
WORKDIR /build WORKDIR /build
# Cache the Tiptap editor's node_modules by copying only package manifests first. # Cache the pnpm workspace node_modules by copying only package manifests first.
COPY libs/tiptap-editor/package*.json libs/tiptap-editor/ # Copying all 4 libs' manifests + the workspace root lets pnpm install everything
RUN cd libs/tiptap-editor && npm ci --include=dev # in one shot; this layer is reused as long as the manifests don't change.
COPY libs/package.json libs/pnpm-workspace.yaml libs/pnpm-lock.yaml libs/
COPY libs/tiptap-editor/package.json libs/tiptap-editor/
COPY libs/codemirror-editor/package.json libs/codemirror-editor/
COPY libs/lightbox/package.json libs/lightbox/
COPY libs/yggdrasil-core/package.json libs/yggdrasil-core/
RUN cd libs && pnpm install --frozen-lockfile
# Copy the rest of the source tree and build everything. # Copy the rest of the source tree and build everything.
COPY . . COPY . .
# Build the Tiptap editor, syntax-highlight CSS and Tailwind stylesheet. # Build all 4 JS libs, syntax-highlight CSS and Tailwind stylesheet.
# These steps produce the contents of the public/ directory. # These steps produce the contents of the public/ directory.
RUN make build-editor && make highlight-css && tailwindcss -i input.css -o public/style.css --minify RUN make build-libs && make highlight-css && tailwindcss -i input.css -o public/style.css --minify
# Build the client-side Dioxus WASM bundle. We use dx only for the client assets; # Build the client-side Dioxus WASM bundle. We use dx only for the client assets;
# dx's linker wrapper is incompatible with a raw static linker, so the server # dx's linker wrapper is incompatible with a raw static linker, so the server