diff --git a/.dockerignore b/.dockerignore
index 9b914fafec..95d7045edf 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -23,7 +23,7 @@
/datas
/docker-compose.*
/logs
-/nodes_modules
+/node_modules
/plugins
/tmp
/vendor
diff --git a/.env b/.env
index 2fb773529c..75c614e5d6 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,4 @@
+PHRASEANET_PROJECT_NAME=Phraseanet
# Registry from where you pull Docker images
PHRASEANET_DOCKER_REGISTRY=local
# Tag of the Docker images
@@ -70,3 +71,10 @@ PHRASEANET_DB_DIR=./volumes/db
PHRASEANET_ELASTICSEARCH_DIR=./volumes/elasticsearch
PHRASEANET_THUMBNAILS_DIR=./www/thumbnails
PHRASEANET_TMP_DIR=./tmp
+
+# For dev who don't have SSH_AUTH_SOCK (avoid an empty volume name)
+SSH_AUTH_SOCK=/dev/null
+
+# Plugin support
+PHRASEANET_PLUGINS=
+PHRASEANET_SSH_PRIVATE_KEY=
diff --git a/Dockerfile b/Dockerfile
index e900d1aea4..3feefd2d3b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -82,8 +82,19 @@ RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
&& apt-get install -y --no-install-recommends \
nodejs \
yarn \
+ nano \
+ vim \
+ iputils-ping \
+ zsh \
+ ssh \
+ telnet \
+ autoconf \
+ libtool \
+ python \
+ pkg-config \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists \
+ && git clone https://github.com/robbyrussell/oh-my-zsh.git /bootstrap/.oh-my-zsh \
&& mkdir -p /var/alchemy/Phraseanet \
&& chown -R app:app /var/alchemy
@@ -99,10 +110,28 @@ RUN composer install --prefer-dist --no-dev --no-progress --no-suggest --classma
COPY --chown=app . .
-RUN rm -rf docker/phraseanet/root \
- && make install
+RUN make install
-ADD docker/phraseanet/ /
+ADD ./docker/builder/root /
+
+# SSH Private repo
+ARG SSH_PRIVATE_KEY
+ARG PHRASEANET_PLUGINS
+
+RUN ( \
+ test ! -z "${SSH_PRIVATE_KEY}" \
+ && mkdir -p ~/.ssh \
+ && echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa \
+ # make sure github domain.com is accepted
+ && ssh-keyscan -H github.com >> ~/.ssh/known_hosts \
+ && chmod 600 ~/.ssh/id_rsa \
+ ) || echo "Skip SSH key"
+
+RUN ./docker/phraseanet/plugins/console install
+
+ENTRYPOINT ["/bootstrap/entrypoint.sh"]
+
+CMD []
#########################################################################
# Phraseanet web application image
diff --git a/README.md b/README.md
index 4d9509d731..f394ace118 100644
--- a/README.md
+++ b/README.md
@@ -56,10 +56,17 @@ export PHRASEANET_APP_PORT=8082
It may be easier to deal with a local file to manage our env variables.
-You can add your `env.local` at the root of this project and define a command alias in your `~/.bashrc`:
+You can add your `env.local` at the root of this project and define a command function in your `~/.bashrc`:
```bash
-alias dc="env $(cat env.local | grep -v '#' | tr '\n' ' ') docker-compose"
+# ~/.bashrc or ~/.zshrc
+function dc() {
+ if [ -f env.local ]; then
+ env $(cat env.local | grep -v '#' | tr '\n' ' ') docker-compose $@
+ else
+ docker-compose $@
+ fi
+}
```
### Running the application
@@ -105,6 +112,20 @@ This can be made easily from the builder container:
> Please note that the phraseanet image does not contain nor `composer` neither `node` tools. This allow the final image to be slim.
> If you need to use dev tools, ensure you are running the `builder` image!
+### Developer shell
+
+You can also obtain a shell access in builder container:
+
+```bash
+docker-compose run --rm builder /bin/bash
+# or
+docker-compose run --rm builder /bin/zsh
+```
+
+In this container you will have the same libraries (PHP, Node, composer, ...) that are used to build images.
+Also you have utils for development like telnet, ping, ssh, git, ...
+Your $HOME/.ssh directory is also mounted to builder's home with your ssh agent.
+
### Using Xdebug
Xdebug is enabled by default with the `docker-compose.override.yml`
@@ -132,6 +153,28 @@ XDEBUG_REMOTE_HOST=host.docker.internal
> Don't forget to recreate your container (`docker-compose up -d phraseanet`)
+### Build images with plugins
+
+Plugins can be installed during build if you set the `PHRASEANET_PLUGINS` env var as follows:
+
+```bash
+PHRASEANET_PLUGINS="git@github.com:alchemy-fr/Phraseanet-plugin-webgallery.git"
+
+# You can optionally precise the branch to install
+# If not precised, the main branch will be pulled
+PHRASEANET_PLUGINS="git@github.com:alchemy-fr/Phraseanet-plugin-webgallery.git(custom-branch)"
+
+# Plugins are separated by spaces
+PHRASEANET_PLUGINS="git@github.com:foo/bar.git(branch-1) git@github.com:baz/42.git"
+```
+
+If you install private plugins, make sure you export your SSH private key content in order to allow docker build to access the GIT repository:
+```bash
+export PHRASEANET_SSH_PRIVATE_KEY=$(cat ~/.ssh/id_rsa)
+# or if your private key is protected by a passphrase:
+export PHRASEANET_SSH_PRIVATE_KEY=$(openssl rsa -in ~/.ssh/id_rsa -out /tmp/id_rsa_raw && cat /tmp/id_rsa_raw && rm /tmp/id_rsa_raw)
+```
+
# With Vagrant (deprecated)
## Development :
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
index c9b846aa84..7e2b3dd2b8 100644
--- a/docker-compose.override.yml
+++ b/docker-compose.override.yml
@@ -10,25 +10,34 @@ services:
gateway:
volumes:
+ - ../:/var/alchemy
- .:/var/alchemy/Phraseanet
- ./docker/nginx/root/entrypoint.sh:/entrypoint.sh
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
- ${PHRASEANET_TMP_DIR}:/var/alchemy/Phraseanet/tmp:rw
-
-
builder:
build:
context: .
target: builder
- command: exit 0
+ args:
+ - SSH_PRIVATE_KEY=${PHRASEANET_SSH_PRIVATE_KEY}
+ - PHRASEANET_PLUGINS=${PHRASEANET_PLUGINS}
+ stdin_open: true
+ tty: true
volumes:
+ - ../:/var/alchemy
- .:/var/alchemy/Phraseanet
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
- ${PHRASEANET_DATA_DIR}:/var/alchemy/Phraseanet/datas:rw
- ${PHRASEANET_THUMBNAILS_DIR}:/var/alchemy/Phraseanet/www/thumbnails:rw
+ - ${SSH_AUTH_SOCK}:/ssh-auth-sock
+ - ${HOME}/.ssh:/home/app/.ssh
+ - dev_vol:/home/app
+ environment:
+ - PHRASEANET_PROJECT_NAME
phraseanet:
environment:
@@ -36,6 +45,7 @@ services:
- XDEBUG_CONFIG=remote_host=${XDEBUG_REMOTE_HOST} idekey=${IDE_KEY} remote_enable=1 profiler_enable=${XDEBUG_PROFILER_ENABLED} profiler_output_dir=/var/alchemy/Phraseanet/cache/profiler
- PHP_IDE_CONFIG
volumes:
+ - ../:/var/alchemy
- .:/var/alchemy/Phraseanet
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
@@ -45,6 +55,7 @@ services:
worker:
volumes:
+ - ../:/var/alchemy
- .:/var/alchemy/Phraseanet
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw
@@ -72,8 +83,17 @@ services:
volumes:
- ${PHRASEANET_ELASTICSEARCH_DIR}:/usr/share/elasticsearch/data:rw
+ kibana:
+ image: kibana:4.6.6
+ ports:
+ - 5601:5601
+
networks:
default:
ipam:
config:
- subnet: $PHRASEANET_SUBNET_IPS
+
+volumes:
+ dev_vol:
+ driver: local
diff --git a/docker-compose.yml b/docker-compose.yml
index 4c6f435ee7..e0bd547fbb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,9 @@ services:
build:
context: .
target: phraseanet-nginx
+ args:
+ - SSH_PRIVATE_KEY=${PHRASEANET_SSH_PRIVATE_KEY}
+ - PHRASEANET_PLUGINS=${PHRASEANET_PLUGINS}
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-nginx:$PHRASEANET_DOCKER_TAG
restart: on-failure
volumes:
@@ -21,6 +24,9 @@ services:
build:
context: .
target: phraseanet-fpm
+ args:
+ - SSH_PRIVATE_KEY=${PHRASEANET_SSH_PRIVATE_KEY}
+ - PHRASEANET_PLUGINS=${PHRASEANET_PLUGINS}
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-fpm:$PHRASEANET_DOCKER_TAG
restart: on-failure
depends_on:
@@ -29,6 +35,7 @@ services:
- rabbitmq
- elasticsearch
environment:
+ - PHRASEANET_PROJECT_NAME
- MAX_BODY_SIZE
- MAX_INPUT_VARS
- OPCACHE_ENABLED
@@ -57,6 +64,9 @@ services:
build:
context: .
target: phraseanet-worker
+ args:
+ - SSH_PRIVATE_KEY=${PHRASEANET_SSH_PRIVATE_KEY}
+ - PHRASEANET_PLUGINS=${PHRASEANET_PLUGINS}
image: $PHRASEANET_DOCKER_REGISTRY/phraseanet-worker:$PHRASEANET_DOCKER_TAG
restart: on-failure
depends_on:
@@ -65,6 +75,7 @@ services:
- rabbitmq
- elasticsearch
environment:
+ - PHRASEANET_PROJECT_NAME
- MAX_BODY_SIZE
- MAX_INPUT_VARS
- OPCACHE_ENABLED
diff --git a/docker/builder/root/bootstrap/.oh-my-zsh/themes/alchemy.zsh-theme b/docker/builder/root/bootstrap/.oh-my-zsh/themes/alchemy.zsh-theme
new file mode 100644
index 0000000000..807e8b6ef7
--- /dev/null
+++ b/docker/builder/root/bootstrap/.oh-my-zsh/themes/alchemy.zsh-theme
@@ -0,0 +1,7 @@
+local ret_status="%(?:%{$fg_bold[green]%}➜ :%{$fg_bold[red]%}➜ %s)"
+PROMPT='%* %{$fg_bold[green]%}%n%{$fg[grey]%}@%m%{$fg_bold[green]%}%u ${ret_status}%{$fg_bold[green]%}%p %{$fg[cyan]%}%c %{$fg_bold[blue]%}$(git_prompt_info)%{$fg_bold[blue]%} % %{$reset_color%}'
+
+ZSH_THEME_GIT_PROMPT_PREFIX="[%{$fg[red]%}"
+ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%}"
+ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[blue]%}] %{$fg[yellow]%}✗%{$reset_color%}"
+ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg[blue]%}]"
diff --git a/docker/builder/root/bootstrap/.zshrc b/docker/builder/root/bootstrap/.zshrc
new file mode 100644
index 0000000000..ae18e5a4ef
--- /dev/null
+++ b/docker/builder/root/bootstrap/.zshrc
@@ -0,0 +1,56 @@
+export LC_ALL=en_US.UTF-8
+export LANG=en_US.UTF-8
+
+export ZSH=$HOME/.oh-my-zsh
+
+ZSH_THEME="alchemy"
+
+# Uncomment the following line to use case-sensitive completion.
+# CASE_SENSITIVE="true"
+
+# Uncomment the following line to use hyphen-insensitive completion. Case
+# sensitive completion must be off. _ and - will be interchangeable.
+# HYPHEN_INSENSITIVE="true"
+
+# Uncomment the following line to disable bi-weekly auto-update checks.
+# DISABLE_AUTO_UPDATE="true"
+
+# Uncomment the following line to change how often to auto-update (in days).
+# export UPDATE_ZSH_DAYS=13
+
+# Uncomment the following line to disable colors in ls.
+# DISABLE_LS_COLORS="true"
+
+# Uncomment the following line to disable auto-setting terminal title.
+# DISABLE_AUTO_TITLE="true"
+
+# Uncomment the following line to enable command auto-correction.
+# ENABLE_CORRECTION="true"
+
+# Uncomment the following line to display red dots whilst waiting for completion.
+# COMPLETION_WAITING_DOTS="true"
+
+# Uncomment the following line if you want to disable marking untracked files
+# under VCS as dirty. This makes repository status check for large repositories
+# much, much faster.
+# DISABLE_UNTRACKED_FILES_DIRTY="true"
+
+# Uncomment the following line if you want to change the command execution time
+# stamp shown in the history command output.
+# The optional three formats: "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
+# HIST_STAMPS="mm/dd/yyyy"
+
+# Would you like to use another custom folder than $ZSH/custom?
+# ZSH_CUSTOM=/path/to/new-custom-folder
+
+# Which plugins would you like to load? (plugins can be found in ~/.oh-my-zsh/plugins/*)
+# Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/
+# Example format: plugins=(rails git textmate ruby lighthouse)
+# Add wisely, as too many plugins slow down shell startup.
+plugins=(git symfony2)
+
+# User configuration
+
+source $ZSH/oh-my-zsh.sh
+
+alias ll='ls -alFh'
diff --git a/docker/builder/root/bootstrap/entrypoint.d/ohmyzsh.sh b/docker/builder/root/bootstrap/entrypoint.d/ohmyzsh.sh
new file mode 100644
index 0000000000..aded04c7c7
--- /dev/null
+++ b/docker/builder/root/bootstrap/entrypoint.d/ohmyzsh.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+if [ ! -d "$HOME/.oh-my-zsh" ]; then
+ cp -r "/bootstrap/.oh-my-zsh" "$HOME/.oh-my-zsh"
+fi
diff --git a/docker/builder/root/bootstrap/entrypoint.d/zshrc.sh b/docker/builder/root/bootstrap/entrypoint.d/zshrc.sh
new file mode 100644
index 0000000000..71d47ed475
--- /dev/null
+++ b/docker/builder/root/bootstrap/entrypoint.d/zshrc.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+ZSH_FILE="$HOME/.zshrc"
+
+if [ ! -f "$HOME/.zshrc" ]; then
+ cp "/bootstrap/.zshrc" "$HOME/.zshrc"
+fi
diff --git a/docker/builder/root/bootstrap/entrypoint.sh b/docker/builder/root/bootstrap/entrypoint.sh
new file mode 100755
index 0000000000..c8bff0d1bb
--- /dev/null
+++ b/docker/builder/root/bootstrap/entrypoint.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+if [ -d /bootstrap/entrypoint.d ]; then
+ for i in /bootstrap/entrypoint.d/*.sh; do
+ if [ -r $i ]; then
+ . $i
+ fi
+ done
+ unset i
+fi
+
+if [ ! -t 1 ] ; then
+ echo "No tty available."
+ exit 0
+fi
+
+exec "$@"
diff --git a/docker/phraseanet/entrypoint.sh b/docker/phraseanet/entrypoint.sh
index 1d1fecb989..037fe0aa02 100755
--- a/docker/phraseanet/entrypoint.sh
+++ b/docker/phraseanet/entrypoint.sh
@@ -26,4 +26,6 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
docker-php-ext-enable xdebug
fi
+./docker/phraseanet/plugins/console init
+
bash -e docker-php-entrypoint $@
diff --git a/docker/phraseanet/php.ini.sample b/docker/phraseanet/php.ini.sample
index 774389b614..7f5fc97430 100644
--- a/docker/phraseanet/php.ini.sample
+++ b/docker/phraseanet/php.ini.sample
@@ -935,7 +935,7 @@ cli_server.color = On
[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
-date.timezone = Europe/Paris
+date.timezone = UTC
; http://php.net/date.default-latitude
;date.default_latitude = 31.7667
diff --git a/docker/phraseanet/plugins/InitCommand.php b/docker/phraseanet/plugins/InitCommand.php
new file mode 100644
index 0000000000..23b7d644b7
--- /dev/null
+++ b/docker/phraseanet/plugins/InitCommand.php
@@ -0,0 +1,29 @@
+setName('init')
+ ->setDescription('Initialize plugins');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ foreach (glob('./plugins/*') as $dir) {
+ if (is_dir($dir)) {
+ $output->writeln(sprintf('Init %s plugin', basename($dir)));
+ SubCommand::run(sprintf('bin/setup plugin:add %s', $dir));
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/docker/phraseanet/plugins/InstallCommand.php b/docker/phraseanet/plugins/InstallCommand.php
new file mode 100644
index 0000000000..7b8a28c4cd
--- /dev/null
+++ b/docker/phraseanet/plugins/InstallCommand.php
@@ -0,0 +1,64 @@
+setName('install')
+ ->setDescription('Install plugins');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $plugins = trim(getenv('PHRASEANET_PLUGINS'));
+ if (empty($plugins)) {
+ $output->writeln('No plugin to install... SKIP');
+
+ return 0;
+ }
+
+ $pluginsDir = 'plugins';
+ if (!is_dir($pluginsDir)) {
+ mkdir($pluginsDir);
+ }
+
+ foreach (explode(' ', $plugins) as $key => $plugin) {
+ $plugin = trim($plugin);
+ $repo = $plugin;
+ $branch = 'master';
+ if (1 === preg_match('#^(.+)\(([^)]+)\)$#', $plugin, $matches)) {
+ $repo = $matches[1];
+ $branch = $matches[2];
+ }
+
+ $pluginPath = './plugin' . $key;
+ if (is_dir($pluginPath)) {
+ SubCommand::run(sprintf('rm -rf %s', $pluginPath));
+ }
+
+ $output->writeln(sprintf('Installing %s (branch: %s)', $repo, $branch));
+ SubCommand::run(sprintf('git clone --single-branch --branch %s %s %s', $branch, $repo, $pluginPath));
+
+ $manifestSrc = $pluginPath.'/manifest.json';
+ if (!file_exists($manifestSrc)) {
+ throw new \Exception(sprintf('Cannot install plugin %s: no manifest.json file found', $plugin));
+ }
+ $pluginDestName = json_decode(file_get_contents($manifestSrc), true)['name'];
+ rename($pluginPath, $pluginsDir.'/'.$pluginDestName);
+ $pluginPath = $pluginsDir.'/'.$pluginDestName;
+
+ if (file_exists($pluginPath.'/composer.json')) {
+ SubCommand::run(sprintf('cd %s && composer install --no-dev', $pluginPath));
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/docker/phraseanet/plugins/SubCommand.php b/docker/phraseanet/plugins/SubCommand.php
new file mode 100644
index 0000000000..fd2dbc68a1
--- /dev/null
+++ b/docker/phraseanet/plugins/SubCommand.php
@@ -0,0 +1,26 @@
+add(new InstallCommand());
+$application->add(new InitCommand());
+
+$application->run();
diff --git a/docker/phraseanet/worker/entrypoint.sh b/docker/phraseanet/worker/entrypoint.sh
index d4d773c829..763a07d8ba 100755
--- a/docker/phraseanet/worker/entrypoint.sh
+++ b/docker/phraseanet/worker/entrypoint.sh
@@ -15,4 +15,4 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
docker-php-ext-enable xdebug
fi
-runuser -u app "$@"
+runuser -u app -- $@
diff --git a/lib/Alchemy/Phrasea/Command/Plugin/AbstractPluginCommand.php b/lib/Alchemy/Phrasea/Command/Plugin/AbstractPluginCommand.php
index 2d9d211612..53e4ffed39 100644
--- a/lib/Alchemy/Phrasea/Command/Plugin/AbstractPluginCommand.php
+++ b/lib/Alchemy/Phrasea/Command/Plugin/AbstractPluginCommand.php
@@ -15,6 +15,21 @@ use Alchemy\Phrasea\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+function normalizePath($path) {
+ return array_reduce(explode('/', $path), function ($a, $b) {
+ if($a === 0)
+ $a = '/';
+
+ if($b === '' || $b === '.')
+ return $a;
+
+ if($b === '..')
+ return dirname($a);
+
+ return preg_replace('/\/+/', '/', "$a/$b");
+ }, 0);
+}
+
abstract class AbstractPluginCommand extends Command
{
protected function validatePlugins(InputInterface $input, OutputInterface $output)
@@ -54,33 +69,42 @@ abstract class AbstractPluginCommand extends Command
protected function doInstallPlugin($source, InputInterface $input, OutputInterface $output)
{
- $temporaryDir = $this->container['temporary-filesystem']->createTemporaryDirectory();
-
- $output->write("Importing $source...");
- $this->container['plugins.importer']->import($source, $temporaryDir);
- $output->writeln(" OK");
-
$output->write("Validating plugin...");
- $manifest = $this->container['plugins.plugins-validator']->validatePlugin($temporaryDir);
+ $manifest = $this->container['plugins.plugins-validator']->validatePlugin($source);
$output->writeln(" OK found ".$manifest->getName()."");
$targetDir = $this->container['plugin.path'] . DIRECTORY_SEPARATOR . $manifest->getName();
+ if (normalizePath($targetDir) !== normalizePath($source)) {
+ $temporaryDir = $this->container['temporary-filesystem']->createTemporaryDirectory();
+ $output->write("Importing $source...");
+ $this->container['plugins.importer']->import($source, $temporaryDir);
+ $output->writeln(" OK");
+ $workingDir = $temporaryDir;
+ } else {
+ $workingDir = $targetDir;
+ }
- $output->write("Setting up composer...");
- $this->container['plugins.composer-installer']->install($temporaryDir);
- $output->writeln(" OK");
+ if (!is_dir($workingDir.'/vendor')) {
+ $output->write("Setting up composer...");
+ $this->container['plugins.composer-installer']->install($workingDir);
+ $output->writeln(" OK");
+ }
$output->write("Installing plugin ".$manifest->getName()."...");
- $this->container['filesystem']->mirror($temporaryDir, $targetDir);
+ if (isset($temporaryDir)) {
+ $this->container['filesystem']->mirror($temporaryDir, $targetDir);
+ }
$output->writeln(" OK");
$output->write("Copying public files ".$manifest->getName()."...");
$this->container['plugins.assets-manager']->update($manifest);
$output->writeln(" OK");
- $output->write("Removing temporary directory...");
- $this->container['filesystem']->remove($temporaryDir);
- $output->writeln(" OK");
+ if (isset($temporaryDir)) {
+ $output->write("Removing temporary directory...");
+ $this->container['filesystem']->remove($temporaryDir);
+ $output->writeln(" OK");
+ }
$output->write("Activating plugin...");
$this->container['conf']->set(['plugins', $manifest->getName(), 'enabled'], true);
diff --git a/tests/Alchemy/Tests/Phrasea/Command/Plugin/AddPluginTest.php b/tests/Alchemy/Tests/Phrasea/Command/Plugin/AddPluginTest.php
index 523b4c00da..506c4f104a 100644
--- a/tests/Alchemy/Tests/Phrasea/Command/Plugin/AddPluginTest.php
+++ b/tests/Alchemy/Tests/Phrasea/Command/Plugin/AddPluginTest.php
@@ -69,7 +69,7 @@ class AddPluginTest extends PluginCommandTestCase
// the plugin is checked when updating config files
self::$DI['cli']['plugins.plugins-validator']->expects($this->at(0))
->method('validatePlugin')
- ->with('tempdir')
+ ->with('TestPlugin')
->will($this->returnValue($manifest));
self::$DI['cli']['plugins.plugins-validator']->expects($this->at(1))
diff --git a/yarn.lock b/yarn.lock
index 4af2ac395c..319e85d277 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5305,10 +5305,14 @@ jquery-simplecolorpicker@^0.3.1:
resolved "https://registry.yarnpkg.com/jquery-simplecolorpicker/-/jquery-simplecolorpicker-0.3.1.tgz#4f6befd380ab05470f585d5482e5180556e460eb"
integrity sha1-T2vv04CrBUcPWF1UguUYBVbkYOs=
-"jquery-treeview@git+https://github.com/alchemy-fr/jquery-treeview.git", "jquery-treeview@git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e":
+"jquery-treeview@git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e":
+ version "1.4.2"
+ resolved "git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e"
+
+"jquery-treeview@https://github.com/alchemy-fr/jquery-treeview.git":
version "1.4.2"
uid "1e9e5a49d2875b878801e904cd08c2d25e85af1e"
- resolved "git+https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e"
+ resolved "https://github.com/alchemy-fr/jquery-treeview.git#1e9e5a49d2875b878801e904cd08c2d25e85af1e"
jquery-ui-datepicker-with-i18n@^1.10.4:
version "1.10.4"