-
Notifications
You must be signed in to change notification settings - Fork 5.5k
fix(devcontainer): make repository setup work #7875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3927926
1656e3e
2e533d2
6e68ff3
4a6d4e5
8a1718d
186f447
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,73 @@ | ||
| // Dev container for T3 Code. Mirrors CI (ubuntu-24.04, Node 24, Rust stable) | ||
| // and the canonical setup in docs/operations/development.md: global `vp`, `vp i`. | ||
| // Contributor doc: docs/internals/devcontainer.md | ||
| { | ||
| "name": "T3 Code Dev", | ||
| "image": "debian:bookworm", | ||
| "name": "T3 Code", | ||
| "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", | ||
| "features": { | ||
| "ghcr.io/devcontainers/features/git:1": {}, | ||
| "ghcr.io/devcontainers-extra/features/bun:1": { | ||
| "version": "1.3.11" | ||
| // nodeGypDependencies (default true) brings python3/make/g++, which Linux | ||
| // needs for node-pty's node-gyp fallback (its prebuilds are mac/win only). | ||
| "ghcr.io/devcontainers/features/node:2": { | ||
| "version": "24" | ||
| }, | ||
| "ghcr.io/devcontainers/features/node:1": { | ||
| "version": "24.13.1" | ||
| }, | ||
| "ghcr.io/devcontainers/features/python:1": { | ||
| "version": "3.10", | ||
| "installTools": false | ||
| } | ||
| // native/resource-monitor (edition 2024, needs stable >= 1.85). The server | ||
| // degrades gracefully without the binary, but CI checks cargo fmt + test. | ||
| "ghcr.io/devcontainers/features/rust:1": {}, | ||
| "ghcr.io/devcontainers/features/github-cli:1": {} | ||
| }, | ||
| "hostRequirements": { | ||
| "cpus": 4, | ||
| "memory": "8gb", | ||
| "storage": "32gb" | ||
| }, | ||
| "containerEnv": { | ||
| // Keep all runtime state inside the (gitignored) workspace .t3, matching | ||
| // the worktree default. An explicit --home-dir still wins. | ||
| "T3CODE_HOME": "${containerWorkspaceFolder}/.t3" | ||
| }, | ||
| "overrideFeatureInstallOrder": [ | ||
| "ghcr.io/devcontainers/features/git", | ||
| "ghcr.io/devcontainers-extra/features/bun" | ||
| "mounts": [ | ||
| // vp keeps the pnpm content-addressable store and metadata cache under | ||
| // ~/.cache/pnpm (verified: the v11 store dir lives there); mounting a | ||
| // volume there lets installs survive container rebuilds. | ||
| "source=t3code-pnpm-store,target=/home/vscode/.cache/pnpm,type=volume", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 3680 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- devcontainer ---'
cat -n .devcontainer/devcontainer.json
printf '%s\n' '--- repository pnpm configuration ---'
find . -maxdepth 3 -type f \( -name 'pnpm-workspace.yaml' -o -name '.npmrc' -o -name 'package.json' -o -name 'pnpm-lock.yaml' \) -print
for f in .npmrc pnpm-workspace.yaml package.json .devcontainer/devcontainer.json docs/internals/devcontainer.md; do
if [ -f "$f" ]; then
printf '\n--- %s ---\n' "$f"
cat -n "$f"
fi
done
printf '%s\n' '--- pnpm/store/cache references ---'
rg -n --hidden -g '!node_modules' -g '!dist' -g '!build' 'store-dir|store path|cacheDir|PNPM_HOME|pnpm.*cache|pnpm.*store|VP_CACHE_DIR|Vite\+' .Repository: pingdotgg/t3code Length of output: 32441 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- devcontainer lifecycle scripts ---'
for f in .devcontainer/on-create.sh .devcontainer/update-content.sh; do
printf '\n--- %s ---\n' "$f"
cat -n "$f"
done
printf '%s\n' '--- package manager and Vite+ setup references ---'
rg -n --hidden -g '!node_modules' -g '!dist' -g '!build' 'packageManager|pnpm@|store-dir|storeDir|PNPM_CONFIG|pnpm_config|VP_CACHE_DIR|vite-plus' .devcontainer docs package.json pnpm-workspace.yaml .github/workflowsRepository: pingdotgg/t3code Length of output: 5955 🌐 Web query:
💡 Result: On Linux, the default locations for the pnpm store and cache directories in pnpm 11.10.0 are determined by environment variables adhering to the XDG Base Directory Specification [1][2]. Store Directory (storeDir) The default location for the pnpm store is [1]: - If $PNPM_HOME is set: $PNPM_HOME/store - If $XDG_DATA_HOME is set: $XDG_DATA_HOME/pnpm/store - Default: ~/.local/share/pnpm/store [1] Cache Directory (cacheDir) The default location for the pnpm cache directory is [2]: - If $XDG_CACHE_HOME is set: $XDG_CACHE_HOME/pnpm - Default: ~/.cache/pnpm [2] You can verify the active store path for your current configuration by running the command: pnpm store path [3] You can verify the active cache path by running the command: pnpm cache path [4] Note that pnpm requires the store to be on the same disk/filesystem as the project installation to enable hard linking; if the store and project are on different filesystems, pnpm will copy files instead of linking them [1][5]. If the store path is not explicitly configured, pnpm will create a store on each drive or filesystem where installations occur [1][5]. Citations:
🌐 Web query:
💡 Result:
Citations:
🌐 Web query:
💡 Result: In pnpm, the store-dir configuration option defines the directory where the global content-addressable store is located. This store is used to share a single copy of each package between different projects on your machine, enabling efficient installation and disk space savings. You can configure the store directory in a few ways: 1. CLI Argument: You can pass the path directly when running commands: pnpm install --store-dir /path/to/store 2. Environment Variable: You can set the PNPM_HOME or specifically influence store resolution via environment variables depending on your environment. 3. Configuration File: It is typically defined in your.npmrc file: store-dir=/path/to/store Note that pnpm uses this store to maintain a global virtual store layout, which facilitates shared package access. If you are using tool-specific wrappers or managers like Vite+, note that these tools may have their own management logic for environments and paths, often prioritizing specific project-local configurations or managed directories (such as ~/.vite-plus) to handle package manager versions and runtimes. For projects using pnpm workspaces, you may also see configuration related to pnpm stored in pnpm-workspace.yaml, though core store-dir settings remain a function of the pnpm configuration. Citations:
Mount pnpm’s store, not only its cache.
🤖 Prompt for AI Agents |
||
| // Root node_modules holds the whole pnpm virtual store (.pnpm), so one | ||
| // volume keeps the heavy tree off the slow macOS/Windows bind mount. | ||
| // Scoped by devcontainerId so parallel checkouts do not share it. | ||
| // Codespaces note: prebuild snapshots exclude volumes; drop these mounts | ||
| // if prebuilt codespaces become the primary workflow. | ||
| "source=t3code-node-modules-${devcontainerId},target=${containerWorkspaceFolder}/node_modules,type=volume" | ||
| ], | ||
| "postCreateCommand": { | ||
| "bun-install": "bun install --backend=copyfile --frozen-lockfile" | ||
| "onCreateCommand": "bash .devcontainer/on-create.sh", | ||
| "updateContentCommand": "bash .devcontainer/update-content.sh", | ||
| "forwardPorts": [5733, 13773], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 2649 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- devcontainer.json ---'
cat -n .devcontainer/devcontainer.json
printf '%s\n' '--- development documentation ---'
fd -i 'development.md' . | while read -r f; do
printf '%s\n' "--- $f ---"
cat -n "$f"
done
printf '%s\n' '--- port and forwarding references ---'
rg -n -C 3 'T3CODE_PORT_OFFSET|T3CODE_DEV_INSTANCE|forwardPorts|5733|13773|port.?offset|dev.?instance' . \
-g '!node_modules' -g '!dist' -g '!build'Repository: pingdotgg/t3code Length of output: 50374 🏁 Script executed: #!/bin/bash
set -eu
cat -n .devcontainer/devcontainer.json
fd -i 'development.md' . -x sh -c 'echo "--- $1"; cat -n "$1"' sh
rg -n -C 3 'T3CODE_PORT_OFFSET|T3CODE_DEV_INSTANCE|forwardPorts|5733|13773' . -g '!node_modules' -g '!dist' -g '!build'Repository: pingdotgg/t3code Length of output: 50374 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- dev-runner port resolution ---'
sed -n '20,45p;220,285p;560,620p' scripts/dev-runner.ts
printf '%s\n' '--- devcontainer documentation ---'
cat -n docs/internals/devcontainer.mdRepository: pingdotgg/t3code Length of output: 8965 🌐 Web query:
💡 Result: In devcontainer.json, the forwardPorts property is used to automatically forward ports from inside the container to the local machine [1][2]. Regarding port assignment behavior: When using forwardPorts, VS Code (the primary supporting tool) will first attempt to allocate the same port number on your local machine that is being used inside the container [3]. If that local port is already in use or otherwise unavailable, it will automatically fall back to assigning a different, random available port [3]. This provides a flexible "dynamic" forwarding mechanism while prioritizing the desired port number [3]. Key details regarding forwardPorts: - Format: It accepts an array of integers (e.g.,) or strings in the format "host:port" (e.g., ["db:5432"]) [1][4]. - Purpose: It is specifically designed for ports that need to be accessed from the local host but are not explicitly "published" via Docker Compose or the appPort property [1][2]. - PortsAttributes: You can further control the behavior of these forwarded ports (such as setting labels or defining what happens when a port is auto-forwarded) by using the portsAttributes property [4][5][6]. - Forwarding vs. Publishing: Unlike "publishing" (which makes a port available to the local network), "forwarding" makes the port appear as localhost to your application, which is typically what is required for development workflows [1][2]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
sed -n '20,35p;220,285p;560,620p' scripts/dev-runner.tsRepository: pingdotgg/t3code Length of output: 4864 Document forwarding for dynamically selected ports.
🤖 Prompt for AI Agents |
||
| "portsAttributes": { | ||
| "5733": { | ||
| "label": "t3 web (open via the pairing URL, not the bare origin)", | ||
| "onAutoForward": "notify" | ||
| }, | ||
| "13773": { | ||
| "label": "t3 server", | ||
| "onAutoForward": "silent" | ||
| } | ||
| }, | ||
| "customizations": { | ||
| "codespaces": { | ||
| "openFiles": ["docs/internals/devcontainer.md"] | ||
| }, | ||
| "vscode": { | ||
| "extensions": ["oxc.oxc-vscode"] | ||
| "extensions": ["oxc.oxc-vscode", "rust-lang.rust-analyzer"], | ||
| "settings": { | ||
| // .repos is a large vendored read-only reference tree; watching it | ||
| // burns CPU and file handles. | ||
| "files.watcherExclude": { | ||
| "**/.repos/**": true, | ||
| "**/.t3/**": true | ||
| }, | ||
| "search.exclude": { | ||
| "**/.repos": true | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "remoteUser": "vscode" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| #!/usr/bin/env bash | ||
| # One-time container setup, baked into prebuilds. Content-dependent work | ||
| # (dependency install, Chromium) lives in update-content.sh. | ||
| set -euo pipefail | ||
|
|
||
| # The Vite+ CLI is the repo task runner (vp i, vp run dev, vp test run). | ||
| # Download to a file first: a curl failure inside $( ) would yield an empty | ||
| # script and a false success. VP_NODE_MANAGER=no skips the installer's node | ||
| # shims; Node comes from the devcontainer feature. The VP_*_DIR group (set | ||
| # together, absolute) pins the install location instead of the installer's | ||
| # XDG platform defaults, which moved the binary from ~/.vite-plus/bin to | ||
| # ~/.local/share/vite-plus/bin between releases and would break the check | ||
| # below either way. | ||
| export VP_BIN_DIR="$HOME/.local/share/vite-plus/bin" | ||
| export VP_DATA_DIR="$HOME/.local/share/vite-plus" | ||
| export VP_CACHE_DIR="$HOME/.cache/vite-plus" | ||
| installer=$(mktemp) | ||
| curl -fsSL https://vite.plus -o "$installer" | ||
| VP_NODE_MANAGER=no bash "$installer" | ||
| rm -f "$installer" | ||
|
|
||
| # Non-login lifecycle shells never source the profile the installer edits, | ||
| # so expose vp on the default PATH. test -x keeps a bad install loud. | ||
| test -x "$VP_BIN_DIR/vp" | ||
| sudo ln -sf "$VP_BIN_DIR/vp" /usr/local/bin/vp | ||
|
|
||
| # First-run terminal notice, rendered by the devcontainers base image. | ||
| sudo mkdir -p /usr/local/etc/vscode-dev-containers | ||
| sudo tee /usr/local/etc/vscode-dev-containers/first-run-notice.txt >/dev/null <<'EOF' | ||
| T3 Code devcontainer | ||
|
|
||
| vp run dev start server + web, then open the pairing URL it | ||
| prints (the bare forwarded port will not authenticate) | ||
| cp .env.example .env optional: enable T3 Connect cloud features | ||
| (public identifiers, not secrets) | ||
|
|
||
| Details: docs/internals/devcontainer.md | ||
| EOF |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #!/usr/bin/env bash | ||
| # Runs at creation and on every prebuild content refresh, so codespaces start | ||
| # with deps installed and caches warm. Everything here is idempotent. | ||
| set -euo pipefail | ||
|
|
||
| # Volume mounts (pnpm store, node_modules) and the directories docker creates | ||
| # for them arrive root-owned; hand them to the dev user before installing. | ||
| for dir in "$HOME/.cache" "$HOME/.cache/pnpm" node_modules; do | ||
| if [ -d "$dir" ] && [ "$(stat -c %U "$dir")" != "$(id -un)" ]; then | ||
| sudo chown "$(id -un):$(id -gn)" "$dir" | ||
| fi | ||
| done | ||
|
|
||
| CI=true vp i | ||
| # Repairs electron's path.txt and exec bits after install, same as CI. | ||
| vp run --filter @t3tools/desktop ensure:electron | ||
| # Pre-warms Vite's dep optimizer (cache is keyed on the absolute path, which | ||
| # is stable inside the container). | ||
| node apps/web/scripts/warm-dep-cache.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Dev container | ||
|
|
||
| > For maintainers. Using T3 Code? See [docs/user](../user/). | ||
|
|
||
| `.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. | ||
|
|
||
| ## What works in the container | ||
|
|
||
| - 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`. | ||
| - 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.) | ||
|
|
||
| ## State and safety | ||
|
|
||
| `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. | ||
|
|
||
| ## Caching | ||
|
|
||
| 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. | ||
|
|
||
| ## Out of scope | ||
|
|
||
| - 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. | ||
| - Mobile native builds are host-only (Xcode for iOS, Android SDK for Android). Typecheck, lint, and the mobile static checks run fine. | ||
| - `vp run dev --share` needs a tailscale binary and a tailnet; not provisioned here. | ||
|
|
||
| ## Prebuilds | ||
|
|
||
| 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: | ||
|
|
||
| ```bash | ||
| devcontainer build --workspace-folder . --push true --image-name <registry>/t3code-devcontainer:latest | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🤖 get_repo_knowledge executed:
get_repo_knowledge pingdotgg/t3code /tmp/coderabbit-repo-knowledge/pingdotgg-t3code-ca3faa49Length of output: 1542
🏁 Script executed:
Repository: pingdotgg/t3code
Length of output: 21068
🌐 Web query:
site:viteplus.dev/guide/installer-env-vars VP_BIN_DIR VP_DATA_DIR VP_CACHE_DIR runtime integrations provide each process💡 Result:
VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR are environment variables used by Vite+ to define internal absolute directories for split installations [1]. These variables provide configuration for the installed Vite+ CLI and are used by integrations that must pin a specific directory structure [1]. To function correctly, these variables must be set as a complete group; the installer will reject the configuration if the set is incomplete [1]. If a user sets the VP_HOME environment variable, or if Vite+ reuses an existing ~/.vite-plus installation, these variables are ignored [1]. Crucially, because the generated environment files for Vite+ do not export these variables, any integration that relies on them must explicitly provide the complete group (VP_BIN_DIR, VP_DATA_DIR, and VP_CACHE_DIR) to each Vite+ process at runtime [1]. If these variables are not provided by the integration, Vite+ defaults to standard XDG or platform-specific directory conventions [1].
Citations:
Persist the Vite+ directory variables in
containerEnv..devcontainer/on-create.shexports these variables only for the installer process..devcontainer/update-content.shruns separately and invokesvpwithout them. Vite+ then falls back to its default directories, which can make the split installation unavailable."containerEnv": { - "T3CODE_HOME": "${containerWorkspaceFolder}/.t3" + "T3CODE_HOME": "${containerWorkspaceFolder}/.t3", + "VP_BIN_DIR": "/home/vscode/.local/share/vite-plus/bin", + "VP_DATA_DIR": "/home/vscode/.local/share/vite-plus", + "VP_CACHE_DIR": "/home/vscode/.cache/vite-plus" },📝 Committable suggestion
🤖 Prompt for AI Agents