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
41 changes: 41 additions & 0 deletions .github/actions/publish-to-gh-pages/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: 'Publish to GitHub Pages'
description: >-
Publishes files to a GitHub Pages branch via the GraphQL
createCommitOnBranch mutation. GitHub signs the resulting commit with its
internal key (the same way web-UI commits are signed), so the push
satisfies the repo's required-signatures rule that rejects plain
`git push` from CI.
inputs:
source-dir:
description: 'Local directory whose contents will be published'
required: true
destination-dir:
description: 'Directory inside the target branch where files are placed'
required: true
branch:
description: 'Target branch'
required: false
default: 'gh-pages'
commit-message:
description: 'Commit message headline (body may follow after a blank line)'
required: false
default: 'deploy: ${{ github.sha }}'
github-token:
description: 'Token with `contents: write` permission on the target branch'
required: true
runs:
using: 'composite'
steps:
- name: Publish via createCommitOnBranch
uses: actions/github-script@v7
env:
SOURCE_DIR: ${{ inputs.source-dir }}
DEST_DIR: ${{ inputs.destination-dir }}
BRANCH: ${{ inputs.branch }}
COMMIT_MESSAGE: ${{ inputs.commit-message }}
ACTION_PATH: ${{ github.action_path }}
with:
github-token: ${{ inputs.github-token }}
script: |
const publish = require(`${process.env.ACTION_PATH}/publish.js`);
await publish({ github, context, core });
110 changes: 110 additions & 0 deletions .github/actions/publish-to-gh-pages/publish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Publishes files from a local directory to `destination-dir` on a
// target branch using the GraphQL `createCommitOnBranch` mutation.
// GitHub signs the resulting commit with its internal key, which is
// required because the repo's enterprise-level ruleset rejects unsigned
// pushes on every ref (including gh-pages).
const fs = require('node:fs');
const path = require('node:path');

