mirror of
https://github.com/yuzu-emu/citra-qt-installer.git
synced 2024-11-25 02:15:39 +01:00
server.js: Migrate to Deno
This commit is contained in:
parent
9ab3db6764
commit
894ffab4e4
2031
package-lock.json
generated
2031
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "citra-downloads",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Generates a Qt Installer framework repository.",
|
|
||||||
"homepage": "https://citra-emu.org/",
|
|
||||||
"author": "Selby <jselby@jselby.net>",
|
|
||||||
"main": "app.js",
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "^4.8.0",
|
|
||||||
"eslint-config-standard": "^10.2.1",
|
|
||||||
"eslint-plugin-import": "^2.7.0",
|
|
||||||
"eslint-plugin-node": "^5.2.0",
|
|
||||||
"eslint-plugin-promise": "^3.5.0",
|
|
||||||
"eslint-plugin-standard": "^3.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"7zip-bin": "^2.1.0",
|
|
||||||
"execa": "^0.7.0",
|
|
||||||
"fs-extra": "^4.0.1",
|
|
||||||
"request": "^2.81.0",
|
|
||||||
"request-promise": "^4.2.1",
|
|
||||||
"sha1-file": "^1.0.0",
|
|
||||||
"winston": "^2.3.1",
|
|
||||||
"xml": "^1.0.1"
|
|
||||||
},
|
|
||||||
"preferGlobal": false,
|
|
||||||
"private": true,
|
|
||||||
"license": "GPLv3"
|
|
||||||
}
|
|
316
server.js
316
server.js
@ -1,185 +1,243 @@
|
|||||||
const xml = require('xml');
|
import * as xml from "https://deno.land/x/xml@2.0.4/mod.ts";
|
||||||
const fs = require('fs-extra');
|
import { which } from "https://deno.land/x/which@0.2.1/mod.ts";
|
||||||
const exec = require('execa');
|
|
||||||
const sha1 = require('sha1-file');
|
|
||||||
const req = require('request-promise');
|
|
||||||
const zipBin = require('7zip-bin').path7za;
|
|
||||||
|
|
||||||
const logger = require('winston');
|
const tempDir = "./temp";
|
||||||
logger.exitOnError = false;
|
const distDir = "/citra/nginx/citra_repo";
|
||||||
logger.add(logger.transports.File, { filename: '/var/log/citra-qt-installer/citra-qt-installer-repository.log' });
|
|
||||||
|
|
||||||
const tempDir = './temp';
|
async function getReleases(repo) {
|
||||||
const distDir = '/citra/nginx/citra_repo';
|
const result = await fetch(`https://api.github.com/repos/${repo}/releases`, {
|
||||||
|
|
||||||
async function getReleases (repo) {
|
|
||||||
const result = await req({
|
|
||||||
uri: `https://api.github.com/repos/${repo}/releases`,
|
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/vnd.github.v3+json',
|
Accept: "application/vnd.github.v3+json",
|
||||||
'User-Agent': 'Citra Installer - Repo (j-selby)'
|
"User-Agent": "Citra Installer - Repo (j-selby)",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return JSON.parse(result);
|
return result.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTopResultFor (jsonData, platform) {
|
async function checkExists(directory) {
|
||||||
for (let releaseKey in jsonData) {
|
try {
|
||||||
|
await Deno.stat(directory);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check7z() {
|
||||||
|
for (const bin of ["7zz", "7za"]) {
|
||||||
|
const path = await which(bin);
|
||||||
|
if (path) return path;
|
||||||
|
}
|
||||||
|
throw new Error("7-zip is not available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function bufferToHex(buffer) {
|
||||||
|
return [...new Uint8Array(buffer)]
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTopResultFor(jsonData, platform) {
|
||||||
|
for (const releaseKey in jsonData) {
|
||||||
const release = jsonData[releaseKey];
|
const release = jsonData[releaseKey];
|
||||||
for (let assetKey in release.assets) {
|
for (const assetKey in release.assets) {
|
||||||
const asset = release.assets[assetKey];
|
const asset = release.assets[assetKey];
|
||||||
if (asset.name.indexOf(platform) !== -1 && asset.name.endsWith('.7z')) {
|
if (asset.name.indexOf(platform) !== -1 && asset.name.endsWith(".7z")) {
|
||||||
return {
|
return {
|
||||||
'release_id': release.tag_name.split('-')[1],
|
release_id: release.tag_name.split("-")[1],
|
||||||
'published_at': release.published_at.substr(0, 10),
|
published_at: release.published_at.substr(0, 10),
|
||||||
'name': asset.name,
|
name: asset.name,
|
||||||
'size': asset.size,
|
size: asset.size,
|
||||||
'hash': release.tag_name
|
hash: release.tag_name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {'notFound': true};
|
return { notFound: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zipBin = await check7z();
|
||||||
// The Qt Installer Framework is a pain to build or download.
|
// The Qt Installer Framework is a pain to build or download.
|
||||||
// Because all we need are a few 7-zipped + xml files, we might as well generate them for ourselves.
|
// Because all we need are a few 7-zipped + xml files, we might as well generate them for ourselves.
|
||||||
let targets = [
|
const targets = [
|
||||||
{
|
{
|
||||||
'Name': 'org.citra.nightly.%platform%',
|
Name: "org.citra.nightly.%platform%",
|
||||||
'DisplayName': 'Citra Nightly',
|
DisplayName: "Citra Nightly",
|
||||||
'Description': 'The nightly builds of Citra are official, tested versions of Citra that are known to work.\n' +
|
Description:
|
||||||
'(%platform%, commit: %commithash%, release date: %releasedate%)',
|
"The nightly builds of Citra are official, tested versions of Citra that are known to work.\n" +
|
||||||
'Repo': 'citra-emu/citra-nightly',
|
"(%platform%, commit: %commithash%, release date: %releasedate%)",
|
||||||
'ScriptName': 'nightly',
|
Repo: "citra-emu/citra-nightly",
|
||||||
'Default': 'script',
|
ScriptName: "nightly",
|
||||||
'Licenses': [
|
Default: "script",
|
||||||
{'License': [{_attr: { file: 'license.txt', name: 'GNU General Public License v2.0' }}]}
|
Licenses: [
|
||||||
]
|
{
|
||||||
|
License: {
|
||||||
|
"@file": "license.txt",
|
||||||
|
"@name": "GNU General Public License v2.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'Name': 'org.citra.canary.%platform%',
|
Name: "org.citra.canary.%platform%",
|
||||||
'DisplayName': 'Citra Canary',
|
DisplayName: "Citra Canary",
|
||||||
'Description': 'An in-development version of Citra that uses changes that are relatively untested.\n' +
|
Description:
|
||||||
'(%platform%, commit: %commithash%, release date: %releasedate%)',
|
"An in-development version of Citra that uses changes that are relatively untested.\n" +
|
||||||
'Repo': 'citra-emu/citra-canary',
|
"(%platform%, commit: %commithash%, release date: %releasedate%)",
|
||||||
'ScriptName': 'canary',
|
Repo: "citra-emu/citra-canary",
|
||||||
'Default': 'script',
|
ScriptName: "canary",
|
||||||
'Licenses': [
|
Default: "script",
|
||||||
{'License': [{_attr: { file: 'license.txt', name: 'GNU General Public License v2.0' }}]}
|
Licenses: [
|
||||||
]
|
{
|
||||||
}
|
License: {
|
||||||
|
"@file": "license.txt",
|
||||||
|
"@name": "GNU General Public License v2.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
async function execute () {
|
async function execute() {
|
||||||
// Clean up any temporary directories.
|
// Clean up any temporary directories.
|
||||||
fs.emptyDirSync(tempDir);
|
try {
|
||||||
|
await Deno.remove(tempDir, { recursive: true });
|
||||||
|
} catch (_) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
// Get Git information
|
// Get Git information
|
||||||
logger.debug('Getting release info...');
|
console.debug("Getting release info...");
|
||||||
for (var resultKey in targets) {
|
for (const target of targets) {
|
||||||
const target = targets[resultKey];
|
|
||||||
target.Repo = await getReleases(target.Repo);
|
target.Repo = await getReleases(target.Repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('Building metadata...');
|
console.debug("Building metadata...");
|
||||||
|
|
||||||
// If updates available is still false at the end of the foreach loop
|
// If updates available is still false at the end of the foreach loop
|
||||||
// then that means no releases have been made -- nothing to do.
|
// then that means no releases have been made -- nothing to do.
|
||||||
var updatesAvailable = false;
|
let updatesAvailable = false;
|
||||||
|
|
||||||
// Updates.xml
|
// Updates.xml
|
||||||
let updates = [
|
const updates = {
|
||||||
{'ApplicationName': '{AnyApplication}'},
|
ApplicationName: "{AnyApplication}",
|
||||||
{'ApplicationVersion': '1.0.0'}, // Separate from nightly / canary versions
|
ApplicationVersion: "1.0.0", // Separate from nightly / canary versions
|
||||||
{'Checksum': false} // As they are pulled straight from Github
|
Checksum: false, // As they are pulled straight from Github
|
||||||
];
|
PackageUpdate: [],
|
||||||
|
};
|
||||||
|
|
||||||
// 6/19/18 (Flame Sage) - MSVC builds have been disabled, removed it from the below array 'msvc'
|
async function generate(targetSource, platform) {
|
||||||
['mingw', 'osx', 'linux'].forEach((platform) => {
|
// Get Git metadata
|
||||||
targets.forEach((targetSource) => {
|
const releaseData = getTopResultFor(targetSource.Repo, platform);
|
||||||
// Get Git metadata
|
const name = targetSource.Name.replace("%platform%", platform);
|
||||||
const releaseData = getTopResultFor(targetSource.Repo, platform);
|
|
||||||
const name = targetSource.Name.replace('%platform%', platform);
|
|
||||||
|
|
||||||
if (releaseData.notFound === true) {
|
if (releaseData.notFound === true) {
|
||||||
logger.error(`Release information not found for ${name}!`);
|
console.error(`Release information not found for ${name}!`);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptName = platform + "-" + targetSource.ScriptName;
|
||||||
|
const version = releaseData.release_id;
|
||||||
|
|
||||||
|
const targetMetadataFilePath = `${distDir}/${name}/${version}meta.7z`;
|
||||||
|
if (await checkExists(targetMetadataFilePath)) {
|
||||||
|
console.debug(
|
||||||
|
`Metadata information already exists for ${name} ${version}, skipping.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.info(`Building release information for ${name} ${version}.`);
|
||||||
|
updatesAvailable = true;
|
||||||
|
|
||||||
|
// Create the temporary working directory.
|
||||||
|
const workingDirectoryPath = `${tempDir}/${name}`;
|
||||||
|
await Deno.mkdir(workingDirectoryPath, { recursive: true });
|
||||||
|
|
||||||
|
// Copy license
|
||||||
|
await Deno.copyFile("license.txt", `${workingDirectoryPath}/license.txt`);
|
||||||
|
await Deno.copyFile(
|
||||||
|
`scripts/${scriptName}.qs`,
|
||||||
|
`${workingDirectoryPath}/installscript.qs`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create 7zip archive
|
||||||
|
const fileName = `${name}.meta.7z`;
|
||||||
|
const proc = Deno.run({
|
||||||
|
cmd: [zipBin, "a", fileName, name],
|
||||||
|
cwd: tempDir,
|
||||||
|
});
|
||||||
|
const status = (await proc.status()).code;
|
||||||
|
if (status !== 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Error when creating ${name} archive. Exited with ${status}.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const scriptName = platform + '-' + targetSource.ScriptName;
|
// Copy the metadata file into the target path.
|
||||||
const version = releaseData.release_id;
|
console.debug(
|
||||||
|
`Creating target metadata for ${name} at ${targetMetadataFilePath}`
|
||||||
|
);
|
||||||
|
await Deno.mkdir(`${distDir}/${name}`, { recursive: true });
|
||||||
|
await Deno.rename(`${tempDir}/${fileName}`, `${targetMetadataFilePath}`);
|
||||||
|
|
||||||
const targetMetadataFilePath = `${distDir}/${name}/${version}meta.7z`;
|
// Cleanup temporary working directory.
|
||||||
if (fs.existsSync(targetMetadataFilePath)) {
|
await Deno.remove(workingDirectoryPath, { recursive: true });
|
||||||
logger.debug(`Metadata information already exists for ${name} ${version}, skipping.`);
|
}
|
||||||
} else {
|
|
||||||
logger.info(`Building release information for ${name} ${version}.`);
|
|
||||||
updatesAvailable = true;
|
|
||||||
|
|
||||||
// Create the temporary working directory.
|
// Create metadata for the Update.xml
|
||||||
const workingDirectoryPath = `${tempDir}/${name}`;
|
const metaHash = await crypto.subtle.digest(
|
||||||
fs.ensureDirSync(workingDirectoryPath);
|
"SHA-1",
|
||||||
|
await Deno.readFile(targetMetadataFilePath)
|
||||||
// Copy license
|
);
|
||||||
fs.copySync('license.txt', `${workingDirectoryPath}/license.txt`);
|
|
||||||
fs.copySync(`scripts/${scriptName}.qs`, `${workingDirectoryPath}/installscript.qs`);
|
|
||||||
|
|
||||||
// Create 7zip archive
|
|
||||||
exec.sync(zipBin, ['a', 'meta.7z', name], {'cwd': tempDir});
|
|
||||||
|
|
||||||
// Copy the metadata file into the target path.
|
|
||||||
logger.debug(`Creating target metadata for ${name} at ${targetMetadataFilePath}`);
|
|
||||||
fs.moveSync(`${tempDir}/meta.7z`, targetMetadataFilePath);
|
|
||||||
|
|
||||||
// Cleanup temporary working directory.
|
|
||||||
fs.removeSync(workingDirectoryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create metadata for the Update.xml
|
|
||||||
var metaHash = sha1(targetMetadataFilePath);
|
|
||||||
|
|
||||||
let target = [];
|
|
||||||
target.push({'Name': name});
|
|
||||||
target.push({'DisplayName': targetSource.DisplayName.replace('%platform%', platform)});
|
|
||||||
target.push({'Version': version});
|
|
||||||
target.push({'DownloadableArchives': releaseData.name});
|
|
||||||
|
|
||||||
|
const target = {
|
||||||
|
Name: name,
|
||||||
|
DisplayName: targetSource.DisplayName.replace("%platform%", platform),
|
||||||
|
Version: version,
|
||||||
|
DownloadableArchives: releaseData.name,
|
||||||
// Because we cannot compute the uncompressed size ourselves, just give a generous estimate
|
// Because we cannot compute the uncompressed size ourselves, just give a generous estimate
|
||||||
// (to make sure they have enough disk space).
|
// (to make sure they have enough disk space).
|
||||||
// OS flag is useless - i.e the installer stubs it :P
|
// OS flag is useless - i.e the installer stubs it :P
|
||||||
target.push({'UpdateFile': [{_attr: {
|
UpdateFile: {
|
||||||
UncompressedSize: releaseData.size * 2,
|
"@UncompressedSize": releaseData.size * 2,
|
||||||
CompressedSize: releaseData.size,
|
"@CompressedSize": releaseData.size,
|
||||||
OS: 'Any'}
|
"@OS": "Any",
|
||||||
}]});
|
},
|
||||||
|
ReleaseDate: releaseData.published_at,
|
||||||
|
Description: targetSource.Description.replace("%platform%", platform)
|
||||||
|
.replace("%commithash%", releaseData.hash)
|
||||||
|
.replace("%releasedate%", releaseData.published_at),
|
||||||
|
Default: targetSource.Default,
|
||||||
|
Licenses: targetSource.Licenses,
|
||||||
|
Script: "installscript.qs",
|
||||||
|
SHA: bufferToHex(metaHash),
|
||||||
|
};
|
||||||
|
|
||||||
target.push({'ReleaseDate': releaseData.published_at});
|
updates.PackageUpdate.push(target);
|
||||||
target.push({'Description': targetSource.Description
|
}
|
||||||
.replace('%platform%', platform)
|
|
||||||
.replace('%commithash%', releaseData.hash)
|
|
||||||
.replace('%releasedate%', releaseData.published_at)});
|
|
||||||
target.push({'Default': targetSource.Default});
|
|
||||||
target.push({'Licenses': targetSource.Licenses});
|
|
||||||
target.push({'Script': 'installscript.qs'});
|
|
||||||
target.push({'SHA': metaHash});
|
|
||||||
|
|
||||||
updates.push({'PackageUpdate': target});
|
// 6/19/18 (Flame Sage) - MSVC builds have been disabled, removed it from the below array 'msvc'
|
||||||
});
|
await Promise.all(
|
||||||
});
|
["mingw", "osx", "linux"].map((platform) => {
|
||||||
|
return Promise.all(
|
||||||
|
targets.map((targetSource) => generate(targetSource, platform))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (updatesAvailable) {
|
if (updatesAvailable) {
|
||||||
const updatesXml = xml({'Updates': updates}, {indent: ' '});
|
const updatesXml = xml.stringify({ Updates: updates }, { indentSize: 2 });
|
||||||
|
|
||||||
// Save Updates.xml
|
// Save Updates.xml
|
||||||
fs.writeFileSync(`${distDir}/Updates.xml`, updatesXml);
|
await Deno.writeTextFile(`${distDir}/Updates.xml`, updatesXml);
|
||||||
logger.info('Wrote a new Updates.xml file -- updates are available.');
|
console.info("Wrote a new Updates.xml file -- updates available.");
|
||||||
} else {
|
} else {
|
||||||
logger.info('No Citra binary release updates are available for the Updates.xml -- nothing to do.');
|
console.info(
|
||||||
|
"No Citra binary release updates are available for the Updates.xml -- nothing to do."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
execute().catch((err) => {
|
execute().catch((err) => {
|
||||||
logger.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user