| author | |
| committer | |
| log | 7376536b23e8db0d1b09054bbc0b919cf0c85405 |
| tree | b95e7905e5320e85b140cdf7cbea6de347ea2f38 |
| parent | 5b68b2c8e1e971e4ee6956157a1698646ef52a55 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>5 files changed, 154 insertions(+), 19 deletions(-)
.devcontainer/devcontainer.json+63-19| ... | @@ -1,29 +1,73 @@ | ... | @@ -1,29 +1,73 @@ |
| 1 | // Dev container for T3 Code. Mirrors CI (ubuntu-24.04, Node 24, Rust stable) | ||
| 2 | // and the canonical setup in docs/operations/development.md: global `vp`, `vp i`. | ||
| 3 | // Contributor doc: docs/internals/devcontainer.md | ||
| 1 | { | 4 | { |
| 2 | "name": "T3 Code Dev", | 5 | "name": "T3 Code", |
| 3 | "image": "debian:bookworm", | 6 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", |
| 4 | "features": { | 7 | "features": { |
| 5 | "ghcr.io/devcontainers/features/git:1": {}, | 8 | // nodeGypDependencies (default true) brings python3/make/g++, which Linux |
| 6 | "ghcr.io/devcontainers-extra/features/bun:1": { | 9 | // needs for node-pty's node-gyp fallback (its prebuilds are mac/win only). |
| 7 | "version": "1.3.11" | 10 | "ghcr.io/devcontainers/features/node:2": { |
| 11 | "version": "24" | ||
| 8 | }, | 12 | }, |
| 9 | "ghcr.io/devcontainers/features/node:1": { | 13 | // native/resource-monitor (edition 2024, needs stable >= 1.85). The server |
| 10 | "version": "24.13.1" | 14 | // degrades gracefully without the binary, but CI checks cargo fmt + test. |
| 11 | }, | 15 | "ghcr.io/devcontainers/features/rust:1": {}, |
| 12 | "ghcr.io/devcontainers/features/python:1": { | 16 | "ghcr.io/devcontainers/features/github-cli:1": {} |
| 13 | "version": "3.10", | 17 | }, |
| 14 | "installTools": false | 18 | "hostRequirements": { |
| 15 | } | 19 | "cpus": 4, |
| 20 | "memory": "8gb", | ||
| 21 | "storage": "32gb" | ||
| 22 | }, | ||
| 23 | "containerEnv": { | ||
| 24 | // Keep all runtime state inside the (gitignored) workspace .t3, matching | ||
| 25 | // the worktree default. An explicit --home-dir still wins. | ||
| 26 | "T3CODE_HOME": "${containerWorkspaceFolder}/.t3" | ||
| 16 | }, | 27 | }, |
| 17 | "overrideFeatureInstallOrder": [ | 28 | "mounts": [ |
| 18 | "ghcr.io/devcontainers/features/git", | 29 | // vp keeps the pnpm content-addressable store and metadata cache under |
| 19 | "ghcr.io/devcontainers-extra/features/bun" | 30 | // ~/.cache/pnpm (verified: the v11 store dir lives there); mounting a |
| 31 | // volume there lets installs survive container rebuilds. | ||
| 32 | "source=t3code-pnpm-store,target=/home/vscode/.cache/pnpm,type=volume", | ||
| 33 | // Root node_modules holds the whole pnpm virtual store (.pnpm), so one | ||
| 34 | // volume keeps the heavy tree off the slow macOS/Windows bind mount. | ||
| 35 | // Scoped by devcontainerId so parallel checkouts do not share it. | ||
| 36 | // Codespaces note: prebuild snapshots exclude volumes; drop these mounts | ||
| 37 | // if prebuilt codespaces become the primary workflow. | ||
| 38 | "source=t3code-node-modules-${devcontainerId},target=${containerWorkspaceFolder}/node_modules,type=volume" | ||
| 20 | ], | 39 | ], |
| 21 | "postCreateCommand": { | 40 | "onCreateCommand": "bash .devcontainer/on-create.sh", |
| 22 | "bun-install": "bun install --backend=copyfile --frozen-lockfile" | 41 | "updateContentCommand": "bash .devcontainer/update-content.sh", |
| 42 | "forwardPorts": [5733, 13773], | ||
| 43 | "portsAttributes": { | ||
| 44 | "5733": { | ||
| 45 | "label": "t3 web (open via the pairing URL, not the bare origin)", | ||
| 46 | "onAutoForward": "notify" | ||
| 47 | }, | ||
| 48 | "13773": { | ||
| 49 | "label": "t3 server", | ||
| 50 | "onAutoForward": "silent" | ||
| 51 | } | ||
| 23 | }, | 52 | }, |
| 24 | "customizations": { | 53 | "customizations": { |
| 54 | "codespaces": { | ||
| 55 | "openFiles": ["docs/internals/devcontainer.md"] | ||
| 56 | }, | ||
| 25 | "vscode": { | 57 | "vscode": { |
| 26 | "extensions": ["oxc.oxc-vscode"] | 58 | "extensions": ["oxc.oxc-vscode", "rust-lang.rust-analyzer"], |
| 59 | "settings": { | ||
| 60 | // .repos is a large vendored read-only reference tree; watching it | ||
| 61 | // burns CPU and file handles. | ||
| 62 | "files.watcherExclude": { | ||
| 63 | "**/.repos/**": true, | ||
| 64 | "**/.t3/**": true | ||
| 65 | }, | ||
| 66 | "search.exclude": { | ||
| 67 | "**/.repos": true | ||
| 68 | } | ||
| 69 | } | ||
| 27 | } | 70 | } |
| 28 | } | 71 | }, |
| 72 | "remoteUser": "vscode" | ||
| 29 | } | 73 | } |
.devcontainer/on-create.sh created+38| ... | @@ -0,0 +1,38 @@ | ||
| 1 | #!/usr/bin/env bash | ||
| 2 | # One-time container setup, baked into prebuilds. Content-dependent work | ||
| 3 | # (dependency install, Chromium) lives in update-content.sh. | ||
| 4 | set -euo pipefail | ||
| 5 | |||
| 6 | # The Vite+ CLI is the repo task runner (vp i, vp run dev, vp test run). | ||
| 7 | # Download to a file first: a curl failure inside $( ) would yield an empty | ||
| 8 | # script and a false success. VP_NODE_MANAGER=no skips the installer's node | ||
| 9 | # shims; Node comes from the devcontainer feature. The VP_*_DIR group (set | ||
| 10 | # together, absolute) pins the install location instead of the installer's | ||
| 11 | # XDG platform defaults, which moved the binary from ~/.vite-plus/bin to | ||
| 12 | # ~/.local/share/vite-plus/bin between releases and would break the check | ||
| 13 | # below either way. | ||
| 14 | export VP_BIN_DIR="$HOME/.local/share/vite-plus/bin" | ||
| 15 | export VP_DATA_DIR="$HOME/.local/share/vite-plus" | ||
| 16 | export VP_CACHE_DIR="$HOME/.cache/vite-plus" | ||
| 17 | installer=$(mktemp) | ||
| 18 | curl -fsSL https://vite.plus -o "$installer" | ||
| 19 | VP_NODE_MANAGER=no bash "$installer" | ||
| 20 | rm -f "$installer" | ||
| 21 | |||
| 22 | # Non-login lifecycle shells never source the profile the installer edits, | ||
| 23 | # so expose vp on the default PATH. test -x keeps a bad install loud. | ||
| 24 | test -x "$VP_BIN_DIR/vp" | ||
| 25 | sudo ln -sf "$VP_BIN_DIR/vp" /usr/local/bin/vp | ||
| 26 | |||
| 27 | # First-run terminal notice, rendered by the devcontainers base image. | ||
| 28 | sudo mkdir -p /usr/local/etc/vscode-dev-containers | ||
| 29 | sudo tee /usr/local/etc/vscode-dev-containers/first-run-notice.txt >/dev/null <<'EOF' | ||
| 30 | T3 Code devcontainer | ||
| 31 | |||
| 32 | vp run dev start server + web, then open the pairing URL it | ||
| 33 | prints (the bare forwarded port will not authenticate) | ||
| 34 | cp .env.example .env optional: enable T3 Connect cloud features | ||
| 35 | (public identifiers, not secrets) | ||
| 36 | |||
| 37 | Details: docs/internals/devcontainer.md | ||
| 38 | EOF | ||
.devcontainer/update-content.sh created+19| ... | @@ -0,0 +1,19 @@ | ||
| 1 | #!/usr/bin/env bash | ||
| 2 | # Runs at creation and on every prebuild content refresh, so codespaces start | ||
| 3 | # with deps installed and caches warm. Everything here is idempotent. | ||
| 4 | set -euo pipefail | ||
| 5 | |||
| 6 | # Volume mounts (pnpm store, node_modules) and the directories docker creates | ||
| 7 | # for them arrive root-owned; hand them to the dev user before installing. | ||
| 8 | for dir in "$HOME/.cache" "$HOME/.cache/pnpm" node_modules; do | ||
| 9 | if [ -d "$dir" ] && [ "$(stat -c %U "$dir")" != "$(id -un)" ]; then | ||
| 10 | sudo chown "$(id -un):$(id -gn)" "$dir" | ||
| 11 | fi | ||
| 12 | done | ||
| 13 | |||
| 14 | CI=true vp i | ||
| 15 | # Repairs electron's path.txt and exec bits after install, same as CI. | ||
| 16 | vp run --filter @t3tools/desktop ensure:electron | ||
| 17 | # Pre-warms Vite's dep optimizer (cache is keyed on the absolute path, which | ||
| 18 | # is stable inside the container). | ||
| 19 | node apps/web/scripts/warm-dep-cache.ts | ||
docs/internals/devcontainer.md created+32| ... | @@ -0,0 +1,32 @@ | ||
| 1 | # Dev container | ||
| 2 | |||
| 3 | > For maintainers. Using T3 Code? See [docs/user](../user/). | ||
| 4 | |||
| 5 | `.devcontainer/` gives you a ready-to-code Linux environment matching CI: Ubuntu 24.04, Node 24, pnpm, Rust stable, the global `vp` CLI, and the GitHub CLI. Open the repo in VS Code and "Reopen in Container", or create a GitHub Codespace. Dependency install (`vp i`), the Electron exec-bit repair, and the Vite dep-cache warmup all run automatically before you attach. | ||
| 6 | |||
| 7 | ## What works in the container | ||
| 8 | |||
| 9 | - The full dev stack: `vp run dev`, then open the pairing URL it prints through the forwarded web port (5733). The bare origin is useless without the pairing token. In VS Code the forwarded port is a true localhost, so the printed URL works as-is; in browser Codespaces the forwarded origin differs, and if the server rejects it, pass the forwarded origin via `T3CODE_DEV_ALLOWED_ORIGINS`. | ||
| 10 | - Everything the Linux CI jobs run: focused `vp test run <files>`, `vp lint <files>`, package typechecks, `vp run build:desktop`, and the resource-monitor cargo build and tests. (`vpr` is not on PATH here; the curl installer only shims `vp`. Use `vp run <script>` or `node_modules/.bin/vpr` after install.) | ||
| 11 | |||
| 12 | ## State and safety | ||
| 13 | |||
| 14 | `T3CODE_HOME` points at the workspace's gitignored `.t3`, so all runtime state stays inside the container workspace, mirroring the worktree default. There is no live install to damage inside a container, but the test-data rule from AGENTS.md still holds: copy data in, never point at shared state. | ||
| 15 | |||
| 16 | ## Caching | ||
| 17 | |||
| 18 | Two named volumes keep rebuilds fast and installs off the slow macOS/Windows bind mount: the pnpm store (shared across checkouts, mounted where `vp i` keeps it) and root `node_modules` (per-container, which covers the whole `.pnpm` virtual store since workspace packages just symlink into it). Deleting a container and recreating it reuses both, so a rebuild's `vp i` is seconds, not minutes. The host sees an empty `node_modules`; run host-side tooling inside the container. | ||
| 19 | |||
| 20 | ## Out of scope | ||
| 21 | |||
| 22 | - Windowed Electron development is host-only. Building and verifying the desktop bundle works fine in the container (CI does exactly that, headless); launching the app needs a display. | ||
| 23 | - Mobile native builds are host-only (Xcode for iOS, Android SDK for Android). Typecheck, lint, and the mobile static checks run fine. | ||
| 24 | - `vp run dev --share` needs a tailscale binary and a tailnet; not provisioned here. | ||
| 25 | |||
| 26 | ## Prebuilds | ||
| 27 | |||
| 28 | Container creation from scratch does a full `vp i` plus toolchain installs, which is worth prebuilding. Codespaces prebuilds are configured in repo settings, not files, and pick this config up as-is: the heavy steps live in `onCreateCommand` and `updateContentCommand`, which prebuilds bake in. Restrict prebuilds to one region and one retained version; storage bills per region per version. Note that prebuild snapshots exclude the caching volumes, so a prebuild-first workflow may prefer dropping the mounts. Outside Codespaces, the Dev Container CLI can push a prebuilt image: | ||
| 29 | |||
| 30 | ```bash | ||
| 31 | devcontainer build --workspace-folder . --push true --image-name <registry>/t3code-devcontainer:latest | ||
| 32 | ``` | ||
docs/operations/development.md+2| ... | @@ -13,6 +13,8 @@ vp run dev | ... | @@ -13,6 +13,8 @@ vp run dev |
| 13 | Open the one-time pairing URL printed by the dev runner. The bare origin does not authenticate | 13 | Open the one-time pairing URL printed by the dev runner. The bare origin does not authenticate |
| 14 | a new browser. | 14 | a new browser. |
| 15 | 15 | ||
| 16 | Prefer a container? See [Dev container](../internals/devcontainer.md) for VS Code and Codespaces setup. | ||
| 17 | |||
| 16 | ## Choosing a dev process | 18 | ## Choosing a dev process |
| 17 | 19 | ||
| 18 | Use `vp run dev` for server and web, or `vp run dev:desktop` for the Electron client. | 20 | Use `vp run dev` for server and web, or `vp run dev:desktop` for the Electron client. |