Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

automate wallets version updates. #3

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@
"bugs": {
"url": "https://github.com/LePetitBloc/wallets/issues"
},
"homepage": "https://github.com/LePetitBloc/wallets#readme"
"homepage": "https://github.com/LePetitBloc/wallets#readme",
"dependencies": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wallets.json package should come with 0 dependencies when integrated with other projects.
At first I thought about the wallets update automation as a separate project.
But maybe it could live inside this very project, but all dependencies should be considered as dev then

"dotenv": "^6.0.0"
}
}
233 changes: 233 additions & 0 deletions updater.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
require("dotenv").config();
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const versionNumberRegexp = /([vV])?([0-9]{1,2})\.([0-9]{1,2})(?:\.([0-9]{1,2}))?(?:\.([0-9]{1,2}))?[\n|\s]?/g;
const wallets = require("./wallets");
const writeFile = util.promisify(require("fs").writeFile);
const manifest = require("./package");

class Version {
constructor(versionRegexpResult) {
this.prefix = versionRegexpResult[1] || "";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a prettier in this project, all your code should be prettified. The command:
"format": "prettier --trailing-comma es5 --single-quote --print-width 120 --write \"src/**/*.js\"",
might need to be changed to include your code.

this.major = parseInt(versionRegexpResult[2]);
this.minor = parseInt(versionRegexpResult[3]);
this.patch = versionRegexpResult[4] ? parseInt(versionRegexpResult[4]) : null;
this.fourth = versionRegexpResult[5] ? parseInt(versionRegexpResult[5]) : null;
}

static fromVersionString(versionString) {
if (typeof versionString === "string") {
versionNumberRegexp.lastIndex = 0;
let regexpResult = versionNumberRegexp.exec(versionString);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be const

if (regexpResult) {
return new Version(regexpResult);
}
}

throw new Error("Can't parse version string : syntax error");
}

toString() {
let string = this.prefix + this.major + "." + this.minor;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer template litterals for concatenation

string += this.patch !== null ? "." + this.patch : "";
string += this.fourth !== null ? "." + this.fourth : "";
return string;
}
}

class Update {
constructor(walletIdentifier, from, to) {
this.walletIdentifier = walletIdentifier;
this.from = from;
this.to = to;
}

toString() {
return "updated " + this.walletIdentifier + " from " + this.from.toString() + " to " + this.to.toString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For logging purpose you should look into https://github.com/pinojs/pino

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again template litterals

}
}

(async function() {
try {
console.log("--- Wallet updater launched at " + new Date() + "--");
let updates = await checkAllForUpdates();

if (updates.filter(o => { return o !== null; }).length > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use shorthand notation:
o => o !== null

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also updates.filter is immutable, it returns the filtered version of updates, but inside your conditions updates still contains all elements

await updateFile(updates);
await updatePatchNumber();

const commitMessage = buildCommitMessage(updates);

await addDeployKey();
await addChanges();
await commit(commitMessage);
await tag(commitMessage);
await push();
await publish();
} else {
console.log("Wallets are up to date.");
}

console.log("--- Wallet updater ended at " + new Date() + "--");
} catch (e) {
console.error(e);
}
})();


async function addDeployKey() {
if (!process.env.deploy_key) {
throw new Error("Environment variable deploy_key is not set - cannot send modifications to server.");
}
const { stdout, stderr } = await exec("eval \"$(ssh-agent -s)\" && echo $deploy_key | ssh-add -");
console.log(stdout);
console.log(stderr);
}

async function addChanges() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could create a helper to create these methods, and plz use spawn instead of exec, but be careful of the different synthax.

const { stdout } = await exec("git add wallets.json package.json");
console.log(stdout);
}

async function commit(message) {
const { stdout } = await exec("git commit -m \"" + message + "\"");
console.log(stdout);
}

async function tag(message) {
const { stdout } = await exec("git tag " + manifest.version + " -m \" " + message + "\"");
console.log(stdout);
}

async function push() {
const { stderr } = await exec("git push --follow-tags origin HEAD");
console.log(stderr);
console.log("Updated wallet.json successfully");
}

function buildCommitMessage(updates) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Array.reduce for this method and you're going to feel the power :)

let commitMessage = "Update wallet.json\n\n";
updates.forEach(update => {
if (update) {
commitMessage += update.toString() + "\n";
}
});
return commitMessage;
}

async function updateFile(updates) {
updates.forEach(update => {
if (update) {
wallets[update.walletIdentifier].tag = update.to.toString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not mutate wallets variable.
You could use the spread operator for doing so:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

}
});
return writeFile("./wallets.json", JSON.stringify(wallets, null, " "));
}

async function checkAllForUpdates() {
const pendingUpdates = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems convoluted, even If I know we use to do things this way....
Might get something better with something like:

const pendingUpdates = Object.keys(wallets).map(key => checkForUpdates(key, wallets[key]))
```.

for (let property in wallets) {
if (wallets.hasOwnProperty(property)) {
pendingUpdates.push(checkForUpdates(wallets[property], property));
}
}
return Promise.all(pendingUpdates);
}

async function checkForUpdates(wallet, identifier) {
const tags = await listRemoteTags(wallet.repository);
let versions = parseVersionsTags(tags);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could chain parseVersionsTags(tags).filter( ).sort(

const currentVersion = findCurrentVersion(wallet, identifier);
if (currentVersion) {
versions = versions.filter(superiorVersionsFilter(currentVersion));
versions = versions.sort(versionsSorter);

if (versions.length > 0) {
const targetVersion = versions[versions.length - 1];
return new Update(identifier, currentVersion, targetVersion);
}
}
return null;
}

function findCurrentVersion(wallet, identifier) {
try {
return Version.fromVersionString(wallet.tag);
}catch (e) {
console.warn("Can't determined current version for wallet : " + identifier + " missing tag");
return null;
}
}

async function publish() {
if (!process.env.NPM_TOKEN) {
throw new Error("Environment variable NPM_TOKEN is not set - cannot publish package.");
}
try {
await exec("npm whoami");
} catch (e) {
throw new Error("Something went wrong authenticating you on npm - Check your NPM_TOKEN validity");
}

const { stdout } = await exec("npm publish");
console.log(stdout);
return stdout;
}

async function updatePatchNumber() {
const currentVersion = Version.fromVersionString(manifest.version);
currentVersion.patch += 1;

manifest.version = currentVersion.toString();
return writeFile("./package.json", JSON.stringify(manifest, null, " "));
}


function superiorVersionsFilter(currentVersion) {
return version => {
if (version.major === currentVersion.major) {
if (version.minor > currentVersion.minor) {
return 1;
} else if (version.minor === currentVersion.minor) {
if (version.patch > currentVersion.patch) {
return 1;
} else if (version.patch === currentVersion.patch) {
if (version.fourth > currentVersion.fourth) {
return 1;
}
}
}
}
return 0;
};
}

function versionsSorter(a, b) {
if (
a.major > b.major ||
(a.major >= b.major && a.minor > b.minor) ||
(a.major >= b.major && a.minor >= b.minor && a.patch > b.patch) ||
(a.major >= b.major && a.minor >= b.minor && a.patch >= b.patch && a.fourth > b.fourth)
) {
return 1;
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else not needed

return -1;
}
}

function parseVersionsTags(tagLists) {
const versions = [];
let version = {};
versionNumberRegexp.lastIndex = 0;
while ((version = versionNumberRegexp.exec(tagLists))) {
versions.push(new Version(version));
}
return versions;
}

async function listRemoteTags(remote) {
const { stdout } = await exec("git ls-remote --tags " + remote);
return stdout;
}