diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index 2de7fe41f..de9e055dc 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts @@ -144,8 +144,13 @@ async function installPlugin (req: express.Request, res: express.Response) { const fromDisk = !!body.path const toInstall = body.npmName || body.path + + const pluginVersion = body.pluginVersion && body.npmName + ? body.pluginVersion + : undefined + try { - const plugin = await PluginManager.Instance.install(toInstall, undefined, fromDisk) + const plugin = await PluginManager.Instance.install(toInstall, pluginVersion, fromDisk) return res.json(plugin.toFormattedJSON()) } catch (err) { diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 21171af23..c1e9ebefb 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts @@ -116,6 +116,9 @@ const installOrUpdatePluginValidator = [ body('npmName') .optional() .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), + body('pluginVersion') + .optional() + .custom(isPluginVersionValid).withMessage('Should have a valid plugin version'), body('path') .optional() .custom(isSafePath).withMessage('Should have a valid safe path'), @@ -129,6 +132,9 @@ const installOrUpdatePluginValidator = [ if (!body.path && !body.npmName) { return res.fail({ message: 'Should have either a npmName or a path' }) } + if (body.pluginVersion && !body.npmName) { + return res.fail({ message: 'Should have a npmName when specifying a pluginVersion' }) + } return next() } diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts index f2a984962..3ac440f84 100644 --- a/server/tests/cli/peertube.ts +++ b/server/tests/cli/peertube.ts @@ -207,6 +207,25 @@ describe('Test CLI wrapper', function () { expect(res).to.not.contain('peertube-plugin-hello-world') }) + + it('Should install a plugin in requested version', async function () { + this.timeout(60000) + + await cliCommand.execWithEnv(`${cmd} plugins install --npm-name peertube-plugin-hello-world --plugin-version 0.0.17`) + }) + + it('Should list installed plugins, in correct version', async function () { + const res = await cliCommand.execWithEnv(`${cmd} plugins list`) + + expect(res).to.contain('peertube-plugin-hello-world') + expect(res).to.contain('0.0.17') + }) + + it('Should uninstall the plugin again', async function () { + const res = await cliCommand.execWithEnv(`${cmd} plugins uninstall --npm-name peertube-plugin-hello-world`) + + expect(res).to.not.contain('peertube-plugin-hello-world') + }) }) describe('Manage video redundancies', function () { diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts index ae625114d..9dd3f08c9 100644 --- a/server/tools/peertube-plugins.ts +++ b/server/tools/peertube-plugins.ts @@ -31,6 +31,7 @@ program .option('-p, --password <token>', 'Password') .option('-P --path <path>', 'Install from a path') .option('-n, --npm-name <npmName>', 'Install from npm') + .option('--plugin-version <pluginVersion>', 'Specify the plugin version to install (only available when installing from npm)') .action((options, command) => installPluginCLI(command, options)) program @@ -109,7 +110,7 @@ async function installPluginCLI (command: Command, options: OptionValues) { await assignToken(server, username, password) try { - await server.plugins.install({ npmName: options.npmName, path: options.path }) + await server.plugins.install({ npmName: options.npmName, path: options.path, pluginVersion: options.pluginVersion }) } catch (err) { console.error('Cannot install plugin.', err) process.exit(-1) diff --git a/shared/extra-utils/server/plugins-command.ts b/shared/extra-utils/server/plugins-command.ts index b944475a2..9bf24afff 100644 --- a/shared/extra-utils/server/plugins-command.ts +++ b/shared/extra-utils/server/plugins-command.ts @@ -158,15 +158,16 @@ export class PluginsCommand extends AbstractCommand { install (options: OverrideCommandOptions & { path?: string npmName?: string + pluginVersion?: string }) { - const { npmName, path } = options + const { npmName, path, pluginVersion } = options const apiPath = '/api/v1/plugins/install' return this.postBodyRequest({ ...options, path: apiPath, - fields: { npmName, path }, + fields: { npmName, path, pluginVersion }, implicitToken: true, defaultExpectedStatus: HttpStatusCode.OK_200 }) diff --git a/shared/models/plugins/server/api/install-plugin.model.ts b/shared/models/plugins/server/api/install-plugin.model.ts index 5a268ebe1..a1d009a00 100644 --- a/shared/models/plugins/server/api/install-plugin.model.ts +++ b/shared/models/plugins/server/api/install-plugin.model.ts @@ -1,4 +1,5 @@ export interface InstallOrUpdatePlugin { npmName?: string + pluginVersion?: string path?: string }