Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions .github/workflows/node-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,28 @@ name: Node dependency audit
description: auto selects the configured cadence; security or upgrades overrides one manual run.
type: string
default: auto
audit-ref:
type: string
default: ''
artifact-prefix:
type: string
default: ''
refresh:
type: boolean
default: false
operation:
type: string
default: propose
proposal-kind:
type: string
default: legacy
outputs:
validation_passed:
description: Whether candidate validation passed.
value: ${{ jobs.audit.outputs.validation_passed }}
proposal_complete:
description: Whether proposal resolution and audit evidence completed.
value: ${{ jobs.audit.outputs.proposal_complete }}
permissions:
contents: read
jobs:
Expand All @@ -20,57 +38,82 @@ jobs:
timeout-minutes: 60
outputs:
validation_passed: ${{ steps.proposal.outputs.validation_passed }}
proposal_complete: ${{ steps.proposal.outputs.proposal_complete }}
steps:
- name: Validate manual dispatch ref
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: "set -euo pipefail\nexpected_ref=\"refs/heads/$DEFAULT_BRANCH\"\nif [ \"\
$GITHUB_EVENT_NAME\" = \"workflow_dispatch\" ] && [ \"$GITHUB_REF\" != \"\
$expected_ref\" ]; then\n echo \"::error::Manual dispatch must target the\
\ repository default branch ($expected_ref); got $GITHUB_REF.\" >&2\n exit\
\ 1\nfi\n"
run: |
set -euo pipefail
expected_ref="refs/heads/$DEFAULT_BRANCH"
if { [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] || [ "$GITHUB_EVENT_NAME" = "push" ]; } && [ "$GITHUB_REF" != "$expected_ref" ]; then
echo "::error::Manual dispatch must target the repository default branch ($expected_ref); got $GITHUB_REF." >&2
exit 1
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ github.sha }}
ref: ${{ inputs.audit-ref || github.sha }}
persist-credentials: false
- name: Audit, propose upgrades, and validate
id: proposal
uses: $/node/audit
with:
audit-kind: ${{ inputs.audit-kind }}
artifact-prefix: ${{ inputs.artifact-prefix }}
refresh: ${{ inputs.refresh }}
operation: ${{ inputs.operation }}
proposal-kind: ${{ inputs.proposal-kind }}
configuration-issue:
needs: audit
if: ${{ always() && !cancelled() }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: write
contents: read
concurrency:
group: node-audit-configuration-issue-${{ github.repository }}
cancel-in-progress: false
queue: max
steps:
- name: Reject stale coordinated issue reports
if: ${{ inputs.audit-ref != '' }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
AUDIT_SHA: ${{ inputs.audit-ref }}
BASE_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
current=$(gh api "repos/$GH_REPO/commits/$BASE_BRANCH" --jq .sha)
if [ "$current" != "$AUDIT_SHA" ]; then
echo "::error::Default branch advanced; stale issue reports discarded."
exit 1
fi
id: freshness
- name: Read configuration status from this run
id: report
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: node-audit-configuration
name: ${{ inputs.artifact-prefix || 'node-audit' }}-configuration
path: ${{ runner.temp }}/node-audit-configuration
if: ${{ success() && steps.freshness.outcome != 'failure' }}
- name: Maintain the configuration issue
if: ${{ steps.report.outcome == 'success' }}
if: ${{ steps.report.outcome == 'success' && steps.freshness.outcome != 'failure' }}
uses: $/node/configuration-issue
env:
GH_TOKEN: ${{ github.token }}
- name: Read local patch status from this run
if: ${{ always() && !cancelled() }}
if: ${{ always() && !cancelled() && steps.freshness.outcome != 'failure' }}
id: local-patches
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: node-audit-local-patches
name: ${{ inputs.artifact-prefix || 'node-audit' }}-local-patches
path: ${{ runner.temp }}/node-audit-local-patches
- name: Maintain the local patch notification issue
if: ${{ always() && !cancelled() && steps.local-patches.outcome == 'success' }}
if: ${{ always() && !cancelled() && steps.local-patches.outcome == 'success' && steps.freshness.outcome != 'failure' }}
uses: $/node/local-patch-issue
env:
GH_TOKEN: ${{ github.token }}
154 changes: 154 additions & 0 deletions .github/workflows/node-maintenance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
name: Coordinated Node dependency maintenance
'on':
workflow_call:
inputs:
audit-kind:
type: string
default: auto
app-client-id:
type: string
default: ''
secrets:
app-private-key:
required: false
permissions:
contents: read
jobs:
plan:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
outputs:
sha: ${{ steps.revision.outputs.sha }}
security: ${{ steps.plan.outputs.security }}
upgrades: ${{ steps.plan.outputs.upgrades }}
security_pr: ${{ steps.plan.outputs.security_pr }}
upgrades_pr: ${{ steps.plan.outputs.upgrades_pr }}
refresh_only: ${{ steps.plan.outputs.refresh_only }}
validate_main: ${{ steps.plan.outputs.validate_main }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
fetch-depth: 0
- id: revision
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- id: plan
uses: $/node/plan
with:
audit-kind: ${{ inputs.audit-kind }}
env:
GH_TOKEN: ${{ github.token }}
main:
needs: plan
if: ${{ needs.plan.outputs.validate_main == 'true' }}
permissions:
contents: read
issues: write
uses: ./.github/workflows/node-audit.yml
with:
audit-ref: ${{ needs.plan.outputs.sha }}
artifact-prefix: node-main
audit-kind: ${{ inputs.audit-kind }}
refresh: ${{ github.event_name == 'push' }}
operation: validate
proposal-kind: main
security:
needs: plan
if: ${{ needs.plan.outputs.security == 'true' }}
permissions:
contents: read
issues: write
uses: ./.github/workflows/node-audit.yml
with:
audit-ref: ${{ needs.plan.outputs.sha }}
artifact-prefix: node-security
audit-kind: ${{ github.event_name == 'push' && 'security' || inputs.audit-kind }}
refresh: ${{ github.event_name == 'push' }}
operation: propose
proposal-kind: security
security-pr:
needs:
- plan
- security
permissions:
contents: write
pull-requests: write
uses: ./.github/workflows/node-publish.yml
with:
validation-passed: ${{ needs.security.outputs.validation_passed }}
proposal-complete: ${{ needs.security.outputs.proposal_complete }}
audit-ref: ${{ needs.plan.outputs.sha }}
artifact-prefix: node-security
proposal-kind: security
refresh-only: ${{ needs.plan.outputs.refresh_only == 'true' }}
expected-pr: ${{ needs.plan.outputs.security_pr }}
app-client-id: ${{ inputs.app-client-id }}
secrets:
app-private-key: ${{ secrets.app-private-key }}
upgrades:
needs: plan
if: ${{ needs.plan.outputs.upgrades == 'true' }}
permissions:
contents: read
issues: write
uses: ./.github/workflows/node-audit.yml
with:
audit-ref: ${{ needs.plan.outputs.sha }}
artifact-prefix: node-upgrades
audit-kind: ${{ github.event_name == 'push' && 'upgrades' || inputs.audit-kind }}
refresh: ${{ github.event_name == 'push' }}
operation: propose
proposal-kind: upgrades
upgrades-pr:
needs:
- plan
- upgrades
permissions:
contents: write
pull-requests: write
uses: ./.github/workflows/node-publish.yml
with:
validation-passed: ${{ needs.upgrades.outputs.validation_passed }}
proposal-complete: ${{ needs.upgrades.outputs.proposal_complete }}
audit-ref: ${{ needs.plan.outputs.sha }}
artifact-prefix: node-upgrades
proposal-kind: upgrades
refresh-only: ${{ needs.plan.outputs.refresh_only == 'true' }}
expected-pr: ${{ needs.plan.outputs.upgrades_pr }}
app-client-id: ${{ inputs.app-client-id }}
secrets:
app-private-key: ${{ secrets.app-private-key }}
planning-issue:
needs: plan
if: ${{ always() && !cancelled() && needs.plan.result == 'failure' }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
issues: write
concurrency:
group: node-audit-configuration-issue-${{ github.repository }}
cancel-in-progress: false
queue: max
steps:
- name: Refuse stale configuration diagnostics
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
BASE_BRANCH: ${{ github.event.repository.default_branch }}
AUDIT_SHA: ${{ needs.plan.outputs.sha }}
run: |
set -euo pipefail
current=$(gh api "repos/$GH_REPO/commits/$BASE_BRANCH" --jq .sha)
test "$current" = "$AUDIT_SHA"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: node-plan-configuration
path: ${{ runner.temp }}/node-audit-configuration
- uses: $/node/configuration-issue
env:
GH_TOKEN: ${{ github.token }}
79 changes: 56 additions & 23 deletions .github/workflows/node-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ name: Publish Node dependency proposal
required: false
type: string
default: ''
audit-ref:
type: string
default: ''
artifact-prefix:
type: string
default: ''
proposal-kind:
type: string
default: legacy
refresh-only:
type: boolean
default: false
expected-pr:
type: string
default: ''
proposal-complete:
type: string
default: 'false'
secrets:
app-private-key:
required: false
Expand Down Expand Up @@ -38,46 +56,61 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
token: ${{ steps.app-token.outputs.token || github.token }}
ref: ${{ github.sha }}
ref: ${{ inputs.audit-ref || github.sha }}
fetch-depth: 0
- name: Verify audit dependency state
env:
AUDIT_SHA: ${{ github.sha }}
AUDIT_SHA: ${{ inputs.audit-ref || github.sha }}
BASE_BRANCH: ${{ github.event.repository.default_branch }}
run: "set -euo pipefail\ngit fetch --no-tags origin \"$BASE_BRANCH:refs/remotes/origin/$BASE_BRANCH\"\
\ngit checkout --detach \"refs/remotes/origin/$BASE_BRANCH\"\nif ! git cat-file\
\ -e \"${AUDIT_SHA}^{commit}\" 2>/dev/null; then\n echo \"Triggering audit\
\ revision ${AUDIT_SHA} is not available locally; fetching it.\"\n if ! git\
\ fetch --no-tags origin \"$AUDIT_SHA\"; then\n echo \"::error::Unable\
\ to fetch the triggering audit revision; refusing stale publication.\"\n\
\ exit 1\n fi\nfi\nif ! git cat-file -e \"${AUDIT_SHA}^{commit}\" 2>/dev/null;\
\ then\n echo \"::error::The triggering audit revision could not be resolved;\
\ refusing publication.\"\n exit 1\nfi\nif git diff --quiet \"$AUDIT_SHA\"\
\ HEAD -- .; then\n :\nelse\n comparison_status=$?\n if [ \"$comparison_status\"\
\ -eq 1 ]; then\n echo \"::error::The default branch repository inputs\
\ changed since ${AUDIT_SHA}; rerun the audit from the current default branch.\"\
\n else\n echo \"::error::Unable to compare default-branch repository\
\ inputs with ${AUDIT_SHA}; refusing publication.\"\n fi\n exit 1\nfi\n"
run: |
set -euo pipefail
git fetch --no-tags origin "$BASE_BRANCH:refs/remotes/origin/$BASE_BRANCH"
git checkout --detach "refs/remotes/origin/$BASE_BRANCH"
if ! git cat-file -e "${AUDIT_SHA}^{commit}" 2>/dev/null; then
echo "Triggering audit revision ${AUDIT_SHA} is not available locally; fetching it."
if ! git fetch --no-tags origin "$AUDIT_SHA"; then
echo "::error::Unable to fetch the triggering audit revision; refusing stale publication."
exit 1
fi
fi
if ! git cat-file -e "${AUDIT_SHA}^{commit}" 2>/dev/null; then
echo "::error::The triggering audit revision could not be resolved; refusing publication."
exit 1
fi
if git diff --quiet "$AUDIT_SHA" HEAD -- .; then
:
else
comparison_status=$?
if [ "$comparison_status" -eq 1 ]; then
echo "::error::The default branch repository inputs changed since ${AUDIT_SHA}; rerun the audit from the current default branch."
else
echo "::error::Unable to compare default-branch repository inputs with ${AUDIT_SHA}; refusing publication."
fi
exit 1
fi
- name: Read project configuration
uses: $/node/project
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: dependency-audit-dependencies
name: ${{ inputs.artifact-prefix || 'dependency-audit' }}-dependencies
path: ${{ env.NPM_PROJECT }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: dependency-audit-report
name: ${{ inputs.artifact-prefix || 'dependency-audit' }}-report
path: ${{ runner.temp }}
- name: Open or update the umbrella PR
- name: Maintain the selected dependency PR
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }}
GH_REPO: ${{ github.repository }}
BASE_BRANCH: ${{ github.event.repository.default_branch }}
PR_BRANCH: maintenance/dependency-audit
VALIDATION_PASSED: ${{ inputs.validation-passed }}
PR_FOOTER: '[Workflow run](${{ github.server_url }}/${{ github.repository
}}/actions/runs/${{ github.run_id }})

'
PR_FOOTER: |
[Workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
PROPOSAL_KIND: ${{ inputs.proposal-kind }}
REFRESH_ONLY: ${{ inputs.refresh-only }}
EXPECTED_PR: ${{ inputs.expected-pr }}
PROPOSAL_COMPLETE: ${{ inputs.proposal-complete }}
AUDIT_SHA: ${{ inputs.audit-ref || github.sha }}
uses: $/node/publish
timeout-minutes: 15
Loading
Loading