Repository: electron/electron
Pull request: ci: fix pulling previous object checksums

#### Description of Change

<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md

NOTE: PRS submitted without this template will be automatically closed.
-->

Follow-up to #50390. There are two issues that this is fixing:
* When `archive: false` is used for `actions/upload-artifact`, the name of the artifact ends up being the filename, not the name supplied in the `name` input, so it needs to be looked up using that name.
* The parent commit is not guaranteed to have the artifact we're looking for:
  * Docs only PRs don't trigger a build and won't upload the artifact
  * On release branches we limit concurrency so a given commit may not get a full build run if another PR is merged shortly after it

Removes `dawidd6/action-download-artifact` which in hindsight I shouldn't have added, and it can't handle the non-archived artifacts.

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included

#### Release Notes

Notes: none <!-- Please add a one-line description for app developers to read in the release notes, or 'none' if no notes relevant to app developers. Examples and help on special cases: https://github.com/electron/clerk/blob/main/README.md#examples -->


Changed files:
filename: .github/actions/build-electron/action.yml
patch: @@ -48,19 +48,15 @@ runs:
       shell: bash
       run: echo "::add-matcher::src/electron/.github/problem-matchers/clang.json"
     - name: Download previous object checksums
-      uses: dawidd6/action-download-artifact@09b07ec687d10771279a426c79925ee415c12906 # v17
-      if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && inputs.is-asan != 'true' }}
-      with:
-        name: object_checksums_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}
-        commit: ${{ case(github.event_name == 'push', github.event.before, github.event.pull_request.base.sha) }}
-        path: src
-        if_no_artifact_found: ignore
-    - name: Move previous object checksums
       shell: bash
-      run: |
-        if [ -f src/object-checksums_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json ]; then
-          mv src/object-checksums_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json src/previous-object-checksums.json
-        fi
+      if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && inputs.is-asan != 'true' }}
+      env:
+        GITHUB_TOKEN: ${{ github.token }}
+        ARTIFACT_NAME: object-checksums.${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json
+        SEARCH_BRANCH: ${{ case(github.event_name == 'push', github.ref_name, github.event.pull_request.base.ref) }}
+        REPO: ${{ github.repository }}
+        OUTPUT_PATH: src/previous-object-checksums.json
+      run: node src/electron/.github/actions/build-electron/download-previous-object-checksums.mjs
     - name: Build Electron ${{ inputs.step-suffix }}
       if: ${{ inputs.target-platform != 'win' }}
       shell: bash

filename: .github/actions/build-electron/download-previous-object-checksums.mjs
patch: @@ -0,0 +1,82 @@
+import { Octokit } from '@octokit/rest';
+
+import { writeFileSync } from 'node:fs';
+
+const token = process.env.GITHUB_TOKEN;
+const repo = process.env.REPO;
+const artifactName = process.env.ARTIFACT_NAME;
+const branch = process.env.SEARCH_BRANCH;
+const outputPath = process.env.OUTPUT_PATH;
+
+const required = { GITHUB_TOKEN: token, REPO: repo, ARTIFACT_NAME: artifactName, SEARCH_BRANCH: branch, OUTPUT_PATH: outputPath };
+const missing = Object.entries(required).filter(([, v]) => !v).map(([k]) => k);
+if (missing.length > 0) {
+  console.error(`Missing required environment variables: ${missing.join(', ')}`);
+  process.exit(1);
+}
+
+const [owner, repoName] = repo.split('/');
+const octokit = new Octokit({ auth: token });
+
+async function main () {
+  console.log(`Searching for artifact '${artifactName}' on branch '${branch}'...`);
+
+  // Resolve the "Build" workflow name to an ID, mirroring how `gh run list --workflow` works
+  // under the hood (it uses /repos/{owner}/{repo}/actions/workflows/{id}/runs).
+  const { data: workflows } = await octokit.actions.listRepoWorkflows({ owner, repo: repoName });
+  const buildWorkflow = workflows.workflows.find((w) => w.name === 'Build');
+  if (!buildWorkflow) {
+    console.log('Could not find "Build" workflow, continuing without previous checksums');
+    return;
+  }
+
+  const { data: runs } = await octokit.actions.listWorkflowRuns({
+    owner,
+    repo: repoName,
+    workflow_id: buildWorkflow.id,
+    branch,
+    status: 'completed',
+    event: 'push',
+    per_page: 20,
+    exclude_pull_requests: true
+  });
+
+  for (const run of runs.workflow_runs) {
+    const { data: artifacts } = await octokit.actions.listWorkflowRunArtifacts({
+      owner,
+      repo: repoName,
+      run_id: run.id,
+      name: artifactName
+    });
+
+    if (artifacts.artifacts.length > 0) {
+      const artifact = artifacts.artifacts[0];
+      console.log(`Found artifact in run ${run.id} (artifact ID: ${artifact.id}), downloading...`);
+
+      // Non-archived artifacts are still downloaded from the /zip endpoint
+      const response = await octokit.actions.downloadArtifact({
+        owner,
+        repo: repoName,
+        artifact_id: artifact.id,
+        archive_format: 'zip'
+      });
+
+      if (response.headers['content-type'] !== 'application/json') {
+        console.error(`Unexpected content type for artifact download: ${response.headers['content-type']}`);
+        console.error('Expected application/json, continuing without previous checksums');
+        return;
+      }
+
+      writeFileSync(outputPath, JSON.stringify(response.data));
+      console.log('Downloaded previous object checksums successfully');
+      return;
+    }
+  }
+
+  console.log(`No previous object checksums found in last ${runs.workflow_runs.length} runs, continuing without them`);
+}
+
+main().catch((err) => {
+  console.error('Failed to download previous object checksums, continuing without them:', err.message);
+  process.exit(0);
+});