continue work on addon manager

pull/67/head
dperolio 3 years ago
parent c57dcd58ab
commit d3b9c04e30
No known key found for this signature in database
GPG Key ID: 3E9BBAA710D3DDCE

@ -25,7 +25,6 @@ const ErrorTypes = Object.freeze({
});
/**
* @extends Events
*/
@ -125,13 +124,26 @@ export default class AddonManager extends Events {
hasSettings (addonId) {
try {
const addon = this.get(addonId);
if (!addon) throw new Error(`${toTitleCase(this.type)} "${addonId}" is not installed!`);
return Boolean(addon.sections?.settings);
return Boolean(addon?.sections?.settings);
} catch (err) {
return this._error(`An error occurred while checking for settings for "${addonId}"!`, err);
}
}
/**
*
* @param {string} addonId Addon ID
* @returns
*/
hasScreenshots (addonId) {
try {
const addon = this.get(addonId);
return Boolean(addon?.screenshots);
} catch (err) {
return this._error(`An error occurred while checking for screenshots for "${addonId}"!`, err);
}
}
/**
*
* @returns
@ -216,9 +228,6 @@ export default class AddonManager extends Events {
});
this._setAddonIcon(addonId, manifest);
// this._setPreviewImages(addonId, manifest);
// this._setBanner(addonId, manifest);
this._items.set(addonId, new Addon());
} catch (err) {
return this._error(`An error occurred while initializing "${addonId}"!`, err);
@ -292,7 +301,7 @@ export default class AddonManager extends Events {
async initialize () {
let addonId;
try {
await this._enableWatcher();
this._enableWatcher();
if (this._watcherEnabled) {
await this._watchFiles();
}
@ -459,79 +468,95 @@ export default class AddonManager extends Events {
}
/**
* Installs one or more addons.
* @param {string} addons Addon ID(s) or GitHub repository URLs
* Installs an addon.
* @param {string} addonId Addon ID or GitHub repository URL
*/
async install (addons) {
async install (addonId) {
try {
/*
* This is temporary until we get the API working to request this info from an endpoint.
*/
const community = [ 'spotify-in-discord', 'copy-raw-message', 'better-code-blocks', 'status-everywhere', 'open-links-in-discord', 'example-plugin-settings', 'channel-members-activity-icons', 'bring-back-gamer-text', 'heyzere' ];
addons = [ addons ].flat();
for (let addon of addons) {
let addonId;
for (const _addon of community) {
if (addon === _addon) {
addonId = _addon;
break;
}
}
if (!addonId) {
if (!new RegExp(/^(((https?:\/\/)(((([a-zA-Z0-9][a-zA-Z0-9\-_]{1,252})\.){1,8}[a-zA-Z]{2,63})\/))|((ssh:\/\/)?git@)(((([a-zA-Z0-9][a-zA-Z0-9\-_]{1,252})\.){1,8}[a-zA-Z]{2,63})(:)))([a-zA-Z0-9][a-zA-Z0-9_-]{1,36})(\/)([a-zA-Z0-9][a-zA-Z0-9_-]{1,36})((\.git)?)$/).test(addon)) {
throw new Error('You must provide a valid GitHub repository URL or an addon ID from https://github.com/vizality-community!');
}
}
console.log(addonId);
let git;
if (vizality.manager.community[toPlural(this.type)].has(addonId)) {
console.log('uh');
({ git } = vizality.manager.community[toPlural(this.type)].get(addonId));
}
// The URL must end in git to get processed by isomorphic-git below
if (!addon.endsWith('.git')) {
addon = `${addon}.git`;
}
console.log('okay');
if (!new RegExp(/^(((https?:\/\/)(((([a-zA-Z0-9][a-zA-Z0-9\-_]{1,252})\.){1,8}[a-zA-Z]{2,63})\/))|((ssh:\/\/)?git@)(((([a-zA-Z0-9][a-zA-Z0-9\-_]{1,252})\.){1,8}[a-zA-Z]{2,63})(:)))([a-zA-Z0-9][a-zA-Z0-9_-]{1,36})(\/)([a-zA-Z0-9][a-zA-Z0-9_-]{1,36})((\.git)?)$/).test(git || addonId)) {
console.log('yes');
throw new Error('You must provide a valid GitHub repository URL or an addon ID from https://github.com/vizality-community!');
} else {
console.log('no');
git = addonId;
}
addonId = addonId || addon.split('.git')[0].split('/')[addon.split('.git')[0].split('/').length - 1];
addonId = toKebabCase(addonId);
console.log('here', git);
/**
* The URL must end in git to get processed by isomorphic-git below.
*/
if (!git.endsWith('.git')) {
git = `${git}.git`;
}
if (this.isInstalled(addonId)) {
throw {
name: ErrorTypes.ADDON_ALREADY_INSTALLED,
message: Messages.VIZALITY_ADDON_ALREADY_INSTALLED_DESC.format({ type: this.type, addonId }),
stack: `\n${(new Error()).stack.replace('Error\n', '')}`,
addonId
};
}
addonId = git.split('.git')[0].split('/')[git.split('.git')[0].split('/').length - 1];
addonId = toKebabCase(addonId);
console.log(git);
console.log(addonId);
if (this.isInstalled(addonId)) {
throw {
name: ErrorTypes.ADDON_ALREADY_INSTALLED,
message: Messages.VIZALITY_ADDON_ALREADY_INSTALLED_DESC.format({ type: this.type, addonId }),
stack: `\n${(new Error()).stack.replace('Error\n', '')}`,
addonId
};
}
if (existsSync(join(this.dir, `__installing__${addonId}`)) && lstatSync(join(this.dir, addonId)).isDirectory()) {
throw new Error(`${toTitleCase(this.type)} "${addonId}" looks like it's already being installed!`);
}
if (existsSync(join(this.dir, `__installing__${addonId}`)) && lstatSync(join(this.dir, addonId))?.isDirectory()) {
throw new Error(`${toTitleCase(this.type)} "${addonId}" looks like it's already being installed!`);
}
if (existsSync(join(this.dir, addonId)) && lstatSync(join(this.dir, addonId)).isDirectory()) {
throw new Error(`${toTitleCase(this.type)} directory "${addonId}" already exists!`);
}
if (existsSync(join(this.dir, addonId)) && lstatSync(join(this.dir, addonId))?.isDirectory()) {
throw new Error(`${toTitleCase(this.type)} directory "${addonId}" already exists!`);
}
try {
await clone({
fs,
http,
singleBranch: true,
depth: 1,
dir: join(this.dir, `__installing__${addonId}`),
url: addon,
onProgress: evt => {
// console.log(evt);
}
});
} catch (err) {
/*
* isomorphic-git creates the directory before it checks anything, whether there is
* a response or not, so let's remove it if there's an error here.
*/
await removeDirRecursive(resolve(this.dir, addonId));
throw new Error(`There was a problem while attempting to install "${addonId}"!`, err);
}
renameSync(join(this.dir, `__installing__${addonId}`), join(this.dir, addonId));
try {
await clone({
fs,
http,
singleBranch: true,
depth: 1,
dir: join(this.dir, `__installing__${addonId}`),
url: git,
onProgress: evt => {
// console.log(evt);
}
});
} catch (err) {
/**
* @note isomorphic-git creates the directory before it checks anything, whether there is
* a response or not, so let's remove it if there's an error here.
*/
await removeDirRecursive(resolve(this.dir, addonId));
throw new Error(`There was a problem while attempting to install "${addonId}"!`, err);
}
renameSync(join(this.dir, `__installing__${addonId}`), join(this.dir, addonId));
/**
* Send a success toast.
*/
vizality.api.notifications.sendToast({
id: ErrorTypes.ADDON_ALREADY_INSTALLED,
header: Messages.VIZALITY_ADDON_SUCCESSFULLY_INSTALLED.format({ type: toTitleCase(this.type) }),
content: <AddonInfoMessage addon={addon} message={Messages.VIZALITY_ADDON_SUCCESSFULLY_INSTALLED_DESC.format({ type: toTitleCase(this.type) })} />,
icon: toTitleCase(this.type),
buttons: [
{
text: 'View',
onClick: () => vizality.api.routes.navigateTo(`/${toPlural(this.type)}/${addon.addonId}`)
}
]
});
} catch (err) {
console.warn(err);
let addon;
if (err.addonId) {
addon = this.get(err.addonId);
@ -594,20 +619,21 @@ export default class AddonManager extends Events {
/**
* Sets an addon's icon image URL.
* @private
* @param {string} addonId Addon ID
* @param {object} manifest Addon manifest
* @private
* @returns {Promise<void>}
*/
async _setAddonIcon (addonId, manifest) {
try {
const validExtensions = [ '.png', '.jpg', '.jpeg' ];
if (manifest.icon) {
if (!manifest.icon.endsWith('.png') && !manifest.icon.endsWith('.jpg') && !manifest.icon.endsWith('.jpeg')) {
if (!validExtensions.some(ext => manifest.icon.endsWith(ext))) {
this._warn(`${toTitleCase(this.type)} icon must be of type .png, .jpg, or .jpeg.`);
} else {
return manifest.icon = `vz-${this.type}://${addonId}/${manifest.icon}`;
}
}
const validExtensions = [ '.png', '.jpg', '.jpeg' ];
if (validExtensions.some(ext => existsSync(resolve(this.dir, addonId, 'assets', `icon${ext}`)))) {
for (const ext of validExtensions) {
if (existsSync(resolve(this.dir, addonId, 'assets', `icon${ext}`))) {
@ -616,8 +642,7 @@ export default class AddonManager extends Events {
}
}
} else {
const addonIdHash = toHash(addonId);
return manifest.icon = Avatars[`DEFAULT_${this.type.toUpperCase()}_${(addonIdHash % 5) + 1}`];
return manifest.icon = this._getDefaultAddonIcon(addonId);
}
} catch (err) {
return this._error(err);
@ -625,34 +650,15 @@ export default class AddonManager extends Events {
}
/**
* Sets an addon's banner image URL.
* @param {string} addonId Addon ID
* @param {object} manifest Addon manifest
* Gets a default addon icon image URL.
* @private
* @param {string} addonId Addon ID
* @returns {string}
*/
async _setBanner (addonId, manifest) {
_getDefaultAddonIcon (addonId) {
try {
if (manifest.icon) {
if (!manifest.icon.endsWith('.png') && !manifest.icon.endsWith('.jpg') && !manifest.icon.endsWith('.jpeg')) {
this._warn(`${toTitleCase(this.type)} icon must be of type .png, .jpg, or .jpeg.`);
} else {
return manifest.icon = `vz-${this.type}://${addonId}/${manifest.icon}`;
}
}
const validExtensions = [ '.png', '.jpg', '.jpeg' ];
if (validExtensions.some(ext => existsSync(resolve(this.dir, addonId, 'assets', `icon${ext}`)))) {
for (const ext of validExtensions) {
if (existsSync(resolve(this.dir, addonId, 'assets', `icon${ext}`))) {
manifest.icon = `vz-${this.type}://${addonId}/assets/icon${ext}`;
break;
}
}
} else {
const addonIdHash = toHash(addonId);
return manifest.icon = Avatars[`DEFAULT_${this.type.toUpperCase()}_${(addonIdHash % 5) + 1}`];
}
const addonIdHash = toHash(addonId);
return Avatars[`DEFAULT_${this.type.toUpperCase()}_${(addonIdHash % 5) + 1}`];
} catch (err) {
return this._error(err);
}
@ -661,14 +667,16 @@ export default class AddonManager extends Events {
/**
* Enables the addon directory watcher.
* @private
* @returns {void}
*/
async _enableWatcher () {
_enableWatcher () {
this._watcherEnabled = true;
}
/**
* Disables the addon directory watcher.
* @private
* @returns {Promise<void>}
*/
async _disableWatcher () {
this._watcherEnabled = false;
@ -681,6 +689,7 @@ export default class AddonManager extends Events {
/**
* Initiates the addon directory watcher.
* @private
* @returns {Promise<void>}
*/
async _watchFiles () {
this._watcher = watch(this.dir, {
@ -695,6 +704,7 @@ export default class AddonManager extends Events {
* @see {@link https://memorytin.com/2015/07/08/node-js-chokidar-wait-for-file-copy-to-complete-before-modifying/}
* @param {string} path Addon folder path
* @param {object} prev Previous folder stats info @see {@link https://nodejs.org/api/fs.html#fs_class_fs_stats}
* @returns {void}
*/
const checkAddDirComplete = (path, prev) => {
try {
@ -718,10 +728,19 @@ export default class AddonManager extends Events {
}
};
/**
*
*/
this._watcher
/**
*
*/
.on('addDir', (path, stat) => {
setTimeout(checkAddDirComplete, 2000, path, stat);
})
/**
*
*/
.on('unlinkDir', path => {
const addonId = path.replace(this.dir + sep, '');
Object.keys(require.cache).forEach(key => {
@ -736,6 +755,7 @@ export default class AddonManager extends Events {
/**
*
* @param {string} addonId Addon ID
* @returns {void}
*/
uninstall (addonId) {
try {

Loading…
Cancel
Save