microsoft/TypeAgent

Public

mirrored from https://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/optimize-grammar-matcher-efficiency

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/workflows/fix-dependabot-alerts.yml

390lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4# Automatically remediate Dependabot security alerts by running the
5# fix-dependabot-alerts script, verifying the build for each fix,
6# and opening a pull request with the passing changes.
7#
8# Uses a GitHub App (via actions/create-github-app-token) to authenticate
9# with the Dependabot alerts REST API, since GITHUB_TOKEN does not have
10# access to this endpoint. Requires DEPENDABOT_APP_ID (variable) and
11# DEPENDABOT_APP_PRIVATE_KEY (secret).
12
13name: fix-dependabot-alerts
14
15on:
16 schedule:
17 # Run weekly on Monday at 9:00 UTC
18 - cron: "0 9 * * 1"
19 workflow_dispatch:
20 inputs:
21 dry-run:
22 description: "Dry run — analyse only, don't apply fixes"
23 type: boolean
24 default: false
25 auto-fix-args:
26 description: "Extra flags for the script (e.g. --apply-overrides=pkg1,pkg2)"
27 type: string
28 default: "--auto-fix"
29
30concurrency:
31 group: ${{ github.workflow }}
32 cancel-in-progress: true
33
34permissions:
35 contents: write
36 pull-requests: write
37
38env:
39 ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron
40 ELECTRON_BUILDER_CACHE: ${{ github.workspace }}/.cache/electron-builder
41
42jobs:
43 fix-alerts:
44 runs-on: ubuntu-latest
45
46 steps:
47 - uses: actions/checkout@v4
48 with:
49 fetch-depth: 0
50
51 - uses: pnpm/action-setup@v4
52 name: Install pnpm
53 with:
54 package_json_file: ts/package.json
55
56 - uses: actions/setup-node@v4
57 with:
58 node-version: 22
59 cache: "pnpm"
60 cache-dependency-path: ts/pnpm-lock.yaml
61
62 - name: Generate GitHub App token
63 id: app-token
64 uses: actions/create-github-app-token@v1
65 with:
66 app-id: ${{ vars.DEPENDABOT_APP_ID }}
67 private-key: ${{ secrets.DEPENDABOT_APP_PRIVATE_KEY }}
68
69 - name: Verify gh authentication
70 env:
71 GH_TOKEN: ${{ steps.app-token.outputs.token }}
72 run: |
73 gh auth status
74 echo "---"
75 gh api repos/${{ github.repository }}/dependabot/alerts?per_page=1 --jq 'length' || echo "API call failed with exit $?"
76
77 # ── Analyse and fix alerts across workspaces ──────────────────────
78 #
79 # Auto-discovers which workspaces have open alerts by querying the
80 # Dependabot API, then for each workspace:
81 # 1. Installs dependencies (if not already installed)
82 # 2. Dry-run to discover fixable packages
83 # 3. Applies each package's fix individually
84 # 4. Build-checks after each; rolls back failures
85 # 5. Only passing fixes survive into the PR
86 - name: Analyse and fix alerts
87 id: fix
88 env:
89 GH_TOKEN: ${{ steps.app-token.outputs.token }}
90 run: |
91 SCRIPT="$GITHUB_WORKSPACE/ts/tools/scripts/fix-dependabot-alerts.mjs"
92 FLAGS="${{ inputs.auto-fix-args || '--auto-fix' }}"
93 ALL_APPLIED=""
94 ALL_ROLLED_BACK=""
95 ALL_BLOCKED_PACKAGES=""
96 ALL_NO_PATCH_PACKAGES=""
97 TOTAL_RESOLVED=0
98 TOTAL_BLOCKED=0
99 TOTAL_FAILED=0
100
101 # Discover workspaces with open npm alerts from the Dependabot API
102 WORKSPACES=$(gh api "repos/${{ github.repository }}/dependabot/alerts?state=open&per_page=100" \
103 --paginate \
104 --jq '[.[] | select(.dependency.package.ecosystem == "npm") | .dependency.manifest_path | split("/")[0]] | unique | .[]')
105
106 if [ -z "$WORKSPACES" ]; then
107 echo "No open npm Dependabot alerts found 🎉"
108 echo "resolved=0" >> "$GITHUB_OUTPUT"
109 echo "blocked=0" >> "$GITHUB_OUTPUT"
110 echo "failed=0" >> "$GITHUB_OUTPUT"
111 echo "changes=false" >> "$GITHUB_OUTPUT"
112 exit 0
113 fi
114 echo "Workspaces with alerts: $WORKSPACES"
115
116 for WS_DIR in $WORKSPACES; do
117 echo "=========================================="
118 echo "Processing workspace: $WS_DIR"
119 echo "=========================================="
120 cd "$GITHUB_WORKSPACE/$WS_DIR"
121
122 # Install dependencies for this workspace
123 echo "::group::Installing $WS_DIR dependencies"
124 corepack enable
125 if ! pnpm install --frozen-lockfile 2>&1; then
126 echo "::warning::Dependency install failed for $WS_DIR — skipping (analysis would be unreliable)"
127 echo "::endgroup::"
128 continue
129 fi
130 echo "::endgroup::"
131
132 # ── Step 1: Discover fixable packages ───────────────────
133 echo "::group::Analysing $WS_DIR alerts"
134 node "$SCRIPT" --dry-run --json --skip-install > /tmp/dep-analysis.json 2>/tmp/dep-analysis.log || true
135
136 if ! jq -e '.summary' /tmp/dep-analysis.json > /dev/null 2>&1; then
137 echo "No valid analysis for $WS_DIR — skipping"
138 cat /tmp/dep-analysis.log || true
139 echo "::endgroup::"
140 continue
141 fi
142
143 WS_BLOCKED=$(jq '.summary.blocked' /tmp/dep-analysis.json)
144 WS_NO_PATCH=$(jq '.summary.noPatch' /tmp/dep-analysis.json)
145 BLOCKED_PKGS=$(jq -r '[.blocked[].package] | unique | join(", ")' /tmp/dep-analysis.json)
146 NO_PATCH_PKGS=$(jq -r '[.noPatch[].package] | unique | join(", ")' /tmp/dep-analysis.json)
147
148 FIXABLE=$(jq -r '.resolved[].package' /tmp/dep-analysis.json | sort -u)
149 FIXABLE_COUNT=$(echo "$FIXABLE" | grep -c . || true)
150 echo "Fixable $WS_DIR packages ($FIXABLE_COUNT): $FIXABLE"
151 echo "::endgroup::"
152
153 # Accumulate blocked/no-patch
154 TOTAL_BLOCKED=$((TOTAL_BLOCKED + WS_BLOCKED))
155 [ -n "$BLOCKED_PKGS" ] && ALL_BLOCKED_PACKAGES="${ALL_BLOCKED_PACKAGES:+$ALL_BLOCKED_PACKAGES, }$BLOCKED_PKGS"
156 [ -n "$NO_PATCH_PKGS" ] && ALL_NO_PATCH_PACKAGES="${ALL_NO_PATCH_PACKAGES:+$ALL_NO_PATCH_PACKAGES, }$NO_PATCH_PKGS"
157
158 if [ "$FIXABLE_COUNT" -eq 0 ]; then
159 echo "No fixable alerts in $WS_DIR"
160 continue
161 fi
162
163 if [ "${{ inputs.dry-run }}" == "true" ]; then
164 TOTAL_RESOLVED=$((TOTAL_RESOLVED + FIXABLE_COUNT))
165 continue
166 fi
167
168 # ── Step 2: Apply fixes one package at a time ───────────
169 for PKG in $FIXABLE; do
170 echo "::group::Fixing $WS_DIR/$PKG"
171
172 # Save a rollback point
173 cp package.json /tmp/pkg-backup.json
174 cp pnpm-lock.yaml /tmp/lock-backup.yaml 2>/dev/null || true
175
176 # Build targeted args
177 read -r -a extra_args <<< "$FLAGS"
178 filtered_args=()
179 for arg in "${extra_args[@]}"; do
180 case "$arg" in
181 --auto-fix|--auto-fix=*)
182 ;;
183 *)
184 filtered_args+=("$arg")
185 ;;
186 esac
187 done
188 filtered_args+=("--auto-fix=$PKG")
189 filtered_args+=("--skip-install")
190 echo "Running: fix-dependabot-alerts.mjs ${filtered_args[*]}"
191 set +e
192 node "$SCRIPT" "${filtered_args[@]}" 2>&1
193 fix_exit=$?
194 set -e
195
196 if [ "$fix_exit" -ne 0 ] && git diff --quiet; then
197 echo "::warning::Script failed for $PKG in $WS_DIR (exit $fix_exit) with no file changes"
198 TOTAL_FAILED=$((TOTAL_FAILED + 1))
199 echo "::endgroup::"
200 continue
201 fi
202
203 if ! git diff --quiet; then
204 echo "Changes detected, reinstalling and verifying build..."
205 set +e
206 if [ "$WS_DIR" = "ts" ]; then
207 pnpm install --frozen-lockfile --strict-peer-dependencies 2>&1
208 else
209 pnpm install --frozen-lockfile 2>&1
210 fi
211 install_exit=$?
212 set -e
213
214 if [ "$install_exit" -ne 0 ]; then
215 echo "::warning::pnpm install failed after fixing $PKG in $WS_DIR — rolling back"
216 cp /tmp/pkg-backup.json package.json
217 cp /tmp/lock-backup.yaml pnpm-lock.yaml 2>/dev/null || true
218 pnpm install 2>&1 || true
219 git checkout -- . 2>/dev/null || true
220 ALL_ROLLED_BACK="$ALL_ROLLED_BACK $PKG"
221 TOTAL_FAILED=$((TOTAL_FAILED + 1))
222 echo "::endgroup::"
223 continue
224 fi
225
226 if [ "$WS_DIR" = "ts" ]; then
227 node tools/scripts/repo-policy-check.mjs --fix 2>/dev/null || true
228 fi
229
230 set +e
231 pnpm run build 2>&1
232 build_exit=$?
233 set -e
234
235 if [ "$build_exit" -ne 0 ]; then
236 echo "::warning::Build failed after fixing $PKG in $WS_DIR — rolling back"
237 cp /tmp/pkg-backup.json package.json
238 cp /tmp/lock-backup.yaml pnpm-lock.yaml 2>/dev/null || true
239 pnpm install 2>&1 || true
240 git checkout -- . 2>/dev/null || true
241 ALL_ROLLED_BACK="$ALL_ROLLED_BACK $PKG"
242 TOTAL_FAILED=$((TOTAL_FAILED + 1))
243 else
244 echo "✅ $PKG fixed and build passed"
245 ALL_APPLIED="$ALL_APPLIED $PKG"
246 TOTAL_RESOLVED=$((TOTAL_RESOLVED + 1))
247 fi
248 else
249 echo "No changes for $PKG (may already be fixed)"
250 fi
251
252 echo "::endgroup::"
253 done
254 done
255
256 # ── Step 3: Report results ──────────────────────────────────
257 echo "resolved=$TOTAL_RESOLVED" >> "$GITHUB_OUTPUT"
258 echo "blocked=$TOTAL_BLOCKED" >> "$GITHUB_OUTPUT"
259 echo "failed=$TOTAL_FAILED" >> "$GITHUB_OUTPUT"
260 echo "applied_packages=$ALL_APPLIED" >> "$GITHUB_OUTPUT"
261 echo "rolled_back_packages=$ALL_ROLLED_BACK" >> "$GITHUB_OUTPUT"
262 echo "blocked_packages=$ALL_BLOCKED_PACKAGES" >> "$GITHUB_OUTPUT"
263 echo "no_patch_packages=$ALL_NO_PATCH_PACKAGES" >> "$GITHUB_OUTPUT"
264
265 cd "$GITHUB_WORKSPACE"
266 if git diff --quiet; then
267 echo "changes=false" >> "$GITHUB_OUTPUT"
268 else
269 echo "changes=true" >> "$GITHUB_OUTPUT"
270 echo "Files changed:"
271 git diff --stat
272 fi
273
274 # ── Final build + shell packaging verification ──────────────────
275 - name: Final build verification
276 if: ${{ steps.fix.outputs.changes == 'true' }}
277 id: build
278 working-directory: ts
279 run: |
280 node tools/scripts/repo-policy-check.mjs --fix || true
281 pnpm run build
282 echo "build_ok=true" >> "$GITHUB_OUTPUT"
283 env:
284 DEBUG_DEMB: true
285
286 - name: Package - shell
287 if: ${{ steps.build.outputs.build_ok == 'true' }}
288 id: shell
289 working-directory: ts
290 run: |
291 pnpm run shell:package
292 echo "shell_ok=true" >> "$GITHUB_OUTPUT"
293
294 # ── Create PR ───────────────────────────────────────────────────
295 - name: Create pull request
296 if: ${{ steps.fix.outputs.changes == 'true' && steps.build.outputs.build_ok == 'true' }}
297 env:
298 GH_TOKEN: ${{ steps.app-token.outputs.token }}
299 run: |
300 BRANCH="automated/fix-dependabot-alerts-$(date +%Y%m%d)-${{ github.run_number }}"
301
302 git config user.name "github-actions[bot]"
303 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
304
305 git checkout -b "$BRANCH"
306 git add -A
307 git commit -m "fix: remediate Dependabot security alerts
308
309 Automated by fix-dependabot-alerts workflow.
310
311 Applied:${{ steps.fix.outputs.applied_packages }}
312 Rolled back:${{ steps.fix.outputs.rolled_back_packages || ' (none)' }}
313 Blocked: ${{ steps.fix.outputs.blocked }} package(s)
314 Shell packaging: ${{ steps.shell.outputs.shell_ok == 'true' && 'passed' || 'skipped' }}
315
316 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
317
318 git push origin "$BRANCH"
319
320 # Build PR body from step outputs
321 APPLIED="${{ steps.fix.outputs.applied_packages }}"
322 ROLLED="${{ steps.fix.outputs.rolled_back_packages }}"
323 RESOLVED="${{ steps.fix.outputs.resolved }}"
324 BLOCKED="${{ steps.fix.outputs.blocked }}"
325 FAILED="${{ steps.fix.outputs.failed }}"
326 BLOCKED_PKGS="${{ steps.fix.outputs.blocked_packages }}"
327 NO_PATCH_PKGS="${{ steps.fix.outputs.no_patch_packages }}"
328
329 BODY=$(cat <<'PREOF'
330 ## Automated Dependabot Alert Remediation
331
332 This PR was automatically generated by the `fix-dependabot-alerts` workflow.
333 Each fix was applied individually and build-verified before inclusion.
334
335 ### Summary
336 PREOF
337 )
338
339 BODY="$BODY
340 - **Applied ($RESOLVED):**$APPLIED
341 - **Blocked ($BLOCKED):**${BLOCKED_PKGS:- (none)}
342 - **No patch available:**${NO_PATCH_PKGS:- (none)}
343 - **Rolled back ($FAILED):**${ROLLED:- (none)}
344 - **Build:** ✅ Passed
345 - **Shell packaging:** ${{ steps.shell.outputs.shell_ok == 'true' && '✅ Passed' || '⚠️ Skipped' }}
346
347 ### How this works
348 1. Analyses all open Dependabot alerts
349 2. Applies each fix **individually** with build verification
350 3. Rolls back any fix that breaks the build
351 4. Only passing fixes are included in this PR
352
353 ### Review checklist
354 - [ ] Check that no breaking changes were introduced
355 - [ ] Verify rolled-back packages are investigated separately
356 - [ ] Run tests locally if concerned about specific packages"
357
358 gh pr create \
359 --base main \
360 --head "$BRANCH" \
361 --title "fix: remediate Dependabot security alerts ($(date +%Y-%m-%d))" \
362 --body "$BODY" \
363 --label "dependencies,security"
364
365 # ── Summary ─────────────────────────────────────────────────────
366 - name: Job summary
367 if: always()
368 run: |
369 echo "## Dependabot Alert Remediation" >> "$GITHUB_STEP_SUMMARY"
370 echo "" >> "$GITHUB_STEP_SUMMARY"
371 echo "| Metric | Count |" >> "$GITHUB_STEP_SUMMARY"
372 echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
373 echo "| Applied | ${{ steps.fix.outputs.resolved || '0' }} |" >> "$GITHUB_STEP_SUMMARY"
374 echo "| Blocked | ${{ steps.fix.outputs.blocked || '0' }} |" >> "$GITHUB_STEP_SUMMARY"
375 echo "| Rolled back | ${{ steps.fix.outputs.failed || '0' }} |" >> "$GITHUB_STEP_SUMMARY"
376 echo "" >> "$GITHUB_STEP_SUMMARY"
377 if [ -n "${{ steps.fix.outputs.applied_packages }}" ]; then
378 echo "**Applied:**${{ steps.fix.outputs.applied_packages }}" >> "$GITHUB_STEP_SUMMARY"
379 fi
380 if [ -n "${{ steps.fix.outputs.rolled_back_packages }}" ]; then
381 echo "**Rolled back:**${{ steps.fix.outputs.rolled_back_packages }}" >> "$GITHUB_STEP_SUMMARY"
382 fi
383 echo "" >> "$GITHUB_STEP_SUMMARY"
384 if [ "${{ steps.fix.outputs.changes }}" == "true" ]; then
385 echo "✅ Changes applied and PR created" >> "$GITHUB_STEP_SUMMARY"
386 elif [ "${{ inputs.dry-run }}" == "true" ]; then
387 echo "ℹ️ Dry run — no changes applied" >> "$GITHUB_STEP_SUMMARY"
388 else
389 echo "ℹ️ No fixable alerts found" >> "$GITHUB_STEP_SUMMARY"
390 fi