Allow installing plugins declaratively
This commit is contained in:
parent
1e3a5b25c3
commit
97b06f2455
|
@ -20,6 +20,7 @@ import { decachePlugin } from '@server/helpers/decache.js'
|
||||||
import { ApplicationModel } from '@server/models/application/application.js'
|
import { ApplicationModel } from '@server/models/application/application.js'
|
||||||
import { MOAuthTokenUser, MUser } from '@server/types/models/index.js'
|
import { MOAuthTokenUser, MUser } from '@server/types/models/index.js'
|
||||||
import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins.js'
|
import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins.js'
|
||||||
|
import { execShell } from '../../helpers/core-utils.js'
|
||||||
import { logger } from '../../helpers/logger.js'
|
import { logger } from '../../helpers/logger.js'
|
||||||
import { CONFIG } from '../../initializers/config.js'
|
import { CONFIG } from '../../initializers/config.js'
|
||||||
import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants.js'
|
import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants.js'
|
||||||
|
@ -334,13 +335,93 @@ export class PluginManager implements ServerHook {
|
||||||
|
|
||||||
// ###################### Installation ######################
|
// ###################### Installation ######################
|
||||||
|
|
||||||
|
async installDeclarativePlugins () {
|
||||||
|
const pluginDirectory = CONFIG.STORAGE.PLUGINS_DIR
|
||||||
|
let declaredPlugins
|
||||||
|
try {
|
||||||
|
declaredPlugins = await readJSON(join(pluginDirectory, "declarative_plugins.json"));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
logger.info("No declarative plugins file found.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingDeclaredPlugins: PluginModel[] = await PluginModel.listDeclarativePluginsAndThemes()
|
||||||
|
|
||||||
|
for (const plugin of existingDeclaredPlugins) {
|
||||||
|
const npmName = PluginModel.buildNpmName(plugin.name, plugin.type)
|
||||||
|
if (!(npmName in declaredPlugins)) {
|
||||||
|
logger.info("Removing previously declared plugin %s", npmName)
|
||||||
|
await this.uninstall({npmName: npmName, unregister: false})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info("Keeping still-declared plugin %s.", npmName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const npmName of Object.keys(declaredPlugins)) {
|
||||||
|
const {
|
||||||
|
pluginPath = null,
|
||||||
|
version = null,
|
||||||
|
extraArgs = null,
|
||||||
|
preInstall = null,
|
||||||
|
postInstall = null
|
||||||
|
} = declaredPlugins[npmName];
|
||||||
|
|
||||||
|
if (preInstall != null) {
|
||||||
|
logger.info("Running pre-install command for plugin %s.", npmName)
|
||||||
|
try {
|
||||||
|
await execShell(preInstall, { cwd: pluginDirectory })
|
||||||
|
} catch (result) {
|
||||||
|
logger.error("Cannot exec pre-install command.", { preInstall, err: result.err, stderr: result.stderr })
|
||||||
|
|
||||||
|
throw result.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pluginPath != null) {
|
||||||
|
logger.info("Installing declared plugin %s from disk (path %s).", npmName, pluginPath)
|
||||||
|
await this.install({
|
||||||
|
toInstall: pluginPath,
|
||||||
|
fromDisk: true,
|
||||||
|
declarative: true,
|
||||||
|
extraArgs: extraArgs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (version != null) {
|
||||||
|
logger.info("Installing declared plugin %s (version %s) from npm.", npmName, version)
|
||||||
|
await this.install({
|
||||||
|
toInstall: npmName,
|
||||||
|
version: version,
|
||||||
|
declarative: true,
|
||||||
|
extraArgs: extraArgs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postInstall != null) {
|
||||||
|
logger.info("Running post-install command for plugin %s.", npmName)
|
||||||
|
try {
|
||||||
|
await execShell(postInstall, { cwd: pluginDirectory })
|
||||||
|
} catch (result) {
|
||||||
|
logger.error("Cannot exec post-install command.", { postInstall, err: result.err, stderr: result.stderr })
|
||||||
|
|
||||||
|
throw result.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortHooksByPriority()
|
||||||
|
}
|
||||||
|
|
||||||
async install (options: {
|
async install (options: {
|
||||||
toInstall: string
|
toInstall: string
|
||||||
version?: string
|
version?: string
|
||||||
fromDisk?: boolean // default false
|
fromDisk?: boolean // default false
|
||||||
register?: boolean // default true
|
register?: boolean // default true
|
||||||
|
declarative?: boolean // default false
|
||||||
|
extraArgs?: string // default empty
|
||||||
}) {
|
}) {
|
||||||
const { toInstall, version, fromDisk = false, register = true } = options
|
const { toInstall, version, fromDisk = false, register = true, declarative = false, extraArgs = null } = options
|
||||||
|
|
||||||
let plugin: PluginModel
|
let plugin: PluginModel
|
||||||
let npmName: string
|
let npmName: string
|
||||||
|
@ -349,8 +430,8 @@ export class PluginManager implements ServerHook {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fromDisk
|
fromDisk
|
||||||
? await installNpmPluginFromDisk(toInstall)
|
? await installNpmPluginFromDisk(toInstall, extraArgs)
|
||||||
: await installNpmPlugin(toInstall, version)
|
: await installNpmPlugin(toInstall, version, extraArgs)
|
||||||
|
|
||||||
npmName = fromDisk ? basename(toInstall) : toInstall
|
npmName = fromDisk ? basename(toInstall) : toInstall
|
||||||
const pluginType = PluginModel.getTypeFromNpmName(npmName)
|
const pluginType = PluginModel.getTypeFromNpmName(npmName)
|
||||||
|
@ -368,6 +449,7 @@ export class PluginManager implements ServerHook {
|
||||||
version: packageJSON.version,
|
version: packageJSON.version,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
uninstalled: false,
|
uninstalled: false,
|
||||||
|
declarative: declarative,
|
||||||
peertubeEngine: packageJSON.engine.peertube
|
peertubeEngine: packageJSON.engine.peertube
|
||||||
}, { returning: true })
|
}, { returning: true })
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { logger } from '../../helpers/logger.js'
|
||||||
import { CONFIG } from '../../initializers/config.js'
|
import { CONFIG } from '../../initializers/config.js'
|
||||||
import { getLatestPluginVersion } from './plugin-index.js'
|
import { getLatestPluginVersion } from './plugin-index.js'
|
||||||
|
|
||||||
async function installNpmPlugin (npmName: string, versionArg?: string) {
|
async function installNpmPlugin (npmName: string, versionArg?: string, extraArgs?: string) {
|
||||||
// Security check
|
// Security check
|
||||||
checkNpmPluginNameOrThrow(npmName)
|
checkNpmPluginNameOrThrow(npmName)
|
||||||
if (versionArg) checkPluginVersionOrThrow(versionArg)
|
if (versionArg) checkPluginVersionOrThrow(versionArg)
|
||||||
|
@ -15,13 +15,15 @@ async function installNpmPlugin (npmName: string, versionArg?: string) {
|
||||||
|
|
||||||
let toInstall = npmName
|
let toInstall = npmName
|
||||||
if (version) toInstall += `@${version}`
|
if (version) toInstall += `@${version}`
|
||||||
|
if (extraArgs) toInstall += ` ${extraArgs}`
|
||||||
|
|
||||||
const { stdout } = await execYarn('add ' + toInstall)
|
const { stdout } = await execYarn('add ' + toInstall)
|
||||||
|
|
||||||
logger.debug('Added a yarn package.', { yarnStdout: stdout })
|
logger.debug('Added a yarn package.', { yarnStdout: stdout })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installNpmPluginFromDisk (path: string) {
|
async function installNpmPluginFromDisk (path: string, extraArgs?: string) {
|
||||||
|
if (extraArgs) path += ` ${extraArgs}`
|
||||||
await execYarn('add file:' + path)
|
await execYarn('add file:' + path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,10 @@ export class PluginModel extends SequelizeModel<PluginModel> {
|
||||||
@Column(DataType.JSONB)
|
@Column(DataType.JSONB)
|
||||||
storage: any
|
storage: any
|
||||||
|
|
||||||
|
@AllowNull(true)
|
||||||
|
@Column
|
||||||
|
declarative: boolean
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
|
@ -103,6 +107,17 @@ export class PluginModel extends SequelizeModel<PluginModel> {
|
||||||
return PluginModel.findAll(query)
|
return PluginModel.findAll(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static listDeclarativePluginsAndThemes (): Promise<MPlugin[]> {
|
||||||
|
const query = {
|
||||||
|
where: {
|
||||||
|
declarative: true,
|
||||||
|
uninstalled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PluginModel.findAll(query)
|
||||||
|
}
|
||||||
|
|
||||||
static loadByNpmName (npmName: string): Promise<MPlugin> {
|
static loadByNpmName (npmName: string): Promise<MPlugin> {
|
||||||
const name = this.normalizePluginName(npmName)
|
const name = this.normalizePluginName(npmName)
|
||||||
const type = this.getTypeFromNpmName(npmName)
|
const type = this.getTypeFromNpmName(npmName)
|
||||||
|
|
|
@ -352,6 +352,7 @@ async function startApplication () {
|
||||||
server.listen(port, hostname, async () => {
|
server.listen(port, hostname, async () => {
|
||||||
if (cliOptions.plugins) {
|
if (cliOptions.plugins) {
|
||||||
try {
|
try {
|
||||||
|
await PluginManager.Instance.installDeclarativePlugins()
|
||||||
await PluginManager.Instance.rebuildNativePluginsIfNeeded()
|
await PluginManager.Instance.rebuildNativePluginsIfNeeded()
|
||||||
|
|
||||||
await PluginManager.Instance.registerPluginsAndThemes()
|
await PluginManager.Instance.registerPluginsAndThemes()
|
||||||
|
|
Loading…
Reference in New Issue