diff --git a/src/archon/devcontainer-feature.json b/src/archon/devcontainer-feature.json new file mode 100644 index 000000000..7fdedbb3f --- /dev/null +++ b/src/archon/devcontainer-feature.json @@ -0,0 +1,18 @@ +{ + "id": "archon", + "version": "1.0.0", + "name": "archon (via Github Releases)", + "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/archon", + "description": "The first open-source harness builder for AI coding. Make AI coding deterministic and repeatable.", + "options": { + "version": { + "default": "latest", + "description": "Select the version to install.", + "proposals": [ + "latest" + ], + "type": "string" + } + }, + "installsAfter": [] +} diff --git a/src/archon/install.sh b/src/archon/install.sh new file mode 100755 index 000000000..94ce02311 --- /dev/null +++ b/src/archon/install.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.6" + +# Example nanolayer installation via devcontainer-feature +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='coleam00/Archon' --option binaryNames='archon' --option version="$VERSION" + +echo 'Done!' diff --git a/src/claude-code/library_scripts.sh b/src/archon/library_scripts.sh similarity index 100% rename from src/claude-code/library_scripts.sh rename to src/archon/library_scripts.sh diff --git a/src/bazel-compile-commands/README.md b/src/bazel-compile-commands/README.md new file mode 100644 index 000000000..dcb9da00c --- /dev/null +++ b/src/bazel-compile-commands/README.md @@ -0,0 +1,42 @@ +# bazel-compile-commands (via Github Releases) (bazel-compile-commands) + +Generates `compile_commands.json` from Bazel builds, enabling accurate code intelligence (go-to-definition, cross-references, diagnostics) in editors that support the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). + +## Example Usage + +```json +"features": { + "ghcr.io/devcontainers-extra/features/bazel-compile-commands:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +| ---------- | -------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------- | +| version | Version to install. Accepts `latest`, a numeric version like `0.22.4`, or the full release tag `bazel-compile-commands-v0.22.4`. | string | latest | + +## Install behaviour + +The feature selects the best available package for the current environment: + +| Environment | Package used | +| -------------------- | ------------------------------- | +| Ubuntu Noble (24.04) | Native `.deb` via `apt-get` | +| Other Linux | Generic `.zip` (amd64 or arm64) | +| macOS | Universal `.zip` | + +## Pairing with Bazel + +This feature pairs naturally with [`ghcr.io/devcontainers-community/features/bazel:1`](https://github.com/devcontainers-community/features-bazel), which installs Bazelisk and Buildifier. When both are present in `devcontainer.json`, the Bazel feature is installed first. + +```json +"features": { + "ghcr.io/devcontainers-community/features/bazel:1": {}, + "ghcr.io/devcontainers-extra/features/bazel-compile-commands:1": {} +} +``` + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/bazel-compile-commands/devcontainer-feature.json b/src/bazel-compile-commands/devcontainer-feature.json new file mode 100644 index 000000000..7d1963345 --- /dev/null +++ b/src/bazel-compile-commands/devcontainer-feature.json @@ -0,0 +1,21 @@ +{ + "id": "bazel-compile-commands", + "version": "1.0.0", + "name": "bazel-compile-commands (via Github Releases)", + "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/bazel-compile-commands", + "description": "Generates compile_commands.json from Bazel builds.", + "options": { + "version": { + "default": "latest", + "description": "Version to install. Accepts 'latest' or a numeric version like '0.22.4'.", + "proposals": [ + "latest", + "0.22.4" + ], + "type": "string" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers-community/features/bazel" + ] +} diff --git a/src/bazel-compile-commands/install.sh b/src/bazel-compile-commands/install.sh new file mode 100755 index 000000000..de53af507 --- /dev/null +++ b/src/bazel-compile-commands/install.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -e + +. ./library_scripts.sh + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.6" + +# Upstream tags use a non-standard "bazel-compile-commands-v" format. Normalise +# any plain version (e.g. "0.22.4" or "v0.22.4") so nanolayer can match it exactly. +if [[ "${VERSION}" != "latest" && "${VERSION}" != bazel-compile-commands-* ]]; then + VERSION="bazel-compile-commands-v${VERSION#v}" +fi + +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='kiron1/bazel-compile-commands' \ + --option binaryNames='bazel-compile-commands' \ + --option version="$VERSION" \ + --option assetRegex='linux_' \ + --option releaseTagRegex='bazel-compile-commands-v' + +echo 'Done!' diff --git a/src/bazel-compile-commands/library_scripts.sh b/src/bazel-compile-commands/library_scripts.sh new file mode 100644 index 000000000..f6d0760d7 --- /dev/null +++ b/src/bazel-compile-commands/library_scripts.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +clean_download() { + # The purpose of this function is to download a file with minimal impact on container layer size + # this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a + # temporary manner, and making sure to + # 1. uninstall the downloader at the return of the function + # 2. revert back any changes to the package installer database/cache (for example apt-get lists) + # The above steps will minimize the leftovers being created while installing the downloader + # Supported distros: + # debian/ubuntu/alpine + + url=$1 + output_location=$2 + tempdir=$(mktemp -d) + downloader_installed="" + + function _apt_get_install() { + tempdir=$1 + + # copy current state of apt list - in order to revert back later (minimize contianer layer size) + cp -p -R /var/lib/apt/lists $tempdir + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + function _apt_get_cleanup() { + tempdir=$1 + + echo "removing wget" + apt-get -y purge wget --auto-remove + + echo "revert back apt lists" + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists + } + + function _apk_install() { + tempdir=$1 + # copy current state of apk cache - in order to revert back later (minimize contianer layer size) + cp -p -R /var/cache/apk $tempdir + + apk add --no-cache wget + } + + function _apk_cleanup() { + tempdir=$1 + + echo "removing wget" + apk del wget + } + # try to use either wget or curl if one of them already installer + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarly + if [ -z $downloader ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ]; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi + +} + +ensure_nanolayer() { + # Ensure existance of the nanolayer cli program + local variable_name=$1 + + local required_version=$2 + # normalize version + if ! [[ $required_version == v* ]]; then + required_version=v$required_version + fi + + local nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then + nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location" + fi + + # make sure its of the required version + if ! [[ -z "${nanolayer_location}" ]]; then + local current_version + current_version=$($nanolayer_location --version) + if ! [[ $current_version == v* ]]; then + current_version=v$current_version + fi + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + nanolayer_location="" + fi + fi + + fi + + # If not previuse installation found, download it temporarly and delete at the end of the script + if [[ -z "${nanolayer_location}" ]]; then + + if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up() { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ]; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + nanolayer_location=$tmp_dir/nanolayer + + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + declare -g ${variable_name}=$nanolayer_location + +} diff --git a/src/caddy/devcontainer-feature.json b/src/caddy/devcontainer-feature.json index e61553eb1..be9dd4712 100644 --- a/src/caddy/devcontainer-feature.json +++ b/src/caddy/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "caddy", - "version": "1.0.10", + "version": "1.0.11", "name": "Caddy (via Github Releases)", "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/caddy", "description": "Caddy is a powerful, enterprise-ready, open source web server with automatic HTTPS.", diff --git a/src/caddy/install.sh b/src/caddy/install.sh index d5d7f9357..fd47c6175 100755 --- a/src/caddy/install.sh +++ b/src/caddy/install.sh @@ -8,13 +8,13 @@ set -e # `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, # and if missing - will download a temporary copy that automatically get deleted at the end # of the script -ensure_nanolayer nanolayer_location "v0.5.4" +ensure_nanolayer nanolayer_location "v0.5.6" $nanolayer_location \ install \ devcontainer-feature \ - "ghcr.io/devcontainers/features/go:1.1.3" \ + "ghcr.io/devcontainers/features/go:1.3.4" \ --option version="$GOLANGVERSION" @@ -22,7 +22,7 @@ $nanolayer_location \ $nanolayer_location \ install \ devcontainer-feature \ - "ghcr.io/devcontainers-extra/features/gh-release:1.0.25" \ + "ghcr.io/devcontainers-extra/features/gh-release:1.0.26" \ --option repo='caddyserver/caddy' --option binaryNames='caddy' --option version="$VERSION" diff --git a/src/claude-code/bootstrap.sh b/src/claude-code/bootstrap.sh new file mode 100644 index 000000000..375daea51 --- /dev/null +++ b/src/claude-code/bootstrap.sh @@ -0,0 +1,158 @@ +#!/bin/bash + +set -e + +# Parse command line arguments +TARGET="$1" # Optional target parameter + +# Validate target if provided +if [[ -n "$TARGET" ]] && [[ ! "$TARGET" =~ ^(stable|latest|[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?)$ ]]; then + echo "Usage: $0 [stable|latest|VERSION]" >&2 + exit 1 +fi + +DOWNLOAD_BASE_URL="https://downloads.claude.ai/claude-code-releases" +DOWNLOAD_DIR="$HOME/.claude/downloads" + +# Check for required dependencies +DOWNLOADER="" +if command -v curl >/dev/null 2>&1; then + DOWNLOADER="curl" +elif command -v wget >/dev/null 2>&1; then + DOWNLOADER="wget" +else + echo "Either curl or wget is required but neither is installed" >&2 + exit 1 +fi + +# Check if jq is available (optional) +HAS_JQ=false +if command -v jq >/dev/null 2>&1; then + HAS_JQ=true +fi + +# Download function that works with both curl and wget +download_file() { + local url="$1" + local output="$2" + + if [ "$DOWNLOADER" = "curl" ]; then + if [ -n "$output" ]; then + curl -fsSL -o "$output" "$url" + else + curl -fsSL "$url" + fi + elif [ "$DOWNLOADER" = "wget" ]; then + if [ -n "$output" ]; then + wget -q -O "$output" "$url" + else + wget -q -O - "$url" + fi + else + return 1 + fi +} + +# Simple JSON parser for extracting checksum when jq is not available +get_checksum_from_manifest() { + local json="$1" + local platform="$2" + + # Normalize JSON to single line and extract checksum + json=$(echo "$json" | tr -d '\n\r\t' | sed 's/ \+/ /g') + + # Extract checksum for platform using bash regex + if [[ $json =~ \"$platform\"[^}]*\"checksum\"[[:space:]]*:[[:space:]]*\"([a-f0-9]{64})\" ]]; then + echo "${BASH_REMATCH[1]}" + return 0 + fi + + return 1 +} + +# Detect platform +case "$(uname -s)" in + Darwin) os="darwin" ;; + Linux) os="linux" ;; + MINGW*|MSYS*|CYGWIN*) echo "Windows is not supported by this script. See https://code.claude.com/docs for installation options." >&2; exit 1 ;; + *) echo "Unsupported operating system: $(uname -s). See https://code.claude.com/docs for supported platforms." >&2; exit 1 ;; +esac + +case "$(uname -m)" in + x86_64|amd64) arch="x64" ;; + arm64|aarch64) arch="arm64" ;; + *) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;; +esac + +# Detect Rosetta 2 on macOS: if the shell is running as x64 under Rosetta on an ARM Mac, +# download the native arm64 binary instead of the x64 one +if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then + if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then + arch="arm64" + fi +fi + +# Check for musl on Linux and adjust platform accordingly +if [ "$os" = "linux" ]; then + if [ -f /lib/libc.musl-x86_64.so.1 ] || [ -f /lib/libc.musl-aarch64.so.1 ] || ldd /bin/ls 2>&1 | grep -q musl; then + platform="linux-${arch}-musl" + else + platform="linux-${arch}" + fi +else + platform="${os}-${arch}" +fi +mkdir -p "$DOWNLOAD_DIR" + +# Always download latest version (which has the most up-to-date installer) +version=$(download_file "$DOWNLOAD_BASE_URL/latest") + +# Download manifest and extract checksum +manifest_json=$(download_file "$DOWNLOAD_BASE_URL/$version/manifest.json") + +# Use jq if available, otherwise fall back to pure bash parsing +if [ "$HAS_JQ" = true ]; then + checksum=$(echo "$manifest_json" | jq -r ".platforms[\"$platform\"].checksum // empty") +else + checksum=$(get_checksum_from_manifest "$manifest_json" "$platform") +fi + +# Validate checksum format (SHA256 = 64 hex characters) +if [ -z "$checksum" ] || [[ ! "$checksum" =~ ^[a-f0-9]{64}$ ]]; then + echo "Platform $platform not found in manifest" >&2 + exit 1 +fi + +# Download and verify +binary_path="$DOWNLOAD_DIR/claude-$version-$platform" +if ! download_file "$DOWNLOAD_BASE_URL/$version/$platform/claude" "$binary_path"; then + echo "Download failed" >&2 + rm -f "$binary_path" + exit 1 +fi + +# Pick the right checksum tool +if [ "$os" = "darwin" ]; then + actual=$(shasum -a 256 "$binary_path" | cut -d' ' -f1) +else + actual=$(sha256sum "$binary_path" | cut -d' ' -f1) +fi + +if [ "$actual" != "$checksum" ]; then + echo "Checksum verification failed" >&2 + rm -f "$binary_path" + exit 1 +fi + +chmod +x "$binary_path" + +# Run claude install to set up launcher and shell integration +echo "Setting up Claude Code..." +"$binary_path" install ${TARGET:+"$TARGET"} + +# Clean up downloaded file +rm -f "$binary_path" + +echo "" +echo "✅ Installation complete!" +echo "" diff --git a/src/claude-code/devcontainer-feature.json b/src/claude-code/devcontainer-feature.json index 281bec53c..d0288726e 100644 --- a/src/claude-code/devcontainer-feature.json +++ b/src/claude-code/devcontainer-feature.json @@ -1,7 +1,7 @@ { "id": "claude-code", - "version": "1.0.0", - "name": "claude-code (via npm)", + "version": "2.0.3", + "name": "claude-code", "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/claude-code", "description": "Claude Code is an agentic coding tool that lives in your terminal", "options": { @@ -22,4 +22,4 @@ } }, "installsAfter": [] -} \ No newline at end of file +} diff --git a/src/claude-code/install.sh b/src/claude-code/install.sh index adc2d3820..e31ac039d 100644 --- a/src/claude-code/install.sh +++ b/src/claude-code/install.sh @@ -2,20 +2,28 @@ set -e -source ./library_scripts.sh +# Make sure that variable _REMOTE_USER_HOME is properly set +if [ -z "$_REMOTE_USER_HOME" ]; then + # shellcheck disable=2016 # this is intentional as the expansion should happen in the context of the new session + _REMOTE_USER_HOME="$(su - "$_REMOTE_USER" -c 'echo "$HOME"')" + export _REMOTE_USER_HOME +fi -# nanolayer is a cli utility which keeps container layers as small as possible -# source code: https://github.com/devcontainers-extra/nanolayer -# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, -# and if missing - will download a temporary copy that automatically get deleted at the end -# of the script -ensure_nanolayer nanolayer_location "v0.5.6" +# Install via Anthropic's recommended method +# Script downloaded from them: +# https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/bootstrap.sh -# Example nanolayer installation via devcontainer-feature -$nanolayer_location \ - install \ - devcontainer-feature \ - "ghcr.io/devcontainers-extra/features/npm-package:1.0.4" \ - --option package='@anthropic-ai/claude-code' --option version="$VERSION" +# Resolve the absolute path before su changes the working directory +BOOTSTRAP_SCRIPT="$(pwd)/bootstrap.sh" + +# Run as the devcontainer user so that ~/.claude/, ~/.local/bin/claude, and +# ~/.local/share/claude/ are owned by the right user, not root. +su - "$_REMOTE_USER" </dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarly + if [ -z $downloader ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ]; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi + +} + +ensure_nanolayer() { + # Ensure existance of the nanolayer cli program + local variable_name=$1 + + local required_version=$2 + # normalize version + if ! [[ $required_version == v* ]]; then + required_version=v$required_version + fi + + local nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then + nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location" + fi + + # make sure its of the required version + if ! [[ -z "${nanolayer_location}" ]]; then + local current_version + current_version=$($nanolayer_location --version) + if ! [[ $current_version == v* ]]; then + current_version=v$current_version + fi + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + nanolayer_location="" + fi + fi + + fi + + # If not previuse installation found, download it temporarly and delete at the end of the script + if [[ -z "${nanolayer_location}" ]]; then + + if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up() { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ]; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + nanolayer_location=$tmp_dir/nanolayer + + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + declare -g ${variable_name}=$nanolayer_location + +} diff --git a/src/golangci-lint/devcontainer-feature.json b/src/golangci-lint/devcontainer-feature.json new file mode 100644 index 000000000..da686f2d9 --- /dev/null +++ b/src/golangci-lint/devcontainer-feature.json @@ -0,0 +1,18 @@ +{ + "id": "golangci-lint", + "version": "1.0.0", + "name": "golangci-lint (via Github Releases)", + "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/golangci-lint", + "description": "Fast linters runner for Go", + "options": { + "version": { + "default": "latest", + "description": "Select the version to install.", + "proposals": [ + "latest" + ], + "type": "string" + } + }, + "installsAfter": [] +} diff --git a/src/golangci-lint/install.sh b/src/golangci-lint/install.sh new file mode 100755 index 000000000..e88801f01 --- /dev/null +++ b/src/golangci-lint/install.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.6" + +# Example nanolayer installation via devcontainer-feature +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='golangci/golangci-lint' --option binaryNames='golangci-lint' --option version="$VERSION" + +echo 'Done!' diff --git a/src/golangci-lint/library_scripts.sh b/src/golangci-lint/library_scripts.sh new file mode 100644 index 000000000..f6d0760d7 --- /dev/null +++ b/src/golangci-lint/library_scripts.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +clean_download() { + # The purpose of this function is to download a file with minimal impact on container layer size + # this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a + # temporary manner, and making sure to + # 1. uninstall the downloader at the return of the function + # 2. revert back any changes to the package installer database/cache (for example apt-get lists) + # The above steps will minimize the leftovers being created while installing the downloader + # Supported distros: + # debian/ubuntu/alpine + + url=$1 + output_location=$2 + tempdir=$(mktemp -d) + downloader_installed="" + + function _apt_get_install() { + tempdir=$1 + + # copy current state of apt list - in order to revert back later (minimize contianer layer size) + cp -p -R /var/lib/apt/lists $tempdir + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + function _apt_get_cleanup() { + tempdir=$1 + + echo "removing wget" + apt-get -y purge wget --auto-remove + + echo "revert back apt lists" + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists + } + + function _apk_install() { + tempdir=$1 + # copy current state of apk cache - in order to revert back later (minimize contianer layer size) + cp -p -R /var/cache/apk $tempdir + + apk add --no-cache wget + } + + function _apk_cleanup() { + tempdir=$1 + + echo "removing wget" + apk del wget + } + # try to use either wget or curl if one of them already installer + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarly + if [ -z $downloader ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ]; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi + +} + +ensure_nanolayer() { + # Ensure existance of the nanolayer cli program + local variable_name=$1 + + local required_version=$2 + # normalize version + if ! [[ $required_version == v* ]]; then + required_version=v$required_version + fi + + local nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then + nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location" + fi + + # make sure its of the required version + if ! [[ -z "${nanolayer_location}" ]]; then + local current_version + current_version=$($nanolayer_location --version) + if ! [[ $current_version == v* ]]; then + current_version=v$current_version + fi + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + nanolayer_location="" + fi + fi + + fi + + # If not previuse installation found, download it temporarly and delete at the end of the script + if [[ -z "${nanolayer_location}" ]]; then + + if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up() { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ]; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + nanolayer_location=$tmp_dir/nanolayer + + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + declare -g ${variable_name}=$nanolayer_location + +} diff --git a/src/opengrep/devcontainer-feature.json b/src/opengrep/devcontainer-feature.json new file mode 100644 index 000000000..594ddc49c --- /dev/null +++ b/src/opengrep/devcontainer-feature.json @@ -0,0 +1,18 @@ +{ + "id": "opengrep", + "version": "1.0.0", + "name": "opengrep (via Github Releases)", + "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/opengrep", + "description": "Static code analysis engine to find security issues in code.", + "options": { + "version": { + "default": "latest", + "description": "Select the version to install.", + "proposals": [ + "latest" + ], + "type": "string" + } + }, + "installsAfter": [] +} diff --git a/src/opengrep/install.sh b/src/opengrep/install.sh new file mode 100755 index 000000000..9b7efc1e1 --- /dev/null +++ b/src/opengrep/install.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.6" + +# Example nanolayer installation via devcontainer-feature +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='opengrep/opengrep' --option binaryNames='opengrep' --option version="$VERSION" --option assetRegex='opengrep_manylinux_[^.]*$' + +echo 'Done!' diff --git a/src/opengrep/library_scripts.sh b/src/opengrep/library_scripts.sh new file mode 100644 index 000000000..f6d0760d7 --- /dev/null +++ b/src/opengrep/library_scripts.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +clean_download() { + # The purpose of this function is to download a file with minimal impact on container layer size + # this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a + # temporary manner, and making sure to + # 1. uninstall the downloader at the return of the function + # 2. revert back any changes to the package installer database/cache (for example apt-get lists) + # The above steps will minimize the leftovers being created while installing the downloader + # Supported distros: + # debian/ubuntu/alpine + + url=$1 + output_location=$2 + tempdir=$(mktemp -d) + downloader_installed="" + + function _apt_get_install() { + tempdir=$1 + + # copy current state of apt list - in order to revert back later (minimize contianer layer size) + cp -p -R /var/lib/apt/lists $tempdir + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + function _apt_get_cleanup() { + tempdir=$1 + + echo "removing wget" + apt-get -y purge wget --auto-remove + + echo "revert back apt lists" + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists + } + + function _apk_install() { + tempdir=$1 + # copy current state of apk cache - in order to revert back later (minimize contianer layer size) + cp -p -R /var/cache/apk $tempdir + + apk add --no-cache wget + } + + function _apk_cleanup() { + tempdir=$1 + + echo "removing wget" + apk del wget + } + # try to use either wget or curl if one of them already installer + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarly + if [ -z $downloader ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ]; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi + +} + +ensure_nanolayer() { + # Ensure existance of the nanolayer cli program + local variable_name=$1 + + local required_version=$2 + # normalize version + if ! [[ $required_version == v* ]]; then + required_version=v$required_version + fi + + local nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then + nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location" + fi + + # make sure its of the required version + if ! [[ -z "${nanolayer_location}" ]]; then + local current_version + current_version=$($nanolayer_location --version) + if ! [[ $current_version == v* ]]; then + current_version=v$current_version + fi + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + nanolayer_location="" + fi + fi + + fi + + # If not previuse installation found, download it temporarly and delete at the end of the script + if [[ -z "${nanolayer_location}" ]]; then + + if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up() { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ]; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + nanolayer_location=$tmp_dir/nanolayer + + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + declare -g ${variable_name}=$nanolayer_location + +} diff --git a/src/outagedeck/README.md b/src/outagedeck/README.md new file mode 100644 index 000000000..5c0bbadc3 --- /dev/null +++ b/src/outagedeck/README.md @@ -0,0 +1,34 @@ +# OutageDeck CLI (via GitHub Releases) (outagedeck) + +Check the live status of 170+ cloud and SaaS providers from a terminal or CI +script. OutageDeck normalizes each vendor's official status feed; it does not +replace synthetic monitoring. + +## Example usage + +```json +"features": { + "ghcr.io/devcontainers-extra/features/outagedeck:1": {} +} +``` + +Then query one or more provider slugs: + +```bash +outagedeck status aws cloudflare github openai +``` + +For structured CI output and a nonzero exit code when a provider is down: + +```bash +outagedeck status --json --fail-on=outage aws github openai +``` + +Find more examples in the [OutageDeck CLI repository](https://github.com/outagedeck/cli) +or review the [API documentation](https://outagedeck.com/developers/api?utm_source=devcontainers-extra&utm_medium=feature&utm_campaign=devcontainer_feature). + +## Options + +| Options Id | Description | Type | Default Value | +|------------|----------------------------------------------|--------|---------------| +| version | Select the OutageDeck CLI version to install. | string | latest | diff --git a/src/outagedeck/devcontainer-feature.json b/src/outagedeck/devcontainer-feature.json new file mode 100644 index 000000000..c8d1c231a --- /dev/null +++ b/src/outagedeck/devcontainer-feature.json @@ -0,0 +1,21 @@ +{ + "id": "outagedeck", + "version": "1.0.0", + "name": "OutageDeck CLI (via GitHub Releases)", + "documentationURL": "https://github.com/devcontainers-extra/features/tree/main/src/outagedeck", + "description": "Check the live status of cloud and SaaS providers from a terminal or CI script.", + "options": { + "version": { + "default": "latest", + "description": "Select the OutageDeck CLI version to install.", + "proposals": [ + "latest", + "0.1.0" + ], + "type": "string" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers-extra/features/gh-release" + ] +} diff --git a/src/outagedeck/install.sh b/src/outagedeck/install.sh new file mode 100755 index 000000000..fd472f850 --- /dev/null +++ b/src/outagedeck/install.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +# nanolayer is a CLI utility which keeps container layers as small as possible. +# Source: https://github.com/devcontainers-extra/nanolayer +ensure_nanolayer nanolayer_location "v0.5.6" + +"$nanolayer_location" \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='outagedeck/cli' \ + --option binaryNames='outagedeck' \ + --option version="$VERSION" + +echo 'Done!' diff --git a/src/outagedeck/library_scripts.sh b/src/outagedeck/library_scripts.sh new file mode 100755 index 000000000..98e0da825 --- /dev/null +++ b/src/outagedeck/library_scripts.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash + +clean_download() { + local url=$1 + local output_location=$2 + local tempdir + tempdir=$(mktemp -d) + local downloader_installed="" + + _apt_get_install() { + local apt_tempdir=$1 + cp -p -R /var/lib/apt/lists "$apt_tempdir" + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + _apt_get_cleanup() { + local apt_tempdir=$1 + apt-get -y purge wget --auto-remove + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists + mv "$apt_tempdir/lists" /var/lib/apt/lists + } + + _apk_install() { + local apk_tempdir=$1 + cp -p -R /var/cache/apk "$apk_tempdir" + apk add --no-cache wget + } + + _apk_cleanup() { + apk del wget + } + + local downloader + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + if [ -z "$downloader" ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install "$tempdir" + elif [ -x "/sbin/apk" ]; then + _apk_install "$tempdir" + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ "$downloader" = "wget" ]; then + wget -q "$url" -O "$output_location" + else + curl -sfL "$url" -o "$output_location" + fi + + if [ -n "$downloader_installed" ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup "$tempdir" + elif [ -x "/sbin/apk" ]; then + _apk_cleanup + fi + fi +} + +ensure_nanolayer() { + local variable_name=$1 + local required_version=$2 + + if [[ $required_version != v* ]]; then + required_version="v$required_version" + fi + + local resolved_nanolayer="" + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION:-}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION:-}" ]]; then + if type nanolayer >/dev/null 2>&1; then + resolved_nanolayer=nanolayer + fi + elif [[ -f "$NANOLAYER_CLI_LOCATION" && -x "$NANOLAYER_CLI_LOCATION" ]]; then + resolved_nanolayer=$NANOLAYER_CLI_LOCATION + fi + + if [[ -n "$resolved_nanolayer" ]]; then + local current_version + current_version=$($resolved_nanolayer --version) + if [[ $current_version != v* ]]; then + current_version="v$current_version" + fi + if [[ $current_version != "$required_version" ]]; then + resolved_nanolayer="" + fi + fi + fi + + if [[ -z "$resolved_nanolayer" ]]; then + if [[ "$(uname -sm)" != "Linux x86_64" && "$(uname -sm)" != "Linux aarch64" ]]; then + echo "No nanolayer binary for $(uname -sm)" + exit 1 + fi + + local nanolayer_tempdir + nanolayer_tempdir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + clean_up() { + local exit_code=$? + rm -rf "$nanolayer_tempdir" + exit "$exit_code" + } + trap clean_up EXIT + + local clib_type=gnu + if [ -x "/sbin/apk" ]; then + clib_type=musl + fi + local tar_filename="nanolayer-$(uname -m)-unknown-linux-$clib_type.tgz" + clean_download \ + "https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename" \ + "$nanolayer_tempdir/$tar_filename" + tar xfz "$nanolayer_tempdir/$tar_filename" -C "$nanolayer_tempdir" + chmod a+x "$nanolayer_tempdir/nanolayer" + resolved_nanolayer="$nanolayer_tempdir/nanolayer" + fi + + declare -g "$variable_name=$resolved_nanolayer" +} diff --git a/src/tflint/README.md b/src/tflint/README.md new file mode 100644 index 000000000..507b023bc --- /dev/null +++ b/src/tflint/README.md @@ -0,0 +1,24 @@ + +# TFLint (via Github Releases) (tflint) + +TFLint is a framework and each feature is provided by plugins, the key features are as follows: + +- Find possible errors (like invalid instance types) for Major Cloud providers (AWS/Azure/GCP). +- Warn about deprecated syntax, unused declarations. +- Enforce best practices, naming conventions. + +## Example Usage + +```json +"features": { + "ghcr.io/devcontainers-extra/features/tflint:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|------------|--------------------------------|--------|---------------| +| version | Select the version to install. | string | latest | + +--- diff --git a/src/tflint/devcontainer-feature.json b/src/tflint/devcontainer-feature.json new file mode 100644 index 000000000..88865b937 --- /dev/null +++ b/src/tflint/devcontainer-feature.json @@ -0,0 +1,20 @@ +{ + "id": "tflint", + "version": "1.0.0", + "name": "tflint (via Github Releases)", + "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/tflint", + "description": "A Pluggable Terraform Linter", + "options": { + "version": { + "default": "latest", + "description": "Select the version to install.", + "proposals": [ + "latest" + ], + "type": "string" + } + }, + "installsAfter": [ + "ghcr.io/devcontainers-extra/features/gh-release" + ] +} \ No newline at end of file diff --git a/src/tflint/install.sh b/src/tflint/install.sh new file mode 100755 index 000000000..bae05e0ed --- /dev/null +++ b/src/tflint/install.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.6" + +# Example nanolayer installation via devcontainer-feature +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='terraform-linters/tflint' --option binaryNames='tflint' --option version="$VERSION" + +echo 'Done!' diff --git a/src/tflint/library_scripts.sh b/src/tflint/library_scripts.sh new file mode 100644 index 000000000..f6d0760d7 --- /dev/null +++ b/src/tflint/library_scripts.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +clean_download() { + # The purpose of this function is to download a file with minimal impact on container layer size + # this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a + # temporary manner, and making sure to + # 1. uninstall the downloader at the return of the function + # 2. revert back any changes to the package installer database/cache (for example apt-get lists) + # The above steps will minimize the leftovers being created while installing the downloader + # Supported distros: + # debian/ubuntu/alpine + + url=$1 + output_location=$2 + tempdir=$(mktemp -d) + downloader_installed="" + + function _apt_get_install() { + tempdir=$1 + + # copy current state of apt list - in order to revert back later (minimize contianer layer size) + cp -p -R /var/lib/apt/lists $tempdir + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + function _apt_get_cleanup() { + tempdir=$1 + + echo "removing wget" + apt-get -y purge wget --auto-remove + + echo "revert back apt lists" + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists + } + + function _apk_install() { + tempdir=$1 + # copy current state of apk cache - in order to revert back later (minimize contianer layer size) + cp -p -R /var/cache/apk $tempdir + + apk add --no-cache wget + } + + function _apk_cleanup() { + tempdir=$1 + + echo "removing wget" + apk del wget + } + # try to use either wget or curl if one of them already installer + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarly + if [ -z $downloader ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ]; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi + +} + +ensure_nanolayer() { + # Ensure existance of the nanolayer cli program + local variable_name=$1 + + local required_version=$2 + # normalize version + if ! [[ $required_version == v* ]]; then + required_version=v$required_version + fi + + local nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then + nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location" + fi + + # make sure its of the required version + if ! [[ -z "${nanolayer_location}" ]]; then + local current_version + current_version=$($nanolayer_location --version) + if ! [[ $current_version == v* ]]; then + current_version=v$current_version + fi + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + nanolayer_location="" + fi + fi + + fi + + # If not previuse installation found, download it temporarly and delete at the end of the script + if [[ -z "${nanolayer_location}" ]]; then + + if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up() { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ]; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + nanolayer_location=$tmp_dir/nanolayer + + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + declare -g ${variable_name}=$nanolayer_location + +} diff --git a/src/worktrunk/devcontainer-feature.json b/src/worktrunk/devcontainer-feature.json new file mode 100644 index 000000000..47811b0f1 --- /dev/null +++ b/src/worktrunk/devcontainer-feature.json @@ -0,0 +1,18 @@ +{ + "id": "worktrunk", + "version": "1.0.0", + "name": "worktrunk (via Github Releases)", + "documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/worktrunk", + "description": "Worktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows", + "options": { + "version": { + "default": "latest", + "description": "Select the version to install.", + "proposals": [ + "latest" + ], + "type": "string" + } + }, + "installsAfter": [] +} diff --git a/src/worktrunk/install.sh b/src/worktrunk/install.sh new file mode 100755 index 000000000..efdd12f8b --- /dev/null +++ b/src/worktrunk/install.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -e + +source ./library_scripts.sh + +# nanolayer is a cli utility which keeps container layers as small as possible +# source code: https://github.com/devcontainers-extra/nanolayer +# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations, +# and if missing - will download a temporary copy that automatically get deleted at the end +# of the script +ensure_nanolayer nanolayer_location "v0.5.6" + +# Example nanolayer installation via devcontainer-feature +$nanolayer_location \ + install \ + devcontainer-feature \ + "ghcr.io/devcontainers-extra/features/gh-release:1" \ + --option repo='max-sixty/worktrunk' --option binaryNames='wt' --option version="$VERSION" \ + --option assetRegex='worktrunk.*(tar).*' + +echo 'Done!' diff --git a/src/worktrunk/library_scripts.sh b/src/worktrunk/library_scripts.sh new file mode 100644 index 000000000..f6d0760d7 --- /dev/null +++ b/src/worktrunk/library_scripts.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +clean_download() { + # The purpose of this function is to download a file with minimal impact on container layer size + # this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a + # temporary manner, and making sure to + # 1. uninstall the downloader at the return of the function + # 2. revert back any changes to the package installer database/cache (for example apt-get lists) + # The above steps will minimize the leftovers being created while installing the downloader + # Supported distros: + # debian/ubuntu/alpine + + url=$1 + output_location=$2 + tempdir=$(mktemp -d) + downloader_installed="" + + function _apt_get_install() { + tempdir=$1 + + # copy current state of apt list - in order to revert back later (minimize contianer layer size) + cp -p -R /var/lib/apt/lists $tempdir + apt-get update -y + apt-get -y install --no-install-recommends wget ca-certificates + } + + function _apt_get_cleanup() { + tempdir=$1 + + echo "removing wget" + apt-get -y purge wget --auto-remove + + echo "revert back apt lists" + rm -rf /var/lib/apt/lists/* + rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists + } + + function _apk_install() { + tempdir=$1 + # copy current state of apk cache - in order to revert back later (minimize contianer layer size) + cp -p -R /var/cache/apk $tempdir + + apk add --no-cache wget + } + + function _apk_cleanup() { + tempdir=$1 + + echo "removing wget" + apk del wget + } + # try to use either wget or curl if one of them already installer + if type curl >/dev/null 2>&1; then + downloader=curl + elif type wget >/dev/null 2>&1; then + downloader=wget + else + downloader="" + fi + + # in case none of them is installed, install wget temporarly + if [ -z $downloader ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_install $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_install $tempdir + else + echo "distro not supported" + exit 1 + fi + downloader="wget" + downloader_installed="true" + fi + + if [ $downloader = "wget" ]; then + wget -q $url -O $output_location + else + curl -sfL $url -o $output_location + fi + + # NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because + # alpine lack bash, and RETURN is not a valid signal under sh shell + if ! [ -z $downloader_installed ]; then + if [ -x "/usr/bin/apt-get" ]; then + _apt_get_cleanup $tempdir + elif [ -x "/sbin/apk" ]; then + _apk_cleanup $tempdir + else + echo "distro not supported" + exit 1 + fi + fi + +} + +ensure_nanolayer() { + # Ensure existance of the nanolayer cli program + local variable_name=$1 + + local required_version=$2 + # normalize version + if ! [[ $required_version == v* ]]; then + required_version=v$required_version + fi + + local nanolayer_location="" + + # If possible - try to use an already installed nanolayer + if [[ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]]; then + if [[ -z "${NANOLAYER_CLI_LOCATION}" ]]; then + if type nanolayer >/dev/null 2>&1; then + echo "Found a pre-existing nanolayer in PATH" + nanolayer_location=nanolayer + fi + elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ]; then + nanolayer_location=${NANOLAYER_CLI_LOCATION} + echo "Found a pre-existing nanolayer which were given in env variable: $nanolayer_location" + fi + + # make sure its of the required version + if ! [[ -z "${nanolayer_location}" ]]; then + local current_version + current_version=$($nanolayer_location --version) + if ! [[ $current_version == v* ]]; then + current_version=v$current_version + fi + + if ! [ $current_version == $required_version ]; then + echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)" + nanolayer_location="" + fi + fi + + fi + + # If not previuse installation found, download it temporarly and delete at the end of the script + if [[ -z "${nanolayer_location}" ]]; then + + if [ "$(uname -sm)" == "Linux x86_64" ] || [ "$(uname -sm)" == "Linux aarch64" ]; then + tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX) + + clean_up() { + ARG=$? + rm -rf $tmp_dir + exit $ARG + } + trap clean_up EXIT + + if [ -x "/sbin/apk" ]; then + clib_type=musl + else + clib_type=gnu + fi + + tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz + + # clean download will minimize leftover in case a downloaderlike wget or curl need to be installed + clean_download https://github.com/devcontainers-extra/nanolayer/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename + + tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir" + chmod a+x $tmp_dir/nanolayer + nanolayer_location=$tmp_dir/nanolayer + + else + echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)" + exit 1 + fi + fi + + # Expose outside the resolved location + declare -g ${variable_name}=$nanolayer_location + +} diff --git a/test/archon/scenarios.json b/test/archon/scenarios.json new file mode 100644 index 000000000..ab5f5292f --- /dev/null +++ b/test/archon/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "archon": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "archon": { + "version": "0.3.4" + } + } + } +} diff --git a/test/archon/test.sh b/test/archon/test.sh new file mode 100755 index 000000000..2739ca851 --- /dev/null +++ b/test/archon/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "archon is installed" archon version + +reportResults diff --git a/test/archon/test_debian.sh b/test/archon/test_debian.sh new file mode 100755 index 000000000..2739ca851 --- /dev/null +++ b/test/archon/test_debian.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "archon is installed" archon version + +reportResults diff --git a/test/archon/test_specific_version.sh b/test/archon/test_specific_version.sh new file mode 100755 index 000000000..68dd898f3 --- /dev/null +++ b/test/archon/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "archon version is equal to 0.3.4" sh -c "archon version | grep '0.3.4'" + +reportResults diff --git a/test/bazel-compile-commands/scenarios.json b/test/bazel-compile-commands/scenarios.json new file mode 100644 index 000000000..a893dbd73 --- /dev/null +++ b/test/bazel-compile-commands/scenarios.json @@ -0,0 +1,25 @@ +{ + "test": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "ghcr.io/devcontainers-community/features/bazel:1": {}, + "bazel-compile-commands": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "ghcr.io/devcontainers-community/features/bazel:1": {}, + "bazel-compile-commands": { + "version": "0.22.4" + } + } + }, + "test_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "ghcr.io/devcontainers-community/features/bazel:1": {}, + "bazel-compile-commands": {} + } + } +} diff --git a/test/bazel-compile-commands/test.sh b/test/bazel-compile-commands/test.sh new file mode 100755 index 000000000..2b2cfd07a --- /dev/null +++ b/test/bazel-compile-commands/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "bazel-compile-commands is installed" bazel-compile-commands --version +check "bazelisk is installed" bazelisk --version + +reportResults diff --git a/test/bazel-compile-commands/test_debian.sh b/test/bazel-compile-commands/test_debian.sh new file mode 100755 index 000000000..2b2cfd07a --- /dev/null +++ b/test/bazel-compile-commands/test_debian.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "bazel-compile-commands is installed" bazel-compile-commands --version +check "bazelisk is installed" bazelisk --version + +reportResults diff --git a/test/bazel-compile-commands/test_specific_version.sh b/test/bazel-compile-commands/test_specific_version.sh new file mode 100644 index 000000000..a42376b83 --- /dev/null +++ b/test/bazel-compile-commands/test_specific_version.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +which bazel-compile-commands +check "bazel-compile-commands version is 0.22.4" sh -c "bazel-compile-commands --version 2>&1 | grep '0.22.4'" +check "bazelisk is installed" bazelisk --version + +reportResults diff --git a/test/claude-code/test_debian.sh b/test/claude-code/test_debian.sh index d8c00b08c..cdaa1733d 100644 --- a/test/claude-code/test_debian.sh +++ b/test/claude-code/test_debian.sh @@ -5,5 +5,7 @@ set -e source dev-container-features-test-lib check "claude is installed" claude --version +check "~/.claude is owned by the current user" test "$(stat -c %u "$HOME/.claude")" = "$(id -u)" +check "~/.local/share/claude is owned by the current user" test "$(stat -c %u "$HOME/.local/share/claude")" = "$(id -u)" reportResults diff --git a/test/gofumpt/scenarios.json b/test/gofumpt/scenarios.json new file mode 100644 index 000000000..ddb717af8 --- /dev/null +++ b/test/gofumpt/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "gofumpt": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "gofumpt": { + "version": "0.9.1" + } + } + } +} diff --git a/test/gofumpt/test.sh b/test/gofumpt/test.sh new file mode 100755 index 000000000..5b32aea25 --- /dev/null +++ b/test/gofumpt/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "gofumpt is installed" gofumpt --version + +reportResults diff --git a/test/gofumpt/test_debian.sh b/test/gofumpt/test_debian.sh new file mode 100755 index 000000000..5b32aea25 --- /dev/null +++ b/test/gofumpt/test_debian.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "gofumpt is installed" gofumpt --version + +reportResults diff --git a/test/gofumpt/test_specific_version.sh b/test/gofumpt/test_specific_version.sh new file mode 100755 index 000000000..cc1d6d3bb --- /dev/null +++ b/test/gofumpt/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "gofumpt version is equal to 0.9.1" sh -c "gofumpt --version | grep '0.9.1'" + +reportResults diff --git a/test/golangci-lint/scenarios.json b/test/golangci-lint/scenarios.json new file mode 100644 index 000000000..e02c2fb21 --- /dev/null +++ b/test/golangci-lint/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "golangci-lint": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "golangci-lint": { + "version": "2.11.3" + } + } + } +} diff --git a/test/golangci-lint/test.sh b/test/golangci-lint/test.sh new file mode 100755 index 000000000..0308f30a2 --- /dev/null +++ b/test/golangci-lint/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "golangci-lint is installed" golangci-lint --version + +reportResults diff --git a/test/golangci-lint/test_debian.sh b/test/golangci-lint/test_debian.sh new file mode 100755 index 000000000..0308f30a2 --- /dev/null +++ b/test/golangci-lint/test_debian.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "golangci-lint is installed" golangci-lint --version + +reportResults diff --git a/test/golangci-lint/test_specific_version.sh b/test/golangci-lint/test_specific_version.sh new file mode 100755 index 000000000..e673274ff --- /dev/null +++ b/test/golangci-lint/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "golangci-lint version is equal to 2.11.3" sh -c "golangci-lint --version | grep '2.11.3'" + +reportResults diff --git a/test/opengrep/scenarios.json b/test/opengrep/scenarios.json new file mode 100644 index 000000000..666eabe99 --- /dev/null +++ b/test/opengrep/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "opengrep": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "opengrep": { + "version": "1.18.0" + } + } + } +} diff --git a/test/opengrep/test.sh b/test/opengrep/test.sh new file mode 100755 index 000000000..54223790c --- /dev/null +++ b/test/opengrep/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "opengrep is installed" opengrep --version + +reportResults diff --git a/test/opengrep/test_debian.sh b/test/opengrep/test_debian.sh new file mode 100755 index 000000000..54223790c --- /dev/null +++ b/test/opengrep/test_debian.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "opengrep is installed" opengrep --version + +reportResults diff --git a/test/opengrep/test_specific_version.sh b/test/opengrep/test_specific_version.sh new file mode 100755 index 000000000..d7b816f74 --- /dev/null +++ b/test/opengrep/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "opengrep version is equal to 1.18.0" sh -c "opengrep --version | grep '1.18.0'" + +reportResults diff --git a/test/outagedeck/scenarios.json b/test/outagedeck/scenarios.json new file mode 100644 index 000000000..bb01553fa --- /dev/null +++ b/test/outagedeck/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_default": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "outagedeck": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "outagedeck": { + "version": "0.1.0" + } + } + } +} diff --git a/test/outagedeck/test.sh b/test/outagedeck/test.sh new file mode 100755 index 000000000..b0120b3d2 --- /dev/null +++ b/test/outagedeck/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "outagedeck is installed" outagedeck --version + +reportResults diff --git a/test/outagedeck/test_default.sh b/test/outagedeck/test_default.sh new file mode 100755 index 000000000..2602aad9b --- /dev/null +++ b/test/outagedeck/test_default.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "outagedeck is installed" outagedeck --version +check "outagedeck status command is available" sh -c "outagedeck help | grep 'outagedeck status'" + +reportResults diff --git a/test/outagedeck/test_specific_version.sh b/test/outagedeck/test_specific_version.sh new file mode 100755 index 000000000..d8f1a0491 --- /dev/null +++ b/test/outagedeck/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "outagedeck version is 0.1.0" sh -c "outagedeck --version | grep '0.1.0'" + +reportResults diff --git a/test/tflint/scenarios.json b/test/tflint/scenarios.json new file mode 100644 index 000000000..7cb7266ef --- /dev/null +++ b/test/tflint/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_default": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "tflint": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "tflint": { + "version": "0.62.0" + } + } + } +} \ No newline at end of file diff --git a/test/tflint/test.sh b/test/tflint/test.sh new file mode 100755 index 000000000..70c491bac --- /dev/null +++ b/test/tflint/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "something is installed" tflint --version + +reportResults diff --git a/test/tflint/test_default.sh b/test/tflint/test_default.sh new file mode 100755 index 000000000..96f1aaddd --- /dev/null +++ b/test/tflint/test_default.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "tflint is installed" tflint --version + +reportResults diff --git a/test/tflint/test_specific_version.sh b/test/tflint/test_specific_version.sh new file mode 100755 index 000000000..742971673 --- /dev/null +++ b/test/tflint/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "tflint version is equal to 0.62.0" sh -c "tflint --version | grep '0.62.0'" + +reportResults diff --git a/test/worktrunk/scenarios.json b/test/worktrunk/scenarios.json new file mode 100644 index 000000000..a40ab0053 --- /dev/null +++ b/test/worktrunk/scenarios.json @@ -0,0 +1,16 @@ +{ + "test_debian": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "worktrunk": {} + } + }, + "test_specific_version": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "worktrunk": { + "version": "0.35.2" + } + } + } +} diff --git a/test/worktrunk/test.sh b/test/worktrunk/test.sh new file mode 100755 index 000000000..b04829f67 --- /dev/null +++ b/test/worktrunk/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "wt is installed" wt --version + +reportResults diff --git a/test/worktrunk/test_debian.sh b/test/worktrunk/test_debian.sh new file mode 100755 index 000000000..b04829f67 --- /dev/null +++ b/test/worktrunk/test_debian.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "wt is installed" wt --version + +reportResults diff --git a/test/worktrunk/test_specific_version.sh b/test/worktrunk/test_specific_version.sh new file mode 100755 index 000000000..8a47888e5 --- /dev/null +++ b/test/worktrunk/test_specific_version.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +source dev-container-features-test-lib + +check "wt version is equal to 0.35.2" sh -c "wt --version 2>&1 | grep '0.35.2'" + +reportResults