module.exports = async ({ github, context, core }) => {
const sourceDir = process.env.SOURCE_DIR;
const destDir = (process.env.DEST_DIR || '').replace(/^\/+|\/+$/g, '');
const branch = process.env.BRANCH;
const message = process.env.COMMIT_MESSAGE;
const { owner, repo } = context.repo;

if (!fs.existsSync(sourceDir)) {
core.warning(
`Source directory "${sourceDir}" does not exist; nothing to publish.`
);
return;
}

const additions = [];
const walk = (dir, rel = '') => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const abs = path.join(dir, entry.name);
const next = rel ? `${rel}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
walk(abs, next);
} else if (entry.isFile()) {
const contents = fs.readFileSync(abs).toString('base64');
additions.push({
path: destDir ? `${destDir}/${next}` : next,
contents,
});
}
}
};
walk(sourceDir);

if (additions.length === 0) {
core.warning(
`Source directory "${sourceDir}" has no files; nothing to publish.`
);
return;
}

// CommitMessage GraphQL input wants headline (first line) + body
// (everything after the first blank line).
const firstNewline = message.indexOf('\n');
let headline;
let body;
if (firstNewline === -1) {
headline = message;
body = '';
} else {
headline = message.slice(0, firstNewline);
const rest = message.slice(firstNewline + 1);
body = rest.startsWith('\n') ? rest.slice(1) : rest;
}

const mutation = `
mutation($input: CreateCommitOnBranchInput!) {
createCommitOnBranch(input: $input) {
commit { oid url }
}
}
`;

// Retry once on `expectedHeadOid` mismatch — possible if another
// workflow run pushes to the same branch between our ref read and
// the mutation.
for (let attempt = 1; attempt <= 2; attempt++) {
const { data: ref } = await github.rest.git.getRef({
owner,
repo,
ref: `heads/${branch}`,
});
const expectedHeadOid = ref.object.sha;

try {
const result = await github.graphql(mutation, {
input: {
branch: {
repositoryNameWithOwner: `${owner}/${repo}`,
branchName: branch,
},
expectedHeadOid,
message: { headline, body },
fileChanges: { additions },
},
});
const { oid, url } = result.createCommitOnBranch.commit;
core.info(`Created signed commit ${oid} on ${branch} (${url})`);
return;
} catch (err) {
const errs = Array.isArray(err.errors) ? err.errors : [];
const conflicted =
errs.some((e) => /expected.*head/i.test(e.message || '')) ||
/expected.*head/i.test(err.message || '');
if (conflicted && attempt === 1) {
core.warning(
`Branch ${branch} HEAD moved during publish; retrying once.`
);
continue;
}
throw err;
}
}
};
4 changes: 2 additions & 2 deletions .github/actions/setup-workflow-dev/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ runs:
steps:
- name: Setup Rust
if: ${{ inputs.setup-rust == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
with:
toolchain: stable

- name: Setup pnpm
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0

- name: Setup Node.js ${{ inputs.node-version }}
uses: actions/setup-node@v4
Expand Down
25 changes: 17 additions & 8 deletions .github/workflows/backport.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ jobs:

- name: Setup pnpm
if: steps.existing-pr.outputs.exists != 'true'
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0

- name: Setup Node.js
if: steps.existing-pr.outputs.exists != 'true'
Expand Down Expand Up @@ -265,7 +265,9 @@ jobs:
echo "Recommend AGAINST backport for:"
echo "- Changes that explicitly build on or require new APIs/behavior introduced only on \`main\`"
echo "- Major breaking changes intended exclusively for the next major release"
echo "- Changes to files/directories that are not maintained on \`stable\` (the \`docs/\` app outside of \`docs/content/\`, and anything under \`skills/\`)"
echo "- Changes confined to files/directories that are not maintained on \`stable\`. **This list is exhaustive** — only the following paths qualify; assume every other path IS actively maintained on \`stable\` unless you verify otherwise (see below):"
echo " - the \`docs/\` app outside of \`docs/content/\` (i.e. \`docs/content/\` IS maintained on \`stable\`, the rest of the docs app is not)"
echo " - anything under \`skills/\`"
echo "- Changesets-only commits, version bump commits (\"Version Packages\"), and similar release plumbing"
echo "- Commits that revert something that only exists on \`main\`"
echo ""
Expand All @@ -275,6 +277,8 @@ jobs:
echo ""
echo "When in doubt, lean toward recommending a backport — humans review the resulting PR and can close it if it's not worth merging."
echo ""
echo "**Important:** before recommending AGAINST a backport on the grounds that a file or directory is not maintained on \`stable\`, you MUST verify your claim. The exhaustive list above is the only set of paths you may assume are absent or stubbed on \`stable\` without checking. For any other path, use the \`bash\` tool to run \`git ls-tree origin/stable -- <path>\` (or \`git show origin/stable:<path>\`) and confirm the path is actually absent or a stub on \`stable\` before citing it as a reason against backport. Do not guess based on file/directory names — for example, names containing words like \"docs\", \"preview\", \"tarball\", or \"workflow\" do not imply a path is main-only."
echo ""
echo "## Output format"
echo ""
echo "Write your decision to ${DECISION_FILE} (relative to the current working directory) as a single JSON object with EXACTLY these two keys (and nothing else):"
Expand Down Expand Up @@ -373,16 +377,18 @@ jobs:
const reasoning = process.env.REASONING;
const sha = process.env.SHA.slice(0, 7);
const prNumber = Number(process.env.PR_NUMBER);
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const workflowUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/workflows/backport.yml`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: [
`**No backport to \`stable\`** for ${sha} (AI decision).`,
`**No backport to \`stable\`** for ${sha} ([AI decision](${runUrl})).`,
'',
reasoning,
'',
'To override, re-run the [Backport to stable](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions/workflows/backport.yml) workflow manually via `workflow_dispatch` with this commit SHA.'
`To override, re-run the [Backport to stable](${workflowUrl}) workflow manually via \`workflow_dispatch\` with this commit SHA.`
].join('\n')
});

Expand Down Expand Up @@ -818,6 +824,7 @@ jobs:
CHERRY_PICK_STATUS: ${{ steps.cherry-pick.outputs.status }}
AI_REASONING: ${{ steps.decide.outputs.reasoning }}
TRIGGER: ${{ steps.resolve.outputs.trigger }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
# Build the PR title.
if [ -n "$PR_NUMBER" ]; then
Expand All @@ -830,9 +837,9 @@ jobs:
# Build the PR body.
{
if [ -n "$PR_NUMBER" ]; then
echo "Automated backport of #${PR_NUMBER} to \`stable\`."
echo "Automated backport of #${PR_NUMBER} to \`stable\` ([backport job run](${RUN_URL}))."
else
echo "Automated backport of ${SHA} to \`stable\`."
echo "Automated backport of ${SHA} to \`stable\` ([backport job run](${RUN_URL}))."
fi
echo ""

Expand Down Expand Up @@ -882,14 +889,15 @@ jobs:
const cherryPickStatus = process.env.CHERRY_PICK_STATUS;
const prUrl = process.env.PR_URL;
const prNumber = Number(process.env.PR_NUMBER);
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const conflictNote = cherryPickStatus === 'conflict'
? ' Merge conflicts were resolved by AI — please review carefully.'
: '';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `Backport PR opened against \`stable\`: ${prUrl}.${conflictNote}`
body: `Backport PR opened against \`stable\`: ${prUrl}.${conflictNote} ([backport job run](${runUrl}))`
});

