diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index 96fed8321..c4f8618bc 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -27,8 +27,8 @@ jobs: strategy: fail-fast: false matrix: - # PRs: test on latest Node only. Push to develop: full matrix. - node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[22, 24, 25]') }} + # Etherpad requires Node >= 24 (see package.json engines.node). + node: ${{ fromJSON('[24]') }} steps: - name: Checkout repository @@ -66,7 +66,24 @@ jobs: run: pnpm build - name: Run the backend tests - run: pnpm test + env: + # --report-on-fatalerror and friends write a Node diagnostic report + # (V8 stack, libuv handles, OS info) on fatal errors that bypass JS + # handlers — the failure mode we've been chasing on Windows + Node + # 24 since PR #7663. Reports land in node-report/ and are uploaded + # as an artifact if the step fails. + NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report" + run: | + mkdir -p "${{ github.workspace }}/node-report" + pnpm test + - name: Upload Node diagnostic reports on failure + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }} + path: node-report/ + if-no-files-found: ignore + retention-days: 7 - name: Run the new vitest tests working-directory: src run: pnpm run test:vitest @@ -84,7 +101,7 @@ jobs: strategy: fail-fast: false matrix: - node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[22, 24, 25]') }} + node: ${{ fromJSON('[24]') }} steps: - name: Checkout repository @@ -136,7 +153,19 @@ jobs: ep_table_of_contents - name: Run the backend tests - run: pnpm test + env: + NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report" + run: | + mkdir -p "${{ github.workspace }}/node-report" + pnpm test + - name: Upload Node diagnostic reports on failure + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }} + path: node-report/ + if-no-files-found: ignore + retention-days: 7 - name: Run the new vitest tests working-directory: src run: pnpm run test:vitest @@ -150,7 +179,8 @@ jobs: strategy: fail-fast: false matrix: - node: [22, 24, 25] + # Etherpad requires Node >= 24 (see package.json engines.node). + node: ${{ fromJSON('[24]') }} name: Windows without plugins runs-on: windows-latest steps: @@ -186,8 +216,25 @@ jobs: powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" - name: Run the backend tests + shell: bash working-directory: src - run: pnpm test + env: + NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report" + run: | + mkdir -p "${{ github.workspace }}/node-report" + # --exit forces process.exit(failures) after the suite completes, + # closing the post-suite event-loop drain window where Windows + + # Node 24 hard-kills the process. Scoped to Windows so Linux/local + # runs still surface real handle leaks via natural drain. + pnpm test -- --exit + - name: Upload Node diagnostic reports on failure + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }} + path: node-report/ + if-no-files-found: ignore + retention-days: 7 - name: Run the new vitest tests working-directory: src run: pnpm run test:vitest @@ -200,7 +247,8 @@ jobs: strategy: fail-fast: false matrix: - node: [22, 24, 25] + # Etherpad requires Node >= 24 (see package.json engines.node). + node: ${{ fromJSON('[24]') }} name: Windows with Plugins runs-on: windows-latest @@ -265,8 +313,25 @@ jobs: powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" - name: Run the backend tests + shell: bash working-directory: src - run: pnpm test + env: + NODE_OPTIONS: "--report-on-fatalerror --report-uncaught-exception --report-on-signal --report-compact --report-directory=${{ github.workspace }}/node-report" + run: | + mkdir -p "${{ github.workspace }}/node-report" + # --exit forces process.exit(failures) after the suite completes, + # closing the post-suite event-loop drain window where Windows + + # Node 24 hard-kills the process. Scoped to Windows so Linux/local + # runs still surface real handle leaks via natural drain. + pnpm test -- --exit + - name: Upload Node diagnostic reports on failure + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: node-diagnostic-report-${{ runner.os }}-node${{ matrix.node }}-${{ github.job }} + path: node-report/ + if-no-files-found: ignore + retention-days: 7 - name: Run the new vitest tests working-directory: src run: pnpm run test:vitest diff --git a/.github/workflows/build-and-deploy-docs.yml b/.github/workflows/build-and-deploy-docs.yml index 826b46877..26d6a5e14 100644 --- a/.github/workflows/build-and-deploy-docs.yml +++ b/.github/workflows/build-and-deploy-docs.yml @@ -56,12 +56,11 @@ jobs: with: run_install: false # Pin Node so the build does not silently fall back to whatever the - # runner image ships with. vite 8 requires Node ^20.19.0 || >=22.12.0; - # the repo declares engines.node >=22.12.0 to match. + # runner image ships with. The repo declares engines.node >=24.0.0. - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Setup Pages if: github.event_name == 'push' diff --git a/.github/workflows/deb-package.yml b/.github/workflows/deb-package.yml index f8e769304..603089c26 100644 --- a/.github/workflows/deb-package.yml +++ b/.github/workflows/deb-package.yml @@ -128,7 +128,7 @@ jobs: run: | set -eux # Ubuntu's default apt nodejs is 18 — too old for our - # `Depends: nodejs (>= 22)`. Add NodeSource's apt repo + # `Depends: nodejs (>= 24)`. Add NodeSource's apt repo # explicitly (key + sources.list) instead of `curl | sudo bash` # so we don't execute network-fetched code as root. NODE_MAJOR=24 @@ -137,7 +137,7 @@ jobs: # existing nodesource entries so the only Node candidate apt sees # is our node_24.x repo. Otherwise `apt-get install -y nodejs` # picks the higher-version 20.x build that's already cached and - # `dpkg -i` then fails on `Depends: nodejs (>= 22)`. + # `dpkg -i` then fails on `Depends: nodejs (>= 24)`. sudo rm -f /etc/apt/sources.list.d/nodesource.list \ /etc/apt/sources.list.d/nodesource.sources \ /etc/apt/preferences.d/nodesource \ @@ -172,10 +172,14 @@ jobs: sudo test -L /opt/etherpad/settings.json sudo test -L /opt/etherpad/var [ "$(sudo readlink /opt/etherpad/var)" = "/var/lib/etherpad/var" ] - sudo test -L /opt/etherpad/src/plugin_packages - [ "$(sudo readlink /opt/etherpad/src/plugin_packages)" = "/var/lib/etherpad/plugin_packages" ] - sudo test -d /var/lib/etherpad/plugin_packages - [ "$(sudo stat -c '%U' /var/lib/etherpad/plugin_packages)" = "etherpad" ] + # plugin_packages must stay in-tree -- Node.js resolves symlinks + # to realpath before walking node_modules, so symlinking it + # outside /opt broke require("ep_etherpad-lite/...") in + # admin-installed plugins (ether/ep_comments_page#416). + sudo test -d /opt/etherpad/src/plugin_packages + sudo test ! -L /opt/etherpad/src/plugin_packages + [ "$(sudo stat -c '%G' /opt/etherpad/src/plugin_packages)" = "etherpad" ] + [ "$(sudo stat -c '%a' /opt/etherpad/src/plugin_packages)" = "2775" ] [ "$(stat -c '%G' /opt/etherpad/src/node_modules)" = "etherpad" ] sudo test -f /var/lib/etherpad/var/installed_plugins.json sudo grep -q '"ep_etherpad-lite"' /var/lib/etherpad/var/installed_plugins.json @@ -197,8 +201,71 @@ jobs: exit 1 fi sudo systemctl stop etherpad + # Regression: simulate a pre-fix install (plugin_packages as a + # symlink to /var/lib/etherpad/plugin_packages, with a marker + # plugin inside) and re-run the postinst. The new postinst must + # migrate the contents back in-tree and drop the symlink so + # admin-installed plugins keep resolving ep_etherpad-lite + # (ether/ep_comments_page#416). + sudo rm -rf /opt/etherpad/src/plugin_packages + sudo mkdir -p /var/lib/etherpad/plugin_packages/.versions/ep_migration_marker + echo '{"name":"ep_migration_marker"}' | \ + sudo tee /var/lib/etherpad/plugin_packages/.versions/ep_migration_marker/package.json >/dev/null + sudo chown -R etherpad:etherpad /var/lib/etherpad/plugin_packages + sudo ln -sfn /var/lib/etherpad/plugin_packages /opt/etherpad/src/plugin_packages + sudo dpkg-reconfigure etherpad + sudo test -d /opt/etherpad/src/plugin_packages + sudo test ! -L /opt/etherpad/src/plugin_packages + sudo test -f /opt/etherpad/src/plugin_packages/.versions/ep_migration_marker/package.json + [ "$(sudo stat -c '%a' /opt/etherpad/src/plugin_packages)" = "2775" ] + + # Regression: stage the ep_layout_trip_wire test fixture into + # plugin_packages and confirm etherpad loads it. The fixture's + # index.js does the require('ep_etherpad-lite/...') calls that + # broke under the old symlinked layout (#416). If the layout + # ever regresses, the marker line never reaches the journal + # and this step fails. + PLUGIN_DIR=/opt/etherpad/src/plugin_packages + FIXTURE_DIR=$GITHUB_WORKSPACE/packaging/test-fixtures/ep_layout_trip_wire + sudo install -d -o etherpad -g etherpad -m 2775 "${PLUGIN_DIR}/.versions" + sudo cp -a "${FIXTURE_DIR}" "${PLUGIN_DIR}/.versions/ep_layout_trip_wire@1.0.0" + sudo ln -sfn .versions/ep_layout_trip_wire@1.0.0 "${PLUGIN_DIR}/ep_layout_trip_wire" + sudo ln -sfn ../plugin_packages/ep_layout_trip_wire \ + /opt/etherpad/src/node_modules/ep_layout_trip_wire + sudo chown -R etherpad:etherpad "${PLUGIN_DIR}/.versions/ep_layout_trip_wire@1.0.0" \ + "${PLUGIN_DIR}/ep_layout_trip_wire" \ + /opt/etherpad/src/node_modules/ep_layout_trip_wire + echo '{"plugins":[{"name":"ep_etherpad-lite","version":"0.0.0"},{"name":"ep_layout_trip_wire","version":"1.0.0"}]}' \ + | sudo tee /var/lib/etherpad/var/installed_plugins.json >/dev/null + sudo chown etherpad:etherpad /var/lib/etherpad/var/installed_plugins.json + sudo systemctl start etherpad + ok= + for i in $(seq 1 30); do + if curl -fsS http://127.0.0.1:9001/health; then ok=1; break; fi + sleep 2 + done + if [ -z "${ok}" ]; then + sudo journalctl -u etherpad --no-pager -n 300 || true + exit 1 + fi + # Marker proves the require('ep_etherpad-lite/...') calls in + # the fixture's index.js all resolved. + sudo journalctl -u etherpad --no-pager -n 500 \ + | grep -F 'ep_layout_trip_wire: plugin_packages layout OK' + # And no MODULE_NOT_FOUND involving ep_etherpad-lite, anywhere. + if sudo journalctl -u etherpad --no-pager -n 500 \ + | grep -E "Cannot find module '?ep_etherpad-lite"; then + echo "::error::ep_etherpad-lite require failed inside an installed plugin" + exit 1 + fi + sudo systemctl stop etherpad + sudo dpkg --purge etherpad ! id etherpad 2>/dev/null + # Purge must clean up runtime-created plugin artifacts that + # dpkg didn't ship (ether/ep_comments_page#416, Qodo #3). + sudo test ! -e /opt/etherpad/src/plugin_packages + sudo test ! -e /var/lib/etherpad - name: Upload artifact uses: actions/upload-artifact@v7 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 774c7434a..1fd74e4a5 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,4 +17,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v6 - name: 'Dependency Review' - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v5 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2d84cafd0..d709d3a50 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -59,7 +59,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm cache-dependency-path: etherpad/pnpm-lock.yaml - @@ -79,7 +79,88 @@ jobs: esac done (cd src && pnpm run test-container) + docker rm -f test git clean -dxf . + - + # Regression test for #7718. Reproduces the production + # docker-compose layout reported in that issue: a named volume + # mounted on src/plugin_packages with no TTY allocated. Under + # the previous `CMD ["pnpm", "run", "prod"]`, pnpm 11's + # runDepsStatusCheck spuriously decided node_modules was out of + # sync at boot and tried to wipe + reinstall, aborting with + # ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY before the HTTP + # server ever came up. If the CMD is ever reverted to go + # through `pnpm run`, this step will time out waiting for the + # health endpoint and fail. + name: Regression — boot with named volume on plugin_packages (#7718) + working-directory: etherpad + run: | + docker volume create ep7718-plugins + docker run --rm -d -p 9001:9001 \ + -v ep7718-plugins:/opt/etherpad-lite/src/plugin_packages \ + --name test-7718 ${{ env.TEST_TAG }} + docker logs -f test-7718 & + ok=0 + for i in $(seq 1 60); do + status=$(docker container inspect -f '{{.State.Health.Status}}' test-7718 2>/dev/null) || { + echo "container exited prematurely — likely #7718 regression" + docker logs test-7718 || true + break + } + case ${status} in + healthy) ok=1; echo "container healthy after $((i*2))s"; break;; + starting) sleep 2;; + *) echo "unexpected status: ${status}"; docker logs test-7718; break;; + esac + done + docker rm -f test-7718 >/dev/null 2>&1 || true + docker volume rm ep7718-plugins >/dev/null 2>&1 || true + [ "$ok" = "1" ] || exit 1 + + build-test-local-plugin: + # Regression coverage for #7687: the Docker image's + # `bin/installLocalPlugins.sh` step runs as the `etherpad` user and + # invokes pnpm via the corepack shim. A previous corepack/cache bug + # made that path fail when ETHERPAD_LOCAL_PLUGINS was set. This job + # builds the development target with a stub local plugin so the + # regression cannot silently come back. + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - + name: Check out + uses: actions/checkout@v6 + with: + path: etherpad + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - + name: Stub a local plugin + run: | + mkdir -p etherpad/local_plugins/ep_test_corepack + cat > etherpad/local_plugins/ep_test_corepack/package.json <<'EOF' + { + "name": "ep_test_corepack", + "version": "0.0.1", + "description": "regression-test stub for ether/etherpad#7687", + "main": "index.js" + } + EOF + cat > etherpad/local_plugins/ep_test_corepack/index.js <<'EOF' + exports.placeholder = true; + EOF + - + name: Build with ETHERPAD_LOCAL_PLUGINS (must succeed) + uses: docker/build-push-action@v7 + with: + context: ./etherpad + target: development + load: false + build-args: | + ETHERPAD_LOCAL_PLUGINS=ep_test_corepack + cache-from: type=gha build-test-db-drivers: # Spinning up MySQL + Postgres + cross-driver smoke is expensive; only @@ -284,13 +365,13 @@ jobs: if: success() && github.ref == 'refs/heads/develop' working-directory: ether-charts run: | - sed -i 's/tag: ".*"/tag: "${{ steps.build-docker.outputs.digest }}"/' values-dev.yaml + sed -i 's/tag: ".*"/tag: "${{ steps.build-docker.outputs.digest }}"/' charts/etherpad/values-dev.yaml - name: Commit and push changes working-directory: ether-charts if: success() && github.ref == 'refs/heads/develop' run: | git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' - git add values-dev.yaml + git add charts/etherpad/values-dev.yaml git commit -m 'Update develop image tag' git push diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index 544f801b3..8d23a23f2 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -21,8 +21,8 @@ jobs: strategy: fail-fast: false matrix: - # PRs: single Node version. Push: full matrix. - node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[22, 24, 25]') }} + # Etherpad requires Node >= 24 (see package.json engines.node). + node: ${{ fromJSON('[24]') }} steps: - diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 0a349e091..81029f00c 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -42,7 +42,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite @@ -114,7 +114,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile @@ -190,7 +190,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile @@ -291,7 +291,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile diff --git a/.github/workflows/handleRelease.yml b/.github/workflows/handleRelease.yml index a2a2bac2e..21189a1e6 100644 --- a/.github/workflows/handleRelease.yml +++ b/.github/workflows/handleRelease.yml @@ -42,7 +42,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile diff --git a/.github/workflows/installer-test.yml b/.github/workflows/installer-test.yml index d77c277a1..36634a37a 100644 --- a/.github/workflows/installer-test.yml +++ b/.github/workflows/installer-test.yml @@ -42,7 +42,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 - name: Pre-install pnpm (avoid sudo prompt in the installer) run: npm install -g pnpm @@ -50,8 +50,8 @@ jobs: - name: Run bin/installer.sh against this commit env: ETHERPAD_DIR: ${{ runner.temp }}/etherpad-installer-test - ETHERPAD_REPO: ${{ github.server_url }}/${{ github.repository }}.git - ETHERPAD_BRANCH: ${{ github.head_ref || github.ref_name }} + ETHERPAD_REPO: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.clone_url || format('{0}/{1}.git', github.server_url, github.repository) }} + ETHERPAD_BRANCH: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} NO_COLOR: "1" run: sh ./bin/installer.sh @@ -104,7 +104,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 - name: Pre-install pnpm run: npm install -g pnpm @@ -113,8 +113,8 @@ jobs: shell: pwsh env: ETHERPAD_DIR: ${{ runner.temp }}\etherpad-installer-test - ETHERPAD_REPO: ${{ github.server_url }}/${{ github.repository }}.git - ETHERPAD_BRANCH: ${{ github.head_ref || github.ref_name }} + ETHERPAD_REPO: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.clone_url || format('{0}/{1}.git', github.server_url, github.repository) }} + ETHERPAD_BRANCH: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} NO_COLOR: "1" run: ./bin/installer.ps1 diff --git a/.github/workflows/load-test.yml b/.github/workflows/load-test.yml index a3be27451..29311e093 100644 --- a/.github/workflows/load-test.yml +++ b/.github/workflows/load-test.yml @@ -39,7 +39,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite @@ -77,7 +77,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install etherpad-load-test @@ -140,7 +140,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite diff --git a/.github/workflows/perform-type-check.yml b/.github/workflows/perform-type-check.yml index af155c327..b3aeb697c 100644 --- a/.github/workflows/perform-type-check.yml +++ b/.github/workflows/perform-type-check.yml @@ -39,7 +39,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile diff --git a/.github/workflows/rate-limit.yml b/.github/workflows/rate-limit.yml index 53f0448e6..c08e6f224 100644 --- a/.github/workflows/rate-limit.yml +++ b/.github/workflows/rate-limit.yml @@ -42,7 +42,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm - @@ -58,11 +58,36 @@ jobs: name: run docker images run: | docker run --name etherpad-docker -p 9000:9001 --rm --network ep_net --ip 172.23.42.2 -e 'TRUST_PROXY=true' epl-debian-slim & - docker run -p 8081:80 --rm --network ep_net --ip 172.23.42.1 -d nginx-latest + docker run --name nginx-docker -p 8081:80 --rm --network ep_net --ip 172.23.42.1 -d nginx-latest docker run --rm --network ep_net --ip 172.23.42.3 --name anotherip -dt anotherip - name: install dependencies and create symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile + - + # The etherpad container is started in the background above; without + # this wait the test step can hit nginx before etherpad is listening + # and nginx returns 502, failing the run on a cold cache. Poll the + # nginx-proxied endpoint (which is also what the test hits) until it + # stops returning 5xx. + name: Wait for etherpad behind nginx to be ready + run: | + # ~60s budget: 30 iterations × (1s curl timeout + 1s sleep). + # Cold-start of the etherpad container is well under that. + for i in $(seq 1 30); do + if curl -fsS -o /dev/null --max-time 1 http://127.0.0.1:8081/; then + echo "etherpad is ready behind nginx" + exit 0 + fi + sleep 1 + done + echo "ERROR: etherpad behind nginx did not become ready in time" + echo "--- docker ps ---" + docker ps -a || true + echo "--- nginx-docker logs ---" + docker logs nginx-docker || true + echo "--- etherpad-docker logs ---" + docker logs etherpad-docker || true + exit 1 - name: run rate limit test run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 583067226..c4d2b5907 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: pnpm cache-dependency-path: etherpad/pnpm-lock.yaml - name: Install dependencies ether.github.com diff --git a/.github/workflows/releaseEtherpad.yml b/.github/workflows/releaseEtherpad.yml index 60d7ce7f5..a30c0c474 100644 --- a/.github/workflows/releaseEtherpad.yml +++ b/.github/workflows/releaseEtherpad.yml @@ -17,9 +17,8 @@ jobs: - uses: actions/setup-node@v6 with: # OIDC trusted publishing needs npm >= 11.5.1, which requires - # Node >= 22.9.0. setup-node's `22` resolves to the latest - # 22.x, which satisfies that. - node-version: 22 + # Node >= 22.9.0. Node 24 satisfies that and matches the rest of CI. + node-version: 24 registry-url: https://registry.npmjs.org/ - name: Upgrade npm to >=11.5.1 (required for trusted publishing) run: npm install -g npm@latest diff --git a/.github/workflows/update-plugins.yml b/.github/workflows/update-plugins.yml index ca9b01844..22c1976b6 100644 --- a/.github/workflows/update-plugins.yml +++ b/.github/workflows/update-plugins.yml @@ -26,7 +26,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 - name: Install bin dependencies working-directory: ./bin diff --git a/.github/workflows/upgrade-from-latest-release.yml b/.github/workflows/upgrade-from-latest-release.yml index 00d2291ec..432e94db7 100644 --- a/.github/workflows/upgrade-from-latest-release.yml +++ b/.github/workflows/upgrade-from-latest-release.yml @@ -27,14 +27,17 @@ jobs: strategy: fail-fast: false matrix: - # PRs: single Node version. Push: full matrix. - node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[22, 24, 25]') }} + # Etherpad requires Node >= 24 (see package.json engines.node). + node: ${{ fromJSON('[24]') }} steps: - name: Check out latest release uses: actions/checkout@v6 with: - ref: develop #FIXME change to master when doing release + fetch-depth: 0 + - + name: Check out latest release tag + run: git checkout "$(git tag --list 'v*' --sort=-version:refname | head -n1)" - uses: actions/cache@v5 name: Cache pnpm store with: @@ -84,13 +87,10 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile - # Because actions/checkout@v6 is called with "ref: master" and without - # "fetch-depth: 0", the local clone does not have the ${GITHUB_SHA} - # commit. Fetch ${GITHUB_REF} to get the ${GITHUB_SHA} commit. Note that a - # plain "git fetch" only fetches "normal" references (refs/heads/* and - # refs/tags/*), and for pull requests none of the normal references - # include ${GITHUB_SHA}, so we have to explicitly tell Git to fetch - # ${GITHUB_REF}. + # The job starts from the latest release tag, so fetch the pull-request + # ref explicitly before checking out ${GITHUB_SHA}. A plain "git fetch" + # only brings "normal" references (refs/heads/* and refs/tags/*), and for + # pull requests none of those include the synthetic merge commit. - name: Fetch the new Git commits run: git fetch --depth=1 origin "${GITHUB_REF}" diff --git a/.gitignore b/.gitignore index 098074ad2..99e10ca74 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,11 @@ stage/ prime/ .craft/ *.snap + +# Generated by `pnpm --filter admin gen:api` from src/node/hooks/express/openapi.ts. +# Regenerated by build/test/dev scripts; not committed. +/admin/src/api/schema.d.ts +/admin/src/api/version.ts + +# Local git worktrees used by /release-review and similar workflows. +/.worktrees/ diff --git a/AGENTS.MD b/AGENTS.MD index 58e02a1d2..5a5fbc4e9 100644 --- a/AGENTS.MD +++ b/AGENTS.MD @@ -46,6 +46,25 @@ pnpm --filter ep_etherpad-lite run prod # Start production server - **Comments:** Provide clear comments for complex logic only. - **Backward Compatibility:** Always ensure compatibility with older versions of the database and configuration files. +### Internationalisation (i18n) — Mandatory +- **Every user-facing string MUST go through i18n.** That means JSX text, `placeholder`, `title`, `aria-label`, `alt`, toast/alert titles, `