diff --git a/.github/actions/smoke-test/build.sh b/.github/actions/smoke-test/build.sh index f7cb9b0..c3b0792 100755 --- a/.github/actions/smoke-test/build.sh +++ b/.github/actions/smoke-test/build.sh @@ -48,7 +48,7 @@ fi export DOCKER_BUILDKIT=1 echo "(*) Installing @devcontainer/cli" -npm install -g @devcontainers/cli +npm install --global @devcontainers/cli@0.88.0 echo "Building Dev Container" ID_LABEL="test-container=${TEMPLATE_ID}" diff --git a/.github/scripts/publish-devcontainer-assets.sh b/.github/scripts/publish-devcontainer-assets.sh new file mode 100755 index 0000000..2c7814e --- /dev/null +++ b/.github/scripts/publish-devcontainer-assets.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +set -euo pipefail + +registry="${1:-}" +namespace="${2:-}" + +if [[ -z "${registry}" || -z "${namespace}" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +for command in devcontainer jq oras; do + if ! command -v "${command}" >/dev/null 2>&1; then + echo "${command} is required." >&2 + exit 1 + fi +done + +collection_ref="${registry}/${namespace}:latest" +work_dir="$(mktemp -d)" +trap 'rm -rf "${work_dir}"' EXIT + +feature_collection_dir="${work_dir}/features" +template_collection_dir="${work_dir}/templates" +combined_collection_dir="${work_dir}/combined" +verified_collection_dir="${work_dir}/verified" +mkdir -p \ + "${feature_collection_dir}" \ + "${template_collection_dir}" \ + "${combined_collection_dir}" \ + "${verified_collection_dir}" + +echo "Publishing Features to ${registry}/${namespace}" +devcontainer features publish features/src \ + --registry "${registry}" \ + --namespace "${namespace}" +oras pull "${collection_ref}" --output "${feature_collection_dir}" + +echo "Publishing Templates to ${registry}/${namespace}" +devcontainer templates publish templates/src \ + --registry "${registry}" \ + --namespace "${namespace}" +oras pull "${collection_ref}" --output "${template_collection_dir}" + +feature_collection="${feature_collection_dir}/devcontainer-collection.json" +template_collection="${template_collection_dir}/devcontainer-collection.json" +combined_collection="${combined_collection_dir}/devcontainer-collection.json" +config_file="${combined_collection_dir}/config.json" + +jq -s \ + '{ + sourceInformation: (.[1].sourceInformation // .[0].sourceInformation), + features: (.[0].features // []), + templates: (.[1].templates // []) + }' \ + "${feature_collection}" \ + "${template_collection}" > "${combined_collection}" + +printf '{}\n' > "${config_file}" + +feature_count="$(find features/src -mindepth 2 -maxdepth 2 -name devcontainer-feature.json | wc -l)" +template_count="$(find templates/src -mindepth 2 -maxdepth 2 -name devcontainer-template.json | wc -l)" + +jq -e \ + --argjson feature_count "${feature_count}" \ + --argjson template_count "${template_count}" \ + '(.features | length) == $feature_count and (.templates | length) == $template_count' \ + "${combined_collection}" >/dev/null + +echo "Publishing combined collection metadata to ${collection_ref}" +( + cd "${combined_collection_dir}" + oras push "${collection_ref}" \ + --config "config.json:application/vnd.devcontainers" \ + --annotation "com.github.package.type=devcontainer_collection" \ + "devcontainer-collection.json:application/vnd.devcontainers.collection.layer.v1+json" +) + +oras pull "${collection_ref}" --output "${verified_collection_dir}" +verified_collection="${verified_collection_dir}/devcontainer-collection.json" + +jq -e \ + --argjson feature_count "${feature_count}" \ + --argjson template_count "${template_count}" \ + '(.features | length) == $feature_count and (.templates | length) == $template_count' \ + "${verified_collection}" >/dev/null + +echo "Verified combined collection metadata with ${feature_count} Feature(s) and ${template_count} Template(s)." diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1a73750..ea6eb30 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -59,6 +59,9 @@ jobs: - uses: actions/checkout@v6 + - name: Install devcontainer CLI + run: npm install --global @devcontainers/cli@0.88.0 + - name: Set release version id: bump run: | @@ -69,17 +72,20 @@ jobs: echo 'EOF' } >> "$GITHUB_OUTPUT" - - name: "Generate Template Docs" - uses: devcontainers/action@v1 - with: - base-path-to-templates: "./templates/src" - generate-docs: "true" + - name: Generate Template Docs + run: | + devcontainer templates generate-docs \ + --project-folder templates/src \ + --github-owner "${GITHUB_REPOSITORY_OWNER}" \ + --github-repo "${GITHUB_REPOSITORY#*/}" - - name: "Generate Feature Docs" - uses: devcontainers/action@v1 - with: - base-path-to-features: "./features/src" - generate-docs: "true" + - name: Generate Feature Docs + run: | + devcontainer features generate-docs \ + --project-folder features/src \ + --namespace "${GITHUB_REPOSITORY}" \ + --github-owner "${GITHUB_REPOSITORY_OWNER}" \ + --github-repo "${GITHUB_REPOSITORY#*/}" - name: Verify release versions env: @@ -109,27 +115,38 @@ jobs: echo "archive_name=$(basename "${archive_path}")" } >> "$GITHUB_OUTPUT" - - name: "Publish Features" - uses: devcontainers/action@v1 - with: - publish-features: ${{ steps.settings.outputs.dry_run != 'true' }} - validate-only: ${{ steps.settings.outputs.dry_run == 'true' }} - base-path-to-features: "./features/src" - disable-repo-tagging: "true" + - name: Validate Feature publishing metadata + if: ${{ steps.settings.outputs.dry_run == 'true' }} + run: | + devcontainer features package features/src \ + --output-folder "${RUNNER_TEMP}/features" \ + --force-clean-output-folder + - name: Install ORAS + if: ${{ steps.settings.outputs.dry_run != 'true' }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: "Publish Templates" + ORAS_VERSION: "1.3.3" + ORAS_SHA256: "9ce999f8d2de03fc03968b29d743077a58783e545e5eaa53917ca177352d0e59" + run: | + archive="${RUNNER_TEMP}/oras.tar.gz" + curl --fail --silent --show-error --location \ + "https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_amd64.tar.gz" \ + --output "${archive}" + echo "${ORAS_SHA256} ${archive}" | sha256sum --check + tar --extract --gzip --file "${archive}" --directory "${RUNNER_TEMP}" oras + echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}" + + - name: Log in to GHCR if: ${{ steps.settings.outputs.dry_run != 'true' }} - uses: devcontainers/action@v1 - with: - publish-templates: "true" - base-path-to-templates: "./templates/src" - disable-repo-tagging: "true" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: echo "${GH_TOKEN}" | oras login ghcr.io --username "${GITHUB_ACTOR}" --password-stdin + - name: Publish Dev Container assets + if: ${{ steps.settings.outputs.dry_run != 'true' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash .github/scripts/publish-devcontainer-assets.sh ghcr.io "${GITHUB_REPOSITORY}" - name: Upload release source bundle if: ${{ github.event_name == 'release' }} diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 1abd886..cefd21d 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -24,16 +24,16 @@ jobs: - uses: actions/checkout@v6 - name: Install devcontainer CLI - run: npm install -g @devcontainers/cli + run: npm install --global @devcontainers/cli@0.88.0 - name: Validate feature metadata run: bash features/test/test_all.sh - name: Validate Feature publishing metadata - uses: devcontainers/action@v1 - with: - validate-only: "true" - base-path-to-features: "./features/src" + run: | + devcontainer features package features/src \ + --output-folder "${RUNNER_TEMP}/features" \ + --force-clean-output-folder gen-docs: if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} @@ -48,17 +48,23 @@ jobs: ref: ${{ github.head_ref }} token: ${{ secrets.CI_BOT_TOKEN }} - - name: "Generate Template Docs" - uses: devcontainers/action@v1 - with: - base-path-to-templates: "./templates/src" - generate-docs: "true" + - name: Install devcontainer CLI + run: npm install --global @devcontainers/cli@0.88.0 - - name: "Generate Feature Docs" - uses: devcontainers/action@v1 - with: - base-path-to-features: "./features/src" - generate-docs: "true" + - name: Generate Template Docs + run: | + devcontainer templates generate-docs \ + --project-folder templates/src \ + --github-owner "${GITHUB_REPOSITORY_OWNER}" \ + --github-repo "${GITHUB_REPOSITORY#*/}" + + - name: Generate Feature Docs + run: | + devcontainer features generate-docs \ + --project-folder features/src \ + --namespace "${GITHUB_REPOSITORY}" \ + --github-owner "${GITHUB_REPOSITORY_OWNER}" \ + --github-repo "${GITHUB_REPOSITORY#*/}" - uses: stefanzweifel/git-auto-commit-action@v7 with: diff --git a/README.md b/README.md index 057ad3e..f48ebe6 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,4 @@ XWayland setups are auto-detected from the mounted runtime directory. ## Workflows - `.github/workflows/test-pr.yaml`: smoke tests template changes, validates features, and refreshes generated docs on pull requests -- `.github/workflows/release.yaml`: publishes from GitHub release runs, attaches a versioned source bundle to the release, and also supports manual versioned runs with a dry-run mode that only bumps manifests and regenerates docs in the workflow workspace +- `.github/workflows/release.yaml`: publishes Features and Templates with the Dev Container CLI, combines their discovery metadata under one OCI collection, attaches a versioned source bundle to GitHub releases, and supports manual dry runs