Skip to content

Commit 149c6a2

Browse files
author
Etherpad Release Bot
committed
Merge branch 'develop'
2 parents 07902d3 + 1864cd2 commit 149c6a2

283 files changed

Lines changed: 38298 additions & 2975 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/backend-tests.yml

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727
strategy:
2828
fail-fast: false
2929
matrix:
30-
# PRs: test on latest Node only. Push to develop: full matrix.
31-
node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[22, 24, 25]') }}
30+
# Etherpad requires Node >= 24 (see package.json engines.node).
31+
node: ${{ fromJSON('[24]') }}
3232
steps:
3333
-
3434
name: Checkout repository
@@ -66,7 +66,24 @@ jobs:
6666
run: pnpm build
6767
-
6868
name: Run the backend tests
69-
run: pnpm test
69+
env:
70+
# --report-on-fatalerror and friends write a Node diagnostic report
71+
# (V8 stack, libuv handles, OS info) on fatal errors that bypass JS
72+
# handlers — the failure mode we've been chasing on Windows + Node
73+
# 24 since PR #7663. Reports land in node-report/ and are uploaded
74+
# as an artifact if the step fails.
75+
NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report"
76+
run: |
77+
mkdir -p "${{ github.workspace }}/node-report"
78+
pnpm test
79+
- name: Upload Node diagnostic reports on failure
80+
if: ${{ failure() }}
81+
uses: actions/upload-artifact@v7
82+
with:
83+
name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }}
84+
path: node-report/
85+
if-no-files-found: ignore
86+
retention-days: 7
7087
- name: Run the new vitest tests
7188
working-directory: src
7289
run: pnpm run test:vitest
@@ -84,7 +101,7 @@ jobs:
84101
strategy:
85102
fail-fast: false
86103
matrix:
87-
node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[22, 24, 25]') }}
104+
node: ${{ fromJSON('[24]') }}
88105
steps:
89106
-
90107
name: Checkout repository
@@ -136,7 +153,19 @@ jobs:
136153
ep_table_of_contents
137154
-
138155
name: Run the backend tests
139-
run: pnpm test
156+
env:
157+
NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report"
158+
run: |
159+
mkdir -p "${{ github.workspace }}/node-report"
160+
pnpm test
161+
- name: Upload Node diagnostic reports on failure
162+
if: ${{ failure() }}
163+
uses: actions/upload-artifact@v7
164+
with:
165+
name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }}
166+
path: node-report/
167+
if-no-files-found: ignore
168+
retention-days: 7
140169
- name: Run the new vitest tests
141170
working-directory: src
142171
run: pnpm run test:vitest
@@ -150,7 +179,8 @@ jobs:
150179
strategy:
151180
fail-fast: false
152181
matrix:
153-
node: [22, 24, 25]
182+
# Etherpad requires Node >= 24 (see package.json engines.node).
183+
node: ${{ fromJSON('[24]') }}
154184
name: Windows without plugins
155185
runs-on: windows-latest
156186
steps:
@@ -186,8 +216,25 @@ jobs:
186216
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
187217
-
188218
name: Run the backend tests
219+
shell: bash
189220
working-directory: src
190-
run: pnpm test
221+
env:
222+
NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report"
223+
run: |
224+
mkdir -p "${{ github.workspace }}/node-report"
225+
# --exit forces process.exit(failures) after the suite completes,
226+
# closing the post-suite event-loop drain window where Windows +
227+
# Node 24 hard-kills the process. Scoped to Windows so Linux/local
228+
# runs still surface real handle leaks via natural drain.
229+
pnpm test -- --exit
230+
- name: Upload Node diagnostic reports on failure
231+
if: ${{ failure() }}
232+
uses: actions/upload-artifact@v7
233+
with:
234+
name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }}
235+
path: node-report/
236+
if-no-files-found: ignore
237+
retention-days: 7
191238
- name: Run the new vitest tests
192239
working-directory: src
193240
run: pnpm run test:vitest
@@ -200,7 +247,8 @@ jobs:
200247
strategy:
201248
fail-fast: false
202249
matrix:
203-
node: [22, 24, 25]
250+
# Etherpad requires Node >= 24 (see package.json engines.node).
251+
node: ${{ fromJSON('[24]') }}
204252
name: Windows with Plugins
205253
runs-on: windows-latest
206254

