Compare commits

...

16 Commits

Author SHA1 Message Date
SamTV12345 6a245a4dc4 Added plugin live view. 2023-10-08 21:56:40 +02:00
webzwo0i cfa5369961 admin/plugins: fix test 2023-10-08 20:15:36 +02:00
webzwo0i 3c39f801fa admin/plugins: Fix UI in case of missed finished:uninstall event 2023-10-08 20:15:36 +02:00
webzwo0i de34e95813 plugin uninstall: Remove unnecessary code 2023-10-08 20:15:36 +02:00
webzwo0i f88b3d0727 debug 2023-10-08 20:15:36 +02:00
webzwo0i dab47d98d6 debug 2023-10-08 20:15:36 +02:00
webzwo0i b7e71a84be temporary: no mocha bin link 2023-10-08 20:15:36 +02:00
webzwo0i ea550a651d remove npm i - is this still needed? 2023-10-08 20:15:36 +02:00
webzwo0i c1086d5645 adminplugins; Fix reloading admin/plugins
In some cases, when the server is restarting and the beforeEach hook
tries to reload the admin/plugins page, it could fail.
2023-10-08 20:15:36 +02:00
webzwo0i 510f0daae3 Remove etherpad-cli-client devDependency.
We use this package when testing rate limiting. We already install it in
Docker, when running the Github workflow, so there is no need to install it by default.

In contrast to other devDependencies this is not required in case you
want to run the backend tests or check the code with eslint etc.
2023-10-08 20:15:34 +02:00
webzwo0i 2e9f39ec01 JSON.stringify error for debugging 2023-10-08 20:15:08 +02:00
webzwo0i 2656f37e30 admin/plugins: explicitly call npm with a plugin version 2023-10-08 20:15:08 +02:00
webzwo0i 10c7871ace Frontend tests: longer timeout in language.js test to make tests for
resilient.
2023-10-08 20:15:08 +02:00
webzwo0i bf34a94190 Add symlinks in ./src/node_modules for backwards compatibility.
See "Note for plugin authors" section in Changelog.

Some packages that are in use by plugins got a symlink in
./src/node_modules so that they still work after updating Etherpad. In
the future don't require('etherpad_ep-lite/node_modules/dependency')
anymore, but change this to require('dependency') and add the dependency
to your plugin's package.json
2023-10-08 20:15:08 +02:00
webzwo0i 1777684cf2 admin tests: refactor 2023-10-08 20:14:56 +02:00
webzwo0i 2f39a7b4bb Use `npm link` to install ep_etherpad-lite. This places a package.json
file in the root directory that references ./src directory as the file
source for `ep_etherpad-lite`.

Remove --legacy-peer-deps and --no-save when invoking npm. There is no
need for them anymore, as we are bumping npm now to v8.

./src/package.json contains all dependencies of Etherpad core
(package name ep_etherpad-lite) as before. The root directory's
package.json file references ep_etherpad-lite and also contains
references to any installed plugins.

Remove npm from package.json as we depend on a recent version now; PATH is still updated as before, so in the future we may install a custom npm version again

lint package-lock: update exception for sqlite3

remove node_modules and package.json during installDeps.sh

update Dockerfile

adapt minify

windows build

Fixed installOnWindows.bat

remove node_modules from git

bump minimal node/npm version in src/bin/functions.sh

add changelog notes

update installdeps

fix dockerfile

docker: test npm prefix set to the etherpad directory

workflow: upgrade-from-latest-release needs to be adapted until next release is out

Revert "docker: test npm prefix set to the etherpad directory"

This reverts commit b856a2488c9dbfb2acf35309cd1ee83016b631ad.

use npm link --bin-links=false to prevent it from copying bin files

temp fix for scripts as they are not installed to bin directory anymore

adjust bin paths in Dockerfile

Dockerfile

add hint for npm link, dockerfile

update dockerfile

Revert "Fixed installOnWindows.bat"

This reverts commit 70d0716bbedc4c0c1043155fcc5d157f01775c61.

try installOnWindows; still TODO: no difference between production and development; no warning like in installDeps.sh before update - it just removes package* and node_modules so admins must be aware of the plugins they want to reinstall later

update installOnWindows.bat

update package-lock.json

Dockerfile

Dockerfile

add file: scheme for lint check - needed as long as we have the plugin compatibility symlinks in ./src/node_modules

fix installOnWindows

upgrade-from-latest-release workflow: adapt cypress installation

src/package.json: test-container fix path to _mocha; maybe revert this in case we enable bin-links again

src/package.json: add test-on-windows script

another try with test-on-windows, without using bin-links

use bin-links on windows

Revert "use bin-links on windows"

This reverts commit f50ec2a9fabe3098d48e8f412b73c01edbe2140e.

invoke mocha binary on windows

run npm i once on windows, to make bin files available - why?

remove supertest on windows production builds

add symlink for mocha

debug

Revert "debug"

This reverts commit 8916a0515ca2897c57ca65fef49fd0b3610d2989.

Revert "add symlink for mocha"

This reverts commit 3c60bef77d2a120d24fce14421fe638598cd849d.

windows workflow: adapt cypress path

frontend admin tests
2023-10-08 20:13:17 +02:00
38 changed files with 11804 additions and 4819 deletions

View File

@ -38,7 +38,7 @@ jobs:
sudo apt update sudo apt update
sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
- -
name: Run the backend tests name: Run the backend tests
@ -74,12 +74,13 @@ jobs:
sudo add-apt-repository -y ppa:libreoffice/ppa sudo add-apt-repository -y ppa:libreoffice/ppa
sudo apt update sudo apt update
sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport sudo apt install -y --no-install-recommends libreoffice libreoffice-pdfimport
-
name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh
- -
name: Install Etherpad plugins name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps npm install
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -93,18 +94,6 @@ jobs:
ep_spellcheck ep_spellcheck
ep_subscript_and_superscript ep_subscript_and_superscript
ep_table_of_contents ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: cd src && npm test
@ -130,8 +119,11 @@ jobs:
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install the ep_etherpad-lite package from ./src
run: src/bin/installOnWindows.bat run: src/bin/installOnWindows.bat
# -
# name: Run npm i once to make bin files available - why is this needed at all?
# run: npm i
- -
name: Fix up the settings.json name: Fix up the settings.json
run: | run: |
@ -139,7 +131,7 @@ jobs:
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: cd src && npm run test-on-windows
withpluginsWindows: withpluginsWindows:
# run on pushes to any branch # run on pushes to any branch
@ -162,12 +154,16 @@ jobs:
cache-dependency-path: | cache-dependency-path: |
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
-
name: Install the ep_etherpad-lite package from ./src
run: src/bin/installOnWindows.bat
# -
# name: Run npm i once to make bin files available - why is this needed at all?
# run: npm i
- -
name: Install Etherpad plugins name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm
# v7: https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps npm install
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -181,18 +177,6 @@ jobs:
ep_spellcheck ep_spellcheck
ep_subscript_and_superscript ep_subscript_and_superscript
ep_table_of_contents ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installOnWindows.bat
- -
name: Fix up the settings.json name: Fix up the settings.json
run: | run: |
@ -200,4 +184,4 @@ jobs:
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: cd src && npm run test-on-windows

View File

