Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x]
node-version: [22.x]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Use Node.js ${{ matrix.node-version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '16.x'
node-version: '22.x'
- name: Install
run: npm ci
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '16.x'
node-version: '22.x'
- name: Install
run: npm ci
56 changes: 38 additions & 18 deletions check-latest.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
/* eslint-disable no-console */
'use strict';

const request = require('request');
const toml = require('toml');
const semver = require('semver');
const currentVersion = require('./package.json').geckodriver_version;
const pkg = require('./package.json');

request(
'https://hg.mozilla.org/mozilla-central/raw-file/tip/testing/geckodriver/Cargo.toml',
(error, response, body) => {
if (error) {
console.log(`Failed to parse latest release version: ${error.message}`);
return process.exit(1);
}
const LATEST_URL =
'https://api.github.com/repos/mozilla/geckodriver/releases/latest';

const latestVersion = toml.parse(body).package.version;
if (semver.gt(latestVersion, currentVersion)) {
console.log(`Upgrade to ${latestVersion}`);
process.exit(1);
}
console.log(`Relax, ${currentVersion} is the latest version`);
function compareVersions(a, b) {
const aParts = a.split('.').map(Number);
const bParts = b.split('.').map(Number);
const len = Math.max(aParts.length, bParts.length);
for (let i = 0; i < len; i++) {
const diff = (aParts[i] || 0) - (bParts[i] || 0);
if (diff !== 0) return diff;
}
);
return 0;
}

async function main() {
const response = await fetch(LATEST_URL, {
headers: { Accept: 'application/vnd.github+json' }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
const data = await response.json();
const tag = data && data.tag_name;
if (!tag) {
throw new Error('Could not find tag_name in GitHub release response');
}
const latestVersion = tag.replace(/^v/, '');
const currentVersion = pkg.geckodriver_version;
if (compareVersions(latestVersion, currentVersion) > 0) {
console.log(`Upgrade to ${latestVersion}`);
process.exit(1);
}
console.log(`Relax, ${currentVersion} is the latest version`);
}

main().catch(err => {
console.log(`Failed to parse latest release version: ${err.message}`);
process.exit(1);
});
194 changes: 96 additions & 98 deletions install.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
'use strict';

const os = require('node:os');
const path = require('node:path');
const { mkdir, unlink, chmod } = require('node:fs/promises');
const { createWriteStream } = require('node:fs');
const { pipeline } = require('node:stream/promises');
const { Readable } = require('node:stream');
const StreamZip = require('node-stream-zip');
const os = require('os');
const fs = require('fs');
const path = require('path');
const pkg = require('./package');
const { DownloaderHelper } = require('node-downloader-helper');
const { promisify } = require('util');
const tar = require('tar');
const unlink = promisify(fs.unlink);
const mkdir = promisify(fs.mkdir);
const chmod = promisify(fs.chmod);
const pkg = require('./package.json');

// The version of the driver that will be installed
const GECKODRIVER_VERSION = process.env.GECKODRIVER_VERSION
Expand All @@ -21,13 +19,10 @@ const isWindows = os.platform() === 'win32';

function byteHelper(value) {
// https://gist.github.com/thomseddon/3511330
const units = ['b', 'kB', 'MB', 'GB', 'TB'],
number = Math.floor(Math.log(value) / Math.log(1024));
return (
(value / Math.pow(1024, Math.floor(number))).toFixed(1) +
' ' +
units[number]
);
if (!value) return '?';
const units = ['B', 'kB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(value) / Math.log(1024));
return `${(value / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
}

function getDriverUrl() {
Expand All @@ -41,21 +36,20 @@ function getDriverUrl() {
}

switch (os.platform()) {
case 'darwin':
case 'darwin': {
// Starting from Geckodriver v0.29.1, there is a separate build for arm64
// architecture. Let's install it if we are on arm64 as well.
// eslint-disable-next-line no-case-declarations
const arch = os.arch() === 'arm64' ? '-aarch64' : '';
return `${urlBase}geckodriver-${GECKODRIVER_VERSION}-macos${arch}.tar.gz`;
}
case 'linux': {
if (os.arch() === 'arm') {
// Don't want to spend hours to build a new one, so for now serve 0.29.0
// or unreleased 0.30.0
return `${urlBase}geckodriver-0.30.0-linux-arm.tar.gz`;
} else {
const arch = os.arch() === 'x64' ? '64' : '32';
return `${urlBase}geckodriver-${GECKODRIVER_VERSION}-linux${arch}.tar.gz`;
}
const arch = os.arch() === 'x64' ? '64' : '32';
return `${urlBase}geckodriver-${GECKODRIVER_VERSION}-linux${arch}.tar.gz`;
}
case 'win32': {
const arch = os.arch() === 'x64' ? 'win64' : 'win32';
Expand All @@ -66,88 +60,92 @@ function getDriverUrl() {
}
}

async function download() {
async function downloadFile(url, destination) {
const response = await fetch(url, { redirect: 'follow' });
if (!response.ok || !response.body) {
throw new Error(
`HTTP ${response.status} ${response.statusText} for ${url}`
);
}
const total = Number(response.headers.get('content-length')) || 0;
let downloaded = 0;
let lastLog = 0;
const body = Readable.fromWeb(response.body);
body.on('data', chunk => {
downloaded += chunk.length;
const now = Date.now();
if (now - lastLog >= 250) {
const pct = total ? ((downloaded / total) * 100).toFixed(1) : '?';
console.log(`${pct}% [${byteHelper(downloaded)}/${byteHelper(total)}]`);
lastLog = now;
}
});
await pipeline(body, createWriteStream(destination));
}

async function extractZip(zipPath, destDir) {
const zip = new StreamZip.async({ file: zipPath });
try {
await zip.extract(null, destDir);
} finally {
await zip.close();
}
}

async function tryUnlink(p) {
try {
await unlink(p);
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
}

async function install() {
if (
process.env.npm_config_geckodriver_skip_download ||
process.env.GECKODRIVER_SKIP_DOWNLOAD
) {
console.log('Skip downloading Geckodriver');
return;
}

const url = getDriverUrl();
if (!url) {
console.log(
`Skipping installing Geckodriver on ${os.platform()} for ${os.arch()} since there's no official build`
);
return;
}

const vendorDir = path.resolve(__dirname, 'vendor');
await mkdir(vendorDir, { recursive: true });

const ext = isWindows ? '.exe' : '';
const binPath = path.join(vendorDir, `geckodriver${ext}`);
await tryUnlink(binPath);

const archivePath = path.join(
vendorDir,
isWindows ? 'geckodriver.zip' : 'geckodriver.tar.gz'
);

console.log(`Downloading Geckodriver ${GECKODRIVER_VERSION} from ${url}`);
await downloadFile(url, archivePath);

if (isWindows) {
await extractZip(archivePath, vendorDir);
} else {
const downloadUrl = getDriverUrl();
if (downloadUrl) {
try {
await mkdir('vendor');
} catch (e) {
try {
await unlink('vendor/geckodriver');
} catch (e) {
// nothing to do here
}
}
const dl = new DownloaderHelper(downloadUrl, 'vendor', {
fileName: 'geckodriver' + (isWindows ? '.zip' : '.tar.gz')
});

dl.on('error', err =>
console.error('Could not download Geckodriver: ' + downloadUrl, err)
)
.on('progress', stats => {
const progress = stats.progress.toFixed(1);
const speed = byteHelper(stats.speed);
const downloaded = byteHelper(stats.downloaded);
const total = byteHelper(stats.total);
console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
})
.on('end', () => {
if (isWindows) {
const zip = new StreamZip({
file: 'vendor/geckodriver.zip',
storeEntries: true
});
zip.on('ready', () => {
zip.extract(null, './vendor', async err => {
console.log(
err
? 'Could not extract and install Geckodriver'
: `Geckodriver ${GECKODRIVER_VERSION} installed in ${path.join(
__dirname,
'vendor'
)}`
);
zip.close();
await unlink('vendor/geckodriver.zip');
await chmod('vendor/geckodriver.exe', '755');
});
});
} else {
tar
.x({
file: 'vendor/geckodriver.tar.gz',
cwd: 'vendor'
})
.then(async () => {
await unlink('vendor/geckodriver.tar.gz');
await chmod('vendor/geckodriver', '755');
console.log(
`Geckodriver ${GECKODRIVER_VERSION} installed in ${path.join(
__dirname,
'vendor'
)}`
);
});
}
});

dl.start();
} else {
console.log(
'Skipping installing Geckodriver on ' +
os.platform() +
' for ' +
os.arch() +
" since there's no official build"
);
}
await tar.x({ file: archivePath, cwd: vendorDir });
}

await unlink(archivePath);
await chmod(binPath, 0o755);
console.log(`Geckodriver ${GECKODRIVER_VERSION} installed in ${vendorDir}`);
}
download();

install().catch(err => {
console.error(
`Geckodriver ${GECKODRIVER_VERSION} could not be installed: ${err.message}`
);
process.exit(1);
});
Loading
Loading