@@ -265,8 +313,25 @@ jobs:
265313
powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json"
266314
-
267315
name: Run the backend tests
316+
shell: bash
268317
working-directory: src
269-
run: pnpm test
318+
env:
319+
NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report"
320+
run: |
321+
mkdir -p "${{ github.workspace }}/node-report"
322+
# --exit forces process.exit(failures) after the suite completes,
323+
# closing the post-suite event-loop drain window where Windows +
324+
# Node 24 hard-kills the process. Scoped to Windows so Linux/local
325+
# runs still surface real handle leaks via natural drain.
326+
pnpm test -- --exit
327+
- name: Upload Node diagnostic reports on failure
328+
if: ${{ failure() }}
329+
uses: actions/upload-artifact@v7
330+
with:
331+
name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }}
332+
path: node-report/
333+
if-no-files-found: ignore
334+
retention-days: 7
270335
- name: Run the new vitest tests
271336
working-directory: src
272337
run: pnpm run test:vitest

.github/workflows/build-and-deploy-docs.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,11 @@ jobs:
5656
with:
5757
run_install: false
5858
# Pin Node so the build does not silently fall back to whatever the
59-
# runner image ships with. vite 8 requires Node ^20.19.0 || >=22.12.0;
60-
# the repo declares engines.node >=22.12.0 to match.
59+
# runner image ships with. The repo declares engines.node >=24.0.0.
6160
- name: Use Node.js
6261
uses: actions/setup-node@v6
6362
with:
64-
node-version: 22
63+
node-version: 24
6564
cache: pnpm
6665
- name: Setup Pages
6766
if: github.event_name == 'push'

