diff --git a/.github/workflows/tests-read.yaml b/.github/workflows/tests-read.yaml index 2db06a4..3d23062 100644 --- a/.github/workflows/tests-read.yaml +++ b/.github/workflows/tests-read.yaml @@ -1,7 +1,7 @@ name: tests-read on: - pull_request: + workflow_call: # read only needs to read; the seed step uses write, which needs write. permissions: @@ -21,6 +21,11 @@ jobs: marker: test-read data: '{"version":"4.5.6","reviewed":true}' + # The seed's pulls.update is not immediately consistent: a pulls.get + # right after it can hit a replica that hasn't caught up. Give it a + # moment to propagate before reading back. + - run: sleep 10 + # Read via an explicit pr-number so the action takes the REST fetch path. # The default path reads the event payload's PR body, which is captured # when the run starts and so can't see the seed written above this run. diff --git a/.github/workflows/tests-write.yaml b/.github/workflows/tests-write.yaml index f67a239..800531c 100644 --- a/.github/workflows/tests-write.yaml +++ b/.github/workflows/tests-write.yaml @@ -1,7 +1,7 @@ name: tests-write on: - pull_request: + workflow_call: # write updates the PR body via the REST API permissions: @@ -21,6 +21,11 @@ jobs: marker: test-write data: '{"version":"1.2.3","reviewed":false}' + # pulls.update is not immediately consistent, and the merge step below + # reads the body back to find the seeded marker. Let the seed propagate + # first, otherwise merge can miss it and append a duplicate block. + - run: sleep 10 + # Merge: shallow-merge new keys into the existing blob (merge=true). - id: merge uses: ./write @@ -29,6 +34,9 @@ jobs: data: '{"reviewed":true,"author":"test"}' merge: "true" + # Same propagation delay before reading the merged result back. + - run: sleep 10 + # Read it back to assert the round-trip survived the composite-input plumbing. # Explicit pr-number forces the REST fetch path; the default path reads the # event payload body captured at run start, before the writes above. @@ -54,3 +62,33 @@ jobs: [ "$(echo "$RESULT" | jq -r '.author')" = "test" ] || { echo "::error::author not added"; exit 1; } echo "write self-test passed" + + # The write action exposes what it persisted as `result`/`changed` outputs. + - name: Assert write outputs + env: + SEED_RESULT: ${{ steps.seed.outputs.result }} + SEED_CHANGED: ${{ steps.seed.outputs.changed }} + MERGE_RESULT: ${{ steps.merge.outputs.result }} + MERGE_CHANGED: ${{ steps.merge.outputs.changed }} + RESULT: ${{ steps.read.outputs.result }} + run: | + echo "seed.result=$SEED_RESULT seed.changed=$SEED_CHANGED" + echo "merge.result=$MERGE_RESULT merge.changed=$MERGE_CHANGED" + + # Both writes mutate the body, so changed must be true. + [ "$SEED_CHANGED" = "true" ] || { echo "::error::seed.changed not true"; exit 1; } + [ "$MERGE_CHANGED" = "true" ] || { echo "::error::merge.changed not true"; exit 1; } + + # Replace output reflects exactly what was passed in. + [ "$(echo "$SEED_RESULT" | jq -r '.version')" = "1.2.3" ] || { echo "::error::seed.result version wrong"; exit 1; } + [ "$(echo "$SEED_RESULT" | jq -r '.reviewed')" = "false" ] || { echo "::error::seed.result reviewed wrong"; exit 1; } + + # Merge output reflects the post-merge value (old ∪ new). + [ "$(echo "$MERGE_RESULT" | jq -r '.version')" = "1.2.3" ] || { echo "::error::merge.result version not preserved"; exit 1; } + [ "$(echo "$MERGE_RESULT" | jq -r '.reviewed')" = "true" ] || { echo "::error::merge.result reviewed not merged"; exit 1; } + [ "$(echo "$MERGE_RESULT" | jq -r '.author')" = "test" ] || { echo "::error::merge.result author not added"; exit 1; } + + # The merge output must match what a subsequent read sees. + [ "$(echo "$MERGE_RESULT" | jq -S .)" = "$(echo "$RESULT" | jq -S .)" ] || { echo "::error::merge.result does not match read result"; exit 1; } + + echo "write outputs self-test passed" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..058f7b2 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,20 @@ +name: tests + +on: + pull_request: + +# Both reusable workflows seed the PR body via the REST API. +permissions: + pull-requests: write + contents: read + +# Run the two suites as a chain rather than in parallel: each one does a +# whole-body read-modify-write on the same PR description, so running them +# concurrently makes their writes clobber each other. `needs` serializes them. +jobs: + read: + uses: ./.github/workflows/tests-read.yaml + + write: + needs: read + uses: ./.github/workflows/tests-write.yaml diff --git a/README.md b/README.md index 6dbfeeb..2e4eea6 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,15 @@ permissions: When `merge` is `true`, both the existing and incoming values must be plain JSON objects. +### Outputs + +| Name | Description | +| --------- | --------------------------------------------------------------------------------- | +| `result` | JSON-stringified metadata that was written, after the merge if `merge` is enabled | +| `changed` | `"true"` if the PR body was updated, `"false"` if it was already up to date | + +`result` is the effective stored value, which is the most reliable way to learn the result of a `merge: "true"` write without reading the body back. + ### Example ```yaml @@ -81,10 +90,12 @@ jobs: write-metadata: runs-on: ubuntu-latest steps: - - uses: taskworld/pr-meta-action/write@v1 + - id: meta + uses: taskworld/pr-meta-action/write@v1 with: data: '{"reviewed":true,"version":"1.2.3"}' merge: "true" + - run: echo 'wrote ${{ steps.meta.outputs.result }} (changed=${{ steps.meta.outputs.changed }})' ``` ## Notes diff --git a/write/action.yml b/write/action.yml index a923a29..8eee880 100644 --- a/write/action.yml +++ b/write/action.yml @@ -21,6 +21,14 @@ inputs: default: "false" required: false +outputs: + result: + description: JSON-stringified metadata that was written (after merge, if enabled) + value: ${{ steps.write.outputs.result }} + changed: + description: true when the PR body was updated, false when it was already up to date + value: ${{ steps.write.outputs.changed }} + runs: using: composite steps: @@ -95,7 +103,9 @@ runs: block : `${body}\n\n${block}\n` - if (newBody !== body) { + const changed = newBody !== body + + if (changed) { await github.rest.pulls.update({ owner: context.repo.owner, repo: context.repo.repo, @@ -103,3 +113,6 @@ runs: body: newBody, }) } + + core.setOutput('result', JSON.stringify(newVal)) + core.setOutput('changed', String(changed))