@ -37,23 +37,11 @@ jobs:
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
- -
name: Install etherpad plugins name: Install the ep_etherpad-lite package from ./src
# We intentionally install an old ep_align version to test upgrades to
# the minor version number. The --legacy-peer-deps flag is required to
# work around a bug in npm v7: https://github.com/npm/cli/issues/2199
run: npm install --no-save --legacy-peer-deps ep_align@0.2.27
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
-
name: Install etherpad plugins
run: npm install ep_align@0.2.27
- -
name: Install etherpad plugins name: Install etherpad plugins
run: rm -Rf node_modules/ep_align/static/tests/* run: rm -Rf node_modules/ep_align/static/tests/*

View File

@ -31,7 +31,7 @@ jobs:
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
- -
name: export GIT_HASH to env name: export GIT_HASH to env
@ -85,12 +85,13 @@ jobs:
cache-dependency-path: | cache-dependency-path: |
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
-
name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh
- -
name: Install Etherpad plugins name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps npm install
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -105,18 +106,6 @@ jobs:
ep_spellcheck ep_spellcheck
ep_subscript_and_superscript ep_subscript_and_superscript
ep_table_of_contents ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.20.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh
- -
name: export GIT_HASH to env name: export GIT_HASH to env
id: environment id: environment

View File

@ -38,4 +38,6 @@ jobs:
--allowed-hosts npm --allowed-hosts npm
--allowed-schemes https: --allowed-schemes https:
--allowed-schemes github: --allowed-schemes github:
--allowed-urls github:mapbox/node-sqlite3#593c9d498be2510d286349134537e3bf89401c4a --allowed-schemes git+ssh:
--allowed-schemes file:
--allowed-urls git+ssh://git@github.com/mapbox/node-sqlite3.git#593c9d498be2510d286349134537e3bf89401c4a

View File

@ -28,7 +28,7 @@ jobs:
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
- -
name: Install etherpad-load-test name: Install etherpad-load-test
@ -57,15 +57,16 @@ jobs:
cache-dependency-path: | cache-dependency-path: |
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
-
name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh
- -
name: Install etherpad-load-test name: Install etherpad-load-test
run: sudo npm install -g etherpad-load-test run: sudo npm install -g etherpad-load-test
- -
name: Install etherpad plugins name: Install etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps npm install
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -78,18 +79,6 @@ jobs:
ep_spellcheck ep_spellcheck
ep_subscript_and_superscript ep_subscript_and_superscript
ep_table_of_contents ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
-
name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh
- -
name: Run load test name: Run load test
run: src/tests/frontend/travis/runnerLoadTest.sh 25 50 run: src/tests/frontend/travis/runnerLoadTest.sh 25 50
@ -115,7 +104,7 @@ jobs:
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
- -
name: Install etherpad-load-test name: Install etherpad-load-test

View File

@ -45,6 +45,9 @@ jobs:
- -
name: install dependencies and create symlink for ep_etherpad-lite name: install dependencies and create symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
-
name: Install etherpad-cli-client
run: npm i etherpad-cli-client
- -
name: run rate limit test name: run rate limit test
run: | run: |

View File

@ -33,12 +33,12 @@ jobs:
cache-dependency-path: | cache-dependency-path: |
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
# FIXME after 1.9.2
# After 1.9.2 is released, we need to remove '--legacy-peer-deps --no-save'
- -
name: Install Etherpad plugins name: Install Etherpad plugins
# The --legacy-peer-deps flag is required to work around a bug in npm
# v7: https://github.com/npm/cli/issues/2199
run: > run: >
npm install --no-save --legacy-peer-deps npm install --legacy-peer-deps --no-save
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -52,15 +52,6 @@ jobs:
ep_spellcheck ep_spellcheck
ep_subscript_and_superscript ep_subscript_and_superscript
ep_table_of_contents ep_table_of_contents
# Etherpad core dependencies must be installed after installing the
# plugin's dependencies, otherwise npm will try to hoist common
# dependencies by removing them from src/node_modules and installing them
# in the top-level node_modules. As of v6.14.10, npm's hoist logic appears
# to be buggy, because it sometimes removes dependencies from
# src/node_modules but fails to add them to the top-level node_modules.
# Even if npm correctly hoists the dependencies, the hoisting seems to
# confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install all dependencies and symlink for ep_etherpad-lite
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
@ -83,17 +74,37 @@ jobs:
# commit that merges the PR's source branch to its destination branch. # commit that merges the PR's source branch to its destination branch.
run: git checkout "${GITHUB_SHA}" run: git checkout "${GITHUB_SHA}"
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Temporary remove ./node_modules; Remove this after 1.9.2 release
run: rm -rf ./node_modules
-
name: Install the ep_etherpad-lite package from ./src
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
-
name: Install Etherpad plugins again; Remove this after 1.9.2 release
run: >
npm install
ep_align
ep_author_hover
ep_cursortrace
ep_font_size
ep_hash_auth
ep_headings2
ep_image_upload
ep_markdown
ep_readonly_guest
ep_set_title_on_pad
ep_spellcheck
ep_subscript_and_superscript
ep_table_of_contents
- -
name: Run the backend tests name: Run the backend tests
run: cd src && npm test run: cd src && npm test
- -
name: Install Cypress name: Install Cypress
run: cd src && npm install cypress run: npm install cypress
- -
name: Run Etherpad & Test Frontend name: Run Etherpad & Test Frontend
run: | run: |
node src/node/server.js & node src/node/server.js &
curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test
./src/node_modules/cypress/bin/cypress run --config-file src/tests/frontend/cypress/cypress.config.js ./node_modules/cypress/bin/cypress run --config-file src/tests/frontend/cypress/cypress.config.js

View File

@ -34,13 +34,16 @@ jobs:
src/package-lock.json src/package-lock.json
src/bin/doc/package-lock.json src/bin/doc/package-lock.json
- -
name: Install all dependencies and symlink for ep_etherpad-lite name: Install the ep_etherpad-lite package from ./src
shell: msys2 {0} shell: msys2 {0}
run: src/bin/installDeps.sh run: src/bin/installDeps.sh
# -
# name: Run npm i once to make bin files available - why is this needed at all?
# run: npm i
- -
name: Run the backend tests name: Run the backend tests
shell: msys2 {0} shell: msys2 {0}
run: cd src && npm test run: cd src && npm run test-on-windows
- -
name: Build the .zip name: Build the .zip
shell: msys2 {0} shell: msys2 {0}
@ -115,21 +118,20 @@ jobs:
etherpad/src/bin/doc/package-lock.json etherpad/src/bin/doc/package-lock.json
- -
name: Install Cypress name: Install Cypress
run: cd etherpad && cd src && npm install cypress run: cd etherpad && npm install cypress
- -
name: Run Etherpad name: Run Etherpad
run: | run: |
cd etherpad cd etherpad
node node_modules\ep_etherpad-lite\node\server.js & node node_modules\ep_etherpad-lite\node\server.js &
curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test
src\node_modules\cypress\bin\cypress run --config-file src\tests\frontendcypress\cypress.config.js node_modules\cypress\bin\cypress run --config-file src\tests\frontendcypress\cypress.config.js
# On release, upload windows zip to GitHub release tab # On release, upload windows zip to GitHub release tab
- - name: Rename to etherpad-lite-win.zip
name: Rename to etherpad-lite-win.zip
shell: powershell shell: powershell
run: mv etherpad-win.zip etherpad-lite-win.zip run: mv etherpad-win.zip etherpad-lite-win.zip
- name: upload binaries to release - name: upload binaries to release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: ${{startsWith(github.ref, 'refs/tags/v') }} if: ${{startsWith(github.ref, 'refs/tags/v') }}
with: with:
files: etherpad-lite-win.zip files: etherpad-lite-win.zip

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ out/
/src/bin/convertSettings.json /src/bin/convertSettings.json
/src/bin/etherpad-1.deb /src/bin/etherpad-1.deb
/src/bin/node.exe /src/bin/node.exe
plugin_packages

View File

@ -28,10 +28,8 @@ _install_libreoffice: &install_libreoffice >-
sudo apt-get update && sudo apt-get update &&
sudo apt-get -y install libreoffice libreoffice-pdfimport sudo apt-get -y install libreoffice libreoffice-pdfimport
# The --legacy-peer-deps flag is required to work around a bug in npm v7:
# https://github.com/npm/cli/issues/2199
_install_plugins: &install_plugins >- _install_plugins: &install_plugins >-
npm install --no-save --legacy-peer-deps npm install
ep_align ep_align
ep_author_hover ep_author_hover
ep_cursortrace ep_cursortrace
@ -64,12 +62,11 @@ jobs:
- *install_libreoffice - *install_libreoffice
- *set_loglevel_warn - *set_loglevel_warn
- "src/bin/installDeps.sh" - "src/bin/installDeps.sh"
- "cd src && npm install && cd -"
script: script:
- "cd src && npm test" - "cd src && npm test"
- name: "Test the Dockerfile" - name: "Test the Dockerfile"
install: install:
- "cd src && npm install && cd -" - "src/bin/installDeps.sh"
script: script:
- "docker build -t etherpad:test ." - "docker build -t etherpad:test ."
- "docker run -d -p 9001:9001 etherpad:test && sleep 3" - "docker run -d -p 9001:9001 etherpad:test && sleep 3"
@ -78,7 +75,6 @@ jobs:
install: install:
- *set_loglevel_warn - *set_loglevel_warn
- "src/bin/installDeps.sh" - "src/bin/installDeps.sh"
- "cd src && npm install && cd -"
- "npm install -g etherpad-load-test" - "npm install -g etherpad-load-test"
script: script:
- "src/tests/frontend/travis/runnerLoadTest.sh" - "src/tests/frontend/travis/runnerLoadTest.sh"
@ -112,7 +108,7 @@ jobs:
- "cd src && npm test" - "cd src && npm test"
- name: "Test the Dockerfile" - name: "Test the Dockerfile"
install: install:
- "cd src && npm install && cd -" - "src/bin/installDeps.sh"
script: script:
- "docker build -t etherpad:test ." - "docker build -t etherpad:test ."
- "docker run -d -p 9001:9001 etherpad:test && sleep 3" - "docker run -d -p 9001:9001 etherpad:test && sleep 3"

View File

@ -1,3 +1,34 @@
# Next release
#### Note for admins
Etherpad does no longer store it's dependencies in ./src/node_modules by default. Also, Etherpad now
stores installed plugins in a package.json file in the root directory and no longer requires quirks
like `--legacy-peer-deps` or `--no-save` when invoking npm during plugin installation.
When you're updating, it's best to use the `./src/bin/installDeps.sh` script. It will `npm link` the
src directory, using the package.json file in ./src. This will create the well-known symlink ep_etherpad-lite
in ./node_modules, that we've been using for years. However, this will also add a dependency in ./package.json.
`./src/bin/installDeps.sh` will fail, if you have no ./package.json or ./package-lock.json and your
./node_modules directory is not empty, as this is an indicator of installed plugins. You need to remove
./node_modules and install all your plugins in the next step.
`./src/bin/installDeps.sh` will remove any existing directories in `./src/node_modules`.
After running `./src/bin/installDeps.sh`, install your plugins with `npm i ep_plugin1 ep_plugin2...` or via
`/admin/plugins`.
#### Note for plugin authors
You can no longer depend on core's dependencies via `require('ep_etherpad-lite/node_modules/$dep')`.
Please run `src/bin/checkPlugins.sh` or manually change to `require('$dep')`. We don't recommend
that you rely on Etherpad to include specific dependencies in the future. So it's best if you add
the dependency in your package.json.
For convenience we have added symlinks in ./src/node_modules for the following dependencies:
async, cheerio, express, formidable, log4js and supertest.
Please note that those symlinks will be removed in a future version, so we strongly recommend that
you adapt your require statements.
# 1.9.3 # 1.9.3
### Compability changes ### Compability changes

View File

@ -90,17 +90,12 @@ WORKDIR "${EP_DIR}"
COPY --chown=etherpad:etherpad ./ ./ COPY --chown=etherpad:etherpad ./ ./
# Plugins must be installed before installing Etherpad's dependencies, otherwise RUN npm config set prefix "${EP_DIR}/.npm-packages"
# npm will try to hoist common dependencies by removing them from
# src/node_modules and installing them in the top-level node_modules. As of RUN ./src/bin/installDeps.sh
# v6.14.10, npm's hoist logic appears to be buggy, because it sometimes removes
# dependencies from src/node_modules but fails to add them to the top-level
# node_modules. Even if npm correctly hoists the dependencies, the hoisting
# seems to confuse tools such as `npm outdated`, `npm update`, and some ESLint
# rules.
RUN { [ -z "${ETHERPAD_PLUGINS}" ] || \ RUN { [ -z "${ETHERPAD_PLUGINS}" ] || \
npm install --no-save --legacy-peer-deps ${ETHERPAD_PLUGINS}; } && \ npm install ${ETHERPAD_PLUGINS}; } && \
src/bin/installDeps.sh && \
rm -rf ~/.npm rm -rf ~/.npm
# Copy the configuration file. # Copy the configuration file.
@ -109,11 +104,9 @@ COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json
# Fix group permissions # Fix group permissions
RUN chmod -R g=u . RUN chmod -R g=u .
USER root
RUN cd src && npm link
USER etherpad USER etherpad
HEALTHCHECK --interval=20s --timeout=3s CMD ["etherpad-healthcheck"] HEALTHCHECK --interval=20s --timeout=3s CMD ["./src/bin/etherpad-healthcheck"]
EXPOSE 9001 EXPOSE 9001
CMD ["etherpad"] CMD ["./src/node/server.js"]

View File

@ -140,9 +140,7 @@ Alternatively, you can install plugins from the command line:
```sh ```sh
cd /path/to/etherpad-lite cd /path/to/etherpad-lite
# The `--no-save` and `--legacy-peer-deps` arguments are necessary to work npm install ep_${plugin_name}
# around npm quirks.
npm install --no-save --legacy-peer-deps ep_${plugin_name}
``` ```
Also see [the plugin wiki Also see [the plugin wiki
@ -154,7 +152,7 @@ Run the following command in your Etherpad folder to get all of the features
visible in the above demo gif: visible in the above demo gif:
```sh ```sh
npm install --no-save --legacy-peer-deps \ npm install \
ep_align \ ep_align \
ep_comments_page \ ep_comments_page \
ep_embedded_hyperlinks2 \ ep_embedded_hyperlinks2 \

View File

@ -7,8 +7,7 @@ execute its own functionality based on these events.
Publicly available plugins can be found in the npm registry (see Publicly available plugins can be found in the npm registry (see
<https://npmjs.org>). Etherpad's naming convention for plugins is to prefix your <https://npmjs.org>). Etherpad's naming convention for plugins is to prefix your
plugins with `ep_`. So, e.g. it's `ep_flubberworms`. Thus you can install plugins with `ep_`. So, e.g. it's `ep_flubberworms`. Thus you can install
plugins from npm, using `npm install --no-save --legacy-peer-deps plugins from npm, using `npm install ep_flubberworm` in Etherpad's root directory.
ep_flubberworm` in Etherpad's root directory.
You can also browse to `http://yourEtherpadInstan.ce/admin/plugins`, which will You can also browse to `http://yourEtherpadInstan.ce/admin/plugins`, which will
list all installed plugins and those available on npm. It even provides list all installed plugins and those available on npm. It even provides

1
node_modules/ep_etherpad-lite generated vendored
View File

@ -1 +0,0 @@
../src

View File

@ -48,6 +48,9 @@ try ./src/bin/installDeps.sh
log "copy the windows settings template..." log "copy the windows settings template..."
try cp settings.json.template settings.json try cp settings.json.template settings.json
log "Because this is a production build, we delete supertest"
try rm src/node_modules/supertest
log "resolve symbolic links..." log "resolve symbolic links..."
try cp -rL node_modules node_modules_resolved try cp -rL node_modules node_modules_resolved
try rm -rf node_modules try rm -rf node_modules

View File

@ -1,10 +1,10 @@
# minimum required node version # minimum required node version
REQUIRED_NODE_MAJOR=12 REQUIRED_NODE_MAJOR=16
REQUIRED_NODE_MINOR=13 REQUIRED_NODE_MINOR=0
# minimum required npm version # minimum required npm version
REQUIRED_NPM_MAJOR=5 REQUIRED_NPM_MAJOR=8
REQUIRED_NPM_MINOR=5 REQUIRED_NPM_MINOR=19
pecho() { printf %s\\n "$*"; } pecho() { printf %s\\n "$*"; }
log() { pecho "$@"; } log() { pecho "$@"; }

View File

@ -37,23 +37,63 @@ if [ ! -f "$settings" ]; then
cp settings.json.template "$settings" || exit 1 cp settings.json.template "$settings" || exit 1
fi fi
log "Removing src/node_modules."
rm -rf ./src/node_modules || true
# try to determine if plugins were installed using --no-save
ROOT_PLUGINS_EXIST=1
for file in node_modules/*
do
if [ ! -e "$file" ]; then break; fi
if [ -L "$file" ] && [ "$file" = "node_modules/ep_etherpad-lite" ]; then break; fi
if expr "$file" : "node_modules/ep_*" > /dev/null; then
ROOT_PLUGINS_EXIST=0
fi
done
PACKAGE_EXISTS=1
PACKAGELOCK_EXISTS=1
if test -f ./package.json; then PACKAGE_EXISTS=0;fi
if test -f ./package-lock.json; then PACKAGELOCK_EXISTS=0;fi
if [ "$PACKAGE_EXISTS" = "1" ] || [ "$PACKAGELOCK_EXISTS" = "1" ]; then
if [ "$ROOT_PLUGINS_EXIST" = "0" ]; then
log "You have plugins in ./node_modules but don't have a package.json or package-lock.json file."
log "Please manually remove your ./node_modules directory, run this script again and install any plugins with npm i ep_plugin1 ep_plugin2 afterwards"
exit 1
fi
fi
log "Linking src as new package ep_etherpad-lite."
exit_code=0
(cd ./src && npm link --bin-links=false) || exit_code=$?
if [ "$exit_code" != 0 ]; then
log "npm link failed. If there was a permission error, please set a prefix for npm."
log "The prefix can be set e.g. with npm config set prefix $HOME/.npm-packages"
log "This will create a symlink in $HOME/.npm-packages/lib/node_modules that points to this directory."
exit 1
fi
log "Installing dependencies..." log "Installing dependencies..."
(mkdir -p node_modules && if [ "$NODE_ENV" = "production" ]; then
cd node_modules &&
{ [ -d ep_etherpad-lite ] || ln -sf ../src ep_etherpad-lite; } &&
cd ep_etherpad-lite)
cd src
if [ -z "${ETHERPAD_PRODUCTION}" ]; then
log "Installing dev dependencies"
npm ci --no-optional --omit=optional --include=dev --lockfile-version 1 || exit 1
else
log "Installing production dependencies" log "Installing production dependencies"
npm ci --no-optional --omit=optional --omit=dev --lockfile-version 1 --production || exit 1 npm link ep_etherpad-lite --omit=optional --omit=dev --save --package-lock=true --bin-links=false || exit 1
else
log "Installing dev dependencies"
npm link ep_etherpad-lite --omit=optional --save --package-lock=true --bin-links=false || exit 1
fi fi
log "Adding symlinks for plugin backwards compatibility"
mkdir src/node_modules -p
ln -s ../../node_modules/async src/node_modules/async
ln -s ../../node_modules/express src/node_modules/express
ln -s ../../node_modules/formidable src/node_modules/formidable
ln -s ../../node_modules/log4js src/node_modules/log4js
ln -s ../../node_modules/supertest src/node_modules/supertest
# Remove all minified data to force node creating it new # Remove all minified data to force node creating it new
log "Clearing minified cache..." log "Clearing minified cache..."
rm -f var/minified* rm -f var/minified*

View File

@ -9,19 +9,34 @@ cmd /C node -e "" || ( echo "Please install node.js ( https://nodejs.org )" && e
echo _ echo _
echo Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient. echo Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient.
mkdir node_modules echo Deleting old node_modules and src/node_modules
cd /D node_modules del /q .\node_modules
mklink /D "ep_etherpad-lite" "..\src" del /q .\src\node_modules
echo Deleting old package.json and package-lock.json
del /q .\package.json
del /q .\package-lock.json
cd /D "ep_etherpad-lite" cd /D src
cmd /C npm ci || exit /B 1 cmd /C npm link --bin-links=false || exit /B 1
cd /D "%~dp0\..\.." cd ..
cmd /C npm link ep_etherpad-lite --omit=optional --omit=dev --save --package-lock=true --bin-links=false || exit /B 1
echo _ echo _
echo Clearing cache... echo Clearing cache...
del /S var\minified* del /S var\minified*
echo Adding symlinks for plugin backwards compatibility
mkdir src\node_modules
cd /D src\node_modules
mklink /D "async" "..\..\node_modules\async"
mklink /D "express" "..\..\node_modules\express"
mklink /D "formidable" "..\..\node_modules\formidable"
mklink /D "log4js" "..\..\node_modules\log4js"
mklink /D "supertest" "..\..\node_modules\supertest"
cd ..\..
echo _ echo _
echo Setting up settings.json... echo Setting up settings.json...
IF NOT EXIST settings.json ( IF NOT EXIST settings.json (

View File

@ -356,18 +356,17 @@ const logger = log4js.getLogger('checkPlugin');
logger.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin'); logger.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin');
} }
// Install dependencies so we can run ESLint. This should also create or update package-lock.json
// if autoFix is enabled.
const npmInstall = `npm install${autoFix ? '' : ' --no-package-lock'}`;
execSync(npmInstall, {stdio: 'inherit'});
// Create the ep_etherpad-lite symlink if necessary. This must be done after running `npm install`
// because that command nukes the symlink.
try { try {
const d = await fsp.realpath(path.join(pluginPath, 'node_modules/ep_etherpad-lite')); const d = await fsp.realpath(path.join(pluginPath, 'node_modules/ep_etherpad-lite'));
assert.equal(d, epSrcDir); assert.equal(d, epSrcDir);
} catch (err) { } catch (err) {
execSync(`${npmInstall} --no-save ep_etherpad-lite@file:${epSrcDir}`, {stdio: 'inherit'}); execSync('./src/bin/installDeps.sh', {stdio: 'inherit'});
} }
// Install dependencies so we can run ESLint. This should also create or update package-lock.json
// if autoFix is enabled.
const npmInstall = `npm install${autoFix ? '' : ' --no-package-lock'}`;
execSync(npmInstall, {stdio: 'inherit'});
// linting begins // linting begins
try { try {
logger.info('Linting...'); logger.info('Linting...');

View File

@ -11,7 +11,7 @@ TODO: Describe the plugin.
From the Etherpad working directory, run: From the Etherpad working directory, run:
```shell ```shell
npm install --no-save --legacy-peer-deps [plugin_name] npm install [plugin_name]
``` ```
Or, install from Etherpad's `/admin/plugins` page. Or, install from Etherpad's `/admin/plugins` page.

View File

@ -8,4 +8,4 @@ OUTDATED=$(npm outdated --depth=0 | awk '{print $1}' | grep '^ep_') || {
} }
set -- ${OUTDATED} set -- ${OUTDATED}
echo "Updating plugins: $*" echo "Updating plugins: $*"
exec npm install --no-save "$@" exec npm install "$@"

View File

@ -39,10 +39,12 @@ exports.expressCreateServer = (hookName, args, cb) => {
exports.socketio = (hookName, args, cb) => { exports.socketio = (hookName, args, cb) => {
const io = args.io.of('/pluginfw/installer'); const io = args.io.of('/pluginfw/installer');
io.on('connection', (socket) => { io.on('connection', (socket) => {
console.log('event connection', new Date())
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request; const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
if (!isAdmin) return; if (!isAdmin) return;
socket.on('getInstalled', (query) => { socket.on('getInstalled', (query) => {
console.log('message getInstalled', new Date())
// send currently installed plugins // send currently installed plugins
const installed = const installed =
Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package); Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
@ -51,6 +53,7 @@ exports.socketio = (hookName, args, cb) => {
}); });
socket.on('checkUpdates', async () => { socket.on('checkUpdates', async () => {
console.log('message checkUpdates', new Date())
// Check plugins for updates // Check plugins for updates
try { try {
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10); const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
@ -62,27 +65,33 @@ exports.socketio = (hookName, args, cb) => {
const currentVersion = pluginDefs.plugins[plugin].package.version; const currentVersion = pluginDefs.plugins[plugin].package.version;
return semver.gt(latestVersion, currentVersion); return semver.gt(latestVersion, currentVersion);
}); }).map((plugin) => ({name: plugin, version: results[plugin].version}));
console.log('emit results:updatable', new Date())
socket.emit('results:updatable', {updatable}); socket.emit('results:updatable', {updatable});
} catch (err) { } catch (err) {
console.warn(err.stack || err.toString()); console.warn(err.stack || err.toString());
console.log('emit results:updatable', new Date())
socket.emit('results:updatable', {updatable: {}}); socket.emit('results:updatable', {updatable: {}});
} }
}); });
socket.on('getAvailable', async (query) => { socket.on('getAvailable', async (query) => {
console.log('message getAvailable', new Date())
try { try {
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false); const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
console.log('emit results:available', new Date())
socket.emit('results:available', results); socket.emit('results:available', results);
} catch (er) { } catch (er) {
console.error(er); console.error(er);
console.log('emit results:available', new Date())
socket.emit('results:available', {}); socket.emit('results:available', {});
} }
}); });
socket.on('search', async (query) => { socket.on('search', async (query) => {
console.log('message search', new Date())
try { try {
const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10); const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
let res = Object.keys(results) let res = Object.keys(results)
@ -90,18 +99,22 @@ exports.socketio = (hookName, args, cb) => {
.filter((plugin) => !pluginDefs.plugins[plugin.name]); .filter((plugin) => !pluginDefs.plugins[plugin.name]);
res = sortPluginList(res, query.sortBy, query.sortDir) res = sortPluginList(res, query.sortBy, query.sortDir)
.slice(query.offset, query.offset + query.limit); .slice(query.offset, query.offset + query.limit);
console.log('emit results:search', new Date())
socket.emit('results:search', {results: res, query}); socket.emit('results:search', {results: res, query});
} catch (er) { } catch (er) {
console.error(er); console.error(er);
console.log('emit results:search', new Date())
socket.emit('results:search', {results: {}, query}); socket.emit('results:search', {results: {}, query});
} }
}); });
socket.on('install', (pluginName) => { socket.on('install', (pluginName, version) => {
installer.install(pluginName, (err) => { console.log('message install', new Date())
installer.install(pluginName, version, (err) => {
if (err) console.warn(err.stack || err.toString()); if (err) console.warn(err.stack || err.toString());
console.log('emit finished:install', new Date())
socket.emit('finished:install', { socket.emit('finished:install', {
plugin: pluginName, plugin: pluginName,
code: err ? err.code : null, code: err ? err.code : null,
@ -111,9 +124,11 @@ exports.socketio = (hookName, args, cb) => {
}); });
socket.on('uninstall', (pluginName) => { socket.on('uninstall', (pluginName) => {
console.log('message uninstall', new Date())
installer.uninstall(pluginName, (err) => { installer.uninstall(pluginName, (err) => {
if (err) console.warn(err.stack || err.toString()); if (err) console.warn(err.stack || err.toString());
console.log('emit finished:uninstall', new Date())
socket.emit('finished:uninstall', {plugin: pluginName, error: err ? err.message : null}); socket.emit('finished:uninstall', {plugin: pluginName, error: err ? err.message : null});
}); });
}); });

View File

@ -111,7 +111,7 @@ exports.start = async () => {
// eslint-disable-next-line promise/no-promise-in-callback // eslint-disable-next-line promise/no-promise-in-callback
exports.exit(err) exports.exit(err)
.catch((err) => { .catch((err) => {
logger.error('Error in process exit', err); logger.error('Error in process exit', JSON.stringify(err));
// eslint-disable-next-line n/no-process-exit // eslint-disable-next-line n/no-process-exit
process.exit(1); process.exit(1);
}); });

View File

@ -163,7 +163,7 @@ const minify = async (req, res) => {
// Go straight into node_modules // Go straight into node_modules
// Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js' // Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js'
// would end up resolving to logically distinct resources. // would end up resolving to logically distinct resources.
filename = path.join('../node_modules/', library, libraryPath); filename = path.join('../../node_modules/', library, libraryPath);
} }
} }
const [, testf] = /^plugins\/ep_etherpad-lite\/(tests\/frontend\/.*)/.exec(filename) || []; const [, testf] = /^plugins\/ep_etherpad-lite\/(tests\/frontend\/.*)/.exec(filename) || [];

1
src/node_modules/async generated vendored Symbolic link
View File

@ -0,0 +1 @@
../../node_modules/async

1
src/node_modules/express generated vendored Symbolic link
View File

@ -0,0 +1 @@
../../node_modules/express

1
src/node_modules/formidable generated vendored Symbolic link
View File

@ -0,0 +1 @@
../../node_modules/formidable

1
src/node_modules/log4js generated vendored Symbolic link
View File

@ -0,0 +1 @@
../../node_modules/log4js

1
src/node_modules/supertest generated vendored Symbolic link
View File

@ -0,0 +1 @@
../../node_modules/supertest

15984
src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -53,9 +53,9 @@
"log4js": "0.6.38", "log4js": "0.6.38",
"measured-core": "^2.0.0", "measured-core": "^2.0.0",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"npm": "^6.14.18",
"openapi-backend": "^5.10.0", "openapi-backend": "^5.10.0",
"proxy-addr": "^2.0.7", "proxy-addr": "^2.0.7",
"live-plugin-manager": "^0.18.1",
"rate-limiter-flexible": "^3.0.0", "rate-limiter-flexible": "^3.0.0",
"rehype": "^13.0.1", "rehype": "^13.0.1",
"rehype-minify-whitespace": "^6.0.0", "rehype-minify-whitespace": "^6.0.0",
@ -80,7 +80,6 @@
"devDependencies": { "devDependencies": {
"eslint": "^8.50.0", "eslint": "^8.50.0",
"eslint-config-etherpad": "^3.0.22", "eslint-config-etherpad": "^3.0.22",
"etherpad-cli-client": "^2.0.2",
"mocha": "^10.0.0", "mocha": "^10.0.0",
"mocha-froth": "^0.2.10", "mocha-froth": "^0.2.10",
"nodeify": "^1.0.1", "nodeify": "^1.0.1",
@ -94,16 +93,17 @@
}, },
"engines": { "engines": {
"node": ">=16.20.1", "node": ">=16.20.1",
"npm": ">=6.14.0" "npm": ">=8.19.4"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/ether/etherpad-lite.git" "url": "https://github.com/ether/etherpad-lite.git"
}, },
"scripts": { "scripts": {
"lint": "eslint .", "lint": "../node_modules/eslint/bin/eslint.js .",
"test": "mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs", "test": "../node_modules/mocha/bin/_mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
"test-container": "mocha --timeout 5000 tests/container/specs/api", "test-on-windows": "..\\node_modules\\.bin\\mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
"test-container": "../node_modules/mocha/bin/_mocha --timeout 5000 tests/container/specs/api",
"dev": "bash ./bin/run.sh" "dev": "bash ./bin/run.sh"
}, },
"version": "1.9.3", "version": "1.9.3",

View File

@ -5,6 +5,7 @@
$(document).ready(() => { $(document).ready(() => {
const socket = socketio.connect('..', '/pluginfw/installer'); const socket = socketio.connect('..', '/pluginfw/installer');
socket.on('disconnect', (reason) => { socket.on('disconnect', (reason) => {
window.console.log('socket disconnect', new Date());
// The socket.io client will automatically try to reconnect for all reasons other than "io // The socket.io client will automatically try to reconnect for all reasons other than "io
// server disconnect". // server disconnect".
if (reason === 'io server disconnect') socket.connect(); if (reason === 'io server disconnect') socket.connect();
@ -123,13 +124,18 @@ $(document).ready(() => {
$('.do-install, .do-update').off('click').on('click', function (e) { $('.do-install, .do-update').off('click').on('click', function (e) {
const $row = $(e.target).closest('tr'); const $row = $(e.target).closest('tr');
const plugin = $row.data('plugin'); const plugin = $row.data('plugin');
const update = $row.find('input.do-update');
// TODO dont store it here in DOM
// version after update or after installing a new package
const version = update.length !== 0 ? update.attr('version') : $row.find('.version').text();
if ($(this).hasClass('do-install')) { if ($(this).hasClass('do-install')) {
$row.remove().appendTo('#installed-plugins'); $row.remove().appendTo('#installed-plugins');
installed.progress.show(plugin, 'Installing'); installed.progress.show(plugin, 'Installing');
} else { } else {
installed.progress.show(plugin, 'Updating'); installed.progress.show(plugin, 'Updating');
} }
socket.emit('install', plugin); window.console.log('before emit install', new Date())
socket.emit('install', plugin, version);
installed.messages.hide('nothing-installed'); installed.messages.hide('nothing-installed');
}); });
@ -137,6 +143,7 @@ $(document).ready(() => {
$('.do-uninstall').off('click').on('click', (e) => { $('.do-uninstall').off('click').on('click', (e) => {
const $row = $(e.target).closest('tr'); const $row = $(e.target).closest('tr');
const pluginName = $row.data('plugin'); const pluginName = $row.data('plugin');
window.console.log('before emit uninstall', new Date())
socket.emit('uninstall', pluginName); socket.emit('uninstall', pluginName);
installed.progress.show(pluginName, 'Uninstalling'); installed.progress.show(pluginName, 'Uninstalling');
installed.list = installed.list.filter((plugin) => plugin.name !== pluginName); installed.list = installed.list.filter((plugin) => plugin.name !== pluginName);
@ -166,7 +173,7 @@ $(document).ready(() => {
search.messages.hide('fetching'); search.messages.hide('fetching');
$('#search-query').prop('disabled', false); $('#search-query').prop('disabled', false);
console.log('got search results', data); window.console.log('got search results', data);
// add to results // add to results
search.results = search.results.concat(data.results); search.results = search.results.concat(data.results);
@ -194,6 +201,7 @@ $(document).ready(() => {
}); });
socket.on('results:installed', (data) => { socket.on('results:installed', (data) => {
window.console.log('socket results:installed', new Date());
installed.messages.hide('fetching'); installed.messages.hide('fetching');
installed.messages.hide('nothing-installed'); installed.messages.hide('nothing-installed');
@ -203,13 +211,29 @@ $(document).ready(() => {
// filter out epl // filter out epl
installed.list = installed.list.filter((plugin) => plugin.name !== 'ep_etherpad-lite'); installed.list = installed.list.filter((plugin) => plugin.name !== 'ep_etherpad-lite');
// remove all installed plugins (leave plugins that are still being installed) // Remove plugins from the list - they will be added later.
// Installed.list contains a list of plugins the server has successfully installed
// At this point plugins are ignored if:
// - they are being installed (as installed.list does not contain them yet)
// - they have been uninstalled (as installed.list does not contain them anymore)
installed.list.forEach((plugin) => { installed.list.forEach((plugin) => {
$(`#installed-plugins .${plugin.name}`).remove(); $(`#installed-plugins .${plugin.name}`).remove();
}); });
// Remove outdated entries
const installedItems = $('#installed-plugins tr');
installedItems.each(function () {
const pluginName = $(this).attr('class');
const message = $(this).find('.progress .message').text();
// In case the finished:uninstall msg was missed, remove the plugin from the list
if (message === 'Uninstalling') {
$(`#installed-plugins .${pluginName}`).remove();
}
});
if (installed.list.length > 0) { if (installed.list.length > 0) {
displayPluginList(installed.list, $('#installed-plugins'), $('#installed-plugin-template')); displayPluginList(installed.list, $('#installed-plugins'), $('#installed-plugin-template'));
window.console.log('before emit checkUpdates', new Date())
socket.emit('checkUpdates'); socket.emit('checkUpdates');
} else { } else {
installed.messages.show('nothing-installed'); installed.messages.show('nothing-installed');
@ -217,16 +241,20 @@ $(document).ready(() => {
}); });
socket.on('results:updatable', (data) => { socket.on('results:updatable', (data) => {
data.updatable.forEach((pluginName) => { window.console.log('socket results:updatable', new Date());
const actions = $(`#installed-plugins > tr.${pluginName} .actions`); data.updatable.forEach((plugin) => {
const {name, version} = plugin;
const actions = $(`#installed-plugins > tr.${name} .actions`);
actions.find('.do-update').remove(); actions.find('.do-update').remove();
// TODO dont store version here in DOM
actions.append( actions.append(
$('<input>').addClass('do-update').attr('type', 'button').attr('value', 'Update')); $('<input>').addClass('do-update').attr('type', 'button').attr('version', version).attr('value', 'Update'));
}); });
updateHandlers(); updateHandlers();
}); });
socket.on('finished:install', (data) => { socket.on('finished:install', (data) => {
window.console.log('socket finished:install', new Date());
if (data.error) { if (data.error) {
if (data.code === 'EPEERINVALID') { if (data.code === 'EPEERINVALID') {
alert("This plugin requires that you update Etherpad so it can operate in it's true glory"); alert("This plugin requires that you update Etherpad so it can operate in it's true glory");
@ -235,6 +263,7 @@ $(document).ready(() => {
$(`#installed-plugins .${data.plugin}`).remove(); $(`#installed-plugins .${data.plugin}`).remove();
} }
window.console.log('before emit getInstalled', new Date())
socket.emit('getInstalled'); socket.emit('getInstalled');
// update search results // update search results
@ -244,13 +273,12 @@ $(document).ready(() => {
}); });
socket.on('finished:uninstall', (data) => { socket.on('finished:uninstall', (data) => {
window.console.log('socket finished:uninstall', new Date());
if (data.error) { if (data.error) {
alert(`An error occurred while uninstalling the ${data.plugin} \n${data.error}`); alert(`An error occurred while uninstalling the ${data.plugin} \n${data.error}`);
} }
// remove plugin from installed list window.console.log('before emit getInstalled', new Date())
$(`#installed-plugins .${data.plugin}`).remove();
socket.emit('getInstalled'); socket.emit('getInstalled');
// update search results // update search results
@ -260,7 +288,9 @@ $(document).ready(() => {
}); });
socket.on('connect', () => { socket.on('connect', () => {
window.console.log('socket connect', new Date());
updateHandlers(); updateHandlers();
window.console.log('before emit getInstalled', new Date())
socket.emit('getInstalled'); socket.emit('getInstalled');
search.searchTerm = null; search.searchTerm = null;
search($('#search-query').val()); search($('#search-query').val());

View File

@ -6,9 +6,12 @@ const hooks = require('./hooks');
const runCmd = require('../../../node/utils/run_cmd'); const runCmd = require('../../../node/utils/run_cmd');
const settings = require('../../../node/utils/Settings'); const settings = require('../../../node/utils/Settings');
const axios = require('axios'); const axios = require('axios');
const {PluginManager} = require('live-plugin-manager');
const logger = log4js.getLogger('plugins'); const logger = log4js.getLogger('plugins');
exports.manager = new PluginManager();
const onAllTasksFinished = async () => { const onAllTasksFinished = async () => {
settings.reloadSettings(); settings.reloadSettings();
await hooks.aCallAll('loadSettings', {settings}); await hooks.aCallAll('loadSettings', {settings});
@ -31,10 +34,7 @@ exports.uninstall = async (pluginName, cb = null) => {
cb = wrapTaskCb(cb); cb = wrapTaskCb(cb);
logger.info(`Uninstalling plugin ${pluginName}...`); logger.info(`Uninstalling plugin ${pluginName}...`);
try { try {
// The --no-save flag prevents npm from creating package.json or package-lock.json. await runCmd(['npm', 'uninstall', pluginName]);
// The --legacy-peer-deps flag is required to work around a bug in npm v7:
// https://github.com/npm/cli/issues/2199
await runCmd(['npm', 'uninstall', '--no-save', '--legacy-peer-deps', pluginName]);
} catch (err) { } catch (err) {
logger.error(`Failed to uninstall plugin ${pluginName}`); logger.error(`Failed to uninstall plugin ${pluginName}`);
cb(err || new Error(err)); cb(err || new Error(err));
@ -46,20 +46,17 @@ exports.uninstall = async (pluginName, cb = null) => {
cb(null); cb(null);
}; };
exports.install = async (pluginName, cb = null) => { exports.install = async (pluginName, version, cb = null) => {
cb = wrapTaskCb(cb); cb = wrapTaskCb(cb);
logger.info(`Installing plugin ${pluginName}...`); logger.info(`Installing plugin ${pluginName}@${version}...`);
try { try {
// The --no-save flag prevents npm from creating package.json or package-lock.json. await runCmd(['npm', 'install', `${pluginName}@${version}`]);
// The --legacy-peer-deps flag is required to work around a bug in npm v7:
// https://github.com/npm/cli/issues/2199
await runCmd(['npm', 'install', '--no-save', '--legacy-peer-deps', pluginName]);
} catch (err) { } catch (err) {
logger.error(`Failed to install plugin ${pluginName}`); logger.error(`Failed to install plugin ${pluginName}@${version}`);
cb(err || new Error(err)); cb(err || new Error(err));
throw err; throw err;
} }
logger.info(`Successfully installed plugin ${pluginName}`); logger.info(`Successfully installed plugin ${pluginName}@${version}`);
await hooks.aCallAll('pluginInstall', {pluginName}); await hooks.aCallAll('pluginInstall', {pluginName});
await plugins.update(); await plugins.update();
cb(null); cb(null);
@ -72,19 +69,19 @@ exports.getAvailablePlugins = (maxCacheAge) => {
const nowTimestamp = Math.round(Date.now() / 1000); const nowTimestamp = Math.round(Date.now() / 1000);
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
// check cache age before making any request // check cache age before making any request
if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) { if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) {
return resolve(exports.availablePlugins); return resolve(exports.availablePlugins);
} }
await axios.get('https://static.etherpad.org/plugins.json') await axios.get('https://static.etherpad.org/plugins.json')
.then(pluginsLoaded => { .then((pluginsLoaded) => {
exports.availablePlugins = pluginsLoaded.data; exports.availablePlugins = pluginsLoaded.data;
cacheTimestamp = nowTimestamp; cacheTimestamp = nowTimestamp;
resolve(exports.availablePlugins); resolve(exports.availablePlugins);
}) });
}) });
} };
exports.search = (searchTerm, maxCacheAge) => exports.getAvailablePlugins(maxCacheAge).then( exports.search = (searchTerm, maxCacheAge) => exports.getAvailablePlugins(maxCacheAge).then(

View File

@ -8,6 +8,7 @@ const runCmd = require('../../../node/utils/run_cmd');
const tsort = require('./tsort'); const tsort = require('./tsort');
const pluginUtils = require('./shared'); const pluginUtils = require('./shared');
const defs = require('./plugin_defs'); const defs = require('./plugin_defs');
const {manager} = require('./installer');
const logger = log4js.getLogger('plugins'); const logger = log4js.getLogger('plugins');
@ -124,6 +125,7 @@ exports.getPackages = async () => {
}; };
const loadPlugin = async (packages, pluginName, plugins, parts) => { const loadPlugin = async (packages, pluginName, plugins, parts) => {
console.log('Plugins', manager.list());
const pluginPath = path.resolve(packages[pluginName].path, 'ep.json'); const pluginPath = path.resolve(packages[pluginName].path, 'ep.json');
try { try {
const data = await fs.readFile(pluginPath); const data = await fs.readFile(pluginPath);

View File

@ -18,96 +18,143 @@ describe('Plugins page', function () {
// create a new pad before each test run // create a new pad before each test run
beforeEach(async function () { beforeEach(async function () {
helper.newAdmin('plugins'); helper.newAdmin('plugins');
await helper.waitForPromise( // retry if helper.newAdmin is called, while the server is not available (e.g. after restart)
() => helper.admin$ && helper.admin$('.menu').find('li').length >= 3, 30000); let maxRetries = 3;
try {
// menu is plugins, settings, help - so at least three entries atm
await helper.waitForPromise(
() => helper.admin$ && helper.admin$('.menu').find('li').length >= 3, 10000);
} catch (err) {
maxRetries -= 1;
if (maxRetries < 0) {
throw err;
}
helper.newAdmin('plugins');
}
}); });
it('Lists some plugins', async function () { it('Lists some plugins assuming more than 50 available plugins', async function () {
await helper.waitForPromise(() => helper.admin$('.results').children().length > 50, 20000); await helper.waitForPromise(() => helper.admin$('.results').children().length > 50, 20000);
}); });
it('Searches for plugin', async function () { it('Searches for plugin ep_font_color', async function () {
helper.admin$('#search-query').val('ep_font_color'); helper.admin$('#search-query').val('ep_font_color');
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 10000); await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 10000);
await helper.waitForPromise(() => helper.admin$('.results').children().length < 300, 10000); // multiple packages may be found
await helper.waitForPromise(() => helper.admin$('.results').children().length < 20, 10000);
await helper.waitForPromise(() => helper.admin$('.results .ep_font_color').length === 1, 10000);
}); });
it('Attempt to Update a plugin', async function () { it('Second search for ep_font_size does not return old result', async function () {
helper.admin$('#search-query').val('ep_font_size');
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 10000);
// multiple packages may be found
await helper.waitForPromise(() => helper.admin$('.results').children().length < 20, 10000);
await helper.waitForPromise(() => helper.admin$('.results .ep_font_size').length === 1, 10000);
await helper.waitForPromise(() => helper.admin$('.results .ep_font_color').length === 0, 10000);
});
it('Searches for plugins ep_font_ (partial match)', async function () {
helper.admin$('#search-query').val('ep_font');
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 10000);
// multiple packages may be found
await helper.waitForPromise(() => helper.admin$('.results').children().length < 50, 10000);
await helper.waitForPromise(() => helper.admin$('.results .ep_font_size').length === 1, 10000);
await helper.waitForPromise(() => helper.admin$('.results .ep_font_color').length === 1, 10000);
});
it('Attempt to Update a plugin (minor version update)', async function () {
this.timeout(280000); this.timeout(280000);
// available plugin list should load
await helper.waitForPromise(() => helper.admin$('.results').children().length > 50, 20000); await helper.waitForPromise(() => helper.admin$('.results').children().length > 50, 20000);
if (helper.admin$('.ep_align').length === 0) this.skip(); // ep_align should be installed (via step in workflow)
await helper.waitForPromise(() => helper.admin$('#installed-plugins .ep_align').length >= 1, 10000);
let latestVersion;
await helper.waitForPromise( await helper.waitForPromise(
() => helper.admin$('.ep_align .version').text().split('.').length >= 2); () => {
latestVersion = helper.admin$('#installed-plugins .ep_align .version').text();
return latestVersion === '0.2.27';
}
);
const minorVersionBefore = const minorVersionBefore =
parseInt(helper.admin$('.ep_align .version').text().split('.')[1]); parseInt(latestVersion.split('.')[1]);
if (!minorVersionBefore) { await helper.waitForPromise(
throw new Error('Unable to get minor number of plugin, is the plugin installed?'); () => helper.admin$('#installed-plugins .ep_align .do-update').length === 1);
}
if (minorVersionBefore !== 2) this.skip(); helper.admin$('#installed-plugins .ep_align .do-update').trigger('click');
helper.waitForPromise(
() => helper.admin$('.ep_align .do-update').length === 1);
await timeout(500); // HACK! Please submit better fix..
const $doUpdateButton = helper.admin$('.ep_align .do-update');
$doUpdateButton.trigger('click');
// ensure its showing as Updating // ensure its showing as Updating
// this assumes that updating will take some time, so there is a message showing up
// in the mean time
await helper.waitForPromise( await helper.waitForPromise(
() => helper.admin$('.ep_align .message').text() === 'Updating'); () => {
const message = helper.admin$('#installed-plugins .ep_align .message').text();
return message === 'Updating';
}, 120000)
// Ensure it's a higher minor version IE 0.3.x as 0.2.x was installed // Ensure it's a higher minor version IE 0.3.x as 0.2.x was installed
// Coverage for https://github.com/ether/etherpad-lite/issues/4536 // Coverage for https://github.com/ether/etherpad-lite/issues/4536
await helper.waitForPromise(() => parseInt(helper.admin$('.ep_align .version') await helper.waitForPromise(() => parseInt(helper.admin$('#installed-plugins .ep_align .version')
.text() .text()
.split('.')[1]) > minorVersionBefore, 60000, 1000); .split('.')[1]) > minorVersionBefore, 60000, 1000);
// allow 50 seconds, check every 1 second.
// ensure it's the latest version
await helper.waitForPromise(
() => helper.admin$('.ep_align .do-update').length === 0);
}); });
it('Attempt to Install a plugin', async function () { it('Attempt to Install a plugin', async function () {
this.timeout(280000); this.timeout(280000);
helper.admin$('#search-query').val('ep_headings2'); helper.admin$('#search-query').val('ep_headings2');
await helper.waitForPromise(() => helper.admin$('.results').children().length < 300, 6000); await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 10000);
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 6000); await helper.waitForPromise(() => helper.admin$('.results').children().length < 50, 10000);
// skip if we already have ep_headings2 installed..
if (helper.admin$('.ep_headings2 .do-install').is(':visible') === false) this.skip();
helper.admin$('.ep_headings2 .do-install').trigger('click'); helper.admin$('.ep_headings2 .do-install').trigger('click');
// ensure install has attempted to be started // ensure install has attempted to be started
await helper.waitForPromise( await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .do-install').length !== 0, 120000); () => helper.admin$('.ep_headings2 .do-install').length > 0, 120000);
// ensure its not showing installing any more // ensure its not showing installing any more
await helper.waitForPromise( await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .message').text() === '', 180000); () => helper.admin$('.ep_headings2 .message').text() === '', 180000);
// ensure uninstall button is visible // ensure uninstall button is visible
await helper.waitForPromise( await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .do-uninstall').length !== 0, 120000); () => helper.admin$('#installed-plugins .ep_headings2 .do-uninstall').length > 0, 120000);
}); });
it('Attempt to Uninstall a plugin', async function () { it('Attempt to Uninstall a plugin', async function () {
this.timeout(360000); this.timeout(280000);
await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .do-uninstall').length !== 0, 120000);
helper.admin$('.ep_headings2 .do-uninstall').trigger('click'); await helper.waitForPromise(
() => helper.admin$('#installed-plugins .ep_headings2 .do-uninstall').length > 0, 120000);
helper.admin$('#installed-plugins .ep_headings2 .do-uninstall').trigger('click');
// ensure its showing uninstalling // ensure its showing uninstalling
// this assumes that uninstalling will take some time, so there is a message showing up
// in the mean time
await helper.waitForPromise( await helper.waitForPromise(
() => helper.admin$('.ep_headings2 .message') () => helper.admin$('#installed-plugins .ep_headings2 .message')
.text() === 'Uninstalling', 120000); .text() === 'Uninstalling', 120000);
// ensure its gone // ensure its gone
await helper.waitForPromise( await helper.waitForPromise(() => helper.admin$('#installed-plugins .ep_headings2').length === 0, 200000);
() => helper.admin$('.ep_headings2').length === 0, 240000);
// delay the search query
let delayed = false;
setTimeout(() => {delayed = true;}, 5000);
await helper.waitForPromise(() => delayed === true, 6000);
// ensure search still works
helper.admin$('#search-query').val('ep_font'); helper.admin$('#search-query').val('ep_font');
await helper.waitForPromise(() => helper.admin$('.results').children().length < 300, 240000); await helper.waitForPromise(() => helper.admin$('.results').children().length < 50, 240000);
await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 1000); await helper.waitForPromise(() => helper.admin$('.results').children().length > 0, 1000);
await helper.waitForPromise(() => helper.admin$('.results .ep_font_size').length === 1, 10000);
await helper.waitForPromise(() => helper.admin$('.results .ep_font_color').length === 1, 10000);
}); });
}); });

View File

@ -11,7 +11,7 @@ describe('Language select and change', function () {
// Destroy language cookies // Destroy language cookies
it('makes text german', async function () { it('makes text german', async function () {
this.timeout(1000); this.timeout(3000);
const chrome$ = helper.padChrome$; const chrome$ = helper.padChrome$;
// click on the settings button to make settings visible // click on the settings button to make settings visible
@ -40,7 +40,7 @@ describe('Language select and change', function () {
}); });
it('makes text English', async function () { it('makes text English', async function () {
this.timeout(1000); this.timeout(3000);
const chrome$ = helper.padChrome$; const chrome$ = helper.padChrome$;
// click on the settings button to make settings visible // click on the settings button to make settings visible

View File

@ -37,4 +37,7 @@ exit_code=$?
kill "$ep_pid" && wait "$ep_pid" kill "$ep_pid" && wait "$ep_pid"
log "Done." log "Done."
try cd "${MY_DIR}/../../../.."
try ls node_modules
exit "$exit_code" exit "$exit_code"