2121jobs :
2222 upload :
2323 runs-on : ubuntu-latest
24- # Only run on manual dispatch,
25- # OR if the parent run succeeded and was triggered by a PR from
26- # a branch in the main repo (not a fork)
24+ permissions :
25+ checks : write
26+ # Only run on manual dispatch,
27+ # OR if the parent run succeeded and was triggered by a PR.
28+ # Fork PRs run through so we can post a "skipped" commit status to the PR
29+ # head and log why; the actual upload steps are guarded by fork-check so no
30+ # dev build is published for fork PRs (they lack DEV_DEPLOY_APP access).
2731 if : |
2832 github.event_name == 'workflow_dispatch' ||
2933 (
30- github.event_name == 'workflow_run' &&
34+ github.event_name == 'workflow_run' &&
3135 github.event.workflow_run.event == 'pull_request' &&
32- github.event.workflow_run.conclusion == 'success' &&
33- github.event.workflow_run.head_repository.full_name == github.repository
36+ github.event.workflow_run.conclusion == 'success'
3437 )
3538 steps :
39+ - name : Check if PR is from a fork
40+ id : fork-check
41+ env :
42+ HEAD_REPO : ${{ github.event.workflow_run.head_repository.full_name }}
43+ REPO : ${{ github.repository }}
44+ run : |
45+ if [ -n "${HEAD_REPO}" ] && [ "${HEAD_REPO}" != "${REPO}" ]; then
46+ echo "::notice::Dev-build upload skipped: PR is from a fork (${HEAD_REPO}). Dev builds are only uploaded for PRs from branches within ${REPO}. A maintainer can trigger a build manually via workflow_dispatch with the PR number as input."
47+ echo "is_fork=true" >> "$GITHUB_OUTPUT"
48+ else
49+ echo "is_fork=false" >> "$GITHUB_OUTPUT"
50+ fi
51+
3652 - name : Get required metadata (PR number, commit SHA, workflow run ID containing artifacts)
3753 id : get-metadata
54+ if : steps.fork-check.outputs.is_fork != 'true'
3855 env :
3956 GH_TOKEN : ${{ github.token }}
4057 GH_EVENT_NAME : ${{ github.event_name }}
91108
92109 - name : Download build artifact
93110 id : download-artifact
111+ if : steps.fork-check.outputs.is_fork != 'true'
94112 uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
95113 with :
96114 name : dist # uploaded by publish-dist.yml > publish-dist
@@ -100,6 +118,7 @@ jobs:
100118
101119 - name : Prepare folders
102120 id : setup-metadata
121+ if : steps.fork-check.outputs.is_fork != 'true'
103122 env :
104123 GH_TOKEN : ${{ github.token }}
105124 PR_NUM : ${{ steps.get-metadata.outputs.PR_NUM }}
@@ -124,6 +143,7 @@ jobs:
124143
125144 - name : Generate GitHub App token
126145 id : generate-token
146+ if : steps.fork-check.outputs.is_fork != 'true'
127147 uses : actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
128148 with :
129149 client-id : ${{ vars.DEV_DEPLOY_APP_ID }}
@@ -132,6 +152,7 @@ jobs:
132152 repositories : plotly.js-dev-builds
133153
134154 - name : Check out plotly.js-dev-builds repo
155+ if : steps.fork-check.outputs.is_fork != 'true'
135156 uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
136157 with :
137158 repository : plotly/plotly.js-dev-builds
@@ -140,6 +161,7 @@ jobs:
140161
141162 - name : Commit and push files
142163 id : commit-and-push
164+ if : steps.fork-check.outputs.is_fork != 'true'
143165 env :
144166 PR_NUM : ${{ steps.get-metadata.outputs.PR_NUM }}
145167 SHORT_SHA : ${{ steps.get-metadata.outputs.SHORT_SHA }}
@@ -172,6 +194,7 @@ jobs:
172194 fi
173195
174196 - name : Generate summary
197+ if : steps.fork-check.outputs.is_fork != 'true'
175198 env :
176199 PR_NUM : ${{ steps.get-metadata.outputs.PR_NUM }}
177200 SHORT_SHA : ${{ steps.get-metadata.outputs.SHORT_SHA }}
@@ -182,3 +205,76 @@ jobs:
182205 echo "- Latest build for this PR: [${BASE_URL}/latest/plotly.min.js](${BASE_URL}/latest/plotly.min.js)" >> $GITHUB_STEP_SUMMARY
183206 echo "- Build for this commit: [${BASE_URL}/${SHORT_SHA}/plotly.min.js](${BASE_URL}/${SHORT_SHA}/plotly.min.js)" >> $GITHUB_STEP_SUMMARY
184207 echo "The above links should start working a minute or two after this job completes." >> $GITHUB_STEP_SUMMARY
208+
209+ - name : Report dev-build outcome to PR head
210+ # Reach this step for any run that resolved a PR head SHA — both
211+ # workflow_run (auto-triggered by Publish Dist) and workflow_dispatch
212+ # (a maintainer manually dispatching a build). The step then either
213+ # creates a "dev build" check-run on the PR head, or PATCHes the
214+ # existing one so the row updates in place instead of stacking.
215+ if : |
216+ always() && (
217+ (github.event_name == 'workflow_run' && github.event.workflow_run.head_sha != '') ||
218+ (github.event_name == 'workflow_dispatch' && steps.get-metadata.outputs.SHA != '')
219+ )
220+ env :
221+ GH_TOKEN : ${{ github.token }}
222+ REPO : ${{ github.repository }}
223+ SHA : ${{ github.event.workflow_run.head_sha || steps.get-metadata.outputs.SHA }}
224+ HEAD_REPO : ${{ github.event.workflow_run.head_repository.full_name }}
225+ IS_FORK : ${{ steps.fork-check.outputs.is_fork }}
226+ PR_NUM : ${{ steps.get-metadata.outputs.PR_NUM }}
227+ UPLOAD_OUTCOME : ${{ steps.commit-and-push.outcome }}
228+ RUN_ID : ${{ github.run_id }}
229+ run : |
230+ RUN_URL="https://github.com/${REPO}/actions/runs/${RUN_ID}"
231+
232+ if [ "${IS_FORK}" == "true" ]; then
233+ TITLE="Skipped: PR is from a fork"
234+ CONCLUSION="neutral"
235+ SUMMARY=$(cat <<EOF
236+ Dev builds are not automatically published for PRs from forks (\`${HEAD_REPO}\`) as a security policy.
237+
238+ If you need a dev build for this PR, please **contact a maintainer** and ask them to trigger the *Upload dev build from PR* workflow manually via the Actions tab, providing this PR's number as input.
239+ EOF
240+ )
241+ DETAILS_URL="${RUN_URL}"
242+ elif [ "${UPLOAD_OUTCOME}" == "success" ]; then
243+ TITLE="Dev build available"
244+ CONCLUSION="success"
245+ URL="https://plotly.github.io/plotly.js-dev-builds/${UPLOAD_DIR_NAME}/pr-${PR_NUM}/latest/plotly.min.js"
246+ SUMMARY="Latest dev build for PR #${PR_NUM}: [${URL}](${URL})"
247+ DETAILS_URL="${URL}"
248+ else
249+ TITLE="Dev build upload failed"
250+ CONCLUSION="failure"
251+ SUMMARY="Upload workflow failed. See [the run](${RUN_URL}) for details."
252+ DETAILS_URL="${RUN_URL}"
253+ fi
254+
255+ PAYLOAD=$(jq -n \
256+ --arg conclusion "${CONCLUSION}" \
257+ --arg title "${TITLE}" \
258+ --arg summary "${SUMMARY}" \
259+ --arg details_url "${DETAILS_URL}" \
260+ '{
261+ status: "completed",
262+ conclusion: $conclusion,
263+ details_url: $details_url,
264+ output: {title: $title, summary: $summary}
265+ }')
266+
267+ # Look for an existing "dev build" check-run on this SHA so we can
268+ # PATCH it in place — e.g. so an initial "Skipped: fork" row updates
269+ # to "Dev build available" when a maintainer later dispatches a build.
270+ # If multiple exist (shouldn't happen under normal use), PATCH the most
271+ # recent one identified by the highest check-run id.
272+ EXISTING_ID=$(gh api "repos/${REPO}/commits/${SHA}/check-runs" \
273+ --jq '[.check_runs[] | select(.name == "dev build")] | sort_by(.id) | last | .id // empty')
274+
275+ if [ -n "${EXISTING_ID}" ]; then
276+ echo "${PAYLOAD}" | gh api --method PATCH --input - "repos/${REPO}/check-runs/${EXISTING_ID}"
277+ else
278+ echo "${PAYLOAD}" | jq --arg sha "${SHA}" '. + {name: "dev build", head_sha: $sha}' \
279+ | gh api --method POST --input - "repos/${REPO}/check-runs"
280+ fi
0 commit comments