| 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 | // 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", | |
| 3 | "image": "debian:bookworm", | |
| 5 | "name": "T3 Code", | |
| 6 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", | |
| 4 | 7 | "features": { |
| 5 | "ghcr.io/devcontainers/features/git:1": {}, | |
| 6 | "ghcr.io/devcontainers-extra/features/bun:1": { | |
| 7 | "version": "1.3.11" | |
| 8 | // nodeGypDependencies (default true) brings python3/make/g++, which Linux | |
| 9 | // needs for node-pty's node-gyp fallback (its prebuilds are mac/win only). | |
| 10 | "ghcr.io/devcontainers/features/node:2": { | |
| 11 | "version": "24" | |
| 8 | 12 | }, |
| 9 | "ghcr.io/devcontainers/features/node:1": { | |
| 10 | "version": "24.13.1" | |
| 11 | }, | |
| 12 | "ghcr.io/devcontainers/features/python:1": { | |
| 13 | "version": "3.10", | |
| 14 | "installTools": false | |
| 15 | } | |
| 13 | // native/resource-monitor (edition 2024, needs stable >= 1.85). The server | |
| 14 | // degrades gracefully without the binary, but CI checks cargo fmt + test. | |
| 15 | "ghcr.io/devcontainers/features/rust:1": {}, | |
| 16 | "ghcr.io/devcontainers/features/github-cli:1": {} | |
| 17 | }, | |
| 18 | "hostRequirements": { | |
| 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": [ | |
| 18 | "ghcr.io/devcontainers/features/git", | |
| 19 | "ghcr.io/devcontainers-extra/features/bun" | |
| 28 | "mounts": [ | |
| 29 | // vp keeps the pnpm content-addressable store and metadata cache under | |
| 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": { | |
| 22 | "bun-install": "bun install --backend=copyfile --frozen-lockfile" | |
| 40 | "onCreateCommand": "bash .devcontainer/on-create.sh", | |
| 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 | 53 | "customizations": { |
| 54 | "codespaces": { | |
| 55 | "openFiles": ["docs/internals/devcontainer.md"] | |
| 56 | }, | |
| 25 | 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 | 13 | Open the one-time pairing URL printed by the dev runner. The bare origin does not authenticate |
| 14 | 14 | a new browser. |
| 15 | 15 | |
| 16 | Prefer a container? See [Dev container](../internals/devcontainer.md) for VS Code and Codespaces setup. | |
| 17 | ||
| 16 | 18 | ## Choosing a dev process |
| 17 | 19 | |
| 18 | 20 | Use `vp run dev` for server and web, or `vp run dev:desktop` for the Electron client. |