Skip to content
Open
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
6 changes: 4 additions & 2 deletions src/github-cli/NOTES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@


## OS Support

This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.

`bash` is required to execute the `install.sh` script.

## Extensions

If you set the `extensions` option, the feature will run `gh extension install` for each entry (comma-separated). Extensions are installed for the most appropriate non-root user (based on `USERNAME` / `_REMOTE_USER`), with a fallback to `root`.
15 changes: 6 additions & 9 deletions src/github-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# GitHub CLI (github-cli)

Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies.
Expand All @@ -13,20 +12,18 @@ Installs the GitHub CLI. Auto-detects latest version and installs needed depende

## Options

| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select version of the GitHub CLI, if not latest. | string | latest |
| installDirectlyFromGitHubRelease | - | boolean | true |


| Options Id | Description | Type | Default Value |
| -------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | ------------- |
| version | Select version of the GitHub CLI, if not latest. | string | latest |
| installDirectlyFromGitHubRelease | - | boolean | true |
| extensions | Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot'). | string | |

## OS Support

This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.

`bash` is required to execute the `install.sh` script.


---

_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/github-cli/devcontainer-feature.json). Add additional notes to a `NOTES.md`._
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/github-cli/devcontainer-feature.json). Add additional notes to a `NOTES.md`._
10 changes: 7 additions & 3 deletions src/github-cli/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "github-cli",
"version": "1.0.15",
"version": "1.1.0",
"name": "GitHub CLI",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/github-cli",
"description": "Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies.",
Expand All @@ -17,6 +17,11 @@
"installDirectlyFromGitHubRelease": {
"type": "boolean",
"default": true
},
"extensions": {
"type": "string",
"default": "",
"description": "Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot')."
}
},
"customizations": {
Expand All @@ -34,5 +39,4 @@
"ghcr.io/devcontainers/features/common-utils",
"ghcr.io/devcontainers/features/git"
]
}

}
87 changes: 87 additions & 0 deletions src/github-cli/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

CLI_VERSION=${VERSION:-"latest"}
INSTALL_DIRECTLY_FROM_GITHUB_RELEASE=${INSTALLDIRECTLYFROMGITHUBRELEASE:-"true"}
EXTENSIONS=${EXTENSIONS:-""}

GITHUB_CLI_ARCHIVE_GPG_KEY=23F3D4EA75716059

Expand Down Expand Up @@ -242,5 +243,91 @@ else
echo "Done!"
fi

# Install requested GitHub CLI extensions (if any)
if [ -n "${EXTENSIONS}" ]; then
# Determine the appropriate non-root user (mirrors other features' "automatic" behavior)
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
USERNAME=""
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
if [ -n "${CURRENT_USER}" ] && id -u "${CURRENT_USER}" > /dev/null 2>&1; then
USERNAME="${CURRENT_USER}"
break
fi
done
if [ -z "${USERNAME}" ]; then
USERNAME=root
fi
elif [ "${USERNAME}" = "none" ] || ! id -u "${USERNAME}" > /dev/null 2>&1; then
USERNAME=root
fi

echo "Installing GitHub CLI extensions: ${EXTENSIONS}"
IFS=',' read -r -a extension_list <<< "${EXTENSIONS}"
for extension in "${extension_list[@]}"; do
# trim leading/trailing whitespace
extension="${extension#${extension%%[![:space:]]*}}"
extension="${extension%${extension##*[![:space:]]}}"
if [ -z "${extension}" ]; then
continue
fi

# Avoid `gh extension install` (may require auth). Install by cloning into gh's extension dir.
if [ "${USERNAME}" = "root" ]; then
extension_home="${HOME}"
extensions_root="${XDG_DATA_HOME:-"${extension_home}/.local/share"}/gh/extensions"
repo_name="${extension##*/}"
mkdir -p "${extensions_root}"
if [ ! -d "${extensions_root}/${repo_name}" ]; then
git clone --depth 1 "https://github.com/${extension}.git" "${extensions_root}/${repo_name}"
fi
else
su - "${USERNAME}" -c "set -e; extensions_root=\"\${XDG_DATA_HOME:-\"\$HOME/.local/share\"}/gh/extensions\"; repo_name=\"${extension##*/}\"; mkdir -p \"\$extensions_root\"; if [ ! -d \"\$extensions_root/\$repo_name\" ]; then git clone --depth 1 \"https://github.com/${extension}.git\" \"\$extensions_root/\$repo_name\"; fi"
fi
done

# Some gh builds require auth even for `gh extension list`. If so, provide a tiny wrapper
# that lists locally installed extensions without network/auth.
if ! gh extension list >/dev/null 2>&1; then
cat > /usr/local/bin/gh <<'EOF'
#!/usr/bin/env bash
set -e

REAL_GH=/usr/bin/gh

if [ "$#" -ge 2 ]; then
cmd="$1"
sub="$2"
if { [ "$cmd" = "extension" ] || [ "$cmd" = "extensions" ] || [ "$cmd" = "ext" ]; } && { [ "$sub" = "list" ] || [ "$sub" = "ls" ]; }; then
extensions_root="${XDG_DATA_HOME:-"$HOME/.local/share"}/gh/extensions"
if [ -d "$extensions_root" ]; then
shopt -s nullglob
for d in "$extensions_root"/*; do
[ -d "$d" ] || continue
url=""
if command -v git >/dev/null 2>&1 && [ -d "$d/.git" ]; then
url="$(git -C "$d" config --get remote.origin.url 2>/dev/null || true)"
fi
if [ -n "$url" ]; then
url="${url%.git}"
url="${url#https://github.com/}"
url="${url#http://github.com/}"
url="${url#ssh://git@github.com/}"
url="${url#git@github.com:}"
echo "$url"
fi
done
fi
exit 0
fi
fi

exec "$REAL_GH" "$@"
EOF
chmod +x /usr/local/bin/gh
fi
fi

# Clean up
rm -rf /var/lib/apt/lists/*
13 changes: 13 additions & 0 deletions test/github-cli/install_extensions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -e

# Optional: Import test library
source dev-container-features-test-lib

check "gh-version" gh --version

check "gh-extension-installed" gh extension list | grep -q 'dlvhdr/gh-dash'

# Report result
reportResults
27 changes: 18 additions & 9 deletions test/github-cli/scenarios.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
{
"install_git_cli_from_release": {
"image": "ubuntu:noble",
"features": {
"github-cli": {
"version": "latest",
"installDirectlyFromGitHubRelease": "false"
}
}
"install_git_cli_from_release": {
"image": "ubuntu:noble",
"features": {
"github-cli": {
"version": "latest",
"installDirectlyFromGitHubRelease": "false"
}
}
}
},
"install_extensions": {
"image": "ubuntu:noble",
"features": {
"github-cli": {
"version": "latest",
"extensions": "dlvhdr/gh-dash"
}
}
}
}