diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f32093ac4..041f7c1a9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -30,8 +30,7 @@ If applicable, add screenshots to help explain your problem. - OS: [e.g., Ubuntu 20.04] - Node.js version (`node --version`): - npm version (`npm --version`): - - Is the server free of plugins: - - Are you using any abstraction IE docker? + - Is the server free of plugins: **Desktop (please complete the following information):** - OS: [e.g. iOS] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e7fcee26f..1e7ac79ed 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,11 +17,4 @@ updates: open-pull-requests-limit: 30 groups: dev-dependencies: - dependency-type: "development" - cooldown: - default-days: 1 # fallback for anything not covered below - semver-major-days: 7 - semver-minor-days: 3 - semver-patch-days: 1 - include: - - "*" + dependency-type: "development" \ No newline at end of file diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index 64ba0e90c..e3cbf9365 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -27,13 +27,13 @@ jobs: strategy: fail-fast: false matrix: - # Etherpad requires Node >= 24 (see package.json engines.node). - node: ${{ fromJSON('[24]') }} + # PRs: test on latest Node only. Push to develop: full matrix. + node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[20, 22, 24]') }} steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -43,6 +43,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Use Node.js uses: actions/setup-node@v6 @@ -51,7 +52,7 @@ jobs: cache: pnpm - name: Install libreoffice - uses: awalsh128/cache-apt-pkgs-action@v1.6.3 + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 with: packages: libreoffice libreoffice-pdfimport version: 1.0 @@ -84,12 +85,12 @@ jobs: strategy: fail-fast: false matrix: - node: ${{ fromJSON('[24]') }} + node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[20, 22, 24]') }} steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -99,6 +100,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Use Node.js uses: actions/setup-node@v6 @@ -107,7 +109,7 @@ jobs: cache: pnpm - name: Install libreoffice - uses: awalsh128/cache-apt-pkgs-action@v1.6.3 + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 with: packages: libreoffice libreoffice-pdfimport version: 1.0 @@ -124,12 +126,10 @@ jobs: ep_align ep_author_hover ep_cursortrace - ep_font_color ep_font_size - ep_hash_auth ep_headings2 ep_markdown - ep_guest + ep_readonly_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript @@ -150,26 +150,14 @@ jobs: strategy: fail-fast: false matrix: - # Etherpad requires Node >= 24 (see package.json engines.node). - # Windows pins Node 24.16.0, not the runner-default 24.15.0. Node 24.15.0's - # bundled libuv (1.51.0) has a stack buffer overrun in the Windows TCP-connect - # path (uv__tcp_connect), proven by a /GS __fastfail full-memory dump under the - # backend suite's heavy localhost connection churn -- the long-standing Windows - # backend silent-ELIFECYCLE flake (memory corruption, so no JS/Node - # observability). Bisect: 24.15.0 crashes (4/4 on 127.0.0.1), 24.16.0 - # (libuv 1.52.1) is clean (0/8). Stays on the 24 LTS line. Pin explicitly - # because setup-node's default check-latest:false reuses the runner's - # pre-cached 24.15.0 for a bare "24". - # Tracking: https://github.com/nodejs/node/issues/63620 — drop this pin - # back to plain "24" once the fix is across the supported 24.x baseline. - node: ${{ fromJSON('["24.16.0"]') }} + node: [20, 22, 24] name: Windows without plugins runs-on: windows-latest steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -179,6 +167,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Use Node.js uses: actions/setup-node@v6 @@ -198,11 +187,8 @@ 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 - # --exit makes mocha call process.exit() after the run so a leaked handle - # cannot hang the job on Windows. - run: pnpm test -- --exit + run: pnpm test - name: Run the new vitest tests working-directory: src run: pnpm run test:vitest @@ -215,27 +201,15 @@ jobs: strategy: fail-fast: false matrix: - # Etherpad requires Node >= 24 (see package.json engines.node). - # Windows pins Node 24.16.0, not the runner-default 24.15.0. Node 24.15.0's - # bundled libuv (1.51.0) has a stack buffer overrun in the Windows TCP-connect - # path (uv__tcp_connect), proven by a /GS __fastfail full-memory dump under the - # backend suite's heavy localhost connection churn -- the long-standing Windows - # backend silent-ELIFECYCLE flake (memory corruption, so no JS/Node - # observability). Bisect: 24.15.0 crashes (4/4 on 127.0.0.1), 24.16.0 - # (libuv 1.52.1) is clean (0/8). Stays on the 24 LTS line. Pin explicitly - # because setup-node's default check-latest:false reuses the runner's - # pre-cached 24.15.0 for a bare "24". - # Tracking: https://github.com/nodejs/node/issues/63620 — drop this pin - # back to plain "24" once the fix is across the supported 24.x baseline. - node: ${{ fromJSON('["24.16.0"]') }} + node: [20, 22, 24] name: Windows with Plugins runs-on: windows-latest steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -245,6 +219,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Use Node.js uses: actions/setup-node@v6 @@ -263,12 +238,10 @@ jobs: ep_align ep_author_hover ep_cursortrace - ep_font_color ep_font_size - ep_hash_auth ep_headings2 ep_markdown - ep_guest + ep_readonly_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript @@ -292,11 +265,8 @@ 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 - # --exit makes mocha call process.exit() after the run so a leaked handle - # cannot hang the job on Windows. - run: pnpm test -- --exit + run: pnpm test - 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 f49cf2974..84cb1bb00 100644 --- a/.github/workflows/build-and-deploy-docs.yml +++ b/.github/workflows/build-and-deploy-docs.yml @@ -1,50 +1,46 @@ -# Workflow for building and deploying the VitePress site to GitHub Pages. -# Build runs on every push to develop and on every PR that touches docs (so -# a build regression is caught at review time instead of breaking develop -# after merge — see #7640). Deploy runs only on push: the github-pages -# environment has protection rules that reject PR refs, and a PR build -# never produced an artifact to deploy anyway. +# Workflow for deploying static content to GitHub Pages name: Deploy Docs to GitHub Pages on: + # Runs on pushes targeting the default branch push: branches: ["develop"] paths: - - doc/** - - .github/workflows/build-and-deploy-docs.yml - pull_request: - paths: - - doc/** - - .github/workflows/build-and-deploy-docs.yml + - doc/** # Only run workflow when changes are made to the doc directory + # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write packages: read -# Allow only one concurrent deployment, skipping runs queued between the run -# in-progress and latest queued. Do NOT cancel in-progress runs — production -# deployments are allowed to complete. +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "pages" cancel-in-progress: false jobs: - build: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ~/.pnpm-store key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - - uses: actions/cache@v6 + - uses: actions/cache@v5 name: Cache vitepress build with: path: doc/.vitepress/cache @@ -54,16 +50,9 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - # Pin Node so the build does not silently fall back to whatever the - # runner image ships with. The repo declares engines.node >=24.0.0. - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Setup Pages - if: github.event_name == 'push' uses: actions/configure-pages@v6 - name: Install dependencies run: pnpm install --frozen-lockfile @@ -71,26 +60,12 @@ jobs: working-directory: doc run: pnpm run docs:build env: - GITHUB_PAGES: ${{ github.event_name == 'push' && 'true' || '' }} COMMIT_REF: ${{ github.sha }} - name: Upload artifact - if: github.event_name == 'push' uses: actions/upload-pages-artifact@v5 with: + # Upload entire repository path: './doc/.vitepress/dist' - - # Deploy to GitHub Pages on push to develop only. Kept as a separate job - # because the github-pages environment's protection rules reject any - # non-develop ref (including PR merge refs), which used to fail the entire - # workflow at job-creation time before any build step could run. - deploy: - needs: build - if: github.event_name == 'push' - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 37d2d28b7..c6c10937b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/deb-package.yml b/.github/workflows/deb-package.yml deleted file mode 100644 index 51bad3b93..000000000 --- a/.github/workflows/deb-package.yml +++ /dev/null @@ -1,493 +0,0 @@ -name: Debian package -on: - push: - tags: - # Actions tag filters are globs, not regex. Mirrors handleRelease.yml. - - 'v*.*.*' - - 'v*.*.*-*' - branches: [develop] - paths: - - 'packaging/**' - - '.github/workflows/deb-package.yml' - - 'src/package.json' - - 'pnpm-lock.yaml' - - 'src/node/server.ts' - - 'src/node/utils/run_cmd.ts' - - 'src/static/js/pluginfw/**' - - 'settings.json.template' - # Also build + smoke-test the package on PRs that touch the production - # footprint. The smoke step boots the packaged server and waits for /health, - # which is the only check that catches "server starts then exits before - # binding the port" startup regressions (e.g. a dependency bump whose change - # lets the event loop drain mid-boot). Previously this workflow ran only on - # push to develop, so such a regression was caught *after* merge — by which - # point develop was already red. Running it pre-merge blocks the PR instead. - # The release/apt-publish jobs are tag-guarded, so PRs run the build job only. - pull_request: - paths: - - 'packaging/**' - - '.github/workflows/deb-package.yml' - - 'src/package.json' - - 'pnpm-lock.yaml' - - 'src/node/server.ts' - - 'src/node/utils/run_cmd.ts' - - 'src/static/js/pluginfw/**' - - 'settings.json.template' - workflow_dispatch: - inputs: - ref: - description: 'Git ref to package (defaults to current)' - required: false - -# Default to read-only for the workflow; the release job opts in to -# `contents: write` for itself only. Build jobs (which run on every PR) -# don't need write and shouldn't have it. -permissions: - contents: read - -env: - NFPM_VERSION: v2.43.0 - -jobs: - build: - name: Build .deb (${{ matrix.arch }}) - runs-on: ${{ matrix.runner }} - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - runner: ubuntu-latest - - arch: arm64 - runner: ubuntu-24.04-arm - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - ref: ${{ inputs.ref || github.ref }} - - - uses: pnpm/action-setup@v6 - - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version: '24' - cache: pnpm - - - name: Resolve version - id: v - # Runs after setup-node so `node` is guaranteed available on - # any runner image (some don't ship it preinstalled). - run: | - if [ "${GITHUB_REF_TYPE}" = "tag" ]; then - VERSION="${GITHUB_REF_NAME#v}" - else - VERSION="$(node -p "require('./package.json').version")" - fi - echo "version=${VERSION}" >>"$GITHUB_OUTPUT" - echo "Packaging version: ${VERSION}" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build UI + admin - run: pnpm run build:etherpad - - - name: Install nfpm - run: | - set -euo pipefail - NFPM_ARCH=amd64 - [ "${{ matrix.arch }}" = "arm64" ] && NFPM_ARCH=arm64 - NFPM_DEB="nfpm_${NFPM_VERSION#v}_${NFPM_ARCH}.deb" - BASE="https://github.com/goreleaser/nfpm/releases/download/${NFPM_VERSION}" - curl -fsSL -o "/tmp/${NFPM_DEB}" "${BASE}/${NFPM_DEB}" - curl -fsSL -o /tmp/nfpm-checksums.txt "${BASE}/checksums.txt" - # Verify upstream artifact before sudo dpkg -i (defense in depth - # against a tampered release asset). - ( cd /tmp && grep " ${NFPM_DEB}\$" nfpm-checksums.txt | sha256sum -c - ) - sudo dpkg -i "/tmp/${NFPM_DEB}" - - - name: Stage tree for packaging - run: | - set -eux - STAGE=staging/opt/etherpad - mkdir -p "${STAGE}" - - # Production footprint = src/ + bin/ + node_modules/ + metadata. - cp -a src bin package.json pnpm-workspace.yaml README.md LICENSE \ - node_modules "${STAGE}/" - - # Make pnpm-workspace.yaml production-only (same trick Dockerfile uses). - printf 'packages:\n - src\n - bin\n' > "${STAGE}/pnpm-workspace.yaml" - - mkdir -p packaging/etc - cp settings.json.template packaging/etc/settings.json.dist - - # Purge test fixtures and dev caches from node_modules to shrink size. - find "${STAGE}/node_modules" -type d \ - \( -name test -o -name tests -o -name '__tests__' \ - -o -name example -o -name examples -o -name docs \) \ - -prune -exec rm -rf {} + 2>/dev/null || true - find "${STAGE}/node_modules" -type f \ - \( -name '*.md' -o -name '*.ts.map' -o -name '*.map' \ - -o -name 'CHANGELOG*' -o -name 'HISTORY*' \) \ - -delete 2>/dev/null || true - - - name: Build .deb - env: - VERSION: ${{ steps.v.outputs.version }} - ARCH: ${{ matrix.arch }} - run: | - mkdir -p dist - nfpm package --packager deb -f packaging/nfpm.yaml --target dist/ - - - name: Smoke-test the package (amd64 only) - if: matrix.arch == 'amd64' - run: | - set -eux - # Ubuntu's default apt nodejs is 18 — too old for our - # `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 - # GitHub runner images often ship a NodeSource node_20.x list - # preinstalled (sometimes as a .sources deb822 file). Wipe any - # 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 (>= 24)`. - sudo rm -f /etc/apt/sources.list.d/nodesource.list \ - /etc/apt/sources.list.d/nodesource.sources \ - /etc/apt/preferences.d/nodesource \ - /etc/apt/preferences.d/nodesource.pref - KEYRING=/usr/share/keyrings/nodesource.gpg - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ - | sudo gpg --dearmor --yes -o "${KEYRING}" - echo "deb [signed-by=${KEYRING}] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \ - | sudo tee /etc/apt/sources.list.d/nodesource.list - # Pin nodejs to the 24.x line so neither Ubuntu's noble-updates - # 20.x nor any leftover NodeSource cache can win the resolver. - printf 'Package: nodejs\nPin: version %s.*\nPin-Priority: 1001\n' "${NODE_MAJOR}" \ - | sudo tee /etc/apt/preferences.d/nodesource >/dev/null - sudo apt-get update - # Pin the major explicitly on the install line as a belt-and- - # suspenders guard against any preference file being ignored. - sudo apt-get install -y "nodejs=${NODE_MAJOR}.*" - # Sanity check before invoking dpkg so the failure mode is obvious - # if pinning ever regresses again. - installed_major=$(dpkg-query -W -f='${Version}' nodejs | cut -d. -f1) - if [ "${installed_major}" != "${NODE_MAJOR}" ]; then - echo "::error::Expected nodejs major ${NODE_MAJOR}, got ${installed_major}" - apt-cache policy nodejs || true - exit 1 - fi - sudo dpkg -i dist/*.deb || sudo apt-get install -f -y - # /etc/etherpad is mode 0750 root:etherpad on purpose (DB creds - # live here) so the runner user can't read into it. Each check - # that crosses /etc/etherpad or /var/lib/etherpad runs under sudo. - sudo test -x /usr/bin/etherpad - sudo test -f /etc/etherpad/settings.json - sudo test -L /opt/etherpad/settings.json - sudo test -L /opt/etherpad/var - [ "$(sudo readlink /opt/etherpad/var)" = "/var/lib/etherpad/var" ] - # 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 - sudo grep -q '"dbType": "sqlite"' /etc/etherpad/settings.json - id etherpad - systemctl cat etherpad.service - 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 - # Attach logs so the failing run is diagnosable. - sudo journalctl -u etherpad --no-pager -n 200 || true - 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 - with: - name: etherpad-${{ steps.v.outputs.version }}-${{ matrix.arch }}-deb - path: dist/*.deb - if-no-files-found: error - - release: - name: Attach to GitHub Release - needs: build - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/download-artifact@v8 - with: - path: dist - pattern: etherpad-*-deb - merge-multiple: true - - name: Create stable "latest" aliases - run: | - set -euo pipefail - # Publish stable filenames alongside the versioned ones so users - # can curl https://github.com/.../releases/latest/download/etherpad-latest_amd64.deb - # without knowing the version. - for f in dist/etherpad_*_amd64.deb; do - [ -e "$f" ] && cp "$f" dist/etherpad-latest_amd64.deb - done - for f in dist/etherpad_*_arm64.deb; do - [ -e "$f" ] && cp "$f" dist/etherpad-latest_arm64.deb - done - ls -la dist/ - - name: Attach .deb files to release - uses: softprops/action-gh-release@v3 - with: - files: dist/*.deb - fail_on_unmatched_files: true - - apt-publish: - # Generates a signed apt repository (Packages.gz + Release/InRelease) - # from the .deb artefacts and cross-pushes it into ether/ether.github.com - # under public/apt/. The Next.js site that powers etherpad.org serves - # public/ verbatim, so the repo lands at: - # - # https://etherpad.org/apt/ (apt repo root) - # https://etherpad.org/key.asc (public key for `apt-key`/keyring) - # - # Tag pushes go into the `stable` suite. Required secrets: - # APT_SIGNING_KEY ASCII-armoured private key for the Etherpad APT - # Repository keypair (fingerprint - # 6953FA0C6431F30347D65B03AF0CD687D51A6E63). - # SITE_DEPLOY_KEY SSH private key matching a deploy key with write - # access on ether/ether.github.com. The site repo - # holds the public half. - name: Publish apt repository to etherpad.org - needs: release - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - steps: - - name: Checkout etherpad source (for packaging/apt/key.asc) - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - - name: Configure deploy key for ether/ether.github.com - env: - SITE_DEPLOY_KEY: ${{ secrets.SITE_DEPLOY_KEY }} - run: | - set -euo pipefail - if [ -z "${SITE_DEPLOY_KEY:-}" ]; then - echo "::error::SITE_DEPLOY_KEY secret is not set on ether/etherpad." - echo "::error::Add an SSH deploy key with write access on ether/ether.github.com and store the private key here." - exit 1 - fi - mkdir -p ~/.ssh - chmod 700 ~/.ssh - printf '%s\n' "${SITE_DEPLOY_KEY}" > ~/.ssh/id_deploy - chmod 600 ~/.ssh/id_deploy - ssh-keyscan -t ed25519,rsa github.com >> ~/.ssh/known_hosts 2>/dev/null - cat > ~/.ssh/config <<'CFG' - Host github.com - HostName github.com - User git - IdentityFile ~/.ssh/id_deploy - IdentitiesOnly yes - CFG - chmod 600 ~/.ssh/config - - - name: Clone ether/ether.github.com - run: git clone --depth 1 git@github.com:ether/ether.github.com.git site - - - uses: actions/download-artifact@v8 - with: - path: dist - pattern: etherpad-*-deb - merge-multiple: true - - - name: Install apt-utils + gpg - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq apt-utils gnupg - - - name: Import signing key - env: - APT_SIGNING_KEY: ${{ secrets.APT_SIGNING_KEY }} - run: | - set -euo pipefail - if [ -z "${APT_SIGNING_KEY:-}" ]; then - echo "::error::APT_SIGNING_KEY secret is not set; cannot sign Release file." - exit 1 - fi - export GNUPGHOME="$(mktemp -d)" - chmod 700 "${GNUPGHOME}" - echo "GNUPGHOME=${GNUPGHOME}" >>"${GITHUB_ENV}" - printf '%s' "${APT_SIGNING_KEY}" | gpg --batch --import - # Sanity check: expected long key id. - gpg --list-secret-keys --keyid-format=long | grep -q AF0CD687D51A6E63 - - - name: Generate apt repo metadata - run: | - set -euo pipefail - REPO=site/public/apt - SUITE=stable - COMP=main - # Wipe any previous repo state so removed versions don't linger - # in pool/. Packages.gz is regenerated from whatever is in pool/ - # right now, so this is the simplest correct option — alternative - # is per-version diffing which is fragile. - rm -rf "${REPO}" - # We ship one architecture-agnostic suite with per-arch pools. - # Layout: apt/dists//main/binary-{amd64,arm64}/ - for arch in amd64 arm64; do - mkdir -p "${REPO}/pool/main/e/etherpad" "${REPO}/dists/${SUITE}/${COMP}/binary-${arch}" - done - # Drop the .debs into pool/. The leading-digit pattern - # excludes the etherpad-latest_*.deb filename aliases the - # release job stages — apt resolves by package name + version, - # not filename, so including the alias would create duplicate - # Packages entries. (Also defends against any future alias that - # accidentally lands on dist/etherpad__.deb.) - shopt -s nullglob - DEBS=(dist/etherpad_[0-9]*_amd64.deb dist/etherpad_[0-9]*_arm64.deb) - shopt -u nullglob - # Refuse to publish nothing. Without this, a missing or renamed - # build artefact would wipe site/public/apt and push an empty, - # signed apt repo — breaking `apt update` for every existing - # subscriber until the next successful release. - if [ ${#DEBS[@]} -lt 2 ]; then - echo "::error::Expected per-arch .deb artifacts in dist/, found ${#DEBS[@]}: ${DEBS[*]:-}" - echo "::error::Refusing to publish a partial / empty apt repository." - exit 1 - fi - cp "${DEBS[@]}" "${REPO}/pool/main/e/etherpad/" - # Generate per-arch Packages files. - ( - cd "${REPO}" - for arch in amd64 arm64; do - apt-ftparchive --arch "${arch}" packages pool/main \ - > "dists/${SUITE}/${COMP}/binary-${arch}/Packages" - gzip -kf "dists/${SUITE}/${COMP}/binary-${arch}/Packages" - done - # Generate the suite's Release file. The heredoc lines - # MUST start at column 1 — apt parsers reject leading - # whitespace on header fields (RFC 822 / Debian control). - # printf is used over a heredoc to make that contract - # impossible to lose to a future re-indent. - printf '%s\n' \ - "Origin: Etherpad" \ - "Label: Etherpad" \ - "Suite: ${SUITE}" \ - "Codename: ${SUITE}" \ - "Architectures: amd64 arm64" \ - "Components: ${COMP}" \ - "Description: Etherpad official apt repository (${SUITE} channel)" \ - "Date: $(date -Ru)" \ - > "dists/${SUITE}/Release" - # apt-ftparchive appends checksums. - apt-ftparchive release "dists/${SUITE}" >> "dists/${SUITE}/Release" - # Sign it (clear-signed InRelease + detached Release.gpg). - gpg --default-key AF0CD687D51A6E63 --batch --yes \ - --clearsign -o "dists/${SUITE}/InRelease" "dists/${SUITE}/Release" - gpg --default-key AF0CD687D51A6E63 --batch --yes \ - -abs -o "dists/${SUITE}/Release.gpg" "dists/${SUITE}/Release" - ) - - - name: Stage public key alongside the site - run: | - # Users curl this to add our key to their keyring before apt update. - cp packaging/apt/key.asc site/public/key.asc - - - name: Commit + push to ether/ether.github.com - env: - TAG: ${{ github.ref_name }} - run: | - set -euo pipefail - cd site - git -c user.email=actions@github.com -c user.name='github-actions[bot]' \ - add public/apt public/key.asc - if git diff --cached --quiet; then - echo "No apt-repo changes to publish." - exit 0 - fi - git -c user.email=actions@github.com -c user.name='github-actions[bot]' \ - commit -m "apt: publish Etherpad ${TAG}" - git push origin HEAD:master diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index da2ac6321..774c7434a 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v7 + uses: actions/checkout@v6 - name: 'Dependency Review' - uses: actions/dependency-review-action@v5 + uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index cca0feb45..35811cad5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: path: etherpad - @@ -42,7 +42,7 @@ jobs: tags: ${{ env.TEST_TAG }} cache-from: type=gha cache-to: type=gha,mode=max - - uses: actions/cache@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -52,27 +52,13 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - # Repo is checked out into ./etherpad, so the action can't find - # packageManager in a root package.json. - package_json_file: etherpad/package.json - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - cache-dependency-path: etherpad/pnpm-lock.yaml - name: Test working-directory: etherpad run: | - # ADMIN_PASSWORD provisions settings.json.docker's admin user so - # the adminSettings_7819 container spec can authenticate against - # /admin and the /settings socket. pad.js doesn't touch /admin - # so the existing API specs are unaffected. - docker run --rm -d -p 9001:9001 \ - -e ADMIN_PASSWORD=changeme1 \ - --name test ${{ env.TEST_TAG }} + docker run --rm -d -p 9001:9001 --name test ${{ env.TEST_TAG }} ./bin/installDeps.sh docker logs -f test & while true; do @@ -85,148 +71,9 @@ jobs: esac done (cd src && pnpm run test-container) - # Regression for #7819. adminSettings_7819.ts saves a marker via - # the admin /settings socket and intentionally leaves it on - # disk. We assert here that the save actually hit the file - # (mocha only sees the next `load` reply — this catches a - # scenario where the load is served from cache and the file - # never actually changed). - docker exec test grep -q persist-marker-7819 /opt/etherpad-lite/settings.json || { - echo "[#7819] socket-driven save did NOT reach /opt/etherpad-lite/settings.json on disk" - docker exec test cat /opt/etherpad-lite/settings.json | head -50 - exit 1 - } - # Now prove the on-disk file survives an in-place container - # restart. This is the scenario a docker-compose user with - # `restart: always` hits on every host reboot. - docker restart test >/dev/null - for i in $(seq 1 60); do - status=$(docker container inspect -f '{{.State.Health.Status}}' test 2>/dev/null) || { docker logs test; exit 1; } - case ${status} in - healthy) break;; - starting) sleep 2;; - *) docker logs test; exit 1;; - esac - done - docker exec test grep -q persist-marker-7819 /opt/etherpad-lite/settings.json || { - echo "[#7819 REGRESSION] settings.json was reset on docker restart — ep_oauth block vanished" - docker logs test - exit 1 - } - 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 - - - - # Regression test: Etherpad container must boot without external - # network access (e.g., air-gapped host or restrictive runtime policy). - name: Regression — boot with disabled network (--network none) - working-directory: etherpad - run: | - docker run --rm -d \ - --network none \ - --name test-offline ${{ env.TEST_TAG }} - docker logs -f test-offline & - ok=0 - for i in $(seq 1 60); do - status=$(docker container inspect -f '{{.State.Health.Status}}' test-offline 2>/dev/null) || { - echo "container exited prematurely while offline" - docker logs test-offline || true - break - } - case ${status} in - healthy) ok=1; echo "offline boot healthy after $((i*2))s"; break;; - starting) sleep 2;; - *) echo "unexpected status: ${status}"; docker logs test-offline || true; break;; - esac - done - docker rm -f test-offline >/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@v7 - 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 - # run it on pushes to develop (and tagged release pushes), not on every - # feature-branch PR. - if: github.event_name == 'push' runs-on: ubuntu-latest permissions: contents: read @@ -261,7 +108,7 @@ jobs: steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: path: etherpad - @@ -358,7 +205,7 @@ jobs: steps: - name: Check out - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: path: etherpad - @@ -416,7 +263,7 @@ jobs: enable-url-completion: true - name: Check out ether-charts if: github.ref == 'refs/heads/develop' - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: path: ether-charts repository: ether/ether-charts @@ -425,13 +272,13 @@ jobs: if: success() && github.ref == 'refs/heads/develop' working-directory: ether-charts run: | - sed -i 's/tag: ".*"/tag: "${{ steps.build-docker.outputs.digest }}"/' charts/etherpad/values-dev.yaml + sed -i 's/tag: ".*"/tag: "${{ steps.build-docker.outputs.digest }}"/' 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 charts/etherpad/values-dev.yaml + git add values-dev.yaml git commit -m 'Update develop image tag' git push diff --git a/.github/workflows/downstream-smoke.yml b/.github/workflows/downstream-smoke.yml deleted file mode 100644 index fc42cfe3e..000000000 --- a/.github/workflows/downstream-smoke.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Downstream smoke - -# Boots a real Etherpad from the PR and verifies the separate downstream clients -# (Rust terminal editor, Node CLI, desktop/mobile) still round-trip against it. -# Phase 1 lands the boot/healthcheck/self-check/teardown harness + manifest; the -# per-client matrix activates as each client flips `enabled:true` in clients.json. - -on: - pull_request: - paths-ignore: - - "doc/**" - - "docs/**" - schedule: - - cron: '0 4 * * *' # nightly against the default branch - -permissions: - contents: read - -jobs: - smoke: - name: Boot + downstream clients - runs-on: ubuntu-latest - timeout-minutes: 25 - env: - PNPM_HOME: ~/.pnpm-store - APIKEY: downstream-smoke-key - steps: - - name: Checkout core (PR) - uses: actions/checkout@v7 - - - uses: actions/cache@v6 - name: Cache pnpm store - with: - path: ${{ env.PNPM_HOME }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - uses: pnpm/action-setup@v6 - name: Install pnpm - with: - run_install: false - - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm i --frozen-lockfile - - - name: Boot Etherpad on :9003 (apikey auth) - run: | - # The template ships a literal "port": 9001 and sso auth; rewrite both. - # (PORT env is ignored once the settings file specifies a port.) - sed -e 's#"port": 9001,#"port": 9003,#' \ - -e 's#${AUTHENTICATION_METHOD:sso}#apikey#' \ - settings.json.template > settings.json - # Fail fast if the template format drifted and sed silently no-op'd. - grep -q '"port": 9003,' settings.json \ - || { echo "::error::port rewrite failed — settings.json.template format changed"; exit 1; } - grep -q '"authenticationMethod": "apikey"' settings.json \ - || { echo "::error::auth rewrite failed — settings.json.template format changed"; exit 1; } - printf '%s' "$APIKEY" > APIKEY.txt - pnpm run prod > /tmp/ep.log 2>&1 & - echo $! > /tmp/ep.pid - echo "booted pid $(cat /tmp/ep.pid)" - - - name: Wait for healthcheck - run: | - for i in $(seq 1 60); do - if curl -fsS "http://localhost:9003/api/" >/dev/null 2>&1; then - echo "server up after ${i} tries"; exit 0 - fi - sleep 2 - done - echo "::error::server did not come up"; tail -50 /tmp/ep.log; exit 1 - - - name: Self-check — authenticated create + read roundtrip - run: | - K="$APIKEY" - curl -fsS "http://localhost:9003/api/1/createPad?apikey=${K}&padID=smoke&text=hi%0A" | tee /tmp/create.json - grep -q '"code":0' /tmp/create.json - curl -fsS "http://localhost:9003/api/1/getText?apikey=${K}&padID=smoke" | tee /tmp/get.json - grep -q '"text":"hi' /tmp/get.json - - - name: Generate canonical wire-vectors - run: cd src && pnpm run vectors:gen - - # Rust toolchain for the `rust`-kind client (etherpad-pad). Other kinds - # use the node+pnpm already set up above. - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Run enabled downstream clients - env: - SMOKE_URL: http://localhost:9003 - SMOKE_APIKEY: ${{ env.APIKEY }} - run: bash src/tests/downstream/run-clients.sh - - - name: Teardown (by PID, never pkill) - if: always() - run: | - if [ -f /tmp/ep.pid ]; then - kill "$(cat /tmp/ep.pid)" 2>/dev/null || true - echo "killed $(cat /tmp/ep.pid)" - fi diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index 5ff9dea29..dff5fc24d 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -21,14 +21,14 @@ jobs: strategy: fail-fast: false matrix: - # Etherpad requires Node >= 24 (see package.json engines.node). - node: ${{ fromJSON('[24]') }} + # PRs: single Node version. Push: full matrix. + node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[20, 22, 24]') }} steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -38,6 +38,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Use Node.js uses: actions/setup-node@v6 @@ -48,7 +49,7 @@ jobs: name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm i - name: Cache Playwright browsers - uses: actions/cache@v6 + uses: actions/cache@v5 id: playwright-cache with: path: ~/.cache/ms-playwright diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 0def07b51..c972000b0 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -20,15 +20,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - - uses: actions/cache@v6 + - uses: actions/cache@v5 name: Cache Playwright browsers with: path: ~/.cache/ms-playwright @@ -38,12 +38,8 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile @@ -53,22 +49,17 @@ jobs: - name: Run the frontend tests shell: bash run: | - set -euo pipefail pnpm run prod > /tmp/etherpad-server.log 2>&1 & - # Generous 90s budget so a slow runner (or, in the with-plugins - # variant, plugin boot) doesn't lose the race against the test - # phase. Fail loudly on timeout rather than silently falling - # through to tests against a half-started server. - # --max-time bounds each probe so a stuck server can't make a - # single curl call eat the whole 90s budget. - can_connect() { curl --max-time 3 -sSfo /dev/null http://localhost:9001/; } - for i in $(seq 1 90); do can_connect && break; sleep 1; done - if ! can_connect; then - echo "::error::Etherpad did not respond on :9001 within 90s" - echo "----- server log -----" - tail -n 200 /tmp/etherpad-server.log || true - exit 1 - fi + connected=false + can_connect() { + curl -sSfo /dev/null http://localhost:9001/ || return 1 + connected=true + } + now() { date +%s; } + start=$(now) + while [ $(($(now) - $start)) -le 15 ] && ! can_connect; do + sleep 1 + done cd src pnpm exec playwright install chromium --with-deps pnpm run test-ui --project=chromium @@ -92,15 +83,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - - uses: actions/cache@v6 + - uses: actions/cache@v5 name: Cache Playwright browsers with: path: ~/.cache/ms-playwright @@ -110,12 +101,8 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile - name: Create settings.json @@ -123,22 +110,17 @@ jobs: - name: Run the frontend tests shell: bash run: | - set -euo pipefail pnpm run prod > /tmp/etherpad-server.log 2>&1 & - # Generous 90s budget so a slow runner (or, in the with-plugins - # variant, plugin boot) doesn't lose the race against the test - # phase. Fail loudly on timeout rather than silently falling - # through to tests against a half-started server. - # --max-time bounds each probe so a stuck server can't make a - # single curl call eat the whole 90s budget. - can_connect() { curl --max-time 3 -sSfo /dev/null http://localhost:9001/; } - for i in $(seq 1 90); do can_connect && break; sleep 1; done - if ! can_connect; then - echo "::error::Etherpad did not respond on :9001 within 90s" - echo "----- server log -----" - tail -n 200 /tmp/etherpad-server.log || true - exit 1 - fi + connected=false + can_connect() { + curl -sSfo /dev/null http://localhost:9001/ || return 1 + connected=true + } + now() { date +%s; } + start=$(now) + while [ $(($(now) - $start)) -le 15 ] && ! can_connect; do + sleep 1 + done cd src pnpm exec playwright install firefox --with-deps pnpm run test-ui --project=firefox @@ -155,198 +137,3 @@ jobs: name: playwright-report-firefox path: src/playwright-report/ retention-days: 30 - - # Frontend tests with the same /ether plugin set that backend-tests.yml - # exercises, so a core change that breaks plugin UX is caught in PR CI - # rather than after release. Re-introduces coverage that was lost when - # the workflows were nuked & rebuilt in 2023 (commit cc80db2d3) and the - # backend equivalent was restored without the frontend half. - playwright-chrome-with-plugins: - env: - PNPM_HOME: ~/.pnpm-store - name: Playwright Chrome with plugins - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 - name: Cache pnpm store - with: - path: ${{ env.PNPM_HOME }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - uses: actions/cache@v6 - name: Cache Playwright browsers - with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-playwright-${{ hashFiles('src/package.json', 'pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-playwright- - - uses: pnpm/action-setup@v6 - name: Install pnpm - with: - run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - name: Install all dependencies and symlink for ep_etherpad-lite - run: pnpm install --frozen-lockfile - - name: Install Etherpad plugins - # Same plugin set as backend-tests.yml's withpluginsLinux job, - # MINUS ep_cursortrace. - # - # ep_cursortrace's `aceEditEvent` hook fires on every keyboard - # event (handleClick, handleKeyEvent, idleWorkTimer) and sends a - # cursor-position socket message per call. Under the test - # harness's `writeToPad` bursts (insertText + Enter loops) that - # stream of socket messages saturates the editor's input - # pipeline in Firefox specifically, causing intermittent - # keystroke drops and a long tail of test flakiness. - # - # Bisected via a 4-iteration probe on this branch — see commit - # history of .github/workflows/frontend-tests.yml around the - # PR-7630 timeframe. Tracked for a follow-up fix - # (debounce / throttle in ep_cursortrace's main.js). - run: > - pnpm add -w - ep_align - ep_author_hover - ep_font_color - ep_font_size - ep_hash_auth - ep_headings2 - ep_markdown - ep_guest - ep_set_title_on_pad - ep_spellcheck - ep_subscript_and_superscript - ep_table_of_contents - - name: Create settings.json - run: cp ./src/tests/settings.json settings.json - - name: Run the frontend tests - shell: bash - run: | - set -euo pipefail - pnpm run prod > /tmp/etherpad-server.log 2>&1 & - # Generous 90s budget so a slow runner (or, in the with-plugins - # variant, plugin boot) doesn't lose the race against the test - # phase. Fail loudly on timeout rather than silently falling - # through to tests against a half-started server. - # --max-time bounds each probe so a stuck server can't make a - # single curl call eat the whole 90s budget. - can_connect() { curl --max-time 3 -sSfo /dev/null http://localhost:9001/; } - for i in $(seq 1 90); do can_connect && break; sleep 1; done - if ! can_connect; then - echo "::error::Etherpad did not respond on :9001 within 90s" - echo "----- server log -----" - tail -n 200 /tmp/etherpad-server.log || true - exit 1 - fi - cd src - pnpm exec playwright install chromium --with-deps - WITH_PLUGINS=1 pnpm run test-ui --project=chromium - - name: Upload server log on failure - uses: actions/upload-artifact@v7 - if: failure() - with: - name: server-log-chrome-with-plugins - path: /tmp/etherpad-server.log - retention-days: 7 - - uses: actions/upload-artifact@v7 - if: always() - with: - name: playwright-report-chrome-with-plugins - path: src/playwright-report/ - retention-days: 30 - - playwright-firefox-with-plugins: - env: - PNPM_HOME: ~/.pnpm-store - name: Playwright Firefox with plugins - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 - name: Cache pnpm store - with: - path: ${{ env.PNPM_HOME }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - uses: actions/cache@v6 - name: Cache Playwright browsers - with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-playwright-${{ hashFiles('src/package.json', 'pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-playwright- - - uses: pnpm/action-setup@v6 - name: Install pnpm - with: - run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - name: Install all dependencies and symlink for ep_etherpad-lite - run: pnpm install --frozen-lockfile - - name: Install Etherpad plugins - # See sibling Playwright Chrome with plugins job for the full - # rationale on why ep_cursortrace is excluded from the test - # plugin set. - run: > - pnpm add -w - ep_align - ep_author_hover - ep_font_color - ep_font_size - ep_hash_auth - ep_headings2 - ep_markdown - ep_guest - ep_set_title_on_pad - ep_spellcheck - ep_subscript_and_superscript - ep_table_of_contents - - name: Create settings.json - run: cp ./src/tests/settings.json settings.json - - name: Run the frontend tests - shell: bash - run: | - set -euo pipefail - pnpm run prod > /tmp/etherpad-server.log 2>&1 & - # Generous 90s budget so a slow runner (or, in the with-plugins - # variant, plugin boot) doesn't lose the race against the test - # phase. Fail loudly on timeout rather than silently falling - # through to tests against a half-started server. - # --max-time bounds each probe so a stuck server can't make a - # single curl call eat the whole 90s budget. - can_connect() { curl --max-time 3 -sSfo /dev/null http://localhost:9001/; } - for i in $(seq 1 90); do can_connect && break; sleep 1; done - if ! can_connect; then - echo "::error::Etherpad did not respond on :9001 within 90s" - echo "----- server log -----" - tail -n 200 /tmp/etherpad-server.log || true - exit 1 - fi - cd src - pnpm exec playwright install firefox --with-deps - WITH_PLUGINS=1 pnpm run test-ui --project=firefox - - name: Upload server log on failure - uses: actions/upload-artifact@v7 - if: failure() - with: - name: server-log-firefox-with-plugins - path: /tmp/etherpad-server.log - retention-days: 7 - - uses: actions/upload-artifact@v7 - if: always() - with: - name: playwright-report-firefox-with-plugins - path: src/playwright-report/ - retention-days: 30 diff --git a/.github/workflows/handleRelease.yml b/.github/workflows/handleRelease.yml index 03ca88667..3149d2172 100644 --- a/.github/workflows/handleRelease.yml +++ b/.github/workflows/handleRelease.yml @@ -27,8 +27,8 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -38,12 +38,8 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile - name: Build etherpad diff --git a/.github/workflows/installer-test.yml b/.github/workflows/installer-test.yml index a40cd9db2..d77c277a1 100644 --- a/.github/workflows/installer-test.yml +++ b/.github/workflows/installer-test.yml @@ -23,7 +23,7 @@ jobs: name: shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - name: Run shellcheck on installer.sh run: | sudo apt-get update @@ -38,11 +38,11 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: - node-version: 24 + node-version: 22 - 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.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 }} + ETHERPAD_REPO: ${{ github.server_url }}/${{ github.repository }}.git + ETHERPAD_BRANCH: ${{ github.head_ref || github.ref_name }} NO_COLOR: "1" run: sh ./bin/installer.sh @@ -70,30 +70,13 @@ jobs: - name: Smoke test - start Etherpad and curl /api shell: bash - # Hard backstop: if teardown ever fails to reap the server the step - # fails in minutes instead of burning to GitHub's 6h job ceiling. - timeout-minutes: 8 env: ETHERPAD_DIR: ${{ runner.temp }}/etherpad-installer-test run: | set -eu - # Enable job control so the backgrounded launcher gets its own - # process group, letting us reap the whole pnpm -> node tree below. - set -m cd "$ETHERPAD_DIR" pnpm run prod >/tmp/etherpad.log 2>&1 & PID=$! - # `pnpm run prod` is a nested launcher (pnpm -> pnpm --filter -> node), - # so killing $PID alone orphans the node server, which keeps the step's - # output pipe open and hangs CI. Kill the entire process group, with a - # SIGKILL fallback in case SIGTERM is swallowed (e.g. a live flush timer - # keeping the event loop alive), so the step always exits cleanly. - reap() { - kill -TERM "-$PID" 2>/dev/null || true - sleep 5 - kill -KILL "-$PID" 2>/dev/null || true - } - trap reap EXIT # Wait up to 60s for the API to come up. ok=0 for i in $(seq 1 60); do @@ -107,18 +90,21 @@ jobs: if [ "$ok" != "1" ]; then echo "Etherpad did not start within 60s. Last 200 lines of log:" >&2 tail -200 /tmp/etherpad.log >&2 || true + kill "$PID" 2>/dev/null || true exit 1 fi + kill "$PID" 2>/dev/null || true + wait "$PID" 2>/dev/null || true installer-windows: name: end-to-end install (windows-latest) runs-on: windows-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: - node-version: 24 + node-version: 22 - name: Pre-install pnpm run: npm install -g pnpm @@ -127,8 +113,8 @@ jobs: shell: pwsh env: ETHERPAD_DIR: ${{ runner.temp }}\etherpad-installer-test - 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 }} + ETHERPAD_REPO: ${{ github.server_url }}/${{ github.repository }}.git + ETHERPAD_BRANCH: ${{ github.head_ref || github.ref_name }} NO_COLOR: "1" run: ./bin/installer.ps1 @@ -145,7 +131,6 @@ jobs: - name: Smoke test - start Etherpad and curl /api shell: pwsh - timeout-minutes: 8 env: ETHERPAD_DIR: ${{ runner.temp }}\etherpad-installer-test run: | diff --git a/.github/workflows/load-test.yml b/.github/workflows/load-test.yml index 9b498416c..06660afdb 100644 --- a/.github/workflows/load-test.yml +++ b/.github/workflows/load-test.yml @@ -24,8 +24,8 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -35,18 +35,14 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile - name: Install etherpad-load-test - run: sudo npm install -g etherpad-load-test + run: sudo npm install -g etherpad-load-test-socket-io - name: Run load test run: src/tests/frontend/travis/runnerLoadTest.sh 25 50 @@ -62,8 +58,8 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -73,15 +69,11 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Install etherpad-load-test - run: sudo npm install -g etherpad-load-test + run: sudo npm install -g etherpad-load-test-socket-io - name: Install etherpad plugins run: > @@ -93,7 +85,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_guest + ep_readonly_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript @@ -125,8 +117,8 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -136,18 +128,14 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile - name: Install etherpad-load-test - run: sudo npm install -g etherpad-load-test + run: sudo npm install -g etherpad-load-test-socket-io - name: Run load test run: src/tests/frontend/travis/runnerLoadTest.sh 5000 5 diff --git a/.github/workflows/perform-type-check.yml b/.github/workflows/perform-type-check.yml index fbed6df2c..aacfe279f 100644 --- a/.github/workflows/perform-type-check.yml +++ b/.github/workflows/perform-type-check.yml @@ -24,8 +24,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -35,12 +35,8 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile - name: Perform type check diff --git a/.github/workflows/rate-limit.yml b/.github/workflows/rate-limit.yml index 3cd791164..861d576c2 100644 --- a/.github/workflows/rate-limit.yml +++ b/.github/workflows/rate-limit.yml @@ -27,8 +27,8 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 - - uses: actions/cache@v6 + uses: actions/checkout@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -38,12 +38,8 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - name: docker network @@ -58,36 +54,11 @@ 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 --name nginx-docker -p 8081:80 --rm --network ep_net --ip 172.23.42.1 -d nginx-latest + docker run -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 b0b697845..e9eb3d373 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: repository: ether/etherpad-lite path: etherpad @@ -42,12 +42,12 @@ jobs: git checkout develop git reset --hard origin/develop - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: repository: ether/ether.github.com path: ether.github.com token: '${{ secrets.ETHER_RELEASE_TOKEN }}' - - uses: actions/cache@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -57,22 +57,10 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - # Repo is checked out into ./etherpad, so the action can't find - # packageManager in a root package.json. - package_json_file: etherpad/package.json - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - cache-dependency-path: etherpad/pnpm-lock.yaml - name: Install dependencies ether.github.com - # ether.github.com depends on sharp (Next.js image pipeline), whose - # install script must run to fetch the platform binary. pnpm 11 - # turned ignored-builds into an error; allow all builds for this - # external repo since we don't control its pnpm-workspace.yaml. - run: pnpm install --frozen-lockfile --config.dangerously-allow-all-builds=true + run: pnpm install --frozen-lockfile working-directory: ether.github.com - name: Set git user run: | @@ -82,7 +70,7 @@ jobs: with: ruby-version: 2.7 - - uses: reitzig/actions-asciidoctor@v2.0.5 + - uses: reitzig/actions-asciidoctor@v2.0.4 with: version: 2.0.18 - name: Prepare release diff --git a/.github/workflows/releaseEtherpad.yml b/.github/workflows/releaseEtherpad.yml index dd53cf9ae..731ae2930 100644 --- a/.github/workflows/releaseEtherpad.yml +++ b/.github/workflows/releaseEtherpad.yml @@ -1,33 +1,9 @@ -# PARKED — npm publish of the core package is not part of the standard release. -# -# This workflow renames `ep_etherpad-lite` -> `ep_etherpad` and publishes -# `./src` to npm. As of 2026-06, that publish serves no load-bearing purpose: -# - `ep_etherpad` has 0 dependents on npm and nothing in this repo depends on it; -# - plugins import `ep_etherpad-lite` resolved from the LOCAL core install, -# and plugin CI clones `ether/etherpad` rather than `npm install`-ing core; -# - Etherpad is run via git clone / Docker / zip / snap, never `npm install`. -# The publish has been failing (E404 PUT — the `ep_etherpad` package has no OIDC -# trusted publisher configured on npmjs.com), which is why npm is stuck at 2.5.0 -# while 3.x shipped fine without it. -# -# It is therefore gated behind an explicit `confirm: true` dispatch input so a -# stray run fails fast with a clear message instead of a confusing 404. To -# actually publish, the npm owner of `ep_etherpad` (samtv12345) must first -# configure a trusted publisher: npmjs.com -> ep_etherpad -> Settings -> -# Trusted Publisher -> repo `ether/etherpad`, workflow `releaseEtherpad.yml`. -# Decision pending: finish that config, or remove this workflow. See AGENTS.MD. name: releaseEtherpad.yaml permissions: contents: read id-token: write # for npm OIDC trusted publishing on: workflow_dispatch: - inputs: - confirm: - description: 'PARKED — publish ep_etherpad to npm? Requires a trusted publisher configured on npmjs.com first (see workflow header). Set true only if that is done.' - required: true - default: false - type: boolean env: PNPM_HOME: ~/.pnpm-store @@ -36,25 +12,18 @@ jobs: release: runs-on: ubuntu-latest steps: - - name: Guard — refuse unless explicitly confirmed - if: ${{ inputs.confirm != true }} - run: | - echo "::error::releaseEtherpad is PARKED. The ep_etherpad npm publish is non-functional" - echo "::error::(no trusted publisher configured on npmjs.com; 0 dependents on npm)." - echo "::error::Re-run with confirm=true only after the owner configures a trusted" - echo "::error::publisher. See the workflow header / AGENTS.MD 'Releasing' section." - exit 1 - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: # OIDC trusted publishing needs npm >= 11.5.1, which requires - # Node >= 22.9.0. Node 24 satisfies that and matches the rest of CI. - node-version: 24 + # Node >= 20.17.0. setup-node's `20` resolves to the latest + # 20.x, which satisfies that. + node-version: 20 registry-url: https://registry.npmjs.org/ - name: Upgrade npm to >=11.5.1 (required for trusted publishing) run: npm install -g npm@latest - - uses: actions/cache@v6 + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -64,6 +33,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/snap-build.yml b/.github/workflows/snap-build.yml deleted file mode 100644 index 9255db173..000000000 --- a/.github/workflows/snap-build.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Snap build verification — runs on every PR (and on develop) that -# touches snap/ or this workflow. Catches breakage in the wrappers and -# the build recipe before it reaches a release tag. -# -# Two jobs: -# wrapper-tests — runs the bash unit tests under snap/tests/. Fast -# (~5s); no snapd / sudo / network needed. Always runs first. -# snap-pack — builds the actual snap with `snapcraft pack -# --destructive-mode` (no LXD). Faster than the publish workflow -# because it skips container provisioning and store auth. Uploads -# the .snap as an artifact so reviewers can sideload it. -# -# Production publishing lives in `snap-publish.yml` (tag-triggered). -name: Snap build (PR) -on: - pull_request: - paths: - - 'snap/**' - - 'settings.json.template' - - '.github/workflows/snap-build.yml' - push: - branches: [develop] - paths: - - 'snap/**' - - 'settings.json.template' - - '.github/workflows/snap-build.yml' - workflow_dispatch: - -permissions: - contents: read - -jobs: - wrapper-tests: - name: Wrapper unit tests - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v7 - - name: Run snap/tests/run-all.sh - run: bash snap/tests/run-all.sh - - snap-pack: - name: Build snap (destructive-mode) - needs: wrapper-tests - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v7 - - - name: Install snapcraft - run: sudo snap install --classic snapcraft - - - name: Pack snap - run: sudo snapcraft pack --destructive-mode --verbose - - - name: Locate built artifact - id: artifact - run: | - set -e - SNAP=$(ls etherpad_*.snap | head -1) - if [ -z "${SNAP}" ]; then - echo "::error::no .snap file produced" - exit 1 - fi - echo "snap=${SNAP}" >> "${GITHUB_OUTPUT}" - - - name: Upload .snap artifact - uses: actions/upload-artifact@v7 - with: - name: etherpad-snap-pr-${{ github.event.pull_request.number || github.run_id }} - path: ${{ steps.artifact.outputs.snap }} - if-no-files-found: error - retention-days: 7 diff --git a/.github/workflows/snap-publish.yml b/.github/workflows/snap-publish.yml deleted file mode 100644 index 6f692d27f..000000000 --- a/.github/workflows/snap-publish.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Builds and publishes the Etherpad snap on tagged releases. -# Mirrors the trigger pattern from .github/workflows/docker.yml / release.yml -# (semver tags, with or without a leading `v`). -# -# Note: `on.push.tags` uses GitHub's filter-pattern globs, NOT regex — so the -# pattern must be expressed as two glob entries, not a single `v?...` regex. -# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet -# -# One-time maintainer setup: -# 1. `snapcraft register etherpad` claims the name. -# 2. Generate a store credential: -# snapcraft export-login --snaps etherpad \ -# --channels edge,stable \ -# --acls package_access,package_push,package_release - -# Store the output as repo secret SNAPCRAFT_STORE_CREDENTIALS. -# 3. Create a GitHub Environment called `snap-store-stable` with required -# reviewers so stable promotion is gated. -# -# Ref: https://documentation.ubuntu.com/snapcraft/latest/how-to/publishing/ -name: Snap -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+' - workflow_dispatch: - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - outputs: - snap-file: ${{ steps.build.outputs.snap }} - steps: - - name: Check out - uses: actions/checkout@v7 - - - name: Build snap - id: build - uses: snapcore/action-build@v1 - - - name: Upload snap artifact - uses: actions/upload-artifact@v7 - with: - name: etherpad-snap - path: ${{ steps.build.outputs.snap }} - if-no-files-found: error - retention-days: 7 - - publish-edge: - needs: build - if: github.event_name == 'push' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Download snap artifact - uses: actions/download-artifact@v8 - with: - name: etherpad-snap - - - name: Publish to edge - uses: snapcore/action-publish@v1 - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} - with: - snap: ${{ needs.build.outputs.snap-file }} - release: edge - - publish-stable: - needs: [build, publish-edge] - if: github.event_name == 'push' - runs-on: ubuntu-latest - permissions: - contents: read - # Manual gate: promote edge -> stable via GitHub Environments approval. - environment: snap-store-stable - steps: - - name: Download snap artifact - uses: actions/download-artifact@v8 - with: - name: etherpad-snap - - - name: Publish to stable - uses: snapcore/action-publish@v1 - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} - with: - snap: ${{ needs.build.outputs.snap-file }} - release: stable diff --git a/.github/workflows/update-plugins.yml b/.github/workflows/update-plugins.yml index 2f3ca4783..447ecdcf1 100644 --- a/.github/workflows/update-plugins.yml +++ b/.github/workflows/update-plugins.yml @@ -16,17 +16,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out etherpad-lite - uses: actions/checkout@v7 + uses: actions/checkout@v6 - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Use Node.js uses: actions/setup-node@v6 with: - node-version: 24 + node-version: 22 - 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 02c527e70..e5260d449 100644 --- a/.github/workflows/upgrade-from-latest-release.yml +++ b/.github/workflows/upgrade-from-latest-release.yml @@ -27,18 +27,15 @@ jobs: strategy: fail-fast: false matrix: - # Etherpad requires Node >= 24 (see package.json engines.node). - node: ${{ fromJSON('[24]') }} + # PRs: single Node version. Push: full matrix. + node: ${{ github.event_name == 'pull_request' && fromJSON('[24]') || fromJSON('[20, 22, 24]') }} steps: - name: Check out latest release - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: - fetch-depth: 0 - - - name: Check out latest release tag - run: git checkout "$(git tag --list 'v*' --sort=-version:refname | head -n1)" - - uses: actions/cache@v6 + ref: develop #FIXME change to master when doing release + - uses: actions/cache@v5 name: Cache pnpm store with: path: ${{ env.PNPM_HOME }} @@ -48,6 +45,7 @@ jobs: - uses: pnpm/action-setup@v6 name: Install pnpm with: + version: 10.33.2 run_install: false - name: Use Node.js uses: actions/setup-node@v6 @@ -55,7 +53,7 @@ jobs: node-version: ${{ matrix.node }} cache: pnpm - name: Install libreoffice - uses: awalsh128/cache-apt-pkgs-action@v1.6.3 + uses: awalsh128/cache-apt-pkgs-action@v1.6.0 with: packages: libreoffice libreoffice-pdfimport version: 1.0 @@ -76,7 +74,7 @@ jobs: ep_hash_auth ep_headings2 ep_markdown - ep_guest + ep_readonly_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript @@ -87,10 +85,13 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: pnpm install --frozen-lockfile - # 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. + # 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}. - name: Fetch the new Git commits run: git fetch --depth=1 origin "${GITHUB_REF}" diff --git a/.gitignore b/.gitignore index 99e10ca74..28fabc00b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,22 +27,3 @@ plugin_packages playwright-report state.json /src/static/oidc -# Build artefacts produced by packaging/test-local.sh and the deb-package CI workflow. -/staging/ -/dist/ -/packaging/etc/ - -# Snapcraft destructive-mode build outputs. -parts/ -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/.pr_agent.toml b/.pr_agent.toml new file mode 100644 index 000000000..93e106ea2 --- /dev/null +++ b/.pr_agent.toml @@ -0,0 +1,5 @@ +[pr_reviewer] +run_on_pr_sync = true + +[pr_description] +run_on_pr_sync = true diff --git a/AGENTS.MD b/AGENTS.MD index 80abdce91..bf8632594 100644 --- a/AGENTS.MD +++ b/AGENTS.MD @@ -6,8 +6,8 @@ Welcome to the Etherpad project. This guide provides essential context and instr Etherpad is a real-time collaborative editor designed to be lightweight, scalable, and highly extensible via plugins. ## Technical Stack -- **Runtime:** Node.js >= 22.12.0 -- **Package Manager:** pnpm (>= 11.0.0) +- **Runtime:** Node.js >= 20.0.0 +- **Package Manager:** pnpm (>= 8.3.0) - **Languages:** TypeScript (primary for new code), JavaScript (legacy), CSS, HTML - **Backend:** Express.js 5, Socket.io 4 - **Frontend:** Legacy core (`src/static`), Modern React UI (`ui/`), Admin UI (`admin/`) @@ -46,25 +46,6 @@ 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, `