- name: Comment on conflict failure
Expand All @@ -913,12 +921,13 @@ jobs:
const sha = process.env.SHA;
const prNumber = Number(process.env.PR_NUMBER);
const shortSha = sha.slice(0, 12);
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: [
'**Backport to `stable` failed** — the cherry-pick had conflicts that could not be resolved automatically.',
`**Backport to \`stable\` failed** — the cherry-pick had conflicts that could not be resolved automatically ([backport job run](${runUrl})).`,
'',
'To resolve manually, push a backport branch and open a PR against `stable` (the workflow never pushes directly to `stable`). Note: this repository requires verified signatures on every branch, so your local commits must be signed (`git config commit.gpgsign true` with a configured GPG/SSH signing key, or `git cherry-pick -S`).',
'```bash',
Expand Down
31 changes: 15 additions & 16 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:

steps:
- name: Find existing benchmark comment
uses: peter-evans/find-comment@v3
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:

- name: Create new benchmark comment
if: steps.find-comment.outputs.comment-id == ''
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: benchmark-results
message: |
Expand All @@ -102,14 +102,14 @@ jobs:

- name: Update existing benchmark comment with stale warning
if: steps.find-comment.outputs.comment-id != '' && steps.get-comment.outputs.has-results == 'true'
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: benchmark-results
path: ${{ steps.get-comment.outputs.stale-comment-path }}

- name: Update existing benchmark comment without results
if: steps.find-comment.outputs.comment-id != '' && steps.get-comment.outputs.has-results != 'true'
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: benchmark-results
message: |
Expand Down Expand Up @@ -192,7 +192,7 @@ jobs:
- name: Download baseline from PR base branch
if: github.event_name == 'pull_request'
continue-on-error: true
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
with:
workflow: benchmarks.yml
branch: ${{ github.event.pull_request.base.ref }}
Expand Down Expand Up @@ -292,7 +292,7 @@ jobs:
- name: Download baseline from PR base branch
if: github.event_name == 'pull_request'
continue-on-error: true
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
with:
workflow: benchmarks.yml
branch: ${{ github.event.pull_request.base.ref }}
Expand Down Expand Up @@ -386,7 +386,7 @@ jobs:
- name: Download baseline from PR base branch
if: github.event_name == 'pull_request'
continue-on-error: true
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
with:
workflow: benchmarks.yml
branch: ${{ github.event.pull_request.base.ref }}
Expand Down Expand Up @@ -511,7 +511,7 @@ jobs:
- name: Download baseline from PR base branch
if: github.event_name == 'pull_request'
continue-on-error: true
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
with:
workflow: benchmarks.yml
branch: ${{ github.event.pull_request.base.ref }}
Expand Down Expand Up @@ -562,14 +562,14 @@ jobs:

- name: Update PR comment with results
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: benchmark-results
path: benchmark-summary.md

- name: Append failure notice to PR comment
if: github.event_name == 'pull_request' && steps.check-status.outputs.has_failures == 'true'
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: benchmark-results
append: true
Expand All @@ -585,7 +585,7 @@ jobs:

- name: Append community warning to PR comment
if: github.event_name == 'pull_request' && steps.check-status.outputs.has_warnings == 'true'
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
with:
header: benchmark-results
append: true
Expand Down Expand Up @@ -638,9 +638,8 @@ jobs:
--branch "${{ github.ref_name }}"

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
uses: ./.github/actions/publish-to-gh-pages
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs-data
destination_dir: ci
keep_files: true
github-token: ${{ secrets.GITHUB_TOKEN }}
source-dir: ./docs-data
destination-dir: ci
Loading
Loading