diff --git a/.devcontainer/devcontainer-local.json b/.devcontainer/devcontainer-local.json new file mode 100644 index 00000000..dc746247 --- /dev/null +++ b/.devcontainer/devcontainer-local.json @@ -0,0 +1,38 @@ +{ + "name": "Copilot Token Tracker Extension (Local with Mounts)", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/powershell:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-vscode.extension-test-runner", + "amodio.tsc-problem-matcher", + "github.copilot", + "github.copilot-chat" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.enable": true, + "typescript.tsdk": "node_modules/typescript/lib", + "chat.tools.autoApprove": true + } + } + }, + "initializeCommand": "powershell -ExecutionPolicy Bypass -File .devcontainer/init-mounts.ps1", + "mounts": [ + "source=${localEnv:APPDATA}/Code/User,target=/home/node/.config/Code/User,type=bind,readonly=true", + "source=${localEnv:APPDATA}/Code - Insiders/User,target=/home/node/.config/Code - Insiders/User,type=bind,readonly=true", + "source=github-copilot-token-usage-nodemodules,target=/workspaces/github-copilot-token-usage/node_modules,type=volume" + ], + "containerEnv": { + "NODE_OPTIONS": "--max-old-space-size=4096" + }, + "updateContentCommand": "cd vscode-extension && npm ci", + "remoteUser": "node" +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 033568a7..e1c4de5b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,8 +2,7 @@ "name": "Copilot Token Tracker Extension", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", "features": { - "ghcr.io/devcontainers/features/git:1": {}, - "ghcr.io/devcontainers/features/powershell:1": {} + "ghcr.io/devcontainers/features/git:1": {} }, "customizations": { "vscode": { @@ -24,15 +23,9 @@ } } }, - "initializeCommand": "powershell -ExecutionPolicy Bypass -File .devcontainer/init-mounts.ps1", - "mounts": [ - "source=${localEnv:APPDATA}/Code/User,target=/home/node/.config/Code/User,type=bind,readonly=true", - "source=${localEnv:APPDATA}/Code - Insiders/User,target=/home/node/.config/Code - Insiders/User,type=bind,readonly=true", - "source=github-copilot-token-usage-nodemodules,target=/workspaces/github-copilot-token-usage/node_modules,type=volume" - ], "containerEnv": { "NODE_OPTIONS": "--max-old-space-size=4096" }, - "postCreateCommand": "npm ci", + "updateContentCommand": "cd vscode-extension && npm ci", "remoteUser": "node" } diff --git a/.github/workflows/test-devcontainer.yml b/.github/workflows/test-devcontainer.yml new file mode 100644 index 00000000..1a13daad --- /dev/null +++ b/.github/workflows/test-devcontainer.yml @@ -0,0 +1,52 @@ +name: Test DevContainer + +on: + push: + branches: [ main, develop, copilot/fix-codespace-boot-issue ] + paths: + - '.devcontainer/**' + - '.github/workflows/test-devcontainer.yml' + pull_request: + branches: [ main, develop ] + paths: + - '.devcontainer/**' + - '.github/workflows/test-devcontainer.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + test-devcontainer: + runs-on: ubuntu-latest + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build and test devcontainer + uses: devcontainers/ci@a56d055efecd725e8cfe370543b6071b79989cc8 # v0.3 + with: + imageName: ghcr.io/${{ github.repository }}/devcontainer + cacheFrom: ghcr.io/${{ github.repository }}/devcontainer + push: never + runCmd: | + echo "=== DevContainer Environment Test ===" + echo "Node version: $(node --version)" + echo "NPM version: $(npm --version)" + echo "Working directory: $(pwd)" + echo "User: $(whoami)" + echo "" + echo "=== Installing dependencies ===" + cd /workspaces/github-copilot-token-usage/vscode-extension + npm ci + echo "" + echo "=== Running build ===" + npm run compile + echo "" + echo "=== DevContainer test successful ===" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a01cca9e..2b064a27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,7 @@ Thank you for your interest in contributing to the Copilot Token Tracker extensi - [Development Environment Setup](#development-environment-setup) - [Using the DevContainer (Recommended)](#using-the-devcontainer-recommended) - [Why Use a DevContainer for AI-Assisted Development?](#why-use-a-devcontainer-for-ai-assisted-development) +- [DevContainer Configuration Notes](#devcontainer-configuration-notes) - [Manual Local Setup](#manual-local-setup) - [Development Workflow](#development-workflow) - [Available Scripts](#available-scripts) @@ -115,7 +116,7 @@ The devcontainer allows you to confidently let AI assistants: - **Zero Configuration:** AI can start working immediately without environment setup - **Pre-installed Tools:** All required dependencies are ready to go - **Known State:** AI agents can make more accurate suggestions knowing the exact environment -- **Automatic Setup:** The `postCreateCommand` ensures dependencies are always up-to-date +- **Automatic Setup:** The `updateContentCommand` ensures dependencies are installed after content updates ### 💡 Real-World Scenario @@ -134,6 +135,43 @@ Without a devcontainer, you'd need to: - Risk system-level changes - Potentially need to uninstall packages or revert changes +### DevContainer Configuration Notes + +The repository provides two devcontainer configurations: + +1. **`.devcontainer/devcontainer.json`** (default) - Optimized for **GitHub Codespaces** and cloud environments +2. **`.devcontainer/devcontainer-local.json`** - Optimized for **local Windows development** with host mounts + +#### Default Configuration (Codespaces) + +The default `devcontainer.json` is designed to work reliably in GitHub Codespaces and other cloud environments: + +- Uses `updateContentCommand` instead of `postCreateCommand` for dependency installation (more reliable timing) +- Minimal features (Git only) for fast container creation +- No host mounts (not available in cloud environments) +- Includes memory optimization (`NODE_OPTIONS: --max-old-space-size=4096`) +- Working directory aware: runs `cd vscode-extension && npm ci` (repo uses monorepo structure) + +**Why `updateContentCommand`?** This lifecycle hook runs after workspace content is updated (including initial clone), making it more reliable than `postCreateCommand` which runs once during container creation and can timeout with large dependency trees. + +**Why minimal features?** PowerShell feature installation can timeout in Codespaces. The default config only includes Git, which is essential for development. + +#### Local Configuration (Windows with Mounts) + +The `devcontainer-local.json` configuration adds features for local development: + +- **Host Mounts:** Binds your VS Code session data (including Copilot logs) into the container so the extension can track your actual usage +- **PowerShell Feature:** Includes PowerShell for running build scripts like `build.ps1` +- **PowerShell Init:** Runs `.devcontainer/init-mounts.ps1` to ensure mount directories exist on Windows +- **Volume for node_modules:** Uses a Docker volume for faster dependency installation + +**To use the local configuration:** +1. Open the repository in VS Code +2. Press `F1` and select "Dev Containers: Reopen in Container" +3. Choose "Copilot Token Tracker Extension (Local with Mounts)" + +**Note:** The local configuration with mounts is **Windows-only** due to the use of `%APPDATA%` environment variables and PowerShell. For local development on Linux/macOS, use the default configuration. + ## Manual Local Setup If you prefer not to use the devcontainer, you can set up the extension locally: