@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
225 lines • 10 kB
JavaScript
import { defaultDir, defaultVariantsDirName } from "../../../params.js";
import { shell } from "../../../utils/shell.js";
import { getGitHead } from "../../../utils/git.js";
import { buildAndComment } from "../build/index.js";
import { writeManifest, writeCompose, readManifest } from "../../../files/index.js";
import { getNextVersionFromApm } from "../../../utils/versions/getNextVersionFromApm.js";
import { printSettings, getInitialSettings } from "./settings/index.js";
import { closeOldPrs, getBumpPrBody, getGithubSettings, isBranchNew } from "./github/index.js";
import path from "path";
import { getAllVariantsInPath } from "../../../files/variants/getAllPackageVariants.js";
// This action should be run periodically
export const gaBumpUpstream = {
command: "bump-upstream",
describe: "Check if upstream repo has released a new version and open a PR with version bump",
builder: {
eth_provider: {
alias: "p",
description: "Specify the eth provider to use: 'remote' (default), 'infura', 'localhost:5002'",
default: "remote",
type: "string"
},
use_fallback: {
alias: "f",
description: "Use fallback eth provider if main provider fails: false (default), true. If main provider fails, it will try to use 'remote' first and then 'infura'",
default: true,
type: "boolean"
},
use_variants: {
alias: "use-variants",
description: `It will use the dappnode_package.json and docker-compose.yml files in the root of the project together with the specific ones defined for each package variant to build all of them`,
type: "boolean"
},
variants_dir: {
description: `Path to the directory where the package variants are located (only for packages that support it and combined with either "--all-variants" or "--variants"). By default, it is ${defaultVariantsDirName}`,
type: "string",
default: defaultVariantsDirName
},
skip_build: {
description: `Only create the bump PR without building the package`,
type: "boolean"
}
},
handler: async (args) => await gaBumpUpstreamHandler(args)
};
async function gaBumpUpstreamHandler({ dir = defaultDir, eth_provider: userEthProvider, use_fallback: useFallback, use_variants: useVariants, variants_dir: variantsDir, skip_build: skipBuild }) {
const { upstreamSettings, manifestData: { manifest, format: manifestFormat }, compose, gitSettings, ethProvider } = await getInitialSettings({ dir, userEthProvider, useFallback });
if (!upstreamSettings) {
console.log("There are no upstream repos/versions defined in the manifest");
return;
}
if (upstreamSettings.every(({ manifestVersion, githubVersion }) => manifestVersion === githubVersion)) {
console.log("All versions are up-to-date");
return;
}
printSettings(upstreamSettings, gitSettings, ethProvider);
const githubSettings = await getGithubSettings(dir, upstreamSettings);
const { branchName, repo } = githubSettings;
if (!(await isBranchNew({ branchName, repo }))) {
// We assume the PR was already opened
console.log(`Branch ${branchName} already exists`);
return;
}
updateComposeUpstreamVersions(dir, compose, upstreamSettings);
updateManifestUpstreamVersion({
manifest,
manifestFormat,
upstreamSettings,
dir
});
await updateManifestPkgVersion({
dir,
ethProvider,
allVariants: useVariants,
variantsDir
});
await prepareAndCommitChanges({
gitSettings,
upstreamSettings,
githubSettings
});
try {
await closeOldPrs(repo, branchName);
}
catch (e) {
console.warn("Could not close old linked PRs", e);
}
if (skipBuild) {
console.log("Skipping build and comment stage due to --skip_build flag");
return;
}
const gitHead = await getGitHead();
await buildAndComment({ dir, commitSha: gitHead.commit, branch: branchName, all_variants: useVariants });
}
/**
* Updates Docker Compose service build arguments with new versions based on `upstreamRepoVersions`.
* Creates a deep copy of `compose`, modifies build arguments as needed, and writes the updated
* compose to `dir` if changes occur. Returns an object detailing updates or `null` if no update is needed.
*
* @param {string} dir - Directory for the Compose file.
* @param {Compose} compose - Original Docker Compose configuration.
* @param {UpstreamSettings[]} upstreamSettings - New versions for the Compose services.
*/
function updateComposeUpstreamVersions(dir, compose, upstreamSettings) {
var _a;
const newCompose = JSON.parse(JSON.stringify(compose)); // Deep copy
for (const upstreamItem of upstreamSettings) {
// Checks if the service includes a build argument with the same name as the
// upstream item (e.g. "GETH_VERSION" is a build argument for the Geth service)
for (const [, service] of Object.entries(newCompose.services))
if (typeof service.build !== "string" &&
((_a = service.build) === null || _a === void 0 ? void 0 : _a.args) &&
upstreamItem.arg in service.build.args)
service.build.args[upstreamItem.arg] = upstreamItem.githubVersion;
}
writeCompose(newCompose, { dir });
}
/**
* Updates the manifest with new version tags based on the provided `composeVersionsToUpdate`
* and optionally fetches a new version for the manifest. The updated manifest is returned.
* @param {Object} options - The options for updating the manifest.
* @param {Manifest} options.manifest - The manifest object to update.
* @param {UpstreamSettings[]} options.upstreamSettings - The new versions for the manifest.
* @param {string} options.dir - Directory path where the manifest will be saved.
* @param {string} options.ethProvider - Ethereum provider URL.
* @returns {Promise<Manifest>} The updated manifest object.
*/
function updateManifestUpstreamVersion({ manifest, manifestFormat, upstreamSettings, dir }) {
var _a;
if (manifest.upstream) {
for (const upstreamItem of manifest.upstream) {
const versionUpdate = (_a = upstreamSettings.find(({ repo }) => repo === upstreamItem.repo)) === null || _a === void 0 ? void 0 : _a.githubVersion;
if (versionUpdate)
upstreamItem.version = versionUpdate;
}
}
else {
// There should be only one upstream repo in the legacy format
manifest.upstreamVersion = upstreamSettings[0].githubVersion;
}
try {
writeManifest(manifest, manifestFormat, { dir });
}
catch (e) {
throw new Error(`Error writing manifest: ${e.message}`);
}
}
async function updateManifestPkgVersion({ dir, ethProvider, allVariants, variantsDir }) {
const manifestDirs = allVariants
? getAllVariantsInPath(variantsDir).map(variant => path.join(variantsDir, variant))
: [dir];
for (const dir of manifestDirs) {
try {
const { manifest, format } = readManifest([{ dir }]);
manifest.version = await getNewManifestVersion({ dir, ethProvider });
console.log(`New manifest version for ${manifest.name}: ${manifest.version}`);
writeManifest(manifest, format, { dir });
}
catch (e) {
// Not throwing an error here because updating the manifest version is not critical
console.error(`Could not fetch new manifest version: ${e}`);
}
}
}
async function getNewManifestVersion({ ethProvider, dir }) {
try {
const { manifest: { name } } = readManifest([{ dir }]);
return await getNextVersionFromApm({
type: "patch",
ethProvider,
ensName: name
});
}
catch (e) {
if (e.message.includes("NOREPO")) {
console.log("Package not found in APM (probably not published yet)");
console.log("Manifest version set to default 0.1.0");
return "0.1.0";
}
else {
e.message = `Error getting next version from apm: ${e.message}`;
throw e;
}
}
}
async function prepareAndCommitChanges({ gitSettings, upstreamSettings, githubSettings }) {
const { branchName, branchRef } = githubSettings;
const commitMsg = createCommitMessage(upstreamSettings);
console.log(`commitMsg: ${commitMsg}`);
if (process.env.SKIP_COMMIT) {
console.log("SKIP_COMMIT=true");
return;
}
await configureGitUser(gitSettings);
await checkoutNewBranch(branchName);
await commitAndPushChanges({ commitMsg, branchRef });
await attemptToOpenPR({ commitMsg, upstreamSettings, githubSettings });
}
function createCommitMessage(upstreamSettings) {
return `bump ${upstreamSettings
.flatMap(({ repo, githubVersion }) => `${repo} to ${githubVersion}`)
.join(", ")}`;
}
async function configureGitUser({ userName, userEmail }) {
await shell(`git config user.name ${userName}`);
await shell(`git config user.email ${userEmail}`);
}
async function checkoutNewBranch(branchName) {
await shell(`git checkout -b ${branchName}`);
}
async function commitAndPushChanges({ commitMsg, branchRef }) {
await shell(`git commit -a -m "${commitMsg}"`, { pipeToMain: true });
await shell(`git push -u origin ${branchRef}`, { pipeToMain: true });
}
async function attemptToOpenPR({ commitMsg, upstreamSettings, githubSettings: { repo, repoData, branchName } }) {
// Skip PR creation for testing
if (process.env.ENVIRONMENT === "TEST")
return;
await repo.openPR({
from: branchName,
to: repoData.default_branch,
title: commitMsg,
body: getBumpPrBody(upstreamSettings)
});
}
//# sourceMappingURL=index.js.map