diff --git a/.github/workflows/ready.yml b/.github/workflows/ready.yml index 830f994..089a399 100644 --- a/.github/workflows/ready.yml +++ b/.github/workflows/ready.yml @@ -142,12 +142,12 @@ jobs: return; } if (final.auto_merge) return core.info('Auto-merge is already enabled.'); - const mutation = `mutation($id: ID!, $sha: GitObjectID!, $method: PullRequestMergeMethod!) { - enablePullRequestAutoMerge(input: {pullRequestId: $id, expectedHeadOid: $sha, mergeMethod: $method}) { + const mutation = `mutation($id: ID!, $sha: GitObjectID!, $mergeMethod: PullRequestMergeMethod!) { + enablePullRequestAutoMerge(input: {pullRequestId: $id, expectedHeadOid: $sha, mergeMethod: $mergeMethod}) { pullRequest { number } } }`; - const variables = {id: final.node_id, sha, method: method.toUpperCase()}; + const variables = {id: final.node_id, sha, mergeMethod: method.toUpperCase()}; if (process.env.MERGE_TOKEN) await mergeRequest('/graphql', 'POST', {query: mutation, variables}); else await github.graphql(mutation, variables); } diff --git a/README.md b/README.md index 6c61165..a2a0af9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Organization-wide GitHub defaults and reusable workflows. Repository callers select their own check command; workflows here are not inherited automatically. +Callers pin a full commit SHA from `main` after the shared workflow PR is merged. + New projects take their callers from [astro-template](https://github.com/fulldotdev/astro-template) or [vite-template](https://github.com/fulldotdev/vite-template). - **Finalize (Prepare)** leaves a complete draft PR. Previews build; full checks wait for Ready. diff --git a/tests/ready.test.mjs b/tests/ready.test.mjs index dae539b..e392d69 100644 --- a/tests/ready.test.mjs +++ b/tests/ready.test.mjs @@ -33,7 +33,14 @@ async function scenario(options = {}) { request: async () => ({data: [{type: 'required_status_checks', parameters: { required_status_checks: (options.required ?? ['Check / Check', 'Review']).map(context => ({context})), }}]}), - graphql: async (_, args) => merges.push(args), + graphql: async (query, args) => { + // Octokit reserves request options instead of treating them as GraphQL variables. + for (const reserved of ['method', 'url', 'baseUrl', 'headers', 'request']) { + assert.equal(Object.hasOwn(args, reserved), false, `Reserved GraphQL variable: ${reserved}`); + } + assert.match(query, /mergeMethod: \$mergeMethod/); + merges.push(args); + }, }; await execute(github, {repo: {owner: 'o', repo: 'r'}, payload: options.event ?? {pull_request: {number: 1}}}, {info: () => {}, error: message => errors.push(message), setFailed: message => errors.push(message)}, {env: {CHECK_NAME: 'Check / Check', NETLIFY_CHECKS: JSON.stringify(options.netlify ?? []), @@ -108,7 +115,7 @@ test('already-green PR merges immediately with the validated SHA', async () => { assert.deepEqual(result.merges, [{owner: 'o', repo: 'r', pull_number: 1, sha: 'head', merge_method: 'squash'}]); }); test('automatic merge is bound to the validated head', async () => { - assert.deepEqual((await scenario()).merges, [{id: 'PR1', sha: 'head', method: 'SQUASH'}]); + assert.deepEqual((await scenario()).merges, [{id: 'PR1', sha: 'head', mergeMethod: 'SQUASH'}]); }); test('every expected Netlify context must be centrally required', async () => { const result = await scenario({netlify: ['netlify/app/deploy-preview', 'netlify/site/deploy-preview'], @@ -178,7 +185,7 @@ test('release app handles only the merge while Review remains an Actions status' assert.equal(requests[0].options.headers.Authorization, 'Bearer synthetic-token'); const body = JSON.parse(requests[0].options.body); if (mergeable_state === 'clean') assert.deepEqual(body, {sha: 'head', merge_method: 'squash'}); - else assert.deepEqual(body.variables, {id: 'PR1', sha: 'head', method: 'SQUASH'}); + else assert.deepEqual(body.variables, {id: 'PR1', sha: 'head', mergeMethod: 'SQUASH'}); } }); test('release app rejection is reported without treating the PR as merged', async () => {