mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-23 11:55:01 +03:00
Added ability to install themes directly from GitHub (#12635)
refs https://github.com/TryGhost/Ghost/issues/12608 - adds `admin/canary/themes/install` endpoint to the Admin API - requires two query params. `source` must be set to "github". `ref` should refer to a GitHub repo in the format "{org}/{repo}" - downloads zip archive for the repo from github - runs downloaded zip through the same process as uploaded zips
This commit is contained in:
parent
b707131bb7
commit
3e228072ba
@ -19,7 +19,10 @@ module.exports.init = function () {
|
||||
},
|
||||
{
|
||||
event: 'theme.uploaded',
|
||||
name: 'Theme Uploaded'
|
||||
name: 'Theme Uploaded',
|
||||
// {keyOnSuppliedEventData: keyOnTrackedEventData}
|
||||
// - used to extract specific properties from event data and give them meaningful names
|
||||
data: {name: 'name'}
|
||||
},
|
||||
{
|
||||
event: 'integration.added',
|
||||
@ -28,8 +31,11 @@ module.exports.init = function () {
|
||||
];
|
||||
|
||||
_.each(toTrack, function (track) {
|
||||
events.on(track.event, function () {
|
||||
analytics.track(_.extend(trackDefaults, {event: prefix + track.name}));
|
||||
events.on(track.event, function (eventData = {}) {
|
||||
// extract desired properties from eventData and rename keys if necessary
|
||||
const data = _.mapValues(track.data || {}, v => eventData[v]);
|
||||
|
||||
analytics.track(_.extend(trackDefaults, data, {event: prefix + track.name}));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -1,6 +1,13 @@
|
||||
const fs = require('fs-extra');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const security = require('@tryghost/security');
|
||||
const {events} = require('../../lib/common');
|
||||
const themeService = require('../../../frontend/services/themes');
|
||||
const models = require('../../models');
|
||||
const request = require('../../lib/request');
|
||||
const errors = require('@tryghost/errors/lib/errors');
|
||||
const i18n = require('../../lib/common/i18n');
|
||||
|
||||
module.exports = {
|
||||
docName: 'themes',
|
||||
@ -46,6 +53,81 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
install: {
|
||||
headers: {},
|
||||
options: [
|
||||
'source',
|
||||
'ref'
|
||||
],
|
||||
validation: {
|
||||
source: {
|
||||
required: true,
|
||||
values: ['github']
|
||||
},
|
||||
ref: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
method: 'add'
|
||||
},
|
||||
async query(frame) {
|
||||
if (frame.options.source === 'github') {
|
||||
const [org, repo] = frame.options.ref.toLowerCase().split('/');
|
||||
|
||||
// omit /:ref so we fetch the default branch
|
||||
const zipUrl = `https://api.github.com/repos/${org}/${repo}/zipball`;
|
||||
const zipName = `${repo}.zip`;
|
||||
|
||||
// store zip in a unique temporary folder to avoid conflicts
|
||||
const downloadBase = path.join(os.tmpdir(), security.identifier.uid(10));
|
||||
const downloadPath = path.join(downloadBase, zipName);
|
||||
|
||||
await fs.ensureDir(downloadBase);
|
||||
|
||||
try {
|
||||
// download zip file
|
||||
const response = await request(zipUrl, {
|
||||
followRedirect: true,
|
||||
headers: {
|
||||
accept: 'application/vnd.github.v3+json'
|
||||
},
|
||||
encoding: null
|
||||
});
|
||||
|
||||
await fs.writeFile(downloadPath, response.body);
|
||||
|
||||
// install theme from zip
|
||||
const zip = {
|
||||
path: downloadPath,
|
||||
name: zipName
|
||||
};
|
||||
const {theme, themeOverridden} = await themeService.storage.setFromZip(zip);
|
||||
|
||||
if (themeOverridden) {
|
||||
this.headers.cacheInvalidate = true;
|
||||
}
|
||||
|
||||
events.emit('theme.uploaded', {name: theme.name});
|
||||
|
||||
return theme;
|
||||
} catch (e) {
|
||||
if (e.statusCode && e.statusCode === 404) {
|
||||
return Promise.reject(new errors.BadRequestError({
|
||||
message: i18n.t('errors.api.themes.repoDoesNotExist'),
|
||||
context: zipUrl
|
||||
}));
|
||||
}
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
// clean up tmp dir with downloaded file
|
||||
fs.remove(downloadBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
upload: {
|
||||
headers: {},
|
||||
permissions: {
|
||||
@ -66,7 +148,7 @@ module.exports = {
|
||||
// CASE: clear cache
|
||||
this.headers.cacheInvalidate = true;
|
||||
}
|
||||
events.emit('theme.uploaded');
|
||||
events.emit('theme.uploaded', {name: theme.name});
|
||||
return theme;
|
||||
});
|
||||
}
|
||||
|
@ -12,6 +12,11 @@ module.exports = {
|
||||
this.browse(...arguments);
|
||||
},
|
||||
|
||||
install() {
|
||||
debug('install');
|
||||
this.browse(...arguments);
|
||||
},
|
||||
|
||||
activate() {
|
||||
debug('activate');
|
||||
this.browse(...arguments);
|
||||
|
@ -431,7 +431,8 @@
|
||||
"invalidFile": "Please select a valid zip file.",
|
||||
"overrideCasper": "Please rename your zip, it's not allowed to override the default casper theme.",
|
||||
"destroyCasper": "Deleting the default casper theme is not allowed.",
|
||||
"destroyActive": "Deleting the active theme is not allowed."
|
||||
"destroyActive": "Deleting the active theme is not allowed.",
|
||||
"repoDoesNotExist": "Supplied GitHub theme does not exist or is inaccessible"
|
||||
},
|
||||
"images": {
|
||||
"missingFile": "Please select an image.",
|
||||
|
@ -142,6 +142,8 @@ module.exports = function apiRoutes() {
|
||||
http(apiCanary.themes.upload)
|
||||
);
|
||||
|
||||
router.post('/themes/install', mw.authAdminApi, http(apiCanary.themes.install));
|
||||
|
||||
router.put('/themes/:name/activate',
|
||||
mw.authAdminApi,
|
||||
http(apiCanary.themes.activate)
|
||||
|
Loading…
Reference in New Issue
Block a user