binary-mirror-config
Version:
Binary mirror config for prebuild and node-pre-gyp
192 lines (176 loc) • 7.33 kB
JavaScript
const urllib = require('urllib');
const semver = require('semver');
const chalk = require('chalk');
const fs = require('fs').promises;
const path = require('path');
const os = require('os');
const { promisify, debuglog } = require('util');
const sleep = promisify(setTimeout);
const debug = debuglog('binary-mirror-config');
exports.MirrorConfig = class MirrorConfig {
constructor(options = {}) {
this.pkgName = 'binary-mirror-config';
this.tagName = 'latest';
this.registry = options.registry || 'https://registry.npmmirror.com';
this.config = {};
this.envs = {};
this.retryCount = options.retryCount || 5;
this.retryTimeout = options.retryTimeout || 5000;
this.console = options.console;
}
async init() {
if (this.retryCount <= 0) {
this.console.warn('[binary-init-config] binary mirror init timeout.');
return;
}
const response = await urllib.request(`${this.registry}/${this.pkgName}/${this.tagName}`, {
method: 'GET',
dataType: 'json',
followRedirect: true,
});
if (response.status !== 200) {
this.retryCount -= 1;
await sleep(this.retryTimeout);
await this.init();
return;
}
this.config = response.data.mirrors.china;
this.envs = this.config.ENVS;
}
async setMirrorUrl(pkg, ungzipDir) {
const pkgName = pkg.name;
const binaryMirror = this.config[pkgName];
if (binaryMirror) {
const installScripts = pkg.scripts?.install;
// node-pre-gyp
if (installScripts && !binaryMirror.replaceHostFiles) {
// leveldown, sqlite3, nodegit
if (/prebuild --install|prebuild --download|node-pre-gyp install/.test(installScripts) ||
// utf-8-validate
/prebuild-install || node-gyp rebuild/.test(installScripts) ||
[ 'nodegit', 'fsevents' ].includes(pkgName)
) {
const newBinary = pkg.binary || {};
for (const key in binaryMirror) {
newBinary[key] = binaryMirror[key];
}
// ignore https protocol check on: node_modules/node-pre-gyp/lib/util/versioning.js
if (/node-pre-gyp install/.test(installScripts)) {
const versioningFile = path.join(ungzipDir, 'node_modules/node-pre-gyp/lib/util/versioning.js');
try {
await fs.stat(versioningFile);
let content = await fs.readFile(versioningFile, 'utf-8');
content = content.replace('if (protocol === \'http:\') {',
'if (false && protocol === \'http:\') { // hack by npminstall');
await fs.writeFile(versioningFile, content);
} catch (_) {
// ignore error
}
}
this.console.info('%s download from binary mirror: %j',
chalk.gray(`${pkgName}@${pkg.version}`), newBinary);
}
} else if ((binaryMirror.replaceHost && binaryMirror.host)
|| binaryMirror.replaceHostMap
|| binaryMirror.replaceHostRegExpMap) {
// use mirror url instead
// e.g.: pngquant-bin
// https://github.com/lovell/sharp/blob/master/install/libvips.js#L19
const replaceHostFiles = binaryMirror.replaceHostFiles || [
'lib/index.js',
'lib/install.js',
];
for (const replaceHostFile of replaceHostFiles) {
const replaceHostFilePath = path.join(ungzipDir, replaceHostFile);
await this.replaceHostInFile(pkg, replaceHostFilePath, binaryMirror);
}
}
// replace cypress download url
// https://github.com/cypress-io/cypress/blob/master/cli/lib/tasks/download.js#L30
// support cypress <= 9, cypress >= 10 using CYPRESS_DOWNLOAD_PATH_TEMPLATE env
if (pkgName === 'cypress') {
const defaultPlatforms = {
darwin: 'osx64',
linux: 'linux64',
win32: 'win64',
};
let platforms = binaryMirror.platforms || defaultPlatforms;
// version >= 3.3.0 should use binaryMirror.newPlatforms by default, other use defaultPlatforms
if (binaryMirror.newPlatforms && semver.gte(pkg.version, '3.3.0')) {
platforms = binaryMirror.newPlatforms;
}
const targetPlatform = platforms[os.platform()];
if (targetPlatform) {
this.console.info('%s download from binary mirror: %j, targetPlatform: %s',
chalk.gray(`${pkgName}@${pkg.version}`), binaryMirror, targetPlatform);
const downloadFile = path.join(ungzipDir, 'lib/tasks/download.js');
try {
await fs.access(downloadFile, { mode: fs.constants.R_OK });
let content = await fs.readFile(downloadFile, 'utf-8');
// return version ? prepend('desktop/' + version) : prepend('desktop');
const afterContent = 'return "' + binaryMirror.host + '/" + version + "/' + targetPlatform + '/cypress.zip"; // hack by npminstall\n';
content = content
.replace('return version ? prepend(\`desktop/${version}\`) : prepend(\'desktop\')', afterContent)
.replace('return version ? prepend(\'desktop/\' + version) : prepend(\'desktop\');', afterContent);
await fs.writeFile(downloadFile, content);
} catch (error) {
// ignore error
}
}
} else if (pkgName === 'vscode') {
// https://github.com/Microsoft/vscode-extension-vscode/blob/master/bin/install#L64
const indexFilepath = path.join(ungzipDir, 'bin/install');
await this.replaceHostInFile(pkg, indexFilepath, binaryMirror);
}
}
}
setEnvs(options) {
for (const key in this.envs) {
options.env[key] = this.envs[key];
}
}
async updatePkg(dir, pkg) {
await this.setMirrorUrl(pkg, dir);
await fs.writeFile(path.join(dir, 'package.json'), JSON.stringify(pkg, null, 2), 'utf8');
}
async replaceHostInFile(pkg, filepath, binaryMirror) {
try {
await fs.stat(filepath);
} catch (e) {
return;
}
let content = await fs.readFile(filepath, 'utf8');
let replaceHostMap;
// support RegExp string
if (binaryMirror.replaceHostRegExpMap) {
replaceHostMap = binaryMirror.replaceHostRegExpMap;
for (const replaceHost in replaceHostMap) {
const replaceAllRE = new RegExp(replaceHost, 'g');
const targetHost = replaceHostMap[replaceHost];
debug('replace %j(%s) => %s', replaceHost, replaceAllRE, targetHost);
content = content.replace(replaceAllRE, targetHost);
}
} else {
replaceHostMap = binaryMirror.replaceHostMap;
if (!replaceHostMap) {
let replaceHosts = binaryMirror.replaceHost;
if (!Array.isArray(replaceHosts)) {
replaceHosts = [ replaceHosts ];
}
replaceHostMap = {};
for (const replaceHost of replaceHosts) {
replaceHostMap[replaceHost] = binaryMirror.host;
}
}
for (const replaceHost in replaceHostMap) {
content = content.replace(replaceHost, replaceHostMap[replaceHost]);
}
}
debug('%s: \n%s', filepath, content);
await fs.writeFile(filepath, content);
this.console.info('%s download from mirrors: %j, changed file: %s',
chalk.gray(`${pkg.name}@${pkg.version}`),
replaceHostMap,
filepath);
}
};