Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"image": "ghcr.io/helpers-no/devcontainer-toolbox:latest",
"initializeCommand": "mkdir -p .devcontainer.secrets/env-vars && hostname -s > .devcontainer.secrets/env-vars/.host-hostname 2>/dev/null || hostname > .devcontainer.secrets/env-vars/.host-hostname 2>/dev/null || true"
}
73 changes: 73 additions & 0 deletions .github/scripts/test-initialize-command.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# test-initialize-command.ps1 - Run a devcontainer.json's initializeCommand the way VS Code does
#
# Uses the real devcontainers CLI (`devcontainer up`), so the command goes through the same
# path as for a user: on Windows the CLI runs it with `cmd.exe /c` (devcontainers/cli
# src/spec-node/utils.ts, runInitializeCommand). The CLI runs initializeCommand before it pulls
# the image, so on a runner that cannot run Linux containers `up` still fails later - that is
# expected. Only the initializeCommand outcome is judged.
#
# Usage: test-initialize-command.ps1 -Config <devcontainer.json> -Expect pass|fail

param(
[Parameter(Mandatory = $true)][string]$Config,
[Parameter(Mandatory = $true)][ValidateSet('pass', 'fail')][string]$Expect
)

$ErrorActionPreference = 'Stop'

$ranMarker = 'Running the initializeCommand from devcontainer.json'
$failedMarker = 'The initializeCommand in the devcontainer.json failed'

# GitHub's Windows runners put Git for Windows' Unix tools (C:\Program Files\Git\usr\bin, with
# true.exe, sh.exe, ...) on PATH. An ordinary office PC does not: Git's default install does not
# add them, and most users have no Git at all. With them on PATH the old bash-only command ends in
# a real `true` and "succeeds", so the check proves nothing. Remove them so cmd.exe sees what a
# normal PC sees, and verify it.
$env:Path = (($env:Path -split ';') | Where-Object {
$_ -and ($_ -notmatch '\\Git\\(usr\\)?bin\\?$') -and ($_ -notmatch '\\Git\\mingw64\\bin\\?$') -and ($_ -notmatch '(msys|cygwin)')
}) -join ';'
$unixTrue = Get-Command true -ErrorAction SilentlyContinue
if ($unixTrue) {
Write-Host "ERROR: a Unix 'true' is still on PATH ($($unixTrue.Source)), so this run would not match a normal PC."
exit 1
}

$workspace = Join-Path ([IO.Path]::GetTempPath()) ("dct-ic-" + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path (Join-Path $workspace '.devcontainer') -Force | Out-Null
Copy-Item -LiteralPath $Config -Destination (Join-Path $workspace '.devcontainer\devcontainer.json')

Write-Host "Config: $Config"
Write-Host "Expect: $Expect"
Write-Host "Workspace: $workspace"
Write-Host ""

$ErrorActionPreference = 'Continue'
$output = (& devcontainer up --workspace-folder $workspace 2>&1 | Out-String)
$ErrorActionPreference = 'Stop'

$ran = $output -match [regex]::Escape($ranMarker)
$failed = $output -match [regex]::Escape($failedMarker)

Write-Host "initializeCommand ran: $ran"
Write-Host "initializeCommand failed: $failed"

$ok = $false
if (-not $ran) {
Write-Host "ERROR: the CLI never reached initializeCommand, so this run proves nothing."
} elseif ($Expect -eq 'pass' -and -not $failed) {
$ok = $true
} elseif ($Expect -eq 'fail' -and $failed) {
$ok = $true
}

if (-not $ok) {
Write-Host ""
Write-Host "----- devcontainer up output -----"
Write-Host $output
Write-Host "----------------------------------"
Write-Host "RESULT: FAIL (expected initializeCommand to $Expect)"
exit 1
}

Write-Host "RESULT: OK (initializeCommand did $Expect, as expected)"
exit 0
67 changes: 67 additions & 0 deletions .github/workflows/host-commands.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Host-side commands in the user template must work on every host OS.
#
# initializeCommand runs on the HOST, before the container exists: with /bin/sh on Mac/Linux and
# with cmd.exe on Windows. A bash-only command broke new Windows installs from 2026-04-07 (733dd73)
# on every PC without Unix tools on PATH, which is the normal case. The test script removes the
# runner's Git-for-Windows Unix tools from PATH, because with them the old command "passes". See website/docs/ai-developer/plans/active/PLAN-fix-windows-quickstart.md.

name: Host Commands

on:
push:
branches: [main]
paths:
- 'devcontainer-user-template.json'
- '.github/workflows/host-commands.yml'
- '.github/scripts/test-initialize-command.ps1'
- '.github/fixtures/host-commands/**'
pull_request:
branches: [main]
paths:
- 'devcontainer-user-template.json'
- '.github/workflows/host-commands.yml'
- '.github/scripts/test-initialize-command.ps1'
- '.github/fixtures/host-commands/**'
workflow_dispatch:

permissions:
contents: read

jobs:
initialize-command-windows:
name: initializeCommand on Windows (cmd.exe)
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install the devcontainers CLI (the engine VS Code uses)
run: npm install -g @devcontainers/cli@0.89.0

- name: Template command must succeed
shell: pwsh
run: ./.github/scripts/test-initialize-command.ps1 -Config devcontainer-user-template.json -Expect pass

- name: Control - the old bash-only command must fail
shell: pwsh
run: ./.github/scripts/test-initialize-command.ps1 -Config .github/fixtures/host-commands/bash-only-initialize-command.json -Expect fail

initialize-command-linux:
name: initializeCommand on Linux (/bin/sh)
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4

- name: Template command must write the host hostname
run: |
cmd=$(jq -r .initializeCommand devcontainer-user-template.json)
ws=$(mktemp -d)
cd "$ws"
/bin/sh -c "$cmd"
test -s .devcontainer.secrets/env-vars/.host-hostname
echo "host hostname captured: $(cat .devcontainer.secrets/env-vars/.host-hostname)"
2 changes: 1 addition & 1 deletion devcontainer-user-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@
"shutdownAction": "stopContainer",
"updateRemoteUserUID": true,
"init": true,
"initializeCommand": "mkdir -p .devcontainer.secrets/env-vars && hostname -s > .devcontainer.secrets/env-vars/.host-hostname 2>/dev/null || hostname > .devcontainer.secrets/env-vars/.host-hostname 2>/dev/null || true",
"initializeCommand": "ver || sh -c \"mkdir -p .devcontainer.secrets/env-vars && { hostname -s 2>/dev/null || hostname; } > .devcontainer.secrets/env-vars/.host-hostname; true\"",
"postStartCommand": "bash /opt/devcontainer-toolbox/additions/config-host-info.sh --verify 2>/dev/null || true"
}
41 changes: 41 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,47 @@ if (Test-Path $extFile) {
Write-Host "Created $extFile with Dev Containers extension recommendation"
}