.github/workflows/deb-package.yml

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ jobs:
128128
run: |
129129
set -eux
130130
# Ubuntu's default apt nodejs is 18 — too old for our
131-
# `Depends: nodejs (>= 22)`. Add NodeSource's apt repo
131+
# `Depends: nodejs (>= 24)`. Add NodeSource's apt repo
132132
# explicitly (key + sources.list) instead of `curl | sudo bash`
133133
# so we don't execute network-fetched code as root.
134134
NODE_MAJOR=24
@@ -137,7 +137,7 @@ jobs:
137137
# existing nodesource entries so the only Node candidate apt sees
138138
# is our node_24.x repo. Otherwise `apt-get install -y nodejs`
139139
# picks the higher-version 20.x build that's already cached and
140-
# `dpkg -i` then fails on `Depends: nodejs (>= 22)`.
140+
# `dpkg -i` then fails on `Depends: nodejs (>= 24)`.
141141
sudo rm -f /etc/apt/sources.list.d/nodesource.list \
142142
/etc/apt/sources.list.d/nodesource.sources \
143143
/etc/apt/preferences.d/nodesource \
@@ -172,10 +172,14 @@ jobs:
172172
sudo test -L /opt/etherpad/settings.json
173173
sudo test -L /opt/etherpad/var
174174
[ "$(sudo readlink /opt/etherpad/var)" = "/var/lib/etherpad/var" ]
175-
sudo test -L /opt/etherpad/src/plugin_packages
176-
[ "$(sudo readlink /opt/etherpad/src/plugin_packages)" = "/var/lib/etherpad/plugin_packages" ]
177-
sudo test -d /var/lib/etherpad/plugin_packages
178-
[ "$(sudo stat -c '%U' /var/lib/etherpad/plugin_packages)" = "etherpad" ]
175+
# plugin_packages must stay in-tree -- Node.js resolves symlinks
176+
# to realpath before walking node_modules, so symlinking it
177+
# outside /opt broke require("ep_etherpad-lite/...") in
178+
# admin-installed plugins (ether/ep_comments_page#416).
179+
sudo test -d /opt/etherpad/src/plugin_packages
180+
sudo test ! -L /opt/etherpad/src/plugin_packages
181+
[ "$(sudo stat -c '%G' /opt/etherpad/src/plugin_packages)" = "etherpad" ]
182+
[ "$(sudo stat -c '%a' /opt/etherpad/src/plugin_packages)" = "2775" ]
179183
[ "$(stat -c '%G' /opt/etherpad/src/node_modules)" = "etherpad" ]
180184
sudo test -f /var/lib/etherpad/var/installed_plugins.json
181185
sudo grep -q '"ep_etherpad-lite"' /var/lib/etherpad/var/installed_plugins.json
@@ -197,8 +201,71 @@ jobs:
197201
exit 1
198202
fi
199203
sudo systemctl stop etherpad
204+
# Regression: simulate a pre-fix install (plugin_packages as a
205+
# symlink to /var/lib/etherpad/plugin_packages, with a marker
206+
# plugin inside) and re-run the postinst. The new postinst must
207+
# migrate the contents back in-tree and drop the symlink so
208+
# admin-installed plugins keep resolving ep_etherpad-lite
209+
# (ether/ep_comments_page#416).
210+
sudo rm -rf /opt/etherpad/src/plugin_packages
211+
sudo mkdir -p /var/lib/etherpad/plugin_packages/.versions/ep_migration_marker
212+
echo '{"name":"ep_migration_marker"}' | \
213+
sudo tee /var/lib/etherpad/plugin_packages/.versions/ep_migration_marker/package.json >/dev/null
214+
sudo chown -R etherpad:etherpad /var/lib/etherpad/plugin_packages
215+
sudo ln -sfn /var/lib/etherpad/plugin_packages /opt/etherpad/src/plugin_packages
216+
sudo dpkg-reconfigure etherpad
217+
sudo test -d /opt/etherpad/src/plugin_packages
218+
sudo test ! -L /opt/etherpad/src/plugin_packages
219+
sudo test -f /opt/etherpad/src/plugin_packages/.versions/ep_migration_marker/package.json
220+
[ "$(sudo stat -c '%a' /opt/etherpad/src/plugin_packages)" = "2775" ]
221+
222+
# Regression: stage the ep_layout_trip_wire test fixture into
223+
# plugin_packages and confirm etherpad loads it. The fixture's
224+
# index.js does the require('ep_etherpad-lite/...') calls that
225+
# broke under the old symlinked layout (#416). If the layout
226+
# ever regresses, the marker line never reaches the journal
227+
# and this step fails.
228+
PLUGIN_DIR=/opt/etherpad/src/plugin_packages
229+
FIXTURE_DIR=$GITHUB_WORKSPACE/packaging/test-fixtures/ep_layout_trip_wire
230+
sudo install -d -o etherpad -g etherpad -m 2775 "${PLUGIN_DIR}/.versions"
231+
sudo cp -a "${FIXTURE_DIR}" "${PLUGIN_DIR}/.versions/ep_layout_trip_wire@1.0.0"
232+
sudo ln -sfn .versions/ep_layout_trip_wire@1.0.0 "${PLUGIN_DIR}/ep_layout_trip_wire"
233+
sudo ln -sfn ../plugin_packages/ep_layout_trip_wire \
234+
/opt/etherpad/src/node_modules/ep_layout_trip_wire
235+
sudo chown -R etherpad:etherpad "${PLUGIN_DIR}/.versions/ep_layout_trip_wire@1.0.0" \
236+
"${PLUGIN_DIR}/ep_layout_trip_wire" \
237+
/opt/etherpad/src/node_modules/ep_layout_trip_wire
238+
echo '{"plugins":[{"name":"ep_etherpad-lite","version":"0.0.0"},{"name":"ep_layout_trip_wire","version":"1.0.0"}]}' \
239+
| sudo tee /var/lib/etherpad/var/installed_plugins.json >/dev/null
240+
sudo chown etherpad:etherpad /var/lib/etherpad/var/installed_plugins.json
241+
sudo systemctl start etherpad
242+
ok=
243+
for i in $(seq 1 30); do
244+
if curl -fsS http://127.0.0.1:9001/health; then ok=1; break; fi
245+
sleep 2
246+
done
247+
if [ -z "${ok}" ]; then
248+
sudo journalctl -u etherpad --no-pager -n 300 || true
249+
exit 1
250+
fi
251+
# Marker proves the require('ep_etherpad-lite/...') calls in
252+
# the fixture's index.js all resolved.
253+
sudo journalctl -u etherpad --no-pager -n 500 \
254+
| grep -F 'ep_layout_trip_wire: plugin_packages layout OK'
255+
# And no MODULE_NOT_FOUND involving ep_etherpad-lite, anywhere.
256+
if sudo journalctl -u etherpad --no-pager -n 500 \
257+
| grep -E "Cannot find module '?ep_etherpad-lite"; then
258+
echo "::error::ep_etherpad-lite require failed inside an installed plugin"
259+
exit 1
260+
fi
261+
sudo systemctl stop etherpad
262+
200263
sudo dpkg --purge etherpad
201264
! id etherpad 2>/dev/null
265+
# Purge must clean up runtime-created plugin artifacts that
266+
# dpkg didn't ship (ether/ep_comments_page#416, Qodo #3).
267+
sudo test ! -e /opt/etherpad/src/plugin_packages
268+
sudo test ! -e /var/lib/etherpad
202269
203270
- name: Upload artifact
204271
uses: actions/upload-artifact@v7

.github/workflows/dependency-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ jobs:
1717
- name: 'Checkout Repository'
1818
uses: actions/checkout@v6
1919
- name: 'Dependency Review'
20-
uses: actions/dependency-review-action@v4
20+
uses: actions/dependency-review-action@v5

