element-desktop/scripts/fetch-package.js
J. Ryan Stinnett 045f122688 Replace Riot with Element in docs and comments
This only covers the simple cases of references to issues and repos. More
complex areas, such as deployment scripts, will be handled separately.

Part of https://github.com/vector-im/element-web/issues/14864
2020-08-03 18:32:42 +01:00

293 lines
9 KiB
JavaScript
Executable file

#!/usr/bin/env node
const process = require('process');
const path = require('path');
const fs = require('fs');
const fsPromises = require('fs').promises;
const childProcess = require('child_process');
const tar = require('tar');
const asar = require('asar');
const needle = require('needle');
const riotDesktopPackageJson = require('../package.json');
const PUB_KEY_URL = "https://packages.riot.im/riot-release-key.asc";
const PACKAGE_URL_PREFIX = "https://github.com/vector-im/element-web/releases/download/";
const ASAR_PATH = 'webapp.asar';
const {setPackageVersion, setDebVersion} = require('./set-version.js');
async function getLatestDevelopUrl(bkToken) {
const buildsResult = await needle('get',
"https://api.buildkite.com/v2/organizations/matrix-dot-org/pipelines/riot-web/builds",
{
branch: 'develop',
state: 'passed',
per_page: 1,
},
{
headers: {
authorization: "Bearer " + bkToken,
},
},
);
const latestBuild = buildsResult.body[0];
console.log("Latest build is " + latestBuild.number);
let artifactUrl;
for (const job of latestBuild.jobs) {
// Strip any colon-form emoji from the build name
if (job.name && job.name.replace(/:\w*:\s*/, '') === 'Package') {
artifactUrl = job.artifacts_url;
break;
}
}
if (artifactUrl === undefined) {
throw new Error("Couldn't find artifact URL - has the name of the package job changed?");
}
const artifactsResult = await needle('get', artifactUrl, {},
{
headers: {
authorization: "Bearer " + bkToken,
},
},
);
let dlUrl;
let dlFilename;
for (const artifact of artifactsResult.body) {
if (artifact.filename && /^riot-.*\.tar.gz$/.test(artifact.filename)) {
dlUrl = artifact.download_url;
dlFilename = artifact.filename;
break;
}
}
if (dlUrl === undefined) {
throw new Error("Couldn't find artifact download URL - has the artifact filename changed?");
}
console.log("Fetching artifact URL...");
const dlResult = await needle('get', dlUrl, {},
{
headers: {
authorization: "Bearer " + bkToken,
},
// This URL will give us a Location header, but will also give us
// a JSON object with the direct URL. We'll take the URL and pass it
// back, then we can easily support specifying a URL directly.
follow_max: 0,
},
);
return [dlFilename, dlResult.body.url];
}
async function downloadToFile(url, filename) {
console.log("Downloading " + url + "...");
try {
await needle('get', url, null,
{
follow_max: 5,
output: filename,
},
);
} catch (e) {
try {
await fsPromises.unlink(filename);
} catch (_) {}
throw e;
}
}
async function verifyFile(filename) {
return new Promise((resolve, reject) => {
childProcess.execFile('gpg', ['--verify', filename + '.asc', filename], (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async function main() {
let verify = true;
let importkey = false;
let pkgDir = 'packages';
let deployDir = 'deploys';
let cfgDir;
let targetVersion;
let filename;
let url;
let setVersion = false;
while (process.argv.length > 2) {
switch (process.argv[2]) {
case '--noverify':
verify = false;
break;
case '--importkey':
importkey = true;
break;
case '--packages':
process.argv.shift();
pkgDir = process.argv[2];
break;
case '--deploys':
process.argv.shift();
deployDir = process.argv[2];
break;
case '--cfgdir':
case '-d':
process.argv.shift();
cfgDir = process.argv[2];
break;
default:
targetVersion = process.argv[2];
}
process.argv.shift();
}
if (targetVersion === undefined) {
targetVersion = 'v' + riotDesktopPackageJson.version;
filename = 'riot-' + targetVersion + '.tar.gz';
url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
} else if (targetVersion === 'develop') {
const buildKiteApiKey = process.env.BUILDKITE_API_KEY;
if (buildKiteApiKey === undefined) {
console.log("Set BUILDKITE_API_KEY to fetch latest develop version");
console.log(
"Sorry - Buildkite's API requires authentication to access builds, " +
"even if those builds are accessible on the web with no auth.",
);
process.exit(1);
}
[filename, url] = await getLatestDevelopUrl(buildKiteApiKey);
verify = false; // develop builds aren't signed
} else {
filename = 'riot-' + targetVersion + '.tar.gz';
url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
setVersion = true;
}
const haveGpg = await new Promise((resolve) => {
childProcess.execFile('gpg', ['--version'], (error) => {
resolve(!error);
});
});
if (importkey) {
if (!haveGpg) {
console.log("Can't import key without working GPG binary: install GPG and try again");
return 1;
}
await new Promise((resolve) => {
const gpgProc = childProcess.execFile('gpg', ['--import'], (error) => {
if (error) {
console.log("Failed to import key", error);
} else {
console.log("Key imported!");
}
resolve(!error);
});
needle.get(PUB_KEY_URL).pipe(gpgProc.stdin);
});
return 0;
}
if (cfgDir === undefined) {
console.log("No config directory set");
console.log("Specify a config directory with --cfgdir or -d");
console.log("To build with no config (and no auto-update), pass the empty string (-d '')");
return 1;
}
if (verify && !haveGpg) {
console.log("No working GPG binary: install GPG or pass --noverify to skip verification");
return 1;
}
let haveDeploy = false;
const expectedDeployDir = path.join(deployDir, path.basename(filename).replace(/\.tar\.gz/, ''));
try {
await fs.opendir(expectedDeployDir);
console.log(expectedDeployDir + "already exists");
haveDeploy = true;
} catch (e) {
}
if (!haveDeploy) {
const outPath = path.join(pkgDir, filename);
try {
await fsPromises.stat(outPath);
console.log("Already have " + filename + ": not redownloading");
} catch (e) {
try {
await downloadToFile(url, outPath);
} catch (e) {
console.log("Failed to download " + url, e);
return 1;
}
}
if (verify) {
try {
await fsPromises.stat(outPath+'.asc');
console.log("Already have " + filename + ".asc: not redownloading");
} catch (e) {
try {
await downloadToFile(url + '.asc', outPath + '.asc');
} catch (e) {
console.log("Failed to download " + url, e);
return 1;
}
}
try {
await verifyFile(outPath);
console.log(outPath + " downloaded and verified");
} catch (e) {
console.log("Signature verification failed!", e);
return 1;
}
} else {
console.log(outPath + " downloaded but NOT verified");
}
await tar.x({
file: outPath,
cwd: deployDir,
});
}
try {
await fsPromises.stat(ASAR_PATH);
console.log(ASAR_PATH + " already present: removing");
await fsPromises.unlink(ASAR_PATH);
} catch (e) {
}
if (cfgDir.length) {
const configJsonSource = path.join(cfgDir, 'config.json');
const configJsonDest = path.join(expectedDeployDir, 'config.json');
console.log(configJsonSource + ' -> ' + configJsonDest);
await fsPromises.copyFile(configJsonSource, configJsonDest);
} else {
console.log("Skipping config file");
}
console.log("Pack " + expectedDeployDir + " -> " + ASAR_PATH);
await asar.createPackage(expectedDeployDir, ASAR_PATH);
if (setVersion) {
const semVer = targetVersion.slice(1);
console.log("Updating version to " + semVer);
await setPackageVersion(semVer);
await setDebVersion(semVer);
}
console.log("Done!");
}
main().then((ret) => process.exit(ret)).catch(e => process.exit(1));