Skip to content

Upgrade Cargo Dependencies #6

Upgrade Cargo Dependencies

Upgrade Cargo Dependencies #6

name: Upgrade Cargo Dependencies
on:
schedule:
# 5th and 20th at 9am Chicago time (15:00 UTC)
- cron: '0 15 5,20 * *'
workflow_dispatch: # Allow manual trigger
permissions:
contents: write
issues: write
pull-requests: write
jobs:
cargo-upgrade:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.89"
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '20'
cache: 'pnpm'
- name: Rust cache
uses: Swatinem/rust-cache@v2
- name: Install cargo-edit
run: cargo install cargo-edit --locked
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Install just
uses: extractions/setup-just@v3
- name: Run cargo upgrade, test, and audit
id: upgrade
run: |
set +e
set -o pipefail
updates_dir="${RUNNER_TEMP}"
upgrade_output_path="${updates_dir}/cargo-upgrade-output.txt"
upgrade_output_plain_path="${updates_dir}/cargo-upgrade-output-plain.txt"
test_output_path="${updates_dir}/cargo-test-output.txt"
test_output_plain_path="${updates_dir}/cargo-test-output-plain.txt"
audit_output_path="${updates_dir}/cargo-audit-output.txt"
audit_output_plain_path="${updates_dir}/cargo-audit-output-plain.txt"
upgrade_exit=0
upgrade_cmd=(cargo upgrade --incompatible)
if cargo upgrade --help | grep -q -- '--workspace'; then
"${upgrade_cmd[@]}" --workspace 2>&1 | tee "${upgrade_output_path}"
upgrade_status=$?
if [ "${upgrade_status}" -ne 0 ]; then
upgrade_exit="${upgrade_status}"
fi
else
"${upgrade_cmd[@]}" 2>&1 | tee "${upgrade_output_path}"
upgrade_status=$?
if [ "${upgrade_status}" -ne 0 ]; then
upgrade_exit="${upgrade_status}"
fi
"${upgrade_cmd[@]}" --manifest-path packages/core/Cargo.toml 2>&1 | tee -a "${upgrade_output_path}"
upgrade_status=$?
if [ "${upgrade_status}" -ne 0 ]; then
upgrade_exit="${upgrade_status}"
fi
"${upgrade_cmd[@]}" --manifest-path packages/cli-rust/Cargo.toml 2>&1 | tee -a "${upgrade_output_path}"
upgrade_status=$?
if [ "${upgrade_status}" -ne 0 ]; then
upgrade_exit="${upgrade_status}"
fi
fi
sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' "${upgrade_output_path}" > "${upgrade_output_plain_path}"
if git diff --quiet; then
echo "updates_available=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "updates_available=true" >> $GITHUB_OUTPUT
test_exit=0
audit_exit=0
if [ "${upgrade_exit}" -eq 0 ]; then
pnpm install --frozen-lockfile --ignore-scripts
just test-all-slow 2>&1 | tee "${test_output_path}"
test_exit=$?
sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' "${test_output_path}" > "${test_output_plain_path}"
cargo audit 2>&1 | tee "${audit_output_path}"
audit_exit=$?
sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' "${audit_output_path}" > "${audit_output_plain_path}"
else
echo "Skipping tests and audit because cargo upgrade exited with ${upgrade_exit}." | tee "${test_output_path}"
echo "Skipping audit because cargo upgrade exited with ${upgrade_exit}." | tee "${audit_output_path}"
sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' "${test_output_path}" > "${test_output_plain_path}"
sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' "${audit_output_path}" > "${audit_output_plain_path}"
fi
echo "upgrade_exit=${upgrade_exit}" >> $GITHUB_OUTPUT
echo "test_exit=${test_exit}" >> $GITHUB_OUTPUT
echo "audit_exit=${audit_exit}" >> $GITHUB_OUTPUT
echo "upgrade_output_plain_path=${upgrade_output_plain_path}" >> $GITHUB_OUTPUT
echo "test_output_plain_path=${test_output_plain_path}" >> $GITHUB_OUTPUT
echo "audit_output_plain_path=${audit_output_plain_path}" >> $GITHUB_OUTPUT
exit 0
- name: Prepare PR body
if: steps.upgrade.outputs.updates_available == 'true'
id: pr
run: |
body_path="${RUNNER_TEMP}/cargo-upgrade.md"
cat > "${body_path}" << 'HEADER'
## Cargo dependency upgrades
This PR was automatically generated by the scheduled cargo upgrade workflow.
### Changes
HEADER
echo '```diff' >> "${body_path}"
git diff >> "${body_path}"
echo '```' >> "${body_path}"
cat >> "${body_path}" << EOF
### Cargo Upgrade Output
\`\`\`
$(cat "${{ steps.upgrade.outputs.upgrade_output_plain_path }}")
\`\`\`
EOF
test_status="passed"
audit_status="passed"
if [ "${{ steps.upgrade.outputs.test_exit }}" != "0" ]; then
test_status="failed"
fi
if [ "${{ steps.upgrade.outputs.audit_exit }}" != "0" ]; then
audit_status="failed"
fi
cat >> "${body_path}" << EOF
### Test Result
Status: **${test_status}**
\`\`\`
$(cat "${{ steps.upgrade.outputs.test_output_plain_path }}")
\`\`\`
### Audit Result
Status: **${audit_status}**
\`\`\`
$(cat "${{ steps.upgrade.outputs.audit_output_plain_path }}")
\`\`\`
EOF
cat >> "${body_path}" << 'FOOTER'
### Testing
- [ ] CI passed
---
*Generated by [cargo-upgrades workflow](https://github.com/${{ github.repository }}/actions/workflows/cargo-upgrades.yml)*
FOOTER
echo "body_path=${body_path}" >> $GITHUB_OUTPUT
- name: Create issue on upgrade failure
if: steps.upgrade.outputs.updates_available == 'true' && (steps.upgrade.outputs.upgrade_exit != '0' || steps.upgrade.outputs.test_exit != '0' || steps.upgrade.outputs.audit_exit != '0')
uses: actions/github-script@v7
env:
UPGRADE_OUTPUT: ${{ steps.upgrade.outputs.upgrade_output_plain_path }}
TEST_OUTPUT: ${{ steps.upgrade.outputs.test_output_plain_path }}
AUDIT_OUTPUT: ${{ steps.upgrade.outputs.audit_output_plain_path }}
UPGRADE_EXIT: ${{ steps.upgrade.outputs.upgrade_exit }}
TEST_EXIT: ${{ steps.upgrade.outputs.test_exit }}
AUDIT_EXIT: ${{ steps.upgrade.outputs.audit_exit }}
with:
script: |
const fs = require('fs');
const upgradeExit = process.env.UPGRADE_EXIT || '0';
const testExit = process.env.TEST_EXIT || '0';
const auditExit = process.env.AUDIT_EXIT || '0';
const readFileSafe = (path) => {
try {
return fs.readFileSync(path, 'utf8');
} catch (error) {
return `Failed to read ${path}: ${error.message}`;
}
};
const tail = (text, maxChars) => {
if (!text || text.length <= maxChars) {
return text || '';
}
return text.slice(text.length - maxChars);
};
const upgradeOutput = readFileSafe(process.env.UPGRADE_OUTPUT);
const testOutput = readFileSafe(process.env.TEST_OUTPUT);
const auditOutput = readFileSafe(process.env.AUDIT_OUTPUT);
const sections = [];
sections.push('## Cargo upgrade failed during CI');
sections.push('');
sections.push('### Summary');
sections.push(`- Upgrade exit: \`${upgradeExit}\``);
sections.push(`- Test exit: \`${testExit}\``);
sections.push(`- Audit exit: \`${auditExit}\``);
sections.push('');
sections.push('### Run');
sections.push(`- ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`);
sections.push('');
sections.push('### Cargo Upgrade Output (tail)');
sections.push('```');
sections.push(tail(upgradeOutput, 12000));
sections.push('```');
sections.push('');
if (testExit !== '0') {
sections.push('### Test Output (tail)');
sections.push('```');
sections.push(tail(testOutput, 12000));
sections.push('```');
sections.push('');
}
if (auditExit !== '0') {
sections.push('### Audit Output (tail)');
sections.push('```');
sections.push(tail(auditOutput, 12000));
sections.push('```');
sections.push('');
}
const title = `cargo upgrade failed in CI (${new Date().toISOString().slice(0, 10)})`;
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body: sections.join('\\n'),
labels: ['dependencies', 'ci'],
});
- name: Create Pull Request
if: steps.upgrade.outputs.updates_available == 'true'
uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: "chore(deps): cargo upgrade"
body-path: ${{ steps.pr.outputs.body_path }}
branch: cargo-dependency-upgrades
commit-message: "chore(deps): cargo upgrade"
labels: dependencies,security
delete-branch: true
add-paths: |
Cargo.lock
Cargo.toml
packages/core/Cargo.toml
packages/cli-rust/Cargo.toml