|
12 | 12 | GROUPS: '["dev-minor-and-patch-dependencies", "gh-actions-packages", "test-versions"]' |
13 | 13 |
|
14 | 14 | jobs: |
15 | | - dependabot: |
16 | | - if: github.event.pull_request.user.login == 'dependabot[bot]' |
17 | | - runs-on: ubuntu-latest |
18 | | - # Keep this job as a stable, always-green check on Dependabot PRs, even when the workflow is |
19 | | - # re-triggered by an automation commit (e.g., vendoring). Sensitive operations (OIDC token mint, |
20 | | - # approving, enabling auto-merge) are delegated to `dependabot-automation` below. |
21 | | - permissions: |
22 | | - contents: read |
23 | | - steps: |
24 | | - - name: Status |
25 | | - run: | |
26 | | - echo "Dependabot PR detected." |
27 | | - if [ "${{ github.actor }}" = "dependabot[bot]" ]; then |
28 | | - echo "Automation steps will run in the 'dependabot-automation' job." |
29 | | - else |
30 | | - echo "Skipping automation: workflow actor is '${{ github.actor }}'." |
31 | | - fi |
32 | | -
|
33 | 15 | dependabot-automation: |
34 | 16 | # Only run automation on the initial Dependabot-triggered run. If an automation commit is pushed |
35 | | - # (e.g. vendor output), GitHub re-triggers this workflow with `github.actor == 'dd-octo-sts[bot]'`. |
36 | | - # We intentionally avoid minting tokens / approving / enabling auto-merge on that follow-up run. |
| 17 | + # GitHub re-triggers this workflow with `github.actor == 'dd-octo-sts[bot]'`. We intentionally |
| 18 | + # avoid minting tokens / approving / enabling auto-merge on that follow-up run. |
37 | 19 | if: github.event.pull_request.user.login == 'dependabot[bot]' && github.actor == 'dependabot[bot]' |
38 | 20 | runs-on: ubuntu-latest |
39 | 21 | permissions: |
@@ -61,294 +43,3 @@ jobs: |
61 | 43 | env: |
62 | 44 | PR_URL: ${{ github.event.pull_request.html_url }} |
63 | 45 | GH_TOKEN: ${{ steps.octo-sts.outputs.token }} |
64 | | - |
65 | | - vendor-build: |
66 | | - if: github.event.pull_request.user.login == 'dependabot[bot]' |
67 | | - runs-on: ubuntu-latest |
68 | | - # Security: this job checks out and runs code from the PR (vendoring build), |
69 | | - # so it is intentionally restricted to read-only permissions and produces a |
70 | | - # patch artifact instead of pushing directly. |
71 | | - permissions: |
72 | | - contents: read |
73 | | - pull-requests: read |
74 | | - outputs: |
75 | | - has_changes: ${{ steps.diff.outputs.has_changes }} |
76 | | - is_vendor_group: ${{ steps.ctx.outputs.is_vendor_group }} |
77 | | - steps: |
78 | | - - name: Dependabot metadata |
79 | | - id: metadata |
80 | | - uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # 2.5.0 |
81 | | - - name: Compute vendor context |
82 | | - id: ctx |
83 | | - run: | |
84 | | - set -euo pipefail |
85 | | -
|
86 | | - echo "is_vendor_group=${{ steps.metadata.outputs.directory == '/vendor' }}" >> $GITHUB_OUTPUT |
87 | | - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
88 | | - if: steps.ctx.outputs.is_vendor_group == 'true' |
89 | | - with: |
90 | | - repository: ${{ github.event.pull_request.head.repo.full_name }} |
91 | | - ref: ${{ github.event.pull_request.head.sha }} |
92 | | - fetch-depth: 1 |
93 | | - persist-credentials: false |
94 | | - - name: Restore trusted Node setup actions |
95 | | - if: steps.ctx.outputs.is_vendor_group == 'true' |
96 | | - run: | |
97 | | - git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}" |
98 | | - git checkout "${{ github.event.pull_request.base.sha }}" -- .github/actions/node |
99 | | - - name: Restore trusted vendoring scripts |
100 | | - if: steps.ctx.outputs.is_vendor_group == 'true' |
101 | | - run: | |
102 | | - git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}" |
103 | | - git checkout "${{ github.event.pull_request.base.sha }}" -- vendor/rspack.js vendor/rspack.config.js |
104 | | - - uses: ./.github/actions/node/active-lts |
105 | | - if: steps.ctx.outputs.is_vendor_group == 'true' |
106 | | - - name: Install vendoring deps (no lifecycle scripts) |
107 | | - if: steps.ctx.outputs.is_vendor_group == 'true' |
108 | | - run: yarn --ignore-scripts --frozen-lockfile --non-interactive |
109 | | - working-directory: ./vendor |
110 | | - - name: Build vendored bundles (trusted script) |
111 | | - if: steps.ctx.outputs.is_vendor_group == 'true' |
112 | | - run: node ./rspack.js |
113 | | - working-directory: ./vendor |
114 | | - - name: Create patch (restricted paths only) |
115 | | - id: diff |
116 | | - run: | |
117 | | - set -euo pipefail |
118 | | -
|
119 | | - if [ "${{ steps.ctx.outputs.is_vendor_group }}" != "true" ]; then |
120 | | - echo "has_changes=false" >> $GITHUB_OUTPUT |
121 | | - exit 0 |
122 | | - fi |
123 | | -
|
124 | | - if git diff --quiet; then |
125 | | - echo "has_changes=false" >> $GITHUB_OUTPUT |
126 | | - exit 0 |
127 | | - fi |
128 | | -
|
129 | | - allowed_prefix_1="vendor/dist/" |
130 | | - allowed_file_1="vendor/package.json" |
131 | | - allowed_file_2="vendor/yarn.lock" |
132 | | -
|
133 | | - bad=0 |
134 | | - while IFS= read -r file; do |
135 | | - case "$file" in |
136 | | - "$allowed_file_1" | "$allowed_file_2" | "$allowed_prefix_1"*) |
137 | | - ;; |
138 | | - *) |
139 | | - echo "Unexpected changed path: $file" |
140 | | - bad=1 |
141 | | - ;; |
142 | | - esac |
143 | | - done < <(git diff --name-only) |
144 | | -
|
145 | | - if [ "$bad" -ne 0 ]; then |
146 | | - echo "Refusing to proceed: unexpected paths changed during vendoring." |
147 | | - exit 1 |
148 | | - fi |
149 | | -
|
150 | | - git diff --binary --no-color > "${RUNNER_TEMP}/vendor.patch" |
151 | | - echo "has_changes=true" >> $GITHUB_OUTPUT |
152 | | - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 |
153 | | - if: steps.diff.outputs.has_changes == 'true' |
154 | | - with: |
155 | | - name: vendor-patch |
156 | | - path: ${{ runner.temp }}/vendor.patch |
157 | | - if-no-files-found: error |
158 | | - |
159 | | - vendor-push: |
160 | | - if: github.event.pull_request.user.login == 'dependabot[bot]' && needs.vendor-build.outputs.is_vendor_group == 'true' && needs.vendor-build.outputs.has_changes == 'true' |
161 | | - runs-on: ubuntu-latest |
162 | | - needs: vendor-build |
163 | | - # Security: this job never runs installs/builds. |
164 | | - # It only applies the vetted patch artifact and writes the update via the GitHub API. |
165 | | - permissions: |
166 | | - id-token: write |
167 | | - steps: |
168 | | - - uses: DataDog/dd-octo-sts-action@acaa02eee7e3bb0839e4272dacb37b8f3b58ba80 # v1.0.3 |
169 | | - id: octo-sts |
170 | | - with: |
171 | | - scope: DataDog/dd-trace-js |
172 | | - policy: dependabot-automation |
173 | | - - name: Dependabot metadata |
174 | | - id: metadata |
175 | | - uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # 2.5.0 |
176 | | - with: |
177 | | - github-token: "${{ steps.octo-sts.outputs.token }}" |
178 | | - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
179 | | - with: |
180 | | - token: ${{ steps.octo-sts.outputs.token }} |
181 | | - repository: ${{ github.event.pull_request.head.repo.full_name }} |
182 | | - ref: ${{ github.event.pull_request.head.sha }} |
183 | | - persist-credentials: false |
184 | | - - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 |
185 | | - with: |
186 | | - name: vendor-patch |
187 | | - path: ${{ runner.temp }}/vendor-artifact |
188 | | - - name: Apply patch |
189 | | - run: git apply --whitespace=nowarn "${{ runner.temp }}/vendor-artifact/vendor.patch" |
190 | | - - name: Validate changed paths |
191 | | - run: | |
192 | | - set -euo pipefail |
193 | | -
|
194 | | - allowed_prefix_1="vendor/dist/" |
195 | | - allowed_file_1="vendor/package.json" |
196 | | - allowed_file_2="vendor/yarn.lock" |
197 | | -
|
198 | | - bad=0 |
199 | | - while IFS= read -r file; do |
200 | | - case "$file" in |
201 | | - "$allowed_file_1" | "$allowed_file_2" | "$allowed_prefix_1"*) |
202 | | - ;; |
203 | | - *) |
204 | | - echo "Unexpected changed path after applying patch: $file" |
205 | | - bad=1 |
206 | | - ;; |
207 | | - esac |
208 | | - done < <(git diff --name-only) |
209 | | -
|
210 | | - if [ "$bad" -ne 0 ]; then |
211 | | - echo "Refusing to proceed: unexpected paths changed." |
212 | | - exit 1 |
213 | | - fi |
214 | | - - name: Create verified commit via GitHub API (server-side) |
215 | | - env: |
216 | | - TARGET_BRANCH: ${{ github.event.pull_request.head.ref }} |
217 | | - GH_TOKEN: ${{ steps.octo-sts.outputs.token }} |
218 | | - run: | |
219 | | - set -euo pipefail |
220 | | -
|
221 | | - repo="${GITHUB_REPOSITORY}" |
222 | | - expected_head_oid="$(git rev-parse HEAD)" |
223 | | -
|
224 | | - max_files=200 |
225 | | - max_total_bytes=$((10 * 1024 * 1024)) # 10 MiB |
226 | | -
|
227 | | - mapfile -t changes < <(git diff --name-status) |
228 | | - change_count="${#changes[@]}" |
229 | | - if [ "$change_count" -eq 0 ]; then |
230 | | - echo "No changed files detected." |
231 | | - exit 1 |
232 | | - fi |
233 | | - if [ "$change_count" -gt "$max_files" ]; then |
234 | | - echo "Too many changed files ($change_count > $max_files)." |
235 | | - exit 1 |
236 | | - fi |
237 | | -
|
238 | | - additions_file="${RUNNER_TEMP}/vendor-additions.ndjson" |
239 | | - deletions_file="${RUNNER_TEMP}/vendor-deletions.ndjson" |
240 | | - encoded_file="${RUNNER_TEMP}/vendor-file.base64" |
241 | | - : > "$additions_file" |
242 | | - : > "$deletions_file" |
243 | | - total_bytes=0 |
244 | | - for change in "${changes[@]}"; do |
245 | | - read -r status path path2 <<<"$change" |
246 | | -
|
247 | | - if [[ "$status" == D ]]; then |
248 | | - jq -nc --arg path "$path" '{path: $path}' >> "$deletions_file" |
249 | | - continue |
250 | | - fi |
251 | | -
|
252 | | - # Treat renames as delete+add to keep the server-side tree in sync. |
253 | | - if [[ "$status" == R* ]]; then |
254 | | - jq -nc --arg path "$path" '{path: $path}' >> "$deletions_file" |
255 | | - path="$path2" |
256 | | - fi |
257 | | -
|
258 | | - test -f "$path" |
259 | | - file_bytes="$(stat -c '%s' "$path")" |
260 | | - total_bytes=$((total_bytes + file_bytes)) |
261 | | - if [ "$total_bytes" -gt "$max_total_bytes" ]; then |
262 | | - echo "Total changes too large (${total_bytes} bytes)." |
263 | | - exit 1 |
264 | | - fi |
265 | | -
|
266 | | - base64 -w 0 "$path" > "$encoded_file" |
267 | | - jq -nc --arg path "$path" --rawfile contents "$encoded_file" \ |
268 | | - '{path: $path, contents: $contents}' >> "$additions_file" |
269 | | - done |
270 | | -
|
271 | | - variables_file="${RUNNER_TEMP}/graphql-variables.json" |
272 | | - jq -n \ |
273 | | - --arg repo "$repo" \ |
274 | | - --arg branch "$TARGET_BRANCH" \ |
275 | | - --arg msg "update vendored dependencies with new versions" \ |
276 | | - --arg expected "$expected_head_oid" \ |
277 | | - --slurpfile additions "$additions_file" \ |
278 | | - --slurpfile deletions "$deletions_file" \ |
279 | | - '{ |
280 | | - input: { |
281 | | - branch: { repositoryNameWithOwner: $repo, branchName: $branch }, |
282 | | - message: { headline: $msg }, |
283 | | - expectedHeadOid: $expected, |
284 | | - fileChanges: { additions: $additions, deletions: $deletions } |
285 | | - } |
286 | | - }' > "$variables_file" |
287 | | -
|
288 | | - query='mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid url } } }' |
289 | | - request_file="${RUNNER_TEMP}/graphql-request.json" |
290 | | - jq -n \ |
291 | | - --arg query "$query" \ |
292 | | - --slurpfile variables "$variables_file" \ |
293 | | - '{query: $query, variables: $variables[0]}' > "$request_file" |
294 | | -
|
295 | | - gh api graphql --input "$request_file" -q '.data.createCommitOnBranch.commit.oid' >/dev/null |
296 | | -
|
297 | | - # If branch protection is configured to dismiss stale approvals when new commits are pushed, |
298 | | - # the vendoring commit will invalidate the earlier approval. Re-approve and (re-)enable |
299 | | - # auto-merge after pushing so Dependabot PRs can still merge automatically. |
300 | | - - name: Approve a PR (after vendoring commit) |
301 | | - if: contains(fromJSON(env.GROUPS), steps.metadata.outputs.dependency-group) |
302 | | - run: gh pr review --approve "$PR_URL" |
303 | | - env: |
304 | | - PR_URL: ${{ github.event.pull_request.html_url }} |
305 | | - GH_TOKEN: ${{ steps.octo-sts.outputs.token }} |
306 | | - - name: Enable auto-merge for Dependabot PRs (after vendoring commit) |
307 | | - if: contains(fromJSON(env.GROUPS), steps.metadata.outputs.dependency-group) |
308 | | - run: gh pr merge --auto --squash "$PR_URL" |
309 | | - env: |
310 | | - PR_URL: ${{ github.event.pull_request.html_url }} |
311 | | - GH_TOKEN: ${{ steps.octo-sts.outputs.token }} |
312 | | - |
313 | | - vendor-validate: |
314 | | - # Run validation after the generated vendor patch has been pushed, to ensure the PR contains |
315 | | - # the committed `vendor/dist/*` outputs. This runs inside the same workflow as the push, so it |
316 | | - # doesn't rely on additional workflows being triggered by that push. |
317 | | - if: github.event.pull_request.user.login == 'dependabot[bot]' && needs.vendor-build.outputs.is_vendor_group == 'true' && needs.vendor-build.outputs.has_changes == 'true' |
318 | | - runs-on: ubuntu-latest |
319 | | - needs: |
320 | | - - vendor-build |
321 | | - - vendor-push |
322 | | - permissions: |
323 | | - contents: read |
324 | | - pull-requests: read |
325 | | - steps: |
326 | | - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
327 | | - with: |
328 | | - repository: ${{ github.event.pull_request.head.repo.full_name }} |
329 | | - ref: ${{ github.event.pull_request.head.ref }} |
330 | | - fetch-depth: 1 |
331 | | - persist-credentials: false |
332 | | - - name: Restore trusted Node setup actions |
333 | | - run: | |
334 | | - git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}" |
335 | | - git checkout "${{ github.event.pull_request.base.sha }}" -- .github/actions/node |
336 | | - - name: Restore trusted vendoring scripts |
337 | | - run: | |
338 | | - git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}" |
339 | | - git checkout "${{ github.event.pull_request.base.sha }}" -- vendor/rspack.js vendor/rspack.config.js |
340 | | - - uses: ./.github/actions/node/active-lts |
341 | | - # Running `yarn` also automatically runs Rspack as a postinstall script. |
342 | | - - run: yarn --frozen-lockfile |
343 | | - working-directory: vendor |
344 | | - - name: Ensure no untracked outputs |
345 | | - run: | |
346 | | - set -euo pipefail |
347 | | -
|
348 | | - if [ -n "$(git status --porcelain)" ]; then |
349 | | - echo "Working tree is dirty after vendoring:" |
350 | | - git status --porcelain |
351 | | - exit 1 |
352 | | - fi |
353 | | - - name: Diff only expected paths |
354 | | - run: git diff --exit-code -- vendor/dist vendor/package.json vendor/yarn.lock |
0 commit comments