.github/workflows/docker.yml

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
- name: Use Node.js
6060
uses: actions/setup-node@v6
6161
with:
62-
node-version: 22
62+
node-version: 24
6363
cache: pnpm
6464
cache-dependency-path: etherpad/pnpm-lock.yaml
6565
-
@@ -79,7 +79,88 @@ jobs:
7979
esac
8080
done
8181
(cd src && pnpm run test-container)
82+
docker rm -f test
8283
git clean -dxf .
84+
-
85+
# Regression test for #7718. Reproduces the production
86+
# docker-compose layout reported in that issue: a named volume
87+
# mounted on src/plugin_packages with no TTY allocated. Under
88+
# the previous `CMD ["pnpm", "run", "prod"]`, pnpm 11's
89+
# runDepsStatusCheck spuriously decided node_modules was out of
90+
# sync at boot and tried to wipe + reinstall, aborting with
91+
# ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY before the HTTP
92+
# server ever came up. If the CMD is ever reverted to go
93+
# through `pnpm run`, this step will time out waiting for the
94+
# health endpoint and fail.
95+
name: Regression — boot with named volume on plugin_packages (#7718)
96+
working-directory: etherpad
97+
run: |
98+
docker volume create ep7718-plugins
99+
docker run --rm -d -p 9001:9001 \
100+
-v ep7718-plugins:/opt/etherpad-lite/src/plugin_packages \
101+
--name test-7718 ${{ env.TEST_TAG }}
102+
docker logs -f test-7718 &
103+
ok=0
104+
for i in $(seq 1 60); do
105+
status=$(docker container inspect -f '{{.State.Health.Status}}' test-7718 2>/dev/null) || {
106+
echo "container exited prematurely — likely #7718 regression"
107+
docker logs test-7718 || true
108+
break
109+
}
110+
case ${status} in
111+
healthy) ok=1; echo "container healthy after $((i*2))s"; break;;
112+
starting) sleep 2;;
113+
*) echo "unexpected status: ${status}"; docker logs test-7718; break;;
114+
esac
115+
done
116+
docker rm -f test-7718 >/dev/null 2>&1 || true
117+
docker volume rm ep7718-plugins >/dev/null 2>&1 || true
118+
[ "$ok" = "1" ] || exit 1
119+
120+
build-test-local-plugin:
121+
# Regression coverage for #7687: the Docker image's
122+
# `bin/installLocalPlugins.sh` step runs as the `etherpad` user and
123+
# invokes pnpm via the corepack shim. A previous corepack/cache bug
124+
# made that path fail when ETHERPAD_LOCAL_PLUGINS was set. This job
125+
# builds the development target with a stub local plugin so the
126+
# regression cannot silently come back.
127+
runs-on: ubuntu-latest
128+
permissions:
129+
contents: read
130+
steps:
131+
-
132+
name: Check out
133+
uses: actions/checkout@v6
134+
with:
135+
path: etherpad
136+
-
137+
name: Set up Docker Buildx
138+
uses: docker/setup-buildx-action@v4
139+
-
140+
name: Stub a local plugin
141+
run: |
142+
mkdir -p etherpad/local_plugins/ep_test_corepack
143+
cat > etherpad/local_plugins/ep_test_corepack/package.json <<'EOF'
144+
{
145+
"name": "ep_test_corepack",
146+
"version": "0.0.1",
147+
"description": "regression-test stub for ether/etherpad#7687",
148+
"main": "index.js"
149+
}
150+
EOF
151+
cat > etherpad/local_plugins/ep_test_corepack/index.js <<'EOF'
152+
exports.placeholder = true;
153+
EOF
154+
-
155+
name: Build with ETHERPAD_LOCAL_PLUGINS (must succeed)
156+
uses: docker/build-push-action@v7
157+
with:
158+
context: ./etherpad
159+
target: development
160+
load: false
161+
build-args: |
162+
ETHERPAD_LOCAL_PLUGINS=ep_test_corepack
163+
cache-from: type=gha
83164

84165
build-test-db-drivers:
85166
# Spinning up MySQL + Postgres + cross-driver smoke is expensive; only
@@ -284,13 +365,13 @@ jobs:
284365
if: success() && github.ref == 'refs/heads/develop'
285366
working-directory: ether-charts
286367
run: |
287-
sed -i 's/tag: ".*"/tag: "${{ steps.build-docker.outputs.digest }}"/' values-dev.yaml
368+
sed -i 's/tag: ".*"/tag: "${{ steps.build-docker.outputs.digest }}"/' charts/etherpad/values-dev.yaml
288369
- name: Commit and push changes
289370
working-directory: ether-charts
290371
if: success() && github.ref == 'refs/heads/develop'
291372
run: |
292373
git config --global user.name 'github-actions[bot]'
293374
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
294-
git add values-dev.yaml
375+
git add charts/etherpad/values-dev.yaml
295376
git commit -m 'Update develop image tag'
296377
git push

.github/workflows/frontend-admin-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ jobs:
2121
strategy:
2222
fail-fast: false
2323
matrix:
24-
# PRs: single Node version. Push: full matrix.
25-
node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[22, 24, 25]') }}
24+
# Etherpad requires Node >= 24 (see package.json engines.node).
25+
node: ${{ fromJSON('[24]') }}
2626

2727
steps:
2828
-

0 commit comments

Comments
 (0)