# --- 4b. Install the Dev Containers extension in VS Code ---------------------------
# Per user, no admin. VS Code itself comes from Intune (Company Portal) or the user's own
# install, so `code` may not be on PATH yet: also look in the user and system install folders.

$codeCmd = $null
$codeOnPath = Get-Command code -ErrorAction SilentlyContinue
if ($codeOnPath) {
$codeCmd = $codeOnPath.Source
} else {
foreach ($candidate in @(
"$env:LOCALAPPDATA\Programs\Microsoft VS Code\bin\code.cmd",
"$env:ProgramFiles\Microsoft VS Code\bin\code.cmd"
)) {
if (Test-Path $candidate) { $codeCmd = $candidate; break }
}
}

Write-Host ""
if (-not $codeCmd) {
Write-Host "VS Code was not found on this PC, so the Dev Containers extension could not be installed." -ForegroundColor Yellow
Write-Host "Install VS Code (on a work PC: from Company Portal), then run this again."
} else {
# Native commands write progress to stderr; keep that from being treated as a script error.
$prevPref = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$installed = & $codeCmd --list-extensions 2>$null
if ($installed -contains $extId) {
Write-Host "Dev Containers extension is already installed in VS Code"
} else {
Write-Host "Installing the Dev Containers extension in VS Code..."
& $codeCmd --install-extension $extId | Out-Host
if ($LASTEXITCODE -eq 0) {
Write-Host "Dev Containers extension installed"
} else {
Write-Host "Could not install the Dev Containers extension (code exit $LASTEXITCODE)." -ForegroundColor Yellow
Write-Host "Open VS Code and accept its offer to install the recommended extensions."
}
}
$ErrorActionPreference = $prevPref
}

# --- 5. Pull the Docker image -----------------------------------------------------

Write-Host ""
Expand Down
34 changes: 34 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,40 @@ EXTENSIONS_EOF
echo "Created $EXT_FILE with Dev Containers extension recommendation"
fi

# ─── 4b. Install the Dev Containers extension in VS Code ─────────────────────
# Per user, no sudo. `code` is often not on PATH on macOS (the shell command is
# opt-in), so also look inside the app bundle.

CODE_CMD=""
if command -v code >/dev/null 2>&1; then
CODE_CMD="$(command -v code)"
else
for candidate in \
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" \
"$HOME/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"; do
if [ -x "$candidate" ]; then
CODE_CMD="$candidate"
break
fi
done
fi

echo ""
if [ -z "$CODE_CMD" ]; then
echo "VS Code was not found, so the Dev Containers extension could not be installed."
echo "Install VS Code (on a work Mac: from Self Service), then run this again."
elif "$CODE_CMD" --list-extensions 2>/dev/null | grep -qx "$EXT_ID"; then
echo "Dev Containers extension is already installed in VS Code"
else
echo "Installing the Dev Containers extension in VS Code..."
if "$CODE_CMD" --install-extension "$EXT_ID"; then
echo "Dev Containers extension installed"
else
echo "Could not install the Dev Containers extension."
echo "Open VS Code and accept its offer to install the recommended extensions."
fi
fi

# ─── 5. Pull the Docker image ────────────────────────────────────────────────

echo ""
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.8.1
1.8.2
Loading
Loading