@dappnode/dappnodesdk
Version:
dappnodesdk is a tool to make the creation of new dappnode packages as simple as possible. It helps to initialize and publish in ethereum blockchain
162 lines • 6.41 kB
JavaScript
import path from "path";
import { got } from "got";
import cliProgress from "cli-progress";
import chalk from "chalk";
import inquirer from "inquirer";
import moment from "moment";
import { getInstallDnpLink } from "../utils/getLinks.js";
import { githubGetReleases } from "../utils/githubGetReleases.js";
import { ipfsAddDirFromUrls } from "../releaseUploader/ipfsNode/addDirFromUrls.js";
import { verifyIpfsConnection } from "../releaseUploader/ipfsNode/verifyConnection.js";
import { defaultArch, releaseFiles } from "@dappnode/types";
import { getLegacyImagePath } from "../utils/getLegacyImagePath.js";
import { getImageFileName } from "../utils/getImageFileName.js";
import { contentHashFileName, releaseFilesDefaultNames } from "../params.js";
export const fromGithub = {
command: "from_github [repoSlug]",
describe: "Gets an existing DNP Github release (assets) and upload it to IPFS",
builder: yargs => yargs
.positional("repoSlug", {
description: `Github repo slug to fetch releases from: "dappnode/DNP_VPN", "vpn"`,
type: "string",
demandOption: true
})
.option("provider", {
alias: "p",
description: `Specify an ipfs provider: "dappnode" (default), "infura", "localhost:5002"`,
default: "dappnode"
})
.option("latest", {
description: "Fetch the latest release only, even if it's a prerelease",
type: "boolean"
})
.option("version", {
description: `Fetch a given version: v0.2.5`,
type: "string"
}),
handler: async (args) => {
const { releaseMultiHash } = await fromGithubHandler(args);
console.log(`Release hash : ${releaseMultiHash}`);
console.log(getInstallDnpLink(releaseMultiHash));
}
};
/**
* Common handler for CLI and programatic usage
*/
export async function fromGithubHandler({ repoSlug, provider, latest, version }) {
// Parse options
const ipfsProvider = provider;
// Assume incomplete repo slugs refer to DAppNode core packages
if (!repoSlug.includes("/"))
repoSlug = `dappnode/DNP_${repoSlug}`;
await verifyIpfsConnection(ipfsProvider);
// Pick version interactively
const release = await getSelectedGithubRelease({
repoSlug,
latest,
version
});
// Sanity check, make sure this release is an actual DAppNode Package
for (const [fileId, fileConfig] of Object.entries(releaseFiles))
if (fileConfig.required &&
!release.assets.some(asset => fileConfig.regex.test(asset.name)))
throw Error(`Release assets do not contain required file ${fileId}`);
// Add extra file for legacy .tar.xz image
const manifestAsset = release.assets.find(asset => asset.name === releaseFilesDefaultNames.manifest);
if (manifestAsset) {
const { name, version } = await got(manifestAsset.browser_download_url).json();
const legacyImagePath = getLegacyImagePath(name, version);
const legacyImageAsset = release.assets.find(asset => asset.name === legacyImagePath);
if (legacyImageAsset) {
const imageAmdPath = getImageFileName(name, version, defaultArch);
release.assets.push({ ...legacyImageAsset, name: imageAmdPath });
}
}
const files = release.assets
.filter(asset => asset.name !== contentHashFileName)
.map(asset => ({
filepath: path.join("release", asset.name),
url: asset.browser_download_url,
name: asset.name,
size: asset.size
}));
// Multiline download > upload progress feedback
const multibarDnl = new cliProgress.MultiBar({
format: "[{bar}] {percentage}%\t| {name}"
});
const bars = new Map();
for (const { filepath, size, name } of files) {
bars.set(filepath, multibarDnl.create(size, 0, { name }));
}
function onProgress(filepath, bytes) {
const bar = bars.get(filepath);
if (bar)
bar.increment(bytes);
}
const releaseMultiHash = await ipfsAddDirFromUrls(files, ipfsProvider, onProgress);
multibarDnl.stop();
console.log(chalk.green(`\nGithub release ${repoSlug} ${release.name} uploaded to IPFS`));
// Verify that the resulting release hash matches the one in Github
const contentHashAsset = release.assets.find(asset => asset.name === contentHashFileName);
if (contentHashAsset) {
const contentHash = await got(contentHashAsset.browser_download_url).text();
if (contentHash.trim() === releaseMultiHash) {
console.log(chalk.dim("✓ Release hash verified, matches Github's"));
}
else {
console.log(`${chalk.red("WARNING!")} resulting hashes do not match`);
console.log(`Github release: ${contentHash}`);
}
}
return { releaseMultiHash };
}
/**
* Given user options, choose a Github Release to get
* @param param0
*/
async function getSelectedGithubRelease({ repoSlug, latest, version }) {
const releases = await githubGetReleases(repoSlug);
if (releases.length === 0)
throw Error(`${repoSlug} has no releases`);
if (latest) {
return releases[0];
}
if (version) {
const release = releases.find(r => r.name === version);
if (!release) {
const releaseList = releases.map(r => r.name).join(",");
throw Error(`No release found for version ${version}. Available releases: ${releaseList}`);
}
return release;
}
// Prompt for a specific version
const answers = await inquirer.prompt([
{
type: "list",
name: "name",
message: "which Github release to get?",
choices: releases.map(release => ({
name: prettyReleaseText(release),
value: release.name
}))
}
]);
const chosenRelease = releases.find(r => r.name === answers.name);
if (!chosenRelease)
throw Error(`chosenRelease ${answers.name} not found`);
return chosenRelease;
}
/**
* Print relevant information about a Github release
* @param release
*/
function prettyReleaseText(release) {
const parts = [
release.name,
moment(release.published_at).fromNow()
];
if (release.prerelease)
parts.push("(prerelease)");
return parts.join(" ");
}
//# sourceMappingURL=from_github.js.map