From 2866f6d9479064eab79ec6e9eb5abcfbfdb121f9 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 14 Nov 2024 19:19:24 -0500 Subject: [PATCH 01/19] feat: e pr download-dist --- .gitignore | 1 + package.json | 1 + src/e-pr.js | 170 ++++++++++++++++++++++++++++++++++++++++++++++----- yarn.lock | 93 +++++++++++++++++++--------- 4 files changed, 221 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index d0901c12..0e64e1e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .nyc_output/ +artifacts configs coverage node_modules diff --git a/package.json b/package.json index 94291a99..bb6ee415 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "command-exists": "^1.2.8", "commander": "^9.0.0", "debug": "^4.3.1", + "extract-zip": "^2.0.1", "inquirer": "^8.2.4", "node-gyp": "^10.0.1", "open": "^6.4.0", diff --git a/src/e-pr.js b/src/e-pr.js index 0ceb756b..3bf51def 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -1,13 +1,17 @@ #!/usr/bin/env node const childProcess = require('child_process'); +const fs = require('fs'); const path = require('path'); + +const extractZip = require('extract-zip'); const querystring = require('querystring'); const semver = require('semver'); - const open = require('open'); const program = require('commander'); +const { Octokit } = require('@octokit/rest'); +const { getGitHubAuthToken } = require('./utils/github-auth'); const { current } = require('./evm-config'); const { color, fatal } = require('./utils/logging'); @@ -134,27 +138,23 @@ function pullRequestSource(source) { } program + .command('open') .description('Open a GitHub URL where you can PR your changes') - .option( - '-s, --source ', - 'Where the changes are coming from', - guessPRSource(current()), - ) - .option( - '-t, --target ', - 'Where the changes are going to', - guessPRTarget(current()), - ) + .option('-s, --source [source_branch]', 'Where the changes are coming from') + .option('-t, --target [target_branch]', 'Where the changes are going to') .option('-b, --backport ', 'Pull request being backported') .action(async (options) => { - if (!options.source) { + const source = options.source || guessPRSource(current()); + const target = options.target || guessPRSource(current()); + + if (!source) { fatal(`'source' is required to create a PR`); - } else if (!options.target) { + } else if (!target) { fatal(`'target' is required to create a PR`); } const repoBaseUrl = 'https://github.com/electron/electron'; - const comparePath = `${options.target}...${pullRequestSource(options.source)}`; + const comparePath = `${target}...${pullRequestSource(source)}`; const queryParams = { expand: 1 }; if (!options.backport) { @@ -188,5 +188,143 @@ program } return open(`${repoBaseUrl}/compare/${comparePath}?${querystring.stringify(queryParams)}`); - }) - .parse(process.argv); + }); + +program + .command('download-dist ') + .description('Download a pull request dist') + .option('--platform [platform]', 'Platform to download dist for', process.platform) + .option('--arch [arch]', 'Architecture to download dist for', process.arch) + .action(async (pullRequestNumber, options) => { + if (!pullRequestNumber) { + fatal(`Pull request number is required to download a PR`); + } + + const octokit = new Octokit({ + auth: await getGitHubAuthToken(['repo']), + }); + + let pullRequest; + try { + const { data } = await octokit.pulls.get({ + owner: 'electron', + repo: 'electron', + pull_number: pullRequestNumber, + }); + pullRequest = data; + } catch (error) { + console.error(`Failed to get pull request: ${error}`); + return; + } + + let workflowRuns; + try { + const { data } = await octokit.rest.actions.listWorkflowRunsForRepo({ + owner: 'electron', + repo: 'electron', + branch: pullRequest.head.ref, + name: 'Build', + event: 'pull_request', + status: 'completed', + per_page: 10, + sort: 'created', + direction: 'desc', + }); + workflowRuns = data.workflow_runs; + } catch (error) { + console.error(`Failed to list workflow runs: ${error}`); + return; + } + + const latestBuildWorkflowRun = workflowRuns.find((run) => run.name === 'Build'); + if (!latestBuildWorkflowRun) { + fatal(`No 'Build' workflow runs found for pull request #${pullRequestNumber}`); + return; + } + + let artifacts; + try { + const { data } = await octokit.actions.listWorkflowRunArtifacts({ + owner: 'electron', + repo: 'electron', + run_id: latestBuildWorkflowRun.id, + }); + artifacts = data.artifacts; + } catch (error) { + console.error(`Failed to list artifacts: ${error}`); + return; + } + + const artifactName = `generated_artifacts_${options.platform}_${options.arch}`; + const artifact = artifacts.find((artifact) => artifact.name === artifactName); + if (!artifact) { + console.error(`Failed to find artifact: ${artifactName}`); + return; + } + + const prDir = path.resolve( + __dirname, + '..', + 'artifacts', + `pr_${pullRequest.number}_${options.platform}_${options.arch}`, + ); + + // Clean up the directory if it exists + try { + await fs.promises.rm(prDir, { recursive: true, force: true }); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + + // Create the directory + await fs.promises.mkdir(prDir, { recursive: true }); + console.log( + `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, + ); + + // Download the artifact + // TODO: use write stream + const response = await octokit.actions.downloadArtifact({ + owner: 'electron', + repo: 'electron', + artifact_id: artifact.id, + archive_format: 'zip', + }); + + const artifactPath = path.join(prDir, `${artifactName}.zip`); + await fs.promises.writeFile(artifactPath, Buffer.from(response.data)); + + console.log('Extracting dist...'); + + // Extract the artifact zip + const extractPath = path.join(prDir, artifactName); + await fs.promises.mkdir(extractPath, { recursive: true }); + await extractZip(artifactPath, { dir: extractPath }); + + // Check if dist.zip exists within the extracted artifact + const distZipPath = path.join(extractPath, 'dist.zip'); + if (!(await fs.promises.stat(distZipPath).catch(() => false))) { + fatal(`dist.zip not found within the extracted artifact.`); + return; + } + + // Extract dist.zip + await extractZip(distZipPath, { dir: prDir }); + + // Check if Electron.app exists within the extracted dist.zip + const electronAppPath = path.join(prDir, 'Electron.app'); + if (!(await fs.promises.stat(electronAppPath).catch(() => false))) { + fatal(`Electron.app not found within the extracted dist.zip.`); + return; + } + + // Remove the artifact and extracted artifact zip + await fs.promises.rm(artifactPath); + await fs.promises.rm(extractPath, { recursive: true }); + + console.info(`Downloaded to ${electronAppPath}`); + }); + +program.parse(process.argv); diff --git a/yarn.lock b/yarn.lock index 5f0edcf5..d4e6b9a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -775,11 +775,25 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/node@*": + version "22.9.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.0.tgz#b7f16e5c3384788542c72dc3d561a7ceae2c0365" + integrity sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ== + dependencies: + undici-types "~6.19.8" + "@types/unist@*", "@types/unist@^2.0.0": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/yauzl@^2.9.1": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + "@vitest/expect@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.3.tgz#4b9a6fff22be4c4cd5d57e687cfda611b514b0ad" @@ -1151,6 +1165,11 @@ btoa-lite@^1.0.0: resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2014,6 +2033,17 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2036,6 +2066,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -2266,6 +2303,13 @@ get-stdin@~9.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" @@ -4091,6 +4135,11 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" @@ -4698,16 +4747,7 @@ string-argv@~0.3.2: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4782,14 +4822,7 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5045,6 +5078,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~6.19.8: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -5333,7 +5371,7 @@ word-wrap@^1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -5351,15 +5389,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -5448,6 +5477,14 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.2" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 6ef4122548803e6d5c660d7293c61b6da3d75de6 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 14 Nov 2024 19:30:23 -0500 Subject: [PATCH 02/19] update docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cd747f5..b81a926b 100644 --- a/README.md +++ b/README.md @@ -482,7 +482,7 @@ git cherry-pick --continue git push # create pull request -e pr --backport 1234 +e pr open --backport 1234 ``` ## Common Usage From 9b2ac1753d104de1cbd39a8a687dda32b49ac665 Mon Sep 17 00:00:00 2001 From: Sam Maddock Date: Thu, 14 Nov 2024 19:53:09 -0500 Subject: [PATCH 03/19] Update src/e-pr.js Co-authored-by: Will Anderson --- src/e-pr.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/e-pr.js b/src/e-pr.js index 3bf51def..21545ce2 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -193,8 +193,8 @@ program program .command('download-dist ') .description('Download a pull request dist') - .option('--platform [platform]', 'Platform to download dist for', process.platform) - .option('--arch [arch]', 'Architecture to download dist for', process.arch) + .option('--platform [platform]', 'Platform to download dist for. Defaults to current platform.', process.platform) + .option('--arch [arch]', 'Architecture to download dist for. Defaults to current arch.', process.arch) .action(async (pullRequestNumber, options) => { if (!pullRequestNumber) { fatal(`Pull request number is required to download a PR`); From 705163f17ce1882be00150f6232b6fa7cb6a99f3 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 14 Nov 2024 19:57:43 -0500 Subject: [PATCH 04/19] use correct executable names per platform --- src/e-pr.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/e-pr.js b/src/e-pr.js index 21545ce2..12576595 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -193,8 +193,16 @@ program program .command('download-dist ') .description('Download a pull request dist') - .option('--platform [platform]', 'Platform to download dist for. Defaults to current platform.', process.platform) - .option('--arch [arch]', 'Architecture to download dist for. Defaults to current arch.', process.arch) + .option( + '--platform [platform]', + 'Platform to download dist for. Defaults to current platform.', + process.platform, + ) + .option( + '--arch [arch]', + 'Architecture to download dist for. Defaults to current arch.', + process.arch, + ) .action(async (pullRequestNumber, options) => { if (!pullRequestNumber) { fatal(`Pull request number is required to download a PR`); @@ -313,10 +321,17 @@ program // Extract dist.zip await extractZip(distZipPath, { dir: prDir }); - // Check if Electron.app exists within the extracted dist.zip - const electronAppPath = path.join(prDir, 'Electron.app'); + // Check if Electron exists within the extracted dist.zip + const platformExecutables = { + win32: 'electron.exe', + darwin: 'Electron.app', + linux: 'electron', + }; + const executableName = platformExecutables[options.platform]; + + const electronAppPath = path.join(prDir, executableName); if (!(await fs.promises.stat(electronAppPath).catch(() => false))) { - fatal(`Electron.app not found within the extracted dist.zip.`); + fatal(`${executableName} not found within the extracted dist.zip.`); return; } From 533cef9e5f129274b06e3d2541d9740d6a0a9dee Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 14 Nov 2024 23:00:43 -0500 Subject: [PATCH 05/19] fix guess target --- src/e-pr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e-pr.js b/src/e-pr.js index 12576595..0a498123 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -145,7 +145,7 @@ program .option('-b, --backport ', 'Pull request being backported') .action(async (options) => { const source = options.source || guessPRSource(current()); - const target = options.target || guessPRSource(current()); + const target = options.target || guessPRTarget(current()); if (!source) { fatal(`'source' is required to create a PR`); From 9e2ad2696b201633cd5ce4fb2dc429a1eecad91a Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 14 Nov 2024 23:02:35 -0500 Subject: [PATCH 06/19] set e pr open as default command --- src/e | 2 +- src/e-pr.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/e b/src/e index 92ec439b..ec0ef6ba 100755 --- a/src/e +++ b/src/e @@ -169,7 +169,7 @@ program .command('backport [pr]', 'Assists with manual backport processes') .command('show ', 'Show info about the current build config') .command('test [specRunnerArgs...]', `Run Electron's spec runner`) - .command('pr [options]', 'Open a GitHub URL where you can PR your changes') + .command('pr [subcommand]', 'Open a GitHub URL where you can PR your changes') .command('patches ', 'Refresh the patches in $root/src/electron/patches/$target') .command('open ', 'Open a GitHub URL for the given commit hash / pull # / issue #') .command('auto-update', 'Check for build-tools updates or enable/disable automatic updates') diff --git a/src/e-pr.js b/src/e-pr.js index 0a498123..4aba2936 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -138,7 +138,7 @@ function pullRequestSource(source) { } program - .command('open') + .command('open', null, { isDefault: true }) .description('Open a GitHub URL where you can PR your changes') .option('-s, --source [source_branch]', 'Where the changes are coming from') .option('-t, --target [target_branch]', 'Where the changes are going to') From 850933dc813091a5dfffe6e2ef49ac8061597bf4 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Fri, 15 Nov 2024 11:03:15 -0500 Subject: [PATCH 07/19] allow specifying output directory --- src/e-pr.js | 62 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/e-pr.js b/src/e-pr.js index 4aba2936..9edd4acd 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -203,6 +203,11 @@ program 'Architecture to download dist for. Defaults to current arch.', process.arch, ) + .option( + '-o, --output ', + 'Specify the output directory for downloaded artifacts. ' + + 'Defaults to ~/.electron_build_tools/artifacts/pr_{number}_{platform}_{arch}', + ) .action(async (pullRequestNumber, options) => { if (!pullRequestNumber) { fatal(`Pull request number is required to download a PR`); @@ -227,7 +232,7 @@ program let workflowRuns; try { - const { data } = await octokit.rest.actions.listWorkflowRunsForRepo({ + const { data } = await octokit.actions.listWorkflowRunsForRepo({ owner: 'electron', repo: 'electron', branch: pullRequest.head.ref, @@ -270,24 +275,38 @@ program return; } - const prDir = path.resolve( - __dirname, - '..', - 'artifacts', - `pr_${pullRequest.number}_${options.platform}_${options.arch}`, - ); + let outputDir; - // Clean up the directory if it exists - try { - await fs.promises.rm(prDir, { recursive: true, force: true }); - } catch (error) { - if (error.code !== 'ENOENT') { - throw error; + if (options.output) { + outputDir = path.resolve(options.output); + + if (!(await fs.promises.stat(outputDir).catch(() => false))) { + fatal(`The output directory '${options.output}' does not exist`); + return; + } + } else { + const defaultDir = path.resolve( + __dirname, + '..', + 'artifacts', + `pr_${pullRequest.number}_${options.platform}_${options.arch}`, + ); + + // Clean up the directory if it exists + try { + await fs.promises.rm(defaultDir, { recursive: true, force: true }); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } } + + // Create the directory + await fs.promises.mkdir(defaultDir, { recursive: true }); + + outputDir = defaultDir; } - // Create the directory - await fs.promises.mkdir(prDir, { recursive: true }); console.log( `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, ); @@ -301,13 +320,13 @@ program archive_format: 'zip', }); - const artifactPath = path.join(prDir, `${artifactName}.zip`); + const artifactPath = path.join(outputDir, `${artifactName}.zip`); await fs.promises.writeFile(artifactPath, Buffer.from(response.data)); console.log('Extracting dist...'); // Extract the artifact zip - const extractPath = path.join(prDir, artifactName); + const extractPath = path.join(outputDir, artifactName); await fs.promises.mkdir(extractPath, { recursive: true }); await extractZip(artifactPath, { dir: extractPath }); @@ -319,7 +338,7 @@ program } // Extract dist.zip - await extractZip(distZipPath, { dir: prDir }); + await extractZip(distZipPath, { dir: outputDir }); // Check if Electron exists within the extracted dist.zip const platformExecutables = { @@ -329,7 +348,12 @@ program }; const executableName = platformExecutables[options.platform]; - const electronAppPath = path.join(prDir, executableName); + if (!executableName) { + fatal(`Unable to extract executable for platform '${options.platform}'`); + return; + } + + const electronAppPath = path.join(outputDir, executableName); if (!(await fs.promises.stat(electronAppPath).catch(() => false))) { fatal(`${executableName} not found within the extracted dist.zip.`); return; From a31b0a23acc4ab871001c80689a101b7412ef742 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Fri, 15 Nov 2024 11:07:11 -0500 Subject: [PATCH 08/19] fix wording --- src/e-pr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e-pr.js b/src/e-pr.js index 9edd4acd..6fbe35cd 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -349,7 +349,7 @@ program const executableName = platformExecutables[options.platform]; if (!executableName) { - fatal(`Unable to extract executable for platform '${options.platform}'`); + fatal(`Unable to find executable for platform '${options.platform}'`); return; } From dc8c56e174c99304d1efbca961e984b03ba3eb2f Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Fri, 15 Nov 2024 11:29:46 -0500 Subject: [PATCH 09/19] download to temporary directory --- src/e-pr.js | 100 ++++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/src/e-pr.js b/src/e-pr.js index 6fbe35cd..7a4fbcb3 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -2,6 +2,7 @@ const childProcess = require('child_process'); const fs = require('fs'); +const os = require('os'); const path = require('path'); const extractZip = require('extract-zip'); @@ -307,63 +308,68 @@ program outputDir = defaultDir; } - console.log( - `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, - ); - - // Download the artifact - // TODO: use write stream - const response = await octokit.actions.downloadArtifact({ - owner: 'electron', - repo: 'electron', - artifact_id: artifact.id, - archive_format: 'zip', - }); + // Download the artifact to a temporary directory + const tempDir = path.join(os.tmpdir(), 'electron-tmp'); + await fs.promises.mkdir(tempDir); - const artifactPath = path.join(outputDir, `${artifactName}.zip`); - await fs.promises.writeFile(artifactPath, Buffer.from(response.data)); + let distExecutablePath; - console.log('Extracting dist...'); + try { + console.log( + `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, + ); - // Extract the artifact zip - const extractPath = path.join(outputDir, artifactName); - await fs.promises.mkdir(extractPath, { recursive: true }); - await extractZip(artifactPath, { dir: extractPath }); + const artifactPath = path.join(tempDir, `${artifactName}.zip`); - // Check if dist.zip exists within the extracted artifact - const distZipPath = path.join(extractPath, 'dist.zip'); - if (!(await fs.promises.stat(distZipPath).catch(() => false))) { - fatal(`dist.zip not found within the extracted artifact.`); - return; - } + const response = await octokit.actions.downloadArtifact({ + owner: 'electron', + repo: 'electron', + artifact_id: artifact.id, + archive_format: 'zip', + }); + await fs.promises.writeFile(artifactPath, Buffer.from(response.data)); - // Extract dist.zip - await extractZip(distZipPath, { dir: outputDir }); + console.log('Extracting dist...'); - // Check if Electron exists within the extracted dist.zip - const platformExecutables = { - win32: 'electron.exe', - darwin: 'Electron.app', - linux: 'electron', - }; - const executableName = platformExecutables[options.platform]; + // Extract the artifact zip + const extractPath = path.join(tempDir, artifactName); + await fs.promises.mkdir(extractPath, { recursive: true }); + await extractZip(artifactPath, { dir: extractPath }); - if (!executableName) { - fatal(`Unable to find executable for platform '${options.platform}'`); - return; - } + // Check if dist.zip exists within the extracted artifact + const distZipPath = path.join(extractPath, 'dist.zip'); + if (!(await fs.promises.stat(distZipPath).catch(() => false))) { + fatal(`dist.zip not found within the extracted artifact.`); + return; + } - const electronAppPath = path.join(outputDir, executableName); - if (!(await fs.promises.stat(electronAppPath).catch(() => false))) { - fatal(`${executableName} not found within the extracted dist.zip.`); - return; - } + // Extract dist.zip to the final outputDir + await extractZip(distZipPath, { dir: outputDir }); + + // Check if Electron exists within the extracted dist.zip + const platformExecutables = { + win32: 'electron.exe', + darwin: 'Electron.app', + linux: 'electron', + }; + const executableName = platformExecutables[options.platform]; + + if (!executableName) { + fatal(`Unable to find executable for platform '${options.platform}'`); + return; + } - // Remove the artifact and extracted artifact zip - await fs.promises.rm(artifactPath); - await fs.promises.rm(extractPath, { recursive: true }); + distExecutablePath = path.join(outputDir, executableName); + if (!(await fs.promises.stat(distExecutablePath).catch(() => false))) { + fatal(`${executableName} not found within the extracted dist.zip.`); + return; + } + } finally { + // Cleanup temporary directory + await fs.promises.rm(tempDir, { recursive: true }); + } - console.info(`Downloaded to ${electronAppPath}`); + console.info(`Downloaded to ${distExecutablePath}`); }); program.parse(process.argv); From 9ac28164da6cedab1200ecff0d6663cec38acd24 Mon Sep 17 00:00:00 2001 From: Sam Maddock Date: Fri, 15 Nov 2024 17:44:18 -0500 Subject: [PATCH 10/19] Update src/e Co-authored-by: David Sanders --- src/e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e b/src/e index ec0ef6ba..65f02ec5 100755 --- a/src/e +++ b/src/e @@ -169,7 +169,7 @@ program .command('backport [pr]', 'Assists with manual backport processes') .command('show ', 'Show info about the current build config') .command('test [specRunnerArgs...]', `Run Electron's spec runner`) - .command('pr [subcommand]', 'Open a GitHub URL where you can PR your changes') + .command('pr [subcommand]', 'Work with PRs to electron/electron') .command('patches ', 'Refresh the patches in $root/src/electron/patches/$target') .command('open ', 'Open a GitHub URL for the given commit hash / pull # / issue #') .command('auto-update', 'Check for build-tools updates or enable/disable automatic updates') From 4bfafc9c4521d3e24476a5e396e27bf0d803ca69 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Fri, 15 Nov 2024 18:30:33 -0500 Subject: [PATCH 11/19] download and extract in-memory --- package.json | 2 +- src/e-pr.js | 98 +++++++++++++++++++++++----------------------------- yarn.lock | 67 +++-------------------------------- 3 files changed, 49 insertions(+), 118 deletions(-) diff --git a/package.json b/package.json index bb6ee415..8518f2c8 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,13 @@ "@marshallofsound/chrome-cookies-secure": "^2.1.1", "@octokit/auth-oauth-device": "^3.1.1", "@octokit/rest": "^18.5.2", + "adm-zip": "^0.5.16", "ajv": "^8.11.0", "ajv-formats": "^2.1.1", "chalk": "^2.4.1", "command-exists": "^1.2.8", "commander": "^9.0.0", "debug": "^4.3.1", - "extract-zip": "^2.0.1", "inquirer": "^8.2.4", "node-gyp": "^10.0.1", "open": "^6.4.0", diff --git a/src/e-pr.js b/src/e-pr.js index 7a4fbcb3..1586d161 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -2,10 +2,9 @@ const childProcess = require('child_process'); const fs = require('fs'); -const os = require('os'); const path = require('path'); -const extractZip = require('extract-zip'); +const AdmZip = require('adm-zip'); const querystring = require('querystring'); const semver = require('semver'); const open = require('open'); @@ -16,6 +15,8 @@ const { getGitHubAuthToken } = require('./utils/github-auth'); const { current } = require('./evm-config'); const { color, fatal } = require('./utils/logging'); +const d = require('debug')('build-tools:pr'); + // Adapted from https://github.com/electron/clerk function findNoteInPRBody(body) { const onelineMatch = /(?:(?:\r?\n)|^)notes: (.+?)(?:(?:\r?\n)|$)/gi.exec(body); @@ -214,10 +215,12 @@ program fatal(`Pull request number is required to download a PR`); } + d('checking auth...'); const octokit = new Octokit({ auth: await getGitHubAuthToken(['repo']), }); + d('fetching pr info...'); let pullRequest; try { const { data } = await octokit.pulls.get({ @@ -231,6 +234,7 @@ program return; } + d('fetching workflow runs...'); let workflowRuns; try { const { data } = await octokit.actions.listWorkflowRunsForRepo({ @@ -256,6 +260,7 @@ program return; } + d('fetching artifacts...'); let artifacts; try { const { data } = await octokit.actions.listWorkflowRunArtifacts({ @@ -308,68 +313,51 @@ program outputDir = defaultDir; } - // Download the artifact to a temporary directory - const tempDir = path.join(os.tmpdir(), 'electron-tmp'); - await fs.promises.mkdir(tempDir); - - let distExecutablePath; - - try { - console.log( - `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, - ); - - const artifactPath = path.join(tempDir, `${artifactName}.zip`); + console.log( + `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, + ); - const response = await octokit.actions.downloadArtifact({ - owner: 'electron', - repo: 'electron', - artifact_id: artifact.id, - archive_format: 'zip', - }); - await fs.promises.writeFile(artifactPath, Buffer.from(response.data)); - - console.log('Extracting dist...'); + const response = await octokit.actions.downloadArtifact({ + owner: 'electron', + repo: 'electron', + artifact_id: artifact.id, + archive_format: 'zip', + }); - // Extract the artifact zip - const extractPath = path.join(tempDir, artifactName); - await fs.promises.mkdir(extractPath, { recursive: true }); - await extractZip(artifactPath, { dir: extractPath }); + // Extract artifact zip in-memory + const artifactZip = new AdmZip(Buffer.from(response.data)); - // Check if dist.zip exists within the extracted artifact - const distZipPath = path.join(extractPath, 'dist.zip'); - if (!(await fs.promises.stat(distZipPath).catch(() => false))) { - fatal(`dist.zip not found within the extracted artifact.`); - return; - } + const distZipEntry = artifactZip.getEntry('dist.zip'); + if (!distZipEntry) { + fatal(`dist.zip not found in build artifact.`); + return; + } - // Extract dist.zip to the final outputDir - await extractZip(distZipPath, { dir: outputDir }); + // Extract dist.zip in-memory + const distZipContents = artifactZip.readFile(distZipEntry); + const distZip = new AdmZip(distZipContents); - // Check if Electron exists within the extracted dist.zip - const platformExecutables = { - win32: 'electron.exe', - darwin: 'Electron.app', - linux: 'electron', - }; - const executableName = platformExecutables[options.platform]; + const platformExecutables = { + win32: 'electron.exe', + darwin: 'Electron.app/', + linux: 'electron', + }; - if (!executableName) { - fatal(`Unable to find executable for platform '${options.platform}'`); - return; - } + const executableName = platformExecutables[options.platform]; + if (!executableName) { + fatal(`Unable to find executable for platform '${options.platform}'`); + return; + } - distExecutablePath = path.join(outputDir, executableName); - if (!(await fs.promises.stat(distExecutablePath).catch(() => false))) { - fatal(`${executableName} not found within the extracted dist.zip.`); - return; - } - } finally { - // Cleanup temporary directory - await fs.promises.rm(tempDir, { recursive: true }); + if (!distZip.getEntry(executableName)) { + fatal(`${executableName} not found within dist.zip.`); + return; } - console.info(`Downloaded to ${distExecutablePath}`); + // Extract dist.zip to the output directory + await distZip.extractAllToAsync(outputDir); + + console.info(`Downloaded to ${outputDir}`); }); program.parse(process.argv); diff --git a/yarn.lock b/yarn.lock index d4e6b9a4..1e064907 100644 --- a/yarn.lock +++ b/yarn.lock @@ -775,25 +775,11 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== -"@types/node@*": - version "22.9.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.0.tgz#b7f16e5c3384788542c72dc3d561a7ceae2c0365" - integrity sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ== - dependencies: - undici-types "~6.19.8" - "@types/unist@*", "@types/unist@^2.0.0": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== -"@types/yauzl@^2.9.1": - version "2.10.3" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" - integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== - dependencies: - "@types/node" "*" - "@vitest/expect@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.3.tgz#4b9a6fff22be4c4cd5d57e687cfda611b514b0ad" @@ -878,6 +864,11 @@ acorn@^8.8.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +adm-zip@^0.5.16: + version "0.5.16" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" + integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1165,11 +1156,6 @@ btoa-lite@^1.0.0: resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2033,17 +2019,6 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2066,13 +2041,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -2303,13 +2271,6 @@ get-stdin@~9.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - get-stream@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" @@ -4135,11 +4096,6 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" @@ -5078,11 +5034,6 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -undici-types@~6.19.8: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -5477,14 +5428,6 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.2" -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 1e45220ee6355692769fc42d15a6b61dedf5f758 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Fri, 15 Nov 2024 19:00:38 -0500 Subject: [PATCH 12/19] add confirmation prompt --- src/e-pr.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/e-pr.js b/src/e-pr.js index 1586d161..60542917 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -10,6 +10,7 @@ const semver = require('semver'); const open = require('open'); const program = require('commander'); const { Octokit } = require('@octokit/rest'); +const inquirer = require('inquirer'); const { getGitHubAuthToken } = require('./utils/github-auth'); const { current } = require('./evm-config'); @@ -210,6 +211,7 @@ program 'Specify the output directory for downloaded artifacts. ' + 'Defaults to ~/.electron_build_tools/artifacts/pr_{number}_{platform}_{arch}', ) + .option('-s, --skip-confirmation', 'Skip the confirmation prompt before downloading the dist.') .action(async (pullRequestNumber, options) => { if (!pullRequestNumber) { fatal(`Pull request number is required to download a PR`); @@ -234,6 +236,24 @@ program return; } + if (!options.skipConfirmation) { + const isElectronRepo = pullRequest.head.repo.full_name !== 'electron/electron'; + const { proceed } = await inquirer.prompt([ + { + type: 'confirm', + name: 'proceed', + message: `You are about to download artifacts from: + +“${pullRequest.title} (#${pullRequest.number})” by ${pullRequest.user.login} +${pullRequest.head.repo.html_url}${isElectronRepo ? ' (fork)' : ''} + +Proceed?`, + }, + ]); + + if (!proceed) return; + } + d('fetching workflow runs...'); let workflowRuns; try { @@ -291,10 +311,9 @@ program return; } } else { + const artifactsDir = path.resolve(__dirname, '..', 'artifacts'); const defaultDir = path.resolve( - __dirname, - '..', - 'artifacts', + artifactsDir, `pr_${pullRequest.number}_${options.platform}_${options.arch}`, ); @@ -329,7 +348,7 @@ program const distZipEntry = artifactZip.getEntry('dist.zip'); if (!distZipEntry) { - fatal(`dist.zip not found in build artifact.`); + fatal('dist.zip not found in build artifact.'); return; } From 53add7849a711ada94046334d743ca70d268b817 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Sun, 17 Nov 2024 00:01:52 -0500 Subject: [PATCH 13/19] download progress --- src/download.js | 22 +--------------------- src/e-pr.js | 40 ++++++++++++++++++++++++++++++---------- src/utils/download.js | 26 ++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 src/utils/download.js diff --git a/src/download.js b/src/download.js index 41ac0bb5..5fffc4d0 100644 --- a/src/download.js +++ b/src/download.js @@ -1,29 +1,9 @@ const fs = require('fs'); const stream = require('stream'); const { pipeline } = require('stream/promises'); -const ProgressBar = require('progress'); const { fatal } = require('./utils/logging'); - -const MB_BYTES = 1024 * 1024; - -const progressStream = function (total, tokens) { - var pt = new stream.PassThrough(); - - pt.on('pipe', function (stream) { - const bar = new ProgressBar(tokens, { total: Math.round(total) }); - - pt.on('data', function (chunk) { - const elapsed = new Date() - bar.start; - const rate = bar.curr / (elapsed / 1000); - bar.tick(chunk.length, { - mbRate: (rate / MB_BYTES).toFixed(2), - }); - }); - }); - - return pt; -}; +const { progressStream } = require('./utils/download'); const write = fs.createWriteStream(process.argv[3]); diff --git a/src/e-pr.js b/src/e-pr.js index 60542917..15fe6b33 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -3,6 +3,9 @@ const childProcess = require('child_process'); const fs = require('fs'); const path = require('path'); +const { Readable } = require('stream'); +const { pipeline } = require('stream/promises'); +const { buffer } = require('stream/consumers'); const AdmZip = require('adm-zip'); const querystring = require('querystring'); @@ -12,6 +15,7 @@ const program = require('commander'); const { Octokit } = require('@octokit/rest'); const inquirer = require('inquirer'); +const { progressStream } = require('./utils/download'); const { getGitHubAuthToken } = require('./utils/github-auth'); const { current } = require('./evm-config'); const { color, fatal } = require('./utils/logging'); @@ -218,9 +222,8 @@ program } d('checking auth...'); - const octokit = new Octokit({ - auth: await getGitHubAuthToken(['repo']), - }); + const auth = await getGitHubAuthToken(['repo']); + const octokit = new Octokit({ auth }); d('fetching pr info...'); let pullRequest; @@ -277,7 +280,6 @@ Proceed?`, const latestBuildWorkflowRun = workflowRuns.find((run) => run.name === 'Build'); if (!latestBuildWorkflowRun) { fatal(`No 'Build' workflow runs found for pull request #${pullRequestNumber}`); - return; } d('fetching artifacts...'); @@ -308,7 +310,6 @@ Proceed?`, if (!(await fs.promises.stat(outputDir).catch(() => false))) { fatal(`The output directory '${options.output}' does not exist`); - return; } } else { const artifactsDir = path.resolve(__dirname, '..', 'artifacts'); @@ -336,23 +337,43 @@ Proceed?`, `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, ); - const response = await octokit.actions.downloadArtifact({ + const { url } = await octokit.actions.downloadArtifact.endpoint({ owner: 'electron', repo: 'electron', artifact_id: artifact.id, archive_format: 'zip', }); + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${auth}`, + }, + }); + + if (!response.ok) { + fatal(`Could not find artifact: ${url} got ${response.status}`); + } + + const total = parseInt(response.headers.get('content-length'), 10); + const artifactStream = Readable.fromWeb(response.body); + + if (!process.env.CI) { + // Show download progress + pipeline(artifactStream, progressStream(total, '[:bar] :mbRateMB/s :percent :etas')); + } + + const artifactData = await buffer(artifactStream); + // Extract artifact zip in-memory - const artifactZip = new AdmZip(Buffer.from(response.data)); + const artifactZip = new AdmZip(Buffer.from(artifactData)); const distZipEntry = artifactZip.getEntry('dist.zip'); if (!distZipEntry) { fatal('dist.zip not found in build artifact.'); - return; } // Extract dist.zip in-memory + d('unzipping dist.zip from artifact...'); const distZipContents = artifactZip.readFile(distZipEntry); const distZip = new AdmZip(distZipContents); @@ -365,15 +386,14 @@ Proceed?`, const executableName = platformExecutables[options.platform]; if (!executableName) { fatal(`Unable to find executable for platform '${options.platform}'`); - return; } if (!distZip.getEntry(executableName)) { fatal(`${executableName} not found within dist.zip.`); - return; } // Extract dist.zip to the output directory + d('unzipping dist.zip...'); await distZip.extractAllToAsync(outputDir); console.info(`Downloaded to ${outputDir}`); diff --git a/src/utils/download.js b/src/utils/download.js new file mode 100644 index 00000000..c8475b1b --- /dev/null +++ b/src/utils/download.js @@ -0,0 +1,26 @@ +const stream = require('stream'); +const ProgressBar = require('progress'); + +const MB_BYTES = 1024 * 1024; + +const progressStream = function (total, tokens) { + var pt = new stream.PassThrough(); + + pt.on('pipe', function (_stream) { + const bar = new ProgressBar(tokens, { total: Math.round(total) }); + + pt.on('data', function (chunk) { + const elapsed = new Date() - bar.start; + const rate = bar.curr / (elapsed / 1000); + bar.tick(chunk.length, { + mbRate: (rate / MB_BYTES).toFixed(2), + }); + }); + }); + + return pt; +}; + +module.exports = { + progressStream, +}; From e2b4c535497927498f314c96a79b0c296d4bc3d4 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Sun, 17 Nov 2024 00:05:35 -0500 Subject: [PATCH 14/19] success color --- src/e-pr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e-pr.js b/src/e-pr.js index 15fe6b33..48cd7404 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -396,7 +396,7 @@ Proceed?`, d('unzipping dist.zip...'); await distZip.extractAllToAsync(outputDir); - console.info(`Downloaded to ${outputDir}`); + console.log(`${color.success} Downloaded to ${outputDir}`); }); program.parse(process.argv); From e273f0a101e03cddeba959f097dc12943ba43c34 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Sun, 17 Nov 2024 00:06:29 -0500 Subject: [PATCH 15/19] skip confirmation in CI --- src/e-pr.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/e-pr.js b/src/e-pr.js index 48cd7404..74259719 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -215,7 +215,11 @@ program 'Specify the output directory for downloaded artifacts. ' + 'Defaults to ~/.electron_build_tools/artifacts/pr_{number}_{platform}_{arch}', ) - .option('-s, --skip-confirmation', 'Skip the confirmation prompt before downloading the dist.') + .option( + '-s, --skip-confirmation', + 'Skip the confirmation prompt before downloading the dist.', + !!process.env.CI, + ) .action(async (pullRequestNumber, options) => { if (!pullRequestNumber) { fatal(`Pull request number is required to download a PR`); From 6db0d855f2471e7df0257349951ec130b33d108b Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Wed, 20 Nov 2024 19:08:38 -0500 Subject: [PATCH 16/19] refactor: use extract-zip to extract symlinks properly --- package.json | 1 + src/e-pr.js | 87 ++++++++++++++++++++++++++++++++-------------------- yarn.lock | 62 +++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 8518f2c8..d0e27ac1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "command-exists": "^1.2.8", "commander": "^9.0.0", "debug": "^4.3.1", + "extract-zip": "^2.0.1", "inquirer": "^8.2.4", "node-gyp": "^10.0.1", "open": "^6.4.0", diff --git a/src/e-pr.js b/src/e-pr.js index 74259719..3b6654a6 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -2,12 +2,12 @@ const childProcess = require('child_process'); const fs = require('fs'); +const os = require('os'); const path = require('path'); const { Readable } = require('stream'); const { pipeline } = require('stream/promises'); -const { buffer } = require('stream/consumers'); -const AdmZip = require('adm-zip'); +const extractZip = require('extract-zip'); const querystring = require('querystring'); const semver = require('semver'); const open = require('open'); @@ -341,6 +341,10 @@ Proceed?`, `Downloading artifact '${artifactName}' from pull request #${pullRequestNumber}...`, ); + // Download the artifact to a temporary directory + const tempDir = path.join(os.tmpdir(), 'electron-tmp'); + await fs.promises.mkdir(tempDir); + const { url } = await octokit.actions.downloadArtifact.endpoint({ owner: 'electron', repo: 'electron', @@ -359,48 +363,63 @@ Proceed?`, } const total = parseInt(response.headers.get('content-length'), 10); - const artifactStream = Readable.fromWeb(response.body); + const artifactDownloadStream = Readable.fromWeb(response.body); - if (!process.env.CI) { - // Show download progress - pipeline(artifactStream, progressStream(total, '[:bar] :mbRateMB/s :percent :etas')); - } + try { + const artifactZipPath = path.join(tempDir, `${artifactName}.zip`); + const artifactFileStream = fs.createWriteStream(artifactZipPath); + await pipeline( + artifactDownloadStream, + // Show download progress + ...(process.env.CI ? [] : [progressStream(total, '[:bar] :mbRateMB/s :percent :etas')]), + artifactFileStream, + ); - const artifactData = await buffer(artifactStream); + // Extract artifact zip + d('unzipping artifact to %s', tempDir); + await extractZip(artifactZipPath, { dir: tempDir }); - // Extract artifact zip in-memory - const artifactZip = new AdmZip(Buffer.from(artifactData)); + // Check if dist.zip exists within the extracted artifact + const distZipPath = path.join(tempDir, 'dist.zip'); + if (!(await fs.promises.stat(distZipPath).catch(() => false))) { + throw new Error(`dist.zip not found in build artifact.`); + } - const distZipEntry = artifactZip.getEntry('dist.zip'); - if (!distZipEntry) { - fatal('dist.zip not found in build artifact.'); - } + // Extract dist.zip + // NOTE: 'extract-zip' is used as it correctly extracts symlinks. + d('unzipping dist.zip to %s', outputDir); + await extractZip(distZipPath, { dir: outputDir }); - // Extract dist.zip in-memory - d('unzipping dist.zip from artifact...'); - const distZipContents = artifactZip.readFile(distZipEntry); - const distZip = new AdmZip(distZipContents); + const platformExecutables = { + win32: 'electron.exe', + darwin: 'Electron.app/', + linux: 'electron', + }; - const platformExecutables = { - win32: 'electron.exe', - darwin: 'Electron.app/', - linux: 'electron', - }; + const executableName = platformExecutables[options.platform]; + if (!executableName) { + throw new Error(`Unable to find executable for platform '${options.platform}'`); + } - const executableName = platformExecutables[options.platform]; - if (!executableName) { - fatal(`Unable to find executable for platform '${options.platform}'`); - } + const executablePath = path.join(outputDir, executableName); + if (!(await fs.promises.stat(executablePath).catch(() => false))) { + throw new Error(`${executableName} not found within dist.zip.`); + } - if (!distZip.getEntry(executableName)) { - fatal(`${executableName} not found within dist.zip.`); - } + // Cleanup temporary files + await fs.promises.rm(tempDir, { recursive: true }); - // Extract dist.zip to the output directory - d('unzipping dist.zip...'); - await distZip.extractAllToAsync(outputDir); + console.log(`${color.success} Downloaded to ${outputDir}`); + } catch (error) { + // Cleanup temporary files + try { + await fs.promises.rm(tempDir, { recursive: true }); + } catch { + // ignore + } - console.log(`${color.success} Downloaded to ${outputDir}`); + fatal(error); + } }); program.parse(process.argv); diff --git a/yarn.lock b/yarn.lock index 1e064907..48b36f45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -775,11 +775,25 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/node@*": + version "22.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.1.tgz#bdf91c36e0e7ecfb7257b2d75bf1b206b308ca71" + integrity sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg== + dependencies: + undici-types "~6.19.8" + "@types/unist@*", "@types/unist@^2.0.0": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/yauzl@^2.9.1": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + "@vitest/expect@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.3.tgz#4b9a6fff22be4c4cd5d57e687cfda611b514b0ad" @@ -1156,6 +1170,11 @@ btoa-lite@^1.0.0: resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -2019,6 +2038,17 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2041,6 +2071,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -2271,6 +2308,13 @@ get-stdin@~9.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" @@ -4096,6 +4140,11 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" @@ -5034,6 +5083,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~6.19.8: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -5428,6 +5482,14 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.2" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 6d408ec29f423b61ca93d3055ed3441e3329f560 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Wed, 20 Nov 2024 19:11:55 -0500 Subject: [PATCH 17/19] chore: remove adm-zip --- package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/package.json b/package.json index d0e27ac1..bb6ee415 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "@marshallofsound/chrome-cookies-secure": "^2.1.1", "@octokit/auth-oauth-device": "^3.1.1", "@octokit/rest": "^18.5.2", - "adm-zip": "^0.5.16", "ajv": "^8.11.0", "ajv-formats": "^2.1.1", "chalk": "^2.4.1", diff --git a/yarn.lock b/yarn.lock index 48b36f45..be5f934c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -878,11 +878,6 @@ acorn@^8.8.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== -adm-zip@^0.5.16: - version "0.5.16" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" - integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" From 60e0f4da2ebe9fb712594fd3a2884b999d23797b Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Tue, 19 Nov 2024 20:24:35 -0500 Subject: [PATCH 18/19] feat: register local version in fiddle --- src/e-pr.js | 26 ++++++++++++++++++++++++++ src/utils/open-external.js | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/utils/open-external.js diff --git a/src/e-pr.js b/src/e-pr.js index 3b6654a6..69ee9745 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -19,6 +19,7 @@ const { progressStream } = require('./utils/download'); const { getGitHubAuthToken } = require('./utils/github-auth'); const { current } = require('./evm-config'); const { color, fatal } = require('./utils/logging'); +const { openExternal } = require('./utils/open-external'); const d = require('debug')('build-tools:pr'); @@ -220,6 +221,7 @@ program 'Skip the confirmation prompt before downloading the dist.', !!process.env.CI, ) + .option('--fiddle', 'Registers build as a local version in Electron Fiddle.') .action(async (pullRequestNumber, options) => { if (!pullRequestNumber) { fatal(`Pull request number is required to download a PR`); @@ -420,6 +422,30 @@ Proceed?`, fatal(error); } + + if (options.fiddle) { + const version = (await fs.promises.readFile(path.join(outputDir, 'version'))).toString( + 'utf8', + ); + if (!semver.valid(version)) { + fatal(`Downloaded build contains invalid version: ${version}`); + } + + // Replace prerelease version to avoid colliding with real versions in the + // version picker. + // 35.0.0-nightly.20241114 => 35.0.0-dist + const parsedVersion = semver.parse(version); + parsedVersion.prerelease = ['dist']; + const localVersion = parsedVersion.format(); + + const fiddleUrl = new URL('electron-fiddle://register-local-version/'); + fiddleUrl.searchParams.append('name', pullRequest.title); + fiddleUrl.searchParams.append('version', localVersion); + fiddleUrl.searchParams.append('path', outputDir); + openExternal(fiddleUrl.href); + + console.log(`${color.success} Registered local version ${localVersion} in Electron Fiddle`); + } }); program.parse(process.argv); diff --git a/src/utils/open-external.js b/src/utils/open-external.js new file mode 100644 index 00000000..f7c41c1b --- /dev/null +++ b/src/utils/open-external.js @@ -0,0 +1,27 @@ +const cp = require('node:child_process'); +const d = require('debug')('build-tools:open-external'); + +function openExternal(url) { + d('opening %s', url); + + let command; + switch (process.platform) { + case 'win32': + command = `start "electron build-tools" "${url}"`; + break; + case 'darwin': + command = `open "${url}"`; + break; + case 'linux': + command = `xdg-open "${url}"`; + break; + default: + throw new Error(`openExternal: Unsupported platform: ${process.platform}`); + } + + cp.execSync(command); +} + +module.exports = { + openExternal, +}; From b3f5dfa4d186221da6c5f161af434168378f2ef4 Mon Sep 17 00:00:00 2001 From: Samuel Maddock Date: Thu, 21 Nov 2024 21:32:41 -0500 Subject: [PATCH 19/19] include short commit hash in version --- src/e-pr.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/e-pr.js b/src/e-pr.js index 69ee9745..19581e80 100644 --- a/src/e-pr.js +++ b/src/e-pr.js @@ -433,9 +433,10 @@ Proceed?`, // Replace prerelease version to avoid colliding with real versions in the // version picker. - // 35.0.0-nightly.20241114 => 35.0.0-dist + // 35.0.0-nightly.20241114 => 35.0.0-dist.c6164aa + const shortCommitHash = latestBuildWorkflowRun.head_sha.substring(0, 7); const parsedVersion = semver.parse(version); - parsedVersion.prerelease = ['dist']; + parsedVersion.prerelease = ['dist', shortCommitHash]; const localVersion = parsedVersion.format(); const fiddleUrl = new URL('electron-fiddle://register-local-version/');