├── .github
├── CODEOWNERS
└── workflows
│ ├── publish-npm.yml
│ ├── release.yml
│ ├── semantic.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── chromedriver.js
├── download-chromedriver.js
├── package-lock.json
├── package.json
├── script
├── publish.js
└── update-version.js
└── test
└── chromedriver-test.js
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This Repo is an AOR of the Releases Working Group
2 | * @electron/wg-releases
--------------------------------------------------------------------------------
/.github/workflows/publish-npm.yml:
--------------------------------------------------------------------------------
1 | name: Publish npm Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - v[0-9]+.[0-9]+.[0-9]+
7 |
8 | jobs:
9 | test:
10 | uses: ./.github/workflows/test.yml
11 | with:
12 | electron-version: ${{ github.ref_name }}
13 | release:
14 | runs-on: ubuntu-latest
15 | needs: test
16 | environment: npm
17 | permissions:
18 | contents: write # for creating new release
19 | id-token: write # for CFA
20 | steps:
21 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
22 | - name: "Use Node.js ${{ matrix.node-version }}"
23 | uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
24 | with:
25 | node-version: "20.16.0"
26 | - name: Update Version
27 | run: node script/update-version.js ${{ github.ref_name }}
28 | - name: Confirm Version Updated
29 | run: node -e "if (require('./package.json').version === '0.0.0-development') process.exit(1)"
30 | - name: Install Dependencies
31 | run: npm ci
32 | - name: Obtain OIDC token
33 | id: oidc
34 | run: |
35 | token=$(curl --fail -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
36 | "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=continuousauth.dev" | jq -r '.value')
37 | echo "::add-mask::${token}"
38 | echo "token=${token}" >> $GITHUB_OUTPUT
39 | - name: Obtain GitHub credentials
40 | id: github_creds
41 | run: |
42 | token=$(curl --fail "https://continuousauth.dev/api/request/${{ secrets.CFA_PROJECT_ID }}/github/credentials" \
43 | -X POST \
44 | -H "Content-Type: application/json" \
45 | -H "Authorization: bearer ${{ secrets.CFA_SECRET }}" \
46 | --data "{\"token\":\"${{ steps.oidc.outputs.token }}\"}" | jq -r '.GITHUB_TOKEN')
47 | echo "::add-mask::${token}"
48 | echo "token=${token}" >> $GITHUB_OUTPUT
49 | - name: Set NPM Credentials
50 | run: echo //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} > ~/.npmrc
51 | - name: Check NPM Credentials
52 | run: npm whoami
53 | - name: CFA Publish
54 | env:
55 | CFA_PROJECT_ID: ${{ secrets.CFA_PROJECT_ID }}
56 | CFA_SECRET: ${{ secrets.CFA_SECRET }}
57 | GITHUB_OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
58 | run: node script/publish.js
59 | - name: Create Release
60 | env:
61 | GITHUB_TOKEN: ${{ steps.github_creds.outputs.token }}
62 | run: gh release create ${{ github.ref_name }} -t ${{ github.ref_name }}
63 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release For New Electron Version
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: Electron version to use with "v" prefix (e.g. v30.0.0)
8 | required: true
9 |
10 | jobs:
11 | test:
12 | uses: ./.github/workflows/test.yml
13 | with:
14 | electron-version: ${{ github.event.inputs.version }}
15 | tag_new_version:
16 | runs-on: ubuntu-latest
17 | environment: deps-releaser
18 | needs: test
19 | steps:
20 | - name: Generate GitHub App token
21 | uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1
22 | id: generate-token
23 | with:
24 | creds: ${{ secrets.DEPS_RELEASER_GH_APP_CREDS }}
25 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
26 | with:
27 | token: ${{ steps.generate-token.outputs.token }}
28 | # Tag here, the publish-npm.yml workflow will trigger on the new tag and do the CFA publish
29 | - name: Push New Tag
30 | run: |
31 | git tag ${{ github.event.inputs.version }}
32 | git push origin ${{ github.event.inputs.version }}
33 |
--------------------------------------------------------------------------------
/.github/workflows/semantic.yml:
--------------------------------------------------------------------------------
1 | name: "Check Semantic Commit"
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | main:
15 | permissions:
16 | pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs
17 | statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR
18 | name: Validate PR Title
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: semantic-pull-request
22 | uses: amannn/action-semantic-pull-request@01d5fd8a8ebb9aafe902c40c53f0f4744f7381eb # tag: v5
23 | env:
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | with:
26 | validateSingleCommit: false
27 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | electron-version:
7 | required: true
8 | type: string
9 | workflow_dispatch:
10 | schedule:
11 | - cron: '0 19 * * 1-5'
12 | push:
13 | branches:
14 | - main
15 | pull_request:
16 | branches:
17 | - main
18 |
19 | permissions:
20 | contents: read
21 |
22 | jobs:
23 | test:
24 | defaults:
25 | run:
26 | shell: bash
27 | strategy:
28 | matrix:
29 | node-version:
30 | - '20.16.0'
31 | - '18.20.4'
32 | - '16.20.2'
33 | - '14.21.3'
34 | os:
35 | - macos-latest
36 | - ubuntu-latest
37 | - windows-latest
38 | exclude:
39 | - os: macos-latest
40 | node-version: 14.21.3
41 | runs-on: "${{ matrix.os }}"
42 | steps:
43 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
44 | - name: "Use Node.js ${{ matrix.node-version }}"
45 | uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
46 | with:
47 | node-version: "${{ matrix.node-version }}"
48 | - name: Update Version
49 | if: ${{ inputs.electron-version != '' }}
50 | run: node script/update-version.js ${{ inputs.electron-version }}
51 | - name: Use Latest Electron Version
52 | if: ${{ inputs.electron-version == '' }}
53 | run: echo "ELECTRON_CHROMEDRIVER_STABLE_FALLBACK=1" >> $GITHUB_ENV
54 | - name: Install Dependencies
55 | run: npm ci
56 | - name: Run Tests
57 | run: |
58 | node --version
59 | npm --version
60 | npm test
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bin
3 | npm-debug.log
4 | .npmrc
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Contributors to the Electron project
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Electron ChromeDriver
2 |
3 | [](https://github.com/electron/chromedriver/actions/workflows/test.yml)
4 | [](https://www.npmjs.com/package/electron-chromedriver)
5 |
6 | [](https://standardjs.com/)
7 | [](https://opensource.org/licenses/MIT)
8 |
9 | [](https://www.npmjs.com/package/electron-chromedriver)
10 |
11 | Simple node module to download the [ChromeDriver](https://sites.google.com/corp/chromium.org/driver/)
12 | version for [Electron](https://electronjs.org).
13 |
14 | The major version of this library tracks the major version of the Electron
15 | versions released. So if you are using Electron `2.0.x` you would want to use
16 | an `electron-chromedriver` dependency of `~2.0.0` in your `package.json` file.
17 |
18 | This library is used by [spectron](https://github.com/electron/spectron).
19 |
20 | ## Using
21 |
22 | ```sh
23 | npm install --save-dev electron-chromedriver
24 | chromedriver -h
25 | ```
26 |
27 | ## Custom Mirror
28 |
29 | You can set the `ELECTRON_MIRROR` or [`NPM_CONFIG_ELECTRON_MIRROR`](https://docs.npmjs.com/misc/config#environment-variables)
30 | environment variables to use a custom base URL for downloading ChromeDriver zips.
31 |
32 | ```sh
33 | # Electron mirror for China
34 | ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
35 |
36 | # Local mirror
37 | # Example of requested URL: http://localhost:8080/1.2.0/chromedriver-v2.21-darwin-x64.zip
38 | ELECTRON_MIRROR="http://localhost:8080/"
39 | ```
40 |
41 | ## Overriding the version downloaded
42 |
43 | The version downloaded can be overriden by setting the `ELECTRON_CUSTOM_VERSION` environment variable.
44 |
--------------------------------------------------------------------------------
/chromedriver.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const ChildProcess = require('child_process')
4 | const path = require('path')
5 |
6 | const command = path.join(__dirname, 'bin', 'chromedriver')
7 | const args = process.argv.slice(2)
8 | const options = {
9 | cwd: process.cwd(),
10 | env: process.env,
11 | stdio: 'inherit'
12 | }
13 |
14 | const chromeDriverProcess = ChildProcess.spawn(command, args, options)
15 |
16 | chromeDriverProcess.on('close', code => {
17 | if (code !== null && code !== 0) {
18 | throw new Error(`Chromedriver exited with error code: ${code}`)
19 | }
20 | })
21 |
22 | chromeDriverProcess.on('error', error => { throw new Error(error) })
23 |
24 | const killChromeDriver = () => {
25 | try {
26 | chromeDriverProcess.kill()
27 | } catch (ignored) {}
28 | }
29 |
30 | process.on('exit', killChromeDriver)
31 | process.on('SIGTERM', killChromeDriver)
32 |
--------------------------------------------------------------------------------
/download-chromedriver.js:
--------------------------------------------------------------------------------
1 | const { promises: fs } = require('fs')
2 | const path = require('path')
3 | const { downloadArtifact } = require('@electron/get')
4 | const extractZip = require('extract-zip')
5 | const versionToDownload = require('./package').version
6 |
7 | function download (version) {
8 | return downloadArtifact({
9 | version,
10 | artifactName: 'chromedriver',
11 | force: process.env.force_no_cache === 'true',
12 | cacheRoot: process.env.electron_config_cache,
13 | platform: process.env.npm_config_platform,
14 | arch: process.env.npm_config_arch,
15 | rejectUnauthorized: process.env.npm_config_strict_ssl === 'true',
16 | quiet: ['info', 'verbose', 'silly', 'http'].indexOf(process.env.npm_config_loglevel) === -1
17 | })
18 | }
19 |
20 | async function attemptDownload (version) {
21 | // Fall back to latest stable if there is not a stamped version, for tests
22 | if (version === '0.0.0-development') {
23 | if (!process.env.ELECTRON_CHROMEDRIVER_STABLE_FALLBACK) {
24 | console.log('WARNING: chromedriver in development needs the environment variable ELECTRON_CHROMEDRIVER_STABLE_FALLBACK set')
25 | process.exit(1)
26 | }
27 |
28 | const { ElectronVersions } = require('@electron/fiddle-core')
29 | const versions = await ElectronVersions.create(undefined, { ignoreCache: true })
30 | version = versions.latestStable.version
31 | }
32 |
33 | try {
34 | const targetFolder = path.join(__dirname, 'bin')
35 | const zipPath = await download(version)
36 | await extractZip(zipPath, { dir: targetFolder })
37 | const platform = process.env.npm_config_platform || process.platform
38 | if (platform !== 'win32') {
39 | await fs.chmod(path.join(targetFolder, 'chromedriver'), 0o755)
40 | }
41 | } catch (err) {
42 | // attempt to fall back to semver minor
43 | const parts = version.split('.')
44 | const baseVersion = `${parts[0]}.${parts[1]}.0`
45 |
46 | // don't recurse infinitely
47 | if (baseVersion === version) {
48 | throw err
49 | } else {
50 | await attemptDownload(baseVersion)
51 | }
52 | }
53 | }
54 |
55 | attemptDownload(versionToDownload)
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-chromedriver",
3 | "version": "0.0.0-development",
4 | "description": "Electron ChromeDriver",
5 | "repository": "https://github.com/electron/chromedriver",
6 | "bin": {
7 | "chromedriver": "./chromedriver.js"
8 | },
9 | "files": [
10 | "./chromedriver.js",
11 | "./download-chromedriver.js"
12 | ],
13 | "scripts": {
14 | "install": "node ./download-chromedriver.js",
15 | "lint": "standard --fix",
16 | "test": "standard && mocha"
17 | },
18 | "engines": {
19 | "node": ">=10.12.0"
20 | },
21 | "author": "Kevin Sawicki",
22 | "license": "MIT",
23 | "dependencies": {
24 | "@electron/get": "^2.0.1",
25 | "extract-zip": "^2.0.0"
26 | },
27 | "devDependencies": {
28 | "@continuous-auth/client": "^2.3.0",
29 | "@electron/fiddle-core": "^1.3.3",
30 | "mocha": "^10.1.0",
31 | "standard": "^13.1.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/script/publish.js:
--------------------------------------------------------------------------------
1 | // Publish the package in the CWD with an OTP code from CFA
2 | const { getOtp } = require('@continuous-auth/client')
3 | const { spawnSync } = require('child_process')
4 |
5 | async function publish () {
6 | const { status } = spawnSync('npm', ['publish', '--provenance', '--otp', await getOtp()])
7 | process.exit(status)
8 | }
9 |
10 | publish()
11 |
--------------------------------------------------------------------------------
/script/update-version.js:
--------------------------------------------------------------------------------
1 | const { promises: fs } = require('fs')
2 | const path = require('path')
3 |
4 | const versionFormat = /^(\d+\.)(\d+\.)(\d+)$/
5 |
6 | const normalizeVersion = (version = '') => {
7 | return version.startsWith('v') ? version.slice(1) : version
8 | }
9 |
10 | async function updateVersion () {
11 | const version = normalizeVersion(process.argv[2])
12 | if (!versionFormat.test(version)) {
13 | console.error(`Unsupported version ${version} - only major, minor, and patch releases are currently supported`)
14 | return
15 | }
16 |
17 | const PJ_PATH = path.join(__dirname, '..', 'package.json')
18 | const pj = require(PJ_PATH)
19 |
20 | const PJLOCK_PATH = path.join(__dirname, '..', 'package-lock.json')
21 | const pjLock = require(PJLOCK_PATH)
22 |
23 | try {
24 | pj.version = version
25 | await fs.writeFile(PJ_PATH, JSON.stringify(pj, null, 2))
26 | console.log(`Updated package.json version to ${version}`)
27 |
28 | pjLock.version = version
29 | await fs.writeFile(PJLOCK_PATH, JSON.stringify(pjLock, null, 2))
30 | console.log(`Updated package-lock.json version to ${version}`)
31 | } catch (e) {
32 | console.error('Failed to update chromedriver version: ', e)
33 | process.exit(1)
34 | }
35 | }
36 |
37 | updateVersion()
38 |
--------------------------------------------------------------------------------
/test/chromedriver-test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 | const { spawnSync } = require('child_process')
3 | const path = require('path')
4 | const { version: releaseVersion } = require('../package')
5 | const { ElectronVersions } = require('@electron/fiddle-core')
6 |
7 | const describe = global.describe
8 | const it = global.it
9 |
10 | function outputHasExpectedVersion (output, version) {
11 | return output.toString().includes(version)
12 | }
13 |
14 | describe('chromedriver binary', function () {
15 | this.timeout(10000)
16 |
17 | it('launches successfully', async function () {
18 | // Skip when this package is not properly configured for an Electron release
19 | if (releaseVersion === '0.0.0-development') {
20 | this.skip()
21 | }
22 |
23 | // Get the expected release information for this release
24 | const versions = await ElectronVersions.create()
25 | const expectedInfo = versions.getReleaseInfo(releaseVersion)
26 |
27 | const { chrome: expectedChromeVersion } = expectedInfo
28 |
29 | // Invoke chormedriver with the flag to output the version
30 | const { stdout, stderr } = spawnSync(process.execPath, [
31 | path.join(__dirname, '..', 'chromedriver.js'),
32 | '-v'
33 | ])
34 |
35 | assert(
36 | outputHasExpectedVersion(stdout, expectedChromeVersion) || outputHasExpectedVersion(stderr, expectedChromeVersion),
37 | `Did not find expected Chromium version: ${expectedChromeVersion}\nstdout:\n---\n${stdout}\n---\nstderr:\n${stderr}\n---`
38 | )
39 | })
40 | })
41 |
--------------------------------------------------------------------------------