mirror of https://github.com/vizality/vizality
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.
154 lines
3.7 KiB
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()
|
|
);
|
|
}
|
|
}
|