You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vizality/renderer/src/entities/Updatable.js

154 lines
3.7 KiB

/**
* This Updatable entity is meant to handle the updating of Vizality's plugins and themes
* and even Vizality itself.
* @todo Finish writing this.
* @module Updatable
* @memberof Entities
* @namespace Entities.Updatable
*/
import { getConfig, statusMatrix } from 'isomorphic-git';
import { getCaller } from '@vizality/util/file';
import http from 'isomorphic-git/http/node';
import { promisify } from 'util';
import { existsSync } from 'fs';
import cp from 'child_process';
import Events from 'events';
import { join } from 'path';
import fs from 'fs';
const exec = promisify(cp.exec);
/**
* @extends Events
*/
export default class Updatable extends Events {
/**
* @property {string} dir Directory path of entity to update
* @property {string} addonId Addon ID
* @property {string} updateIdentifier Entity update identifier
*/
constructor (dir, addonId, updateIdentifier) {
super();
this.dir = dir;
/**
* The addonId might be pre-defined by the plugin manager.
*/
if (!this.addonId) {
// It might be pre-defined by plugin manager
this.addonId = addonId;
}
this.path = join(this.dir, this.addonId);
if (!updateIdentifier) {
updateIdentifier = `${getCaller(this.path)?.type}_${this.addonId}`;
}
this._updateIdentifier = updateIdentifier;
}
/**
*
*/
get _cwd () {
return { cwd: this.path };
}
/**
* @returns {boolean} Whether this can be updated or not
*/
isUpdatable () {
return existsSync(join(this.dir, this.addonId, '.git'));
}
/**
*
* @private
* @returns {Promise<boolean>}
*/
async _checkForUpdates () {
try {
await exec('git fetch', this._cwd);
const gitStatus = await exec('git status -uno', this._cwd).then(({ stdout }) => stdout.toString());
return gitStatus.includes('git pull');
} catch (err) {
return false;
}
}
/**
*
* @private
* @returns {Promise<Array>}
*/
async _getUpdateCommits () {
const branch = await this.getBranch();
const commits = [];
const gitLog = await exec(`git log --format="%H -- %an -- %s" ..origin/${branch}`, this._cwd)
.then(({ stdout }) => stdout.toString());
const lines = gitLog.split('\n');
lines.pop();
lines.forEach(line => {
const data = line.split(' -- ');
commits.push({
id: data.shift(),
author: data.shift(),
message: data.shift()
});
});
return commits;
}
/**
*
* @private
* @param {boolean} [force=false]
* @returns {Promise<boolean>}
*/
async _update (force = false) {
try {
let command = 'git pull --ff-only';
if (force) {
const branch = await this.getBranch();
command = `git reset --hard origin/${branch}`;
}
await exec(command, this._cwd).then(({ stdout }) => stdout.toString());
return true;
} catch (err) {
return false;
}
}
/**
* Fetches the git repository URL for an entity.
* @returns {Promise<string|void>}
*/
async getGitRepo () {
try {
if (!this.path) {
return;
}
const repoUrl = await getConfig({
fs,
dir: this.path,
path: 'remote.origin.url'
});
return repoUrl;
} catch (err) {
if (this.path) {
return this.warn('Failed to fetch git origin URL. Remote updates will be unavailable!');
}
return this.error(err);
}
}
/**
* Fetches the current git branch for an entity.
* @returns {Promise<string|null>}
*/
getBranch () {
return exec('git branch', this._cwd)
.then(({ stdout }) =>
stdout.toString().split('\n').find(l => l.startsWith('*')).slice(2).trim()
);
}
}