-
Notifications
You must be signed in to change notification settings - Fork 1
185 lines (166 loc) · 7.19 KB
/
Copy pathrelease.yml
File metadata and controls
185 lines (166 loc) · 7.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
name: "Release"
# Auto-release: when a PR is merged into main, detect every feature whose
# `version` field changed in `src/<feature>/devcontainer-feature.json`. The
# `devcontainers/action` step always scans the full `./src` tree, but GHCR
# publishing is idempotent: only versions not already present on GHCR are
# pushed, so unchanged features are effectively skipped at the registry level.
# This workflow uses the diff to (1) skip the whole job when nothing was
# bumped, (2) create per-feature git tags `<feature>-v<version>` for the
# bumped ones, and (3) dispatch one website rebuild event per bumped feature.
on:
workflow_dispatch:
inputs:
force-all:
description: "Republish ALL features regardless of version diff (recovery only)"
type: boolean
default: false
push:
branches:
- main
paths:
- "src/*/devcontainer-feature.json"
jobs:
detect:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.diff.outputs.changed }}
count: ${{ steps.diff.outputs.count }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # need the full push range (github.event.before .. HEAD) for diff
- name: Detect features with bumped version
id: diff
shell: bash
env:
FORCE_ALL: ${{ github.event_name == 'workflow_dispatch' && inputs.force-all }}
# `before` is the SHA prior to the push; for workflow_dispatch it is empty.
BEFORE_SHA: ${{ github.event.before }}
run: |
set -euo pipefail
changed_json='[]'
# Manifests use JSONC (line comments) — devcontainers/action tolerates
# them at publish time, so we do the same here: strip leading `//`
# comment lines before parsing with jq.
read_field() {
local file="$1" filter="$2"
sed -E 's@^[[:space:]]*//.*$@@' "$file" | jq -r "$filter"
}
# Resolve the base ref against which we diff. A push can contain
# several commits, so HEAD~1..HEAD is unsafe (it only sees the very
# last commit and would miss manifest bumps pushed earlier in the
# same batch — see release run #35). We use `github.event.before`
# which spans the entire push range. Fallbacks cover the
# workflow_dispatch and initial-push cases.
ZERO_SHA="0000000000000000000000000000000000000000"
if [ -n "${BEFORE_SHA:-}" ] && [ "${BEFORE_SHA}" != "${ZERO_SHA}" ] \
&& git cat-file -e "${BEFORE_SHA}^{commit}" 2>/dev/null; then
BASE_REF="${BEFORE_SHA}"
else
BASE_REF="HEAD~1"
fi
echo "::notice::Diff base = ${BASE_REF} → HEAD ($(git rev-parse HEAD))"
if [ "${FORCE_ALL}" = "true" ]; then
echo "::notice::workflow_dispatch with force-all=true → republishing every feature"
mapfile -t files < <(ls src/*/devcontainer-feature.json 2>/dev/null || true)
else
# All feature manifests touched anywhere in the push range.
mapfile -t files < <(git diff --name-only "${BASE_REF}..HEAD" -- 'src/*/devcontainer-feature.json' || true)
fi
for f in "${files[@]}"; do
[ -f "$f" ] || continue
name=$(read_field "$f" '.id')
new_version=$(read_field "$f" '.version')
if [ "${FORCE_ALL}" = "true" ]; then
old_version="<forced>"
else
# Best-effort previous version (file may not exist on the base ref → new feature)
old_version=$(git show "${BASE_REF}:$f" 2>/dev/null | sed -E 's@^[[:space:]]*//.*$@@' | jq -r '.version' 2>/dev/null || echo "")
fi
if [ "${FORCE_ALL}" != "true" ] && [ "$old_version" = "$new_version" ]; then
echo "→ $name unchanged ($new_version), skipping"
continue
fi
echo "→ $name: ${old_version:-<new>} → $new_version"
changed_json=$(jq -c --arg n "$name" --arg v "$new_version" '. + [{name:$n, version:$v}]' <<< "$changed_json")
done
count=$(jq 'length' <<< "$changed_json")
echo "count=$count" >> "$GITHUB_OUTPUT"
echo "changed=$changed_json" >> "$GITHUB_OUTPUT"
echo "Detected $count feature(s) to publish."
release:
needs: detect
if: ${{ needs.detect.outputs.count != '0' }}
runs-on: ubuntu-latest
permissions:
contents: write # tags
packages: write # GHCR
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Log in to the Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish features
uses: devcontainers/action@v1
with:
publish-features: "true"
base-path-to-features: "./src"
generate-docs: "true"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create per-feature git tags
env:
CHANGED: ${{ needs.detect.outputs.changed }}
run: |
set -euo pipefail
echo "$CHANGED" | jq -c '.[]' | while read -r row; do
name=$(jq -r '.name' <<< "$row")
version=$(jq -r '.version' <<< "$row")
tag="${name}-v${version}"
if git rev-parse -q --verify "refs/tags/${tag}" > /dev/null; then
echo "→ tag ${tag} already exists, skipping"
continue
fi
git tag -a "${tag}" -m "Release ${name} v${version}"
git push origin "${tag}"
echo "→ created tag ${tag}"
done
- name: Get Trigganator token
id: trigganator
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.TRIGGANATOR_ID }}
private-key: ${{ secrets.TRIGGANATOR_KEY }}
owner: helpers4
repositories: website
permission-contents: write
- name: Trigger website update
continue-on-error: true
env:
CHANGED: ${{ needs.detect.outputs.changed }}
GH_TOKEN: ${{ steps.trigganator.outputs.token }}
run: |
set -euo pipefail
# One dispatch per feature so the website workflow can react per-feature.
echo "$CHANGED" | jq -c '.[]' | while read -r row; do
name=$(jq -r '.name' <<< "$row")
version=$(jq -r '.version' <<< "$row")
payload=$(jq -nc --arg n "$name" --arg v "$version" --arg sha "${{ github.sha }}" \
'{feature:$n, version:$v, ref:$sha, sha:$sha, source:"devcontainer-release"}')
echo "→ dispatch website: $name v$version"
gh api \
-X POST \
-H "Accept: application/vnd.github+json" \
"/repos/helpers4/website/dispatches" \
-f event_type=devcontainer-release \
--raw-field "client_payload=$payload" || echo "⚠️ dispatch failed for $name"
done