server.js: Migrate to Deno

This commit is contained in:
liushuyu 2022-10-07 02:30:57 -06:00
parent 9ab3db6764
commit 894ffab4e4
No known key found for this signature in database
GPG Key ID: 23D1CE4534419437
3 changed files with 187 additions and 2189 deletions

2031
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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
View File

@ -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);
}); });