diff --git a/.github/workflows/autocheckers.yml b/.github/workflows/autocheckers.yml deleted file mode 100644 index c245a5bfed27c..0000000000000 --- a/.github/workflows/autocheckers.yml +++ /dev/null @@ -1,131 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Code checkers - -on: - pull_request: - -permissions: - contents: read - -concurrency: - group: autocheckers-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - '**.php' - - build/autoloaderchecker.sh - - autoloader: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - matrix: - php-versions: ['8.2'] - - name: PHP checkers - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: none - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check auto loaders - run: bash ./build/autoloaderchecker.sh - - autocheckers: - runs-on: ubuntu-latest-low - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - matrix: - php-versions: ['8.2'] - - name: Translation and Files checkers - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: none - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check translations are JSON decodeable - run: php ./build/translation-checker.php - - - name: Check translations do not contain triple dot but ellipsis - run: php ./build/triple-dot-checker.php - - - name: Check .htaccess does not contain invalid changes - run: php ./build/htaccess-checker.php - - - name: Check that all and only expected files are included - run: php ./build/files-checker.php - - - name: Check that all shipped apps are linted by psalm - run: sh ./build/psalm-checker.sh - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, autoloader, autocheckers] - - if: always() - - name: autocheckers-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && (needs.autocheckers.result != 'success' || needs.autoloader.result != 'success') }}; then exit 1; fi diff --git a/.github/workflows/block-merge-eol.yml b/.github/workflows/block-merge-eol.yml deleted file mode 100644 index ab04945680864..0000000000000 --- a/.github/workflows/block-merge-eol.yml +++ /dev/null @@ -1,49 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Block merges for EOL - -on: pull_request - -permissions: - contents: read - -concurrency: - group: block-merge-eol-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - block-merges-eol: - name: Block merges for EOL branches - - # Only run on stableXX branches - if: startsWith( github.base_ref, 'stable') - runs-on: ubuntu-latest-low - - steps: - - name: Set server major version environment - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const regex = /^stable(\d+)$/ - const baseRef = context.payload.pull_request.base.ref - const match = baseRef.match(regex) - if (match) { - console.log('Setting server_major to ' + match[1]); - core.exportVariable('server_major', match[1]); - console.log('Setting current_day to ' + (new Date()).toISOString().substr(0, 10)); - core.exportVariable('current_day', (new Date()).toISOString().substr(0, 10)); - } - - - name: Checking if server ${{ env.server_major }} is EOL - if: ${{ env.server_major != '' }} - run: | - curl -s https://raw.githubusercontent.com/nextcloud-releases/updater_server/production/config/major_versions.json \ - | jq '.["${{ env.server_major }}"]["eol"] // "9999-99-99" | . >= "${{ env.current_day }}"' \ - | grep -q true diff --git a/.github/workflows/block-merge-freeze.yml b/.github/workflows/block-merge-freeze.yml deleted file mode 100644 index 3a9d5cc37836c..0000000000000 --- a/.github/workflows/block-merge-freeze.yml +++ /dev/null @@ -1,57 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Block merges during freezes - -on: - pull_request: - types: [opened, ready_for_review, reopened, synchronize] - -permissions: - contents: read - -concurrency: - group: block-merge-freeze-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - block-merges-during-freeze: - name: Block merges during freezes - - if: github.event.pull_request.draft == false - - runs-on: ubuntu-latest-low - - steps: - - name: Register server reference to fallback to master branch - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const baseRef = context.payload.pull_request.base.ref - if (baseRef === 'main' || baseRef === 'master') { - core.exportVariable('server_ref', 'master'); - console.log('Setting server_ref to master'); - } else { - const regex = /^stable(\d+)$/ - const match = baseRef.match(regex) - if (match) { - core.exportVariable('server_ref', match[0]); - console.log('Setting server_ref to ' + match[0]); - } else { - console.log('Not based on master/main/stable*, so skipping freeze check'); - } - } - - - name: Download version.php from ${{ env.server_ref }} - if: ${{ env.server_ref != '' }} - run: curl 'https://raw.githubusercontent.com/nextcloud/server/${{ env.server_ref }}/version.php' --output version.php - - - name: Run check - if: ${{ env.server_ref != '' }} - run: cat version.php | grep 'OC_VersionString' | grep -i -v 'RC' diff --git a/.github/workflows/block-outdated-3rdparty.yml b/.github/workflows/block-outdated-3rdparty.yml deleted file mode 100644 index 7beef53946b7f..0000000000000 --- a/.github/workflows/block-outdated-3rdparty.yml +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Block merging with outdated 3rdparty/ - -on: - pull_request: - types: [opened, ready_for_review, reopened, synchronize] - -permissions: - contents: read - -concurrency: - group: block-outdated-3rdparty-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - block-outdated-3rdparty: - name: Block merging with outdated 3rdparty/ - - runs-on: ubuntu-latest-low - - steps: - - name: Check requirement - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '3rdparty' - - 'version.php' - - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: 3rdparty commit hash on current branch - id: actual - run: | - echo "commit=$(git submodule status | grep ' 3rdparty' | egrep -o '[a-f0-9]{40}')" >> "$GITHUB_OUTPUT" - - - name: Register server reference to fallback to master branch - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const baseRef = context.payload.pull_request.base.ref - if (baseRef === 'main' || baseRef === 'master') { - core.exportVariable('server_ref', 'master'); - console.log('Setting server_ref to master'); - } else { - const regex = /^stable(\d+)$/ - const match = baseRef.match(regex) - if (match) { - core.exportVariable('server_ref', match[0]); - console.log('Setting server_ref to ' + match[0]); - } else { - console.log('Not based on master/main/stable*, so skipping outdated 3rdparty check'); - } - } - - - name: Last 3rdparty commit on target branch - if: ${{ env.server_ref != '' }} - id: target - run: | - echo "commit=$(git ls-remote https://github.com/nextcloud/3rdparty refs/heads/${{ env.server_ref }} | awk '{ print $1}')" >> "$GITHUB_OUTPUT" - - - name: Compare if 3rdparty commits are different - if: ${{ env.server_ref != '' }} - run: | - echo '3rdparty/ seems to not point to the last commit of the dedicated branch:' - echo 'Branch has: ${{ steps.actual.outputs.commit }}' - echo '${{ env.server_ref }} has: ${{ steps.target.outputs.commit }}' - - - name: Fail if 3rdparty commits are different - if: ${{ env.server_ref != '' && steps.changes.outputs.src != 'false' && steps.actual.outputs.commit != steps.target.outputs.commit }} - run: | - exit 1 diff --git a/.github/workflows/block-unconventional-commits.yml b/.github/workflows/block-unconventional-commits.yml deleted file mode 100644 index 258951e8e5b9e..0000000000000 --- a/.github/workflows/block-unconventional-commits.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Block unconventional commits - -on: - pull_request: - types: [opened, ready_for_review, reopened, synchronize] - -permissions: - contents: read - -concurrency: - group: block-unconventional-commits-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - block-unconventional-commits: - name: Block unconventional commits - - runs-on: ubuntu-latest-low - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - uses: webiny/action-conventional-commits@7f91b1595ca1951cdb671ddc9f07a49081ec5b69 # v1.4.2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/bug-report-labeler.yml b/.github/workflows/bug-report-labeler.yml deleted file mode 100644 index 369f95b7947fa..0000000000000 --- a/.github/workflows/bug-report-labeler.yml +++ /dev/null @@ -1,45 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: AGPL-3.0-or-later - -name: Auto-label bug reports -on: - issues: - types: [opened] - -jobs: - add-version-label: - if: contains(github.event.issue.title, '[Bug]') - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - name: Extract version number and apply label - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const body = context.payload.issue.body || ''; - const normalizedBody = body.replace(/\r\n?/g, '\n'); - let label = ''; - - // Extract Nextcloud Server version number from a block like: - // ### Nextcloud Server version - // 32 - const versionMatch = normalizedBody.match(/### Nextcloud Server version\s*\n+([0-9]{1,3})\b/); - let nextcloudVersion = null; - if (versionMatch) { - nextcloudVersion = parseInt(versionMatch[1], 10); - label = nextcloudVersion + '-feedback'; - } - - if (label) { - try { - await github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [label] - }); - } catch (error) { - core.setFailed(`Failed to add label "${label}": ${error.message || error}`); - } - } diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 232743ef4e0db..0000000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: AGPL-3.0-or-later - -name: "CodeQL Advanced" - -on: - push: - branches: [ "master", "stable*" ] - pull_request: - branches: [ "master", "stable*" ] - schedule: - - cron: '28 18 * * 1' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest - permissions: - # required for all workflows - security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - strategy: - fail-fast: false - matrix: - include: - - language: actions - build-mode: none - - language: javascript-typescript - build-mode: none - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Initialize CodeQL - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - config-file: ./.github/codeql-config.yml - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/command-compile.yml b/.github/workflows/command-compile.yml deleted file mode 100644 index 396e92985f73c..0000000000000 --- a/.github/workflows/command-compile.yml +++ /dev/null @@ -1,222 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Compile Command -on: - issue_comment: - types: [created] - -permissions: - contents: read - -jobs: - init: - runs-on: ubuntu-latest - - # On pull requests and if the comment starts with `/compile` - if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/compile') - - outputs: - git_path: ${{ steps.git-path.outputs.path }} - arg1: ${{ steps.command.outputs.arg1 }} - arg2: ${{ steps.command.outputs.arg2 }} - head_ref: ${{ steps.comment-branch.outputs.head_ref }} - base_ref: ${{ steps.comment-branch.outputs.base_ref }} - - steps: - - name: Get repository from pull request comment - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - id: get-repository - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const pull = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number - }); - - const repositoryName = pull.data.head?.repo?.full_name - console.log(repositoryName) - return repositoryName - - - name: Disabled on forks - if: ${{ fromJSON(steps.get-repository.outputs.result) != github.repository }} - run: | - echo 'Can not execute /compile on forks' - exit 1 - - - name: Check actor permission - uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v2 - with: - require: write - - - name: Add reaction on start - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reactions: '+1' - - - name: Parse command - uses: skjnldsv/parse-command-comment@5c955203c52424151e6d0e58fb9de8a9f6a605a1 # v2 - id: command - - # Init path depending on which command is run - - name: Init path - id: git-path - run: | - if ${{ startsWith(steps.command.outputs.arg1, '/') }}; then - echo "path=${{steps.command.outputs.arg1}}" >> $GITHUB_OUTPUT - else - echo "path=${{steps.command.outputs.arg2}}" >> $GITHUB_OUTPUT - fi - - - name: Init branch - uses: xt0rted/pull-request-comment-branch@e8b8daa837e8ea7331c0003c9c316a64c6d8b0b1 # v3.0.0 - id: comment-branch - - - name: Add reaction on failure - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 - if: failure() - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reactions: '-1' - - process: - runs-on: ubuntu-latest - needs: init - - steps: - - name: Restore cached git repository - uses: buildjet/cache@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2 - with: - path: .git - key: git-repo - - - name: Checkout ${{ needs.init.outputs.head_ref }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - # Needed to allow force push later - persist-credentials: true - token: ${{ secrets.COMMAND_BOT_PAT }} - fetch-depth: 0 - ref: ${{ needs.init.outputs.head_ref }} - - - name: Setup git - run: | - git config --local user.email 'nextcloud-command@users.noreply.github.com' - git config --local user.name 'nextcloud-command' - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: package-engines-versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.package-engines-versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.package-engines-versions.outputs.nodeVersion }} - cache: npm - - - name: Set up npm ${{ steps.package-engines-versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.package-engines-versions.outputs.npmVersion }}' - - - name: Rebase to ${{ needs.init.outputs.base_ref }} - if: ${{ contains(needs.init.outputs.arg1, 'rebase') }} - run: | - git fetch origin '${{ needs.init.outputs.base_ref }}:${{ needs.init.outputs.base_ref }}' - - # Start the rebase - git rebase 'origin/${{ needs.init.outputs.base_ref }}' || { - # Handle rebase conflicts in a loop - while [ -d .git/rebase-merge ] || [ -d .git/rebase-apply ]; do - echo "Handling rebase conflict..." - - # Remove and checkout /dist and /js folders from the base branch - if [ -d "dist" ]; then - rm -rf dist - git checkout origin/${{ needs.init.outputs.base_ref }} -- dist/ 2>/dev/null || echo "No dist folder in base branch" - fi - if [ -d "js" ]; then - rm -rf js - git checkout origin/${{ needs.init.outputs.base_ref }} -- js/ 2>/dev/null || echo "No js folder in base branch" - fi - - # Stage all changes - git add . - - # Check if there are any changes after resolving conflicts - if git diff --cached --quiet; then - echo "No changes after conflict resolution, skipping commit" - git rebase --skip - else - echo "Changes found, continuing rebase without editing commit message" - git -c core.editor=true rebase --continue - fi - - # Break if rebase is complete - if [ ! -d .git/rebase-merge ] && [ ! -d .git/rebase-apply ]; then - break - fi - done - } - - - name: Install dependencies & build - env: - CYPRESS_INSTALL_BINARY: 0 - PUPPETEER_SKIP_DOWNLOAD: true - run: | - npm ci - npm run build --if-present - - - name: Commit default - if: ${{ !contains(needs.init.outputs.arg1, 'fixup') && !contains(needs.init.outputs.arg1, 'amend') }} - run: | - git add '${{ github.workspace }}${{ needs.init.outputs.git_path }}' - git commit --signoff -m 'chore(assets): Recompile assets' - - - name: Commit fixup - if: ${{ contains(needs.init.outputs.arg1, 'fixup') }} - run: | - git add '${{ github.workspace }}${{ needs.init.outputs.git_path }}' - git commit --fixup=HEAD --signoff - - - name: Commit amend - if: ${{ contains(needs.init.outputs.arg1, 'amend') }} - run: | - git add '${{ github.workspace }}${{ needs.init.outputs.git_path }}' - git commit --amend --no-edit --signoff - # Remove any [skip ci] from the amended commit - git commit --amend -m "$(git log -1 --format='%B' | sed '/\[skip ci\]/d')" - - - name: Push normally - if: ${{ !contains(needs.init.outputs.arg1, 'rebase') && !contains(needs.init.outputs.arg1, 'amend') }} - env: - HEAD_REF: ${{ needs.init.outputs.head_ref }} - run: git push origin "$HEAD_REF" - - - name: Force push - if: ${{ contains(needs.init.outputs.arg1, 'rebase') || contains(needs.init.outputs.arg1, 'amend') }} - env: - HEAD_REF: ${{ needs.init.outputs.head_ref }} - run: git push --force-with-lease origin "$HEAD_REF" - - - name: Add reaction on failure - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 - if: failure() - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reactions: '-1' diff --git a/.github/workflows/command-pull-3rdparty.yml b/.github/workflows/command-pull-3rdparty.yml deleted file mode 100644 index 9c58d785c1c42..0000000000000 --- a/.github/workflows/command-pull-3rdparty.yml +++ /dev/null @@ -1,111 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Update 3rdparty command - -on: - issue_comment: - types: created - -permissions: - contents: read - -jobs: - rebase: - runs-on: ubuntu-latest - permissions: - contents: none - pull-requests: read - - # On pull requests and if the comment starts with `/update-3rdparty` - if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/update-3rdparty') - - steps: - - name: Add reaction on start - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v3.0.1 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reactions: '+1' - - # issue_comment events carry no pull_request context in their payload, so we - # must fetch the PR via the API. This also gives us base.ref for free, avoiding - # a second API call. The GITHUB_TOKEN needs pull-requests:read (granted above). - - name: Get pull request metadata - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - id: get-pr - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const pull = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - core.setOutput('head_repo', pull.data.head.repo?.full_name ?? ''); - core.setOutput('base_ref', pull.data.base.ref); - - - name: Disabled on forks - if: steps.get-pr.outputs.head_repo != github.repository - run: | - echo 'Can not execute /update-3rdparty on forks' - exit 1 - - - name: Init branch - uses: xt0rted/pull-request-comment-branch@e8b8daa837e8ea7331c0003c9c316a64c6d8b0b1 # v1 - id: comment-branch - - - name: Checkout ${{ steps.comment-branch.outputs.head_ref }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - fetch-depth: 0 - token: ${{ secrets.COMMAND_BOT_PAT }} - ref: ${{ steps.comment-branch.outputs.head_ref }} - - - name: Register server reference to fallback to master branch - run: | - base_ref="${{ steps.get-pr.outputs.base_ref }}" - if [[ "$base_ref" == "main" || "$base_ref" == "master" ]]; then - echo "server_ref=master" >> "$GITHUB_ENV" - echo "Setting server_ref to master" - elif [[ "$base_ref" =~ ^stable[0-9]+$ ]]; then - echo "server_ref=$base_ref" >> "$GITHUB_ENV" - echo "Setting server_ref to $base_ref" - else - echo "Not based on master/main/stable*, so skipping pull 3rdparty command" - fi - - - name: Setup git - run: | - git config --local user.email 'nextcloud-command@users.noreply.github.com' - git config --local user.name 'nextcloud-command' - - - name: Add reaction on failure - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v3.0.1 - if: ${{ env.server_ref == '' }} - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reactions: '-1' - - - name: Pull 3rdparty - if: ${{ env.server_ref != '' }} - run: git submodule foreach 'if [ "$sm_path" == "3rdparty" ]; then git pull origin '"'"'${{ env.server_ref }}'"'"'; fi' - - - name: Commit and push changes - if: ${{ env.server_ref != '' }} - run: | - git add 3rdparty - git commit -s -m 'Update submodule 3rdparty to latest ${{ env.server_ref }}' - git push - - - name: Add reaction on failure - uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v3.0.1 - if: failure() - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - repository: ${{ github.event.repository.full_name }} - comment-id: ${{ github.event.comment.id }} - reactions: '-1' diff --git a/.github/workflows/dependabot-approve-merge.yml b/.github/workflows/dependabot-approve-merge.yml deleted file mode 100644 index c0411c057b99f..0000000000000 --- a/.github/workflows/dependabot-approve-merge.yml +++ /dev/null @@ -1,58 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Auto approve Dependabot PRs - -on: - pull_request_target: # zizmor: ignore[dangerous-triggers] - branches: - - main - - master - - stable* - -permissions: - contents: read - -concurrency: - group: dependabot-approve-merge-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - auto-approve-merge: - if: github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'renovate[bot]' - runs-on: ubuntu-latest-low - permissions: - # for hmarr/auto-approve-action to approve PRs - pull-requests: write - # for alexwilson/enable-github-automerge-action to approve PRs - contents: write - - steps: - - name: Disabled on forks - if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} - run: | - echo 'Can not approve PRs from forks' - exit 1 - - - uses: mdecoleman/pr-branch-name@55795d86b4566d300d237883103f052125cc7508 # v3.0.0 - id: branchname - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - # GitHub actions bot approve - - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0 - if: startsWith(steps.branchname.outputs.branch, 'dependabot/') - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - - # Enable GitHub auto merge - - name: Auto merge - uses: alexwilson/enable-github-automerge-action@56e3117d1ae1540309dc8f7a9f2825bc3c5f06ff # v2.0.0 - if: startsWith(steps.branchname.outputs.branch, 'dependabot/') - with: - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/files-external-ftp.yml b/.github/workflows/files-external-ftp.yml deleted file mode 100644 index be734084a956b..0000000000000 --- a/.github/workflows/files-external-ftp.yml +++ /dev/null @@ -1,142 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit files_external FTP -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: files-external-ftp-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - files-external-ftp: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2', '8.4'] - ftpd: ['proftpd', 'vsftpd', 'pure-ftpd'] - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-${{ matrix.ftpd }} - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up ftpd - run: | - sudo mkdir /tmp/ftp - sudo chmod -R 0777 /tmp/ftp - if [[ "${{ matrix.ftpd }}" == 'proftpd' ]]; then echo '$6$Q7V2n3q2GRVv5YeQ$/AhLu07H76Asojy7bxGXMY1caKLAbp5Vt82LOZYMkD/8uDzyMAEXwk0c1Bdz1DkBsk2Vh/9SF130mOPavRGMo.' > /tmp/secret.txt; fi - if [[ "${{ matrix.ftpd }}" == 'proftpd' ]]; then echo 'FTP_ROOT=/home/test' > $GITHUB_ENV; fi - if [[ "${{ matrix.ftpd }}" == 'proftpd' ]]; then docker run --name ftp -d --net host -e PASV_ADDRESS=127.0.0.1 -e FTPUSER_NAME=test -v /tmp/secret.txt:/run/secrets/ftp-user-password-secret -v /tmp/ftp:/home/test instantlinux/proftpd; fi - if [[ "${{ matrix.ftpd }}" == 'vsftpd' ]]; then docker run --name ftp -d --net host -e FTP_USER=test -e FTP_PASS=test -e PASV_ADDRESS=127.0.0.1 -v /tmp/ftp:/home/vsftpd/test fauria/vsftpd; fi - if [[ "${{ matrix.ftpd }}" == 'pure-ftpd' ]]; then docker run --name ftp -d --net host -e "PUBLICHOST=localhost" -e FTP_USER_NAME=test -e FTP_USER_PASS=test -e FTP_USER_HOME=/home/test -v /tmp/ftp:/home/test -v /tmp/ftp:/etc/pure-ftpd/passwd stilliard/pure-ftpd; fi - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - run: | - composer install - mkdir data - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - ./occ app:enable --force files_external - echo " true,'host' => 'localhost','user' => 'test','password' => 'test', 'root' => '${{ env.FTP_ROOT }}'];" > apps/files_external/tests/config.ftp.php - - - name: smoketest ftp - run: | - php -r 'var_dump(file_put_contents("ftp://test:test@localhost${{ env.FTP_ROOT }}/ftp.txt", "asd"));' - php -r 'var_dump(file_get_contents("ftp://test:test@localhost${{ env.FTP_ROOT }}/ftp.txt"));' - php -r 'var_dump(mkdir("ftp://test:test@localhost${{ env.FTP_ROOT }}/asdads"));' - ls -l /tmp/ftp - [ -f /tmp/ftp/ftp.txt ] - - - name: PHPUnit - run: composer run test:files_external -- \ - apps/files_external/tests/Storage/FtpTest.php \ - --log-junit junit.xml \ - ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-files-external-ftp - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-files-external-ftp - - - name: ftpd logs - if: always() - run: | - docker logs ftp - - ftp-summary: - runs-on: ubuntu-latest-low - needs: [changes, files-external-ftp] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.files-external-ftp.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/files-external-s3.yml b/.github/workflows/files-external-s3.yml deleted file mode 100644 index 03762278c3f70..0000000000000 --- a/.github/workflows/files-external-s3.yml +++ /dev/null @@ -1,225 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit files_external S3 -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: files-external-s3-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - files-external-s3-minio: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2', '8.4'] - include: - - php-versions: '8.3' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-s3-minio - - services: - minio: - image: bitnami/minio@sha256:50cec18ac4184af4671a78aedd5554942c8ae105d51a465fa82037949046da01 # v2025.4.22 - env: - MINIO_ROOT_USER: nextcloud - MINIO_ROOT_PASSWORD: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - MINIO_DEFAULT_BUCKETS: nextcloud - ports: - - '9000:9000' - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - env: - OBJECT_STORE_KEY: nextcloud - OBJECT_STORE_SECRET: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - run: | - composer install - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - ./occ app:enable --force files_external - echo " true, 'minio' => true, 'secret' => 'actually-not-secret', 'passwordsalt' => 'actually-not-secret', 'hostname' => 'localhost','key' => '$OBJECT_STORE_KEY','secret' => '$OBJECT_STORE_SECRET', 'bucket' => 'bucket', 'port' => 9000, 'use_ssl' => false, 'autocreate' => true, 'use_path_style' => true];" > apps/files_external/tests/config.amazons3.php - - - name: Wait for S3 - run: | - curl -f -m 1 --retry-connrefused --retry 10 --retry-delay 10 http://localhost:9000/minio/health/ready - - - name: PHPUnit - run: | - composer run test:files_external -- \ - --group S3 \ - --log-junit junit.xml \ - apps/files_external/tests/Storage \ - ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-files-external-s3 - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-files-external-s3 - - - name: Nextcloud logs - if: always() - run: | - cat data/nextcloud.log - - - name: S3 logs - if: always() - run: | - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - files-external-s3-localstack: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - matrix: - php-versions: ['8.2', '8.4'] - include: - - php-versions: '8.3' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-s3-localstack - - services: - localstack: - env: - SERVICES: s3 - DEBUG: 1 - image: localstack/localstack@sha256:9d4253786e0effe974d77fe3c390358391a56090a4fff83b4600d8a64404d95d # v4.5.0 - ports: - - "4566:4566" - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - run: | - composer install - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - ./occ app:enable --force files_external - echo " true, 'localstack' => true, 'hostname' => 'localhost','key' => 'ignored','secret' => 'ignored', 'bucket' => 'bucket', 'port' => 4566, 'use_ssl' => false, 'autocreate' => true, 'use_path_style' => true];" > apps/files_external/tests/config.amazons3.php - - - name: PHPUnit - run: | - composer run test:files_external -- \ - --group S3 \ - --log-junit junit.xml \ - apps/files_external/tests/Storage \ - ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-files-external-s3 - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-files-external-s3 - - - name: S3 logs - if: always() - run: | - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - s3-external-summary: - runs-on: ubuntu-latest-low - needs: [changes, files-external-s3-minio, files-external-s3-localstack] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && (needs.files-external-s3-minio.result != 'success' || needs.files-external-s3-localstack.result != 'success') }}; then exit 1; fi diff --git a/.github/workflows/files-external-sftp.yml b/.github/workflows/files-external-sftp.yml deleted file mode 100644 index 8d6049fc1384d..0000000000000 --- a/.github/workflows/files-external-sftp.yml +++ /dev/null @@ -1,132 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit files_external sFTP -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: files-external-sftp-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - files-external-sftp: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2', '8.4'] - sftpd: ['openssh'] - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-${{ matrix.sftpd }} - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up sftpd - run: | - sudo mkdir /tmp/sftp - sudo chmod -R 0777 /tmp/sftp - if [[ '${{ matrix.sftpd }}' == 'openssh' ]]; then docker run -p 2222:22 --name sftp -d -v /tmp/sftp:/home/test atmoz/sftp 'test:test:::data'; fi - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - run: | - composer install - mkdir data - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - ./occ app:enable --force files_external - echo " true, 'host' => 'localhost:2222','user' => 'test','password' => 'test', 'root' => 'data'];" > apps/files_external/tests/config.sftp.php - - - name: PHPUnit - run: composer run test:files_external -- \ - apps/files_external/tests/Storage/SftpTest.php \ - apps/files_external/tests/Storage/SFTP_KeyTest.php \ - --log-junit junit.xml \ - ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-files-external-sftp - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-files-external-sftp - - - name: sftpd logs - if: always() - run: | - ls -l /tmp/sftp - docker logs sftp - - sftp-summary: - runs-on: ubuntu-latest-low - needs: [changes, files-external-sftp] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.files-external-sftp.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/files-external-smb-kerberos.yml b/.github/workflows/files-external-smb-kerberos.yml deleted file mode 100644 index cc123ff766a0e..0000000000000 --- a/.github/workflows/files-external-smb-kerberos.yml +++ /dev/null @@ -1,128 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Samba Kerberos SSO -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: files-external-smb-kerberos-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - files-external-smb-kerberos: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - name: smb-kerberos-sso - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Checkout user_saml - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - repository: nextcloud/user_saml - path: apps/user_saml - - - name: Install user_saml - run: | - cd apps/user_saml - composer i - cd ../.. - - - name: Pull images - run: | - docker pull ghcr.io/icewind1991/samba-krb-test-dc - docker pull ghcr.io/icewind1991/samba-krb-test-apache-gssapi:8.4 - docker pull ghcr.io/icewind1991/samba-krb-test-client - docker tag ghcr.io/icewind1991/samba-krb-test-dc icewind1991/samba-krb-test-dc - docker tag ghcr.io/icewind1991/samba-krb-test-apache-gssapi:8.4 icewind1991/samba-krb-test-apache-gssapi - docker tag ghcr.io/icewind1991/samba-krb-test-client icewind1991/samba-krb-test-client - - - name: Setup AD-DC - run: | - DC_IP=$(apps/files_external/tests/sso-setup/start-dc.sh) - sleep 1 - apps/files_external/tests/sso-setup/start-apache.sh $DC_IP $PWD - echo "DC_IP=$DC_IP" >> $GITHUB_ENV - - - name: Set up Nextcloud - run: | - apps/files_external/tests/sso-setup/setup-sso-nc.sh - - - name: Test SSO - run: | - apps/files_external/tests/sso-setup/test-sso-smb.sh ${{ env.DC_IP }} - - - name: Show logs DC - if: always() - run: | - docker logs dc - echo "------------" - docker exec dc cat /var/log/samba/log.samba - - - name: Show logs Apache - if: always() - run: | - docker logs apache - - - name: Show logs - if: always() - run: | - FILEPATH=$(docker exec --user 33 apache ./occ log:file | grep "Log file:" | cut -d' ' -f3) - echo "$FILEPATH:" - docker exec --user 33 apache cat $FILEPATH - - smb-kerberos-sso-summary: - runs-on: ubuntu-latest-low - needs: [changes, files-external-smb-kerberos] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.files-external-smb-kerberos.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/files-external-smb.yml b/.github/workflows/files-external-smb.yml deleted file mode 100644 index df934eeffc85f..0000000000000 --- a/.github/workflows/files-external-smb.yml +++ /dev/null @@ -1,138 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit files_external SMB -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: files-external-smb-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - files-external-smb: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-smb - - services: - samba: - image: ghcr.io/servercontainers/samba:smbd-only-a3.18.0-s4.18.2-r0 - env: - ACCOUNT_test: test - UID_test: 1000 - SAMBA_VOLUME_CONFIG_test: "[public]; path=/tmp; valid users = test; guest ok = no; read only = no; browseable = yes" - options: >- - --health-cmd=true - ports: - - 445:445 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, smbclient, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up smbclient - # This is needed as icewind/smb php library for notify - run: sudo apt-get install -y smbclient - - - name: Set up Nextcloud - run: | - composer install - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - ./occ config:system:set --value true --type boolean allow_local_remote_servers - ./occ app:enable --force files_external - echo "true, 'host'=>'localhost', 'user'=>'test', 'password'=>'test', 'root'=>'', 'share'=>'public'];" > apps/files_external/tests/config.smb.php - - - name: Wait for smb - run: | - apps/files_external/tests/env/wait-for-connection 127.0.0.1 445 60 - - - name: PHPUnit - run: composer run test:files_external -- \ - apps/files_external/tests/Storage/SmbTest.php \ - --log-junit junit.xml \ - ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-files-external-smb - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-files-external-smb - - files-external-smb-summary: - runs-on: ubuntu-latest-low - needs: [changes, files-external-smb] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.files-external-smb.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/files-external-webdav.yml b/.github/workflows/files-external-webdav.yml deleted file mode 100644 index 495b184caef77..0000000000000 --- a/.github/workflows/files-external-webdav.yml +++ /dev/null @@ -1,130 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit files_external WebDAV -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: files-external-webdav-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - files-external-webdav-apache: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4'] - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-webdav - - services: - apache: - image: ghcr.io/nextcloud/continuous-integration-webdav-apache:latest # zizmor: ignore[unpinned-images] - ports: - - 8081:80 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - run: | - composer install - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - ./occ config:system:set --value true --type boolean allow_local_remote_servers - ./occ app:enable --force files_external - echo " true, 'host' => 'localhost:8081/webdav/', 'user' => 'test', 'password'=>'pass', 'root' => '', 'wait' => 0];" > apps/files_external/tests/config.webdav.php - - - name: Wait for WebDAV - run: | - sleep 5 - curl -f -m 1 --retry-connrefused --retry 10 --retry-delay 10 http://test:pass@localhost:8081/webdav/ - - - name: PHPUnit - run: composer run test:files_external -- \ - apps/files_external/tests/Storage/WebdavTest.php \ - --log-junit junit.xml \ - ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-files-external-webdav - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-files-external-webdav - - files-external-webdav-summary: - runs-on: ubuntu-latest-low - needs: [changes, files-external-webdav-apache] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.files-external-webdav-apache.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/files-external.yml b/.github/workflows/files-external.yml deleted file mode 100644 index 123e7449d824e..0000000000000 --- a/.github/workflows/files-external.yml +++ /dev/null @@ -1,119 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit files_external generic -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: files-external-generic-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - files-external-generic: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4'] - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-generic - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - env: - OBJECT_STORE_KEY: nextcloud - OBJECT_STORE_SECRET: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - run: | - composer install - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - ./occ app:enable --force files_external - - - name: PHPUnit - run: composer run test:files_external -- \ - --log-junit junit.xml \ - ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-files-external-generic - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-files-external-generic - - files-external-summary: - runs-on: ubuntu-latest-low - needs: [changes, files-external-generic ] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.files-external-generic.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/fixup.yml b/.github/workflows/fixup.yml deleted file mode 100644 index 69da2bbb039f2..0000000000000 --- a/.github/workflows/fixup.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Block fixup and squash commits - -on: - pull_request: - types: [opened, ready_for_review, reopened, synchronize] - -permissions: - contents: read - -concurrency: - group: fixup-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - commit-message-check: - if: github.event.pull_request.draft == false - - permissions: - pull-requests: write - name: Block fixup and squash commits - - runs-on: ubuntu-latest-low - - steps: - - name: Run check - uses: skjnldsv/block-fixup-merge-action@c138ea99e45e186567b64cf065ce90f7158c236a # v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml deleted file mode 100644 index b5504d9f64484..0000000000000 --- a/.github/workflows/generate-release-changelog.yml +++ /dev/null @@ -1,104 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Generate changelog on release - -on: - release: - types: [published] - -permissions: - contents: write - -jobs: - changelog_generate: - runs-on: ubuntu-latest - - # Only allowed to be run on nextcloud-releases repositories - if: ${{ github.repository_owner == 'nextcloud-releases' }} - - steps: - - name: Check actor permission - uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0 - with: - require: write - - - name: Checkout github_helper - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - repository: nextcloud/github_helper - path: github_helper - - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - path: server - fetch-depth: 0 - - - name: Get previous tag - shell: bash - run: | - cd server - # Print all tags - git log --decorate --oneline | egrep 'tag: ' | sed -r 's/^.+tag: ([^,\)]+)[,\)].+$/\1/g' - # Get the current tag - TAGS=$(git log --decorate --oneline | egrep 'tag: ' | sed -r 's/^.+tag: ([^,\)]+)[,\)].+$/\1/g') - CURRENT_TAG=$(echo "$TAGS" | head -n 1) - - # If current tag is the first beta, we use the previous major RC1 - if echo "$CURRENT_TAG" | grep -q 'beta1'; then - MAJOR=$(echo "$CURRENT_TAG" | sed -E 's/^v([0-9]+).*/\1/') - PREV=$((MAJOR - 1)) - PREVIOUS_TAG="v${PREV}.0.0rc1" - # Get the previous tag - filter pre-releases only if current tag is stable - elif echo "$CURRENT_TAG" | grep -q 'rc\|beta\|alpha'; then - # Current tag is pre-release, don't filter - PREVIOUS_TAG=$(echo "$TAGS" | sed -n '2p') - else - # Current tag is stable, filter out pre-releases - PREVIOUS_TAG=$(echo "$TAGS" | grep -v 'rc\|beta\|alpha' | sed -n '2p') - fi - - echo "CURRENT_TAG=$CURRENT_TAG" >> $GITHUB_ENV - echo "PREVIOUS_TAG=$PREVIOUS_TAG" >> $GITHUB_ENV - - # Since this action only runs on nextcloud-releases, ignoring is okay - - name: Verify current tag # zizmor: ignore[template-injection] - run: | - if [ "${{ github.ref_name }}" != "${{ env.CURRENT_TAG }}" ]; then - echo "Current tag does not match the release tag. Exiting." - exit 1 - fi - - - name: Set up php 8.2 - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: 8.2 - coverage: none - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set credentials - run: | - echo '{"username": "github-actions"}' > github_helper/credentials.json - - # Since this action only runs on nextcloud-releases, ignoring is okay - - name: Generate changelog between ${{ env.PREVIOUS_TAG }} and ${{ github.ref_name }} # zizmor: ignore[template-injection] - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd github_helper/changelog - composer install - php index.php generate:changelog --no-bots --format=forum server ${{ env.PREVIOUS_TAG }} ${{ github.ref_name }} > changelog.md - - # Since this action only runs on nextcloud-releases, ignoring is okay - - name: Set changelog to release # zizmor: ignore[template-injection] - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd server - gh release edit ${{ github.ref_name }} --notes-file "../github_helper/changelog/changelog.md" --title "${{ github.ref_name }}" diff --git a/.github/workflows/integration-dav.yml b/.github/workflows/integration-dav.yml deleted file mode 100644 index db670de0b22b3..0000000000000 --- a/.github/workflows/integration-dav.yml +++ /dev/null @@ -1,129 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: DAV integration tests -on: - pull_request: - -permissions: - contents: read - -concurrency: - group: integration-caldav-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/*.php' - - '**/lib/**' - - '**/tests/**' - - '**/vendor-bin/**' - - 'build/integration/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - integration-caldav: - runs-on: ubuntu-latest - needs: changes - - if: needs.changes.outputs.src != 'false' && github.repository_owner != 'nextcloud-gmbh' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - endpoint: ['old', 'new'] - service: ['CalDAV', 'CardDAV'] - - name: ${{ matrix.service }} (${{ matrix.endpoint }} endpoint) php${{ matrix.php-versions }} - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: 'none' - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Python - uses: LizardByte/actions/actions/setup_python@4125866b7b655a6fe038b0e22a43a4c5d259af79 # v2026.417.35446 - with: - python-version: '2.7' - - - name: Set up CalDAVTester - run: | - git clone --depth=1 https://github.com/apple/ccs-caldavtester.git CalDAVTester - git clone --depth=1 https://github.com/apple/ccs-pycalendar.git pycalendar - - - name: Set up Nextcloud - run: | - mkdir data - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - # disable the trashbin, so recurrent deletion of the same object works - ./occ config:app:set dav calendarRetentionObligation --value=0 - # Prepare users - OC_PASS=user01 ./occ user:add --password-from-env user01 - OC_PASS=user02 ./occ user:add --password-from-env user02 - # Prepare calendars - ./occ dav:create-calendar user01 calendar - ./occ dav:create-calendar user01 shared - ./occ dav:create-calendar user02 calendar - # Prepare address books - ./occ dav:create-addressbook user01 addressbook - ./occ dav:create-addressbook user02 addressbook - - - name: Run Nextcloud - run: | - php -S localhost:8888 & - - - name: Run CalDAVTester - run: | - cp "apps/dav/tests/testsuits/caldavtest/serverinfo-${{ matrix.endpoint }}${{ matrix.endpoint == 'old' && (matrix.service == 'CardDAV' && '-carddav' || '-caldav') || '' }}-endpoint.xml" "apps/dav/tests/testsuits/caldavtest/serverinfo.xml" - pushd CalDAVTester - PYTHONPATH="../pycalendar/src" python testcaldav.py --print-details-onfail --basedir "../apps/dav/tests/testsuits/caldavtest" -o cdt.txt \ - "${{ matrix.service }}/current-user-principal.xml" \ - "${{ matrix.service }}/sync-report.xml" \ - ${{ matrix.endpoint == 'new' && format('{0}/sharing-{1}.xml', matrix.service, matrix.service == 'CalDAV' && 'calendars' || 'addressbooks') || ';' }} - popd - - - name: Print Nextcloud logs - if: always() - run: | - cat data/nextcloud.log - - caldav-integration-summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, integration-caldav] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.integration-caldav.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/integration-litmus.yml b/.github/workflows/integration-litmus.yml deleted file mode 100644 index 23db1be07b3ca..0000000000000 --- a/.github/workflows/integration-litmus.yml +++ /dev/null @@ -1,116 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Litmus integration tests -on: - pull_request: - -permissions: - contents: read - -concurrency: - group: integration-litmus-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/*.php' - - '**/lib/**' - - '**/tests/**' - - '**/vendor-bin/**' - - 'build/integration/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - integration-litmus: - runs-on: ubuntu-latest - needs: changes - - if: needs.changes.outputs.src != 'false' && github.repository_owner != 'nextcloud-gmbh' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - endpoint: ['webdav', 'dav'] - - name: Litmus WebDAV ${{ matrix.endpoint }} - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: 'none' - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - run: | - mkdir data - ./occ maintenance:install \ - --verbose \ - --database=sqlite \ - --database-name=nextcloud \ - --database-user=root \ - --database-pass=rootpassword \ - --admin-user admin \ - --admin-pass admin - ./occ config:system:set trusted_domains 2 --value=host.docker.internal:8080 - - - name: Run Nextcloud - run: | - php -S 0.0.0.0:8080 & - - - name: Run Litmus test - run: | - docker run \ - --rm \ - --add-host=host.docker.internal:host-gateway \ - ghcr.io/nextcloud/continuous-integration-litmus-php8.3:latest \ - bash -c '\ - cd /tmp/litmus/litmus-0.13; - make URL=http://host.docker.internal:8080/remote.php/${{ matrix.endpoint }}${{ matrix.endpoint == 'dav' && '/files/admin' || ''}} CREDS="admin admin" TESTS="basic copymove props largefile" check; - status=$?; - cat debug.log; - exit $status;' - - - name: Print Nextcloud logs - if: always() - run: cat data/nextcloud.log - - integration-litmus-summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, integration-litmus] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.integration-litmus.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/integration-s3-primary.yml b/.github/workflows/integration-s3-primary.yml deleted file mode 100644 index 00169985999d5..0000000000000 --- a/.github/workflows/integration-s3-primary.yml +++ /dev/null @@ -1,133 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: S3 primary storage integration tests -on: - pull_request: - -permissions: - contents: read - -concurrency: - group: integration-s3-primary-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'build/integration/**' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - integration-s3-primary: - runs-on: ubuntu-latest - needs: changes - - if: needs.changes.outputs.src != 'false' && github.repository_owner != 'nextcloud-gmbh' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - key: ['objectstore', 'objectstore_multibucket'] - - name: php${{ matrix.php-versions }}-${{ matrix.key }}-minio - - services: - redis: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - ports: - - 6379:6379/tcp - minio: - image: bitnami/minio@sha256:50cec18ac4184af4671a78aedd5554942c8ae105d51a465fa82037949046da01 # v2025.4.22 - env: - MINIO_ROOT_USER: nextcloud - MINIO_ROOT_PASSWORD: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - MINIO_DEFAULT_BUCKETS: nextcloud - ports: - - "9000:9000" - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: 'none' - ini-file: development - ini-values: disable_functions="" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Wait for S3 - run: | - curl -f -m 1 --retry-connrefused --retry 10 --retry-delay 10 http://localhost:9000/minio/health/ready - - - name: Set up Nextcloud - run: | - composer install - mkdir data - echo ' ["class" => "OC\Files\ObjectStore\S3", "arguments" => ["bucket" => "nextcloud", "autocreate" => true, "key" => "nextcloud", "secret" => "bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ=", "hostname" => "localhost", "port" => 9000, "use_ssl" => false, "use_path_style" => true, "uploadPartSize" => 52428800]]];' > config/config.php - echo ' ["host" => "localhost", "port" => 6379], "memcache.local" => "\OC\Memcache\Redis", "memcache.distributed" => "\OC\Memcache\Redis"];' > config/redis.config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f index.php - - - name: Integration - run: | - cd build/integration - bash run.sh --tags "~@failure-s3" dav_features/webdav-related.feature - - - name: S3 logs - if: always() - run: | - cat data/nextcloud.log - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - - s3-primary-integration-summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, integration-s3-primary] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.integration-s3-primary.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/integration-sqlite.yml b/.github/workflows/integration-sqlite.yml deleted file mode 100644 index f5f4571d767c0..0000000000000 --- a/.github/workflows/integration-sqlite.yml +++ /dev/null @@ -1,196 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Integration sqlite - -on: - pull_request: - push: - branches: - - main - - master - - stable* - -permissions: - contents: read - -concurrency: - group: integration-sqlite-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/*.php' - - '**/lib/**' - - '**/tests/**' - - '**/vendor-bin/**' - - 'build/integration/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - 'core/shipped.json' - - integration-sqlite: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - test-suite: - - 'capabilities_features' - - 'collaboration_features' - - 'comments_features' - - 'dav_features' - - 'features' - - 'federation_features' - - '--tags ~@large files_features' - - 'filesdrop_features' - - 'file_conversions' - - 'files_reminders' - - 'openldap_features' - - 'openldap_numerical_features' - - 'ldap_features' - - 'remoteapi_features' - - 'routing_features' - - 'setup_features' - - 'sharees_features' - - 'sharing_features' - - 'theming_features' - - 'videoverification_features' - - 'guests_features' - - php-versions: ['8.4'] - guests-versions: ['main'] - spreed-versions: ['main'] - activity-versions: ['master'] - - services: - redis: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - ports: - - 6379:6379/tcp - openldap: - image: ghcr.io/nextcloud/continuous-integration-openldap:openldap-8 # zizmor: ignore[unpinned-images] - ports: - - 389:389 - - 636:636 - env: - SLAPD_DOMAIN: nextcloud.ci - SLAPD_ORGANIZATION: Nextcloud - SLAPD_PASSWORD: admin - SLAPD_ADDITIONAL_MODULES: memberof - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Checkout Talk app - if: ${{ matrix.test-suite == 'videoverification_features' }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - repository: nextcloud/spreed - path: apps/spreed - ref: ${{ matrix.spreed-versions }} - - - name: Checkout Guests app - if: ${{ matrix.test-suite == 'guests_features' }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - repository: nextcloud/guests - path: apps/guests - ref: ${{ matrix.guests-versions }} - - - name: Checkout Activity app - if: ${{ matrix.test-suite == 'sharing_features' }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - repository: nextcloud/activity - path: apps/activity - ref: ${{ matrix.activity-versions }} - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, ldap, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: none - ini-file: development - ini-values: disable_functions="" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: | - composer install - - - name: Set up Talk dependencies - if: ${{ matrix.test-suite == 'videoverification_features' }} - working-directory: apps/spreed - run: composer i --no-dev - - - name: Set up Nextcloud - run: | - mkdir data - ./occ maintenance:install --verbose ${{ contains(matrix.test-suite,'ldap') && '--data-dir=/dev/shm/nc_int' || '' }} --database=sqlite --database-name=nextcloud --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - ./occ config:system:set hashing_default_password --value=true --type=boolean - - - name: Configure caching - if: ${{ contains(matrix.test-suite,'ldap') }} - run: | - ./occ config:system:set redis host --value=localhost - ./occ config:system:set redis port --value=6379 --type=integer - ./occ config:system:set redis timeout --value=0 --type=integer - ./occ config:system:set memcache.local --value='\OC\Memcache\Redis' - ./occ config:system:set memcache.distributed --value='\OC\Memcache\Redis' - - - name: Run integration - working-directory: build/integration - env: - LDAP_HOST: localhost - run: bash run.sh ${{ matrix.test-suite }} no-tail-log - - - name: Print logs - if: always() - run: | - cat $(./occ log:file |grep "Log file"|cut -d" " -f3) - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, integration-sqlite] - - if: always() - - name: integration-sqlite-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.integration-sqlite.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/lint-eslint.yml b/.github/workflows/lint-eslint.yml deleted file mode 100644 index 3b7e7e1906ce5..0000000000000 --- a/.github/workflows/lint-eslint.yml +++ /dev/null @@ -1,100 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Lint eslint - -on: pull_request - -permissions: - contents: read - -concurrency: - group: lint-eslint-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '**/src/**' - - '**/appinfo/info.xml' - - 'package.json' - - 'package-lock.json' - - 'tsconfig.json' - - '.eslintrc.*' - - '.eslintignore' - - '**.js' - - '**.ts' - - '**.vue' - - lint: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - name: NPM lint - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Install dependencies - env: - CYPRESS_INSTALL_BINARY: 0 - PUPPETEER_SKIP_DOWNLOAD: true - run: npm ci - - - name: Lint - run: npm run lint - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, lint] - - if: always() - - # This is the summary, we just avoid to rename it so that branch protection rules still match - name: eslint - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/lint-php-cs.yml b/.github/workflows/lint-php-cs.yml deleted file mode 100644 index 4a3a3ff27fcdb..0000000000000 --- a/.github/workflows/lint-php-cs.yml +++ /dev/null @@ -1,88 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Lint php-cs - -on: pull_request - -permissions: - contents: read - -concurrency: - group: lint-php-cs-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/lib/**' - - '**/tests/**' - - '**/vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - lint: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - name: php-cs - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: 8.2 - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: none - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install dependencies - run: composer i - - - name: Lint - run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 ) - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, lint] - - if: always() - - # This is the summary, we just avoid to rename it so that branch protection rules still match - name: php-cs - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/lint-php.yml b/.github/workflows/lint-php.yml deleted file mode 100644 index 8e38369355ce0..0000000000000 --- a/.github/workflows/lint-php.yml +++ /dev/null @@ -1,86 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Lint php - -on: pull_request - -permissions: - contents: read - -concurrency: - group: lint-php-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - outputs: - src: ${{ steps.changes.outputs.src}} - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/lib/**' - - '**/tests/**' - - '**/vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - lint: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - matrix: - php-versions: [ '8.2', '8.3', '8.4', '8.5' ] - - name: php-lint - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: none - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Lint - run: composer run lint - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, lint] - - if: always() - - name: php-lint-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/lint-stylelint.yml b/.github/workflows/lint-stylelint.yml deleted file mode 100644 index 231a5d2f92a71..0000000000000 --- a/.github/workflows/lint-stylelint.yml +++ /dev/null @@ -1,96 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Lint stylelint - -on: pull_request - -permissions: - contents: read - -concurrency: - group: lint-stylelint-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '**/src/**' - - '**/appinfo/info.xml' - - 'package.json' - - 'package-lock.json' - - '**.css' - - '**.scss' - - '**.vue' - - lint: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - name: stylelint - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Install dependencies - env: - CYPRESS_INSTALL_BINARY: 0 - run: npm ci - - - name: Lint - run: npm run stylelint - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, lint] - - if: always() - - # This is the summary, we just avoid to rename it so that branch protection rules still match - name: stylelint - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/node-test-handlebars.yml b/.github/workflows/node-test-handlebars.yml deleted file mode 100644 index a73f5251cf477..0000000000000 --- a/.github/workflows/node-test-handlebars.yml +++ /dev/null @@ -1,99 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Node handlebars tests - -on: - pull_request: - -permissions: - contents: read - -concurrency: - group: node-tests-handlebars-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '**/__tests__/**' - - '**/__mocks__/**' - - 'apps/*/src/**' - - 'apps/*/appinfo/info.xml' - - 'core/src/**' - - 'package.json' - - '**/package-lock.json' - - 'tsconfig.json' - - '**.js' - - '**.ts' - - '**.vue' - - handlebars: - runs-on: ubuntu-latest - needs: [changes] - if: needs.changes.outputs.src != 'false' - - env: - CYPRESS_INSTALL_BINARY: 0 - PUPPETEER_SKIP_DOWNLOAD: true - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Install dependencies - run: npm ci - - - name: Run compile - run: ./build/compile-handlebars-templates.sh - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, handlebars] - - if: always() - - name: test-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.handlebars.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml deleted file mode 100644 index 359bead65f746..0000000000000 --- a/.github/workflows/node-test.yml +++ /dev/null @@ -1,106 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Node tests - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: node-tests-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '**/__tests__/**' - - '**/__mocks__/**' - - 'apps/*/src/**' - - 'apps/*/appinfo/info.xml' - - 'core/src/**' - - 'package.json' - - '**/package-lock.json' - - 'tsconfig.json' - - '**.js' - - '**.ts' - - '**.vue' - - test: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Install dependencies - env: - CYPRESS_INSTALL_BINARY: 0 - run: | - npm ci - - - name: Test and process coverage - run: npm run test:coverage - - - name: Collect coverage - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./coverage/lcov.info,./coverage/legacy/lcov.info - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, test] - - if: always() - - name: node-test-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.test.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml deleted file mode 100644 index ff6f1a423e4e3..0000000000000 --- a/.github/workflows/node.yml +++ /dev/null @@ -1,117 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Node - -on: pull_request - -permissions: - contents: read - -concurrency: - group: node-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '**/src/**' - - '**/appinfo/info.xml' - - 'core/css/*' - - 'core/img/**' - - 'package.json' - - '**/package-lock.json' - - 'tsconfig.json' - - '**.js' - - '**.ts' - - '**.vue' - - 'version.php' - - build: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - name: NPM build - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - # This does not work on server as we have some git dependencies that "npm-package-lock-add-resolved" cannot handle - # - name: Validate package-lock.json # See https://github.com/npm/cli/issues/4460 - # run: | - # npm i -g npm-package-lock-add-resolved@1.1.4 - # npm-package-lock-add-resolved - # git --no-pager diff --exit-code - - - name: Install dependencies & build - env: - CYPRESS_INSTALL_BINARY: 0 - PUPPETEER_SKIP_DOWNLOAD: true - run: | - npm ci - npm run build --if-present - - - name: Check build changes - run: | - bash -c "[[ ! \"`git status --porcelain `\" ]] || (echo 'Please recompile and commit the assets, see the section \"Show changes on failure\" for details' && exit 1)" - - - name: Show changes on failure - if: failure() - run: | - git status - git --no-pager diff - exit 1 # make it red to grab attention - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, build] - - if: always() - - # This is the summary, we just avoid to rename it so that branch protection rules still match - name: node - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.build.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/npm-audit-fix.yml b/.github/workflows/npm-audit-fix.yml deleted file mode 100644 index 3cf372a436285..0000000000000 --- a/.github/workflows/npm-audit-fix.yml +++ /dev/null @@ -1,85 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Npm audit fix and compile - -on: - workflow_dispatch: - schedule: - # At 2:30 on Sundays - - cron: '30 2 * * 0' - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - branches: - - ${{ github.event.repository.default_branch }} - - 'stable33' - - 'stable32' - - 'stable31' - - name: npm-audit-fix-${{ matrix.branches }} - - steps: - - name: Checkout - id: checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - ref: ${{ matrix.branches }} - continue-on-error: true - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Fix npm audit - id: npm-audit - uses: nextcloud-libraries/npm-audit-action@1b1728b2b4a7a78d69de65608efcf4db0e3e42d0 # v0.2.0 - - - name: Run npm ci and npm run build - if: steps.checkout.outcome == 'success' - env: - CYPRESS_INSTALL_BINARY: 0 - run: | - npm ci - npm run build --if-present - - - name: Create Pull Request - if: steps.checkout.outcome == 'success' - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - commit-message: 'fix(deps): Fix npm audit' - committer: GitHub - author: nextcloud-command - signoff: true - branch: automated/noid/${{ matrix.branches }}-fix-npm-audit - title: '[${{ matrix.branches }}] Fix npm audit' - body: ${{ steps.npm-audit.outputs.markdown }} - labels: | - dependencies - 3. to review diff --git a/.github/workflows/object-storage-azure.yml b/.github/workflows/object-storage-azure.yml deleted file mode 100644 index 974ccf39ea8f2..0000000000000 --- a/.github/workflows/object-storage-azure.yml +++ /dev/null @@ -1,145 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Object storage azure -on: - pull_request: - schedule: - - cron: "15 2 * * *" - -permissions: - contents: read - -concurrency: - group: object-storage-azure-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - azure-primary-tests: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2', '8.3'] - include: - - php-versions: '8.4' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-azure - - services: - azurite: - image: mcr.microsoft.com/azure-storage/azurite@sha256:0a47e12e3693483cef5c71f35468b91d751611f172d2f97414e9c69113b106d9 # v3.34.0 - env: - AZURITE_ACCOUNTS: nextcloud:bmV4dGNsb3Vk - ports: - - 10000:10000 - options: --health-cmd="nc 127.0.0.1 10000 -z" --health-interval=1s --health-retries=30 - - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - env: - OBJECT_STORE: azure - OBJECT_STORE_KEY: nextcloud - OBJECT_STORE_SECRET: bmV4dGNsb3Vk - run: | - composer install - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - php -f tests/enable_all.php - - - name: PHPUnit - env: - OBJECT_STORE: azure - OBJECT_STORE_KEY: nextcloud - OBJECT_STORE_SECRET: bmV4dGNsb3Vk - run: composer run test -- --group PRIMARY-azure --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-azure - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-azure - - - name: Azurite logs - if: always() - run: | - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - azure-primary-summary: - runs-on: ubuntu-latest-low - needs: [changes, azure-primary-tests] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.azure-primary-tests.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/object-storage-s3.yml b/.github/workflows/object-storage-s3.yml deleted file mode 100644 index a69dd8473154e..0000000000000 --- a/.github/workflows/object-storage-s3.yml +++ /dev/null @@ -1,150 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Object storage S3 -on: - pull_request: - schedule: - - cron: "15 2 * * *" - -permissions: - contents: read - -concurrency: - group: object-storage-s3-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - s3-primary-tests-minio: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - include: - - php-versions: '8.3' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-s3 - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - minio: - image: bitnami/minio@sha256:50cec18ac4184af4671a78aedd5554942c8ae105d51a465fa82037949046da01 # v2025.4.22 - env: - MINIO_ROOT_USER: nextcloud - MINIO_ROOT_PASSWORD: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - MINIO_DEFAULT_BUCKETS: nextcloud - ports: - - "9000:9000" - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - env: - OBJECT_STORE: s3 - OBJECT_STORE_KEY: nextcloud - OBJECT_STORE_SECRET: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - run: | - composer install - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - php -f tests/enable_all.php - - - name: Wait for S3 - run: | - curl -f -m 1 --retry-connrefused --retry 10 --retry-delay 10 http://localhost:9000/minio/health/ready - - - name: PHPUnit - env: - OBJECT_STORE: s3 - OBJECT_STORE_KEY: nextcloud - OBJECT_STORE_SECRET: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - run: composer run test -- --group PRIMARY-s3 --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-s3 - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-s3 - - - name: S3 logs - if: always() - run: | - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - s3-primary-summary: - runs-on: ubuntu-latest-low - needs: [changes,s3-primary-tests-minio] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.s3-primary-tests-minio.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/object-storage-swift.yml b/.github/workflows/object-storage-swift.yml deleted file mode 100644 index 558321b1d7a0e..0000000000000 --- a/.github/workflows/object-storage-swift.yml +++ /dev/null @@ -1,141 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Object storage Swift -on: - pull_request: - schedule: - - cron: "15 2 * * *" - -permissions: - contents: read - -concurrency: - group: object-storage-swift-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - 'apps/files/lib/**' - - 'apps/files/tests/**' - - 'apps/files_external/**' - - 'apps/files_sharing/lib/**' - - 'apps/files_sharing/tests/**' - - 'apps/files_trashbin/lib/**' - - 'apps/files_trashbin/tests/**' - - 'apps/files_versions/lib/**' - - 'apps/files_versions/tests/**' - - 'lib/private/Files/**' - - 'lib/public/Files/**' - - 'tests/lib/Files/**' - - swift-primary-tests: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - include: - - php-versions: '8.3' - coverage: ${{ github.event_name != 'pull_request' }} - - name: php${{ matrix.php-versions }}-swift - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - swift: - image: ghcr.io/cscfi/docker-keystone-swift@sha256:e8b1ec21120ab9adc6ac6a2b98785fd273676439a8633fe898e37f2aea7e0712 - ports: - - 5000:5000 - - 8080:8080 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - env: - OBJECT_STORE: swift - OBJECT_STORE_SECRET: veryfast - run: | - composer install - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - php -f tests/enable_all.php - - - name: PHPUnit - env: - OBJECT_STORE: swift - OBJECT_STORE_SECRET: veryfast - run: composer run test -- --group PRIMARY-swift --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-swift - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-swift - - - name: Swift logs - if: always() - run: | - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - swift-primary-summary: - runs-on: ubuntu-latest-low - needs: [changes,swift-primary-tests] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.swift-primary-tests.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml deleted file mode 100644 index f689899b55c57..0000000000000 --- a/.github/workflows/openapi.yml +++ /dev/null @@ -1,48 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-FileCopyrightText: 2024 Arthur Schiwon -# SPDX-License-Identifier: MIT - -name: OpenAPI - -on: pull_request - -permissions: - contents: read - -concurrency: - group: openapi-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - openapi: - runs-on: ubuntu-latest - - if: ${{ github.repository_owner != 'nextcloud-gmbh' }} - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: '8.2' - extensions: ctype, curl, dom, fileinfo, gd, json, libxml, mbstring, openssl, pcntl, pdo, posix, session, simplexml, xml, xmlreader, xmlwriter, zip, zlib - coverage: none - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: OpenAPI checker - run: build/openapi-checker.sh diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml deleted file mode 100644 index 64916c66ee122..0000000000000 --- a/.github/workflows/performance.yml +++ /dev/null @@ -1,129 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Performance testing -on: - pull_request: - -permissions: - contents: read - -concurrency: - group: performance-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - performance-testing: - runs-on: ubuntu-latest - - # Skip entirely on fork PRs so the job result is 'skipped' rather than - # 'failure'. The profiler action uses github.event.pull_request.head.repo.clone_url - # and GITHUB_TOKEN in ways that do not work reliably from forks, and a - # clean skip is far less confusing for contributors than a mid-run error. - if: >- - github.repository_owner != 'nextcloud-gmbh' && - github.event.pull_request.head.repo.full_name == github.repository - - permissions: - pull-requests: write - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - - name: performance-${{ matrix.php-versions }} - - steps: - - - name: Checkout server before PR - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - ref: ${{ github.event.pull_request.base.ref }} - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - run: | - mkdir data - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - - php -S localhost:8080 & - - name: Apply blueprint - uses: icewind1991/blueprint@00504403f76cb2a09efd0d16793575055e6f63cb # v0.1.2 - with: - blueprint: tests/blueprints/basic.toml - ref: ${{ github.event.pull_request.head.ref }} - - - name: Run before measurements - uses: nextcloud/profiler@6a74c915048285b35b8e1cd96c0835a635945044 - with: - run: | - curl -s -X PROPFIND -u test:test http://localhost:8080/remote.php/dav/files/test - curl -s -u test:test http://localhost:8080/remote.php/dav/files/test/test.txt - curl -s -X PROPFIND -u test:test http://localhost:8080/remote.php/dav/files/test/many_files - curl -s -u test:test -T README.md http://localhost:8080/remote.php/dav/files/test/new_file.txt - curl -s -u test:test -X DELETE http://localhost:8080/remote.php/dav/files/test/new_file.txt - output: before.json - profiler-branch: master - - - name: Apply PR # zizmor: ignore[template-injection] - run: | - git remote add pr '${{ github.event.pull_request.head.repo.clone_url }}' - git fetch pr '${{ github.event.pull_request.head.ref }}' - git checkout -b 'pr/${{ github.event.pull_request.head.ref }}' - git submodule update - - ./occ upgrade - - - name: Run after measurements - id: compare - uses: nextcloud/profiler@6a74c915048285b35b8e1cd96c0835a635945044 - with: - run: | - curl -s -X PROPFIND -u test:test http://localhost:8080/remote.php/dav/files/test - curl -s -u test:test http://localhost:8080/remote.php/dav/files/test/test.txt - curl -s -X PROPFIND -u test:test http://localhost:8080/remote.php/dav/files/test/many_files - curl -s -u test:test -T README.md http://localhost:8080/remote.php/dav/files/test/new_file.txt - curl -s -u test:test -X DELETE http://localhost:8080/remote.php/dav/files/test/new_file.txt - output: after.json - profiler-branch: master - compare-with: before.json - - - name: Upload profiles - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a - with: - name: profiles - path: | - before.json - after.json - - - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v7 - if: failure() && steps.compare.outcome == 'failure' - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - let comment = `Possible performance regression detected\n`; - comment += `
Show Output - - \`\`\` - ${{ steps.compare.outputs.compare }} - \`\`\` - -
`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }) diff --git a/.github/workflows/phpunit-32bits.yml b/.github/workflows/phpunit-32bits.yml deleted file mode 100644 index fb6aa7bd4b474..0000000000000 --- a/.github/workflows/phpunit-32bits.yml +++ /dev/null @@ -1,60 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit 32bits - -on: - pull_request: - paths: - - "version.php" - - ".github/workflows/phpunit-32bits.yml" - - "tests/phpunit-autotest.xml" - - "lib/private/Snowflake/*" - - "tests/lib/Preview/PreviewMapperTest.php" - workflow_dispatch: - schedule: - - cron: "15 1 * * 1-6" - -permissions: - contents: read - -concurrency: - group: phpunit-32bits-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - phpunit-32bits: - runs-on: ubuntu-latest - - if: ${{ github.repository_owner != 'nextcloud-gmbh' }} - - strategy: - fail-fast: false - matrix: - php-versions: ["8.4"] - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up dependencies - uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest - with: - args: /bin/sh -c " - git config --global --add safe.directory /github/workspace && - composer install --no-interaction" - - - name: Set up Nextcloud - uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest - with: - args: /bin/sh -c " - mkdir data && - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-user=autotest --database-pass=rootpassword --admin-user admin --admin-pass admin && - php -f tests/enable_all.php" - - - name: PHPUnit - uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest - with: - args: /bin/sh -c "composer run test -- --exclude-group PRIMARY-azure --exclude-group PRIMARY-s3 --exclude-group PRIMARY-swift --exclude-group Memcached --exclude-group Redis --exclude-group RoutingWeirdness" diff --git a/.github/workflows/phpunit-mariadb.yml b/.github/workflows/phpunit-mariadb.yml deleted file mode 100644 index db7de1ba85b74..0000000000000 --- a/.github/workflows/phpunit-mariadb.yml +++ /dev/null @@ -1,161 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: PHPUnit MariaDB - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-mariadb-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-mariadb: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - mariadb-versions: ['10.6'] - include: - - php-versions: '8.3' - mariadb-versions: '10.11' - coverage: ${{ github.event_name != 'pull_request' }} - - php-versions: '8.4' - mariadb-versions: '11.4' - - php-versions: '8.5' - mariadb-versions: '11.8' - - name: MariaDB ${{ matrix.mariadb-versions }} (PHP ${{ matrix.php-versions }}) - database tests - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - mariadb: - image: mariadb:${{ matrix.mariadb-versions }} - ports: - - 4444:3306/tcp - env: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_USER: oc_autotest - MYSQL_PASSWORD: nextcloud - MYSQL_DATABASE: oc_autotest - options: --health-cmd="${{ matrix.mariadb-versions <= 10.4 && 'mysqladmin' || 'mariadb-admin'}} ping" --health-interval 5s --health-timeout 2s --health-retries 5 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, mysql, pdo_mysql - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: Enable ONLY_FULL_GROUP_BY MariaDB option - run: | - echo "SET GLOBAL sql_mode=(SELECT CONCAT(@@sql_mode,',ONLY_FULL_GROUP_BY'));" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword - echo 'SELECT @@sql_mode;' | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword - - - name: Set up Nextcloud - env: - DB_PORT: 4444 - run: | - mkdir data - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: PHPUnit - run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} - env: - DB_ROOT_USER: root - DB_ROOT_PASS: rootpassword - - - name: Upload db code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.db.xml - flags: phpunit-mariadb - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-mariadb - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-mariadb] - - if: always() - - name: phpunit-mariadb-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-mariadb.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-memcached.yml b/.github/workflows/phpunit-memcached.yml deleted file mode 100644 index 3523b2b9e5ea7..0000000000000 --- a/.github/workflows/phpunit-memcached.yml +++ /dev/null @@ -1,135 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: PHPUnit memcached - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-memcached-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-memcached: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4', '8.5'] - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: Memcached (PHP ${{ matrix.php-versions }}) - - services: - memcached: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 11212:11212/tcp - - 11212:11212/udp - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, memcached, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: Set up Nextcloud - run: | - mkdir data - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: PHPUnit memcached tests - run: composer run test -- --group Memcache --group Memcached --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.xml' || '' }} - - - name: Upload code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.xml - flags: phpunit-memcached - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-memcached - - - name: Print logs - if: always() - run: | - cat data/nextcloud.log - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-memcached] - - if: always() - - name: phpunit-memcached-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-memcached.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-mysql-sharding.yml b/.github/workflows/phpunit-mysql-sharding.yml deleted file mode 100644 index 8bee7fbf7cbe9..0000000000000 --- a/.github/workflows/phpunit-mysql-sharding.yml +++ /dev/null @@ -1,193 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: PHPUnit sharding - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-mysql-sharding-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-mysql: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - mysql-versions: ['8.4'] - - name: Sharding - MySQL ${{ matrix.mysql-versions }} (PHP ${{ matrix.php-versions }}) - database tests - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - mysql: - image: ghcr.io/nextcloud/continuous-integration-mysql-${{ matrix.mysql-versions }}:latest # zizmor: ignore[unpinned-images] - ports: - - 4444:3306/tcp - env: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_USER: oc_autotest - MYSQL_PASSWORD: nextcloud - MYSQL_DATABASE: oc_autotest - options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10 - shard1: - image: ghcr.io/nextcloud/continuous-integration-mysql-${{ matrix.mysql-versions }}:latest # zizmor: ignore[unpinned-images] - ports: - - 5001:3306/tcp - env: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_USER: oc_autotest - MYSQL_PASSWORD: nextcloud - MYSQL_DATABASE: nextcloud - options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10 - shard2: - image: ghcr.io/nextcloud/continuous-integration-mysql-${{ matrix.mysql-versions }}:latest # zizmor: ignore[unpinned-images] - ports: - - 5002:3306/tcp - env: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_USER: oc_autotest - MYSQL_PASSWORD: nextcloud - MYSQL_DATABASE: nextcloud - options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10 - shard3: - image: ghcr.io/nextcloud/continuous-integration-mysql-${{ matrix.mysql-versions }}:latest # zizmor: ignore[unpinned-images] - ports: - - 5003:3306/tcp - env: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_USER: oc_autotest - MYSQL_PASSWORD: nextcloud - MYSQL_DATABASE: nextcloud - options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10 - shard4: - image: ghcr.io/nextcloud/continuous-integration-mysql-${{ matrix.mysql-versions }}:latest # zizmor: ignore[unpinned-images] - ports: - - 5004:3306/tcp - env: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_USER: oc_autotest - MYSQL_PASSWORD: nextcloud - MYSQL_DATABASE: nextcloud - options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, mysql, pdo_mysql - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: Enable ONLY_FULL_GROUP_BY MySQL option - run: | - echo "SET GLOBAL sql_mode=(SELECT CONCAT(@@sql_mode,',ONLY_FULL_GROUP_BY'));" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword - echo "SELECT @@sql_mode;" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword - - - name: Set up Nextcloud - env: - DB_PORT: 4444 - SHARDING: 1 - run: | - mkdir data - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: PHPUnit - run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} - - - name: Upload db code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.db.xml - flags: phpunit-mysql - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-mysql - - - name: Print logs - if: always() - run: | - cat data/nextcloud.log - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-mysql] - - if: always() - - name: phpunit-mysql-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-mysql.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-mysql.yml b/.github/workflows/phpunit-mysql.yml deleted file mode 100644 index 1fca9ded979e7..0000000000000 --- a/.github/workflows/phpunit-mysql.yml +++ /dev/null @@ -1,166 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: PHPUnit mysql - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-mysql-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-mysql: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - mysql-versions: ['8.0'] - include: - - mysql-versions: '8.0' - php-versions: '8.3' - coverage: ${{ github.event_name != 'pull_request' }} - - mysql-versions: '8.4' - php-versions: '8.4' - - mysql-versions: '8.4' - php-versions: '8.5' - - name: MySQL ${{ matrix.mysql-versions }} (PHP ${{ matrix.php-versions }}) - database tests - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - mysql: - image: ghcr.io/nextcloud/continuous-integration-mysql-${{ matrix.mysql-versions }}:latest # zizmor: ignore[unpinned-images] - ports: - - 4444:3306/tcp - env: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_USER: oc_autotest - MYSQL_PASSWORD: nextcloud - MYSQL_DATABASE: oc_autotest - options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, mysql, pdo_mysql - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: Enable ONLY_FULL_GROUP_BY MySQL option - run: | - echo "SET GLOBAL sql_mode=(SELECT CONCAT(@@sql_mode,',ONLY_FULL_GROUP_BY'));" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword - echo "SELECT @@sql_mode;" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword - - - name: Set up Nextcloud - env: - DB_PORT: 4444 - run: | - mkdir data - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: PHPUnit - run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} - env: - DB_ROOT_USER: root - DB_ROOT_PASS: rootpassword - - - name: Upload db code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.db.xml - flags: phpunit-mysql - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-mysql - - - name: Print logs - if: always() - run: | - cat data/nextcloud.log - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-mysql] - - if: always() - - name: phpunit-mysql-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-mysql.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-nodb.yml b/.github/workflows/phpunit-nodb.yml deleted file mode 100644 index 023b36175554a..0000000000000 --- a/.github/workflows/phpunit-nodb.yml +++ /dev/null @@ -1,141 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -# -# This is the testsuite running all non-database agnostic unit tests - -name: PHPUnit nodb - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-nodb-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'resources/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-nodb: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4', '8.5'] - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: No DB unit tests (PHP ${{ matrix.php-versions }}) - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - # Required for tests that use pcntl - ini-values: disable_functions="" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: Set up Nextcloud - run: | - mkdir data - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: PHPUnit nodb testsuite - run: composer run test -- --exclude-group DB --exclude-group SLOWDB --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.nodb.xml' || '' }} - - - name: Upload nodb code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.nodb.xml - flags: phpunit-nodb - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-nodb - - - name: Print logs - if: always() - run: | - cat data/nextcloud.log - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-nodb] - - if: always() - - name: phpunit-nodb-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-nodb.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-object-store-primary.yml b/.github/workflows/phpunit-object-store-primary.yml deleted file mode 100644 index 406890432fe0d..0000000000000 --- a/.github/workflows/phpunit-object-store-primary.yml +++ /dev/null @@ -1,125 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: PHPUnit primary object store -on: - pull_request: - schedule: - - cron: "15 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-object-store-primary-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - object-store-primary-tests-minio: - runs-on: ubuntu-latest - needs: changes - - if: ${{ github.repository_owner != 'nextcloud-gmbh' && needs.changes.outputs.src != 'false' }} - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - key: ['s3', 's3-multibucket'] - - name: php${{ matrix.php-versions }}-${{ matrix.key }}-minio - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - minio: - image: bitnami/minio@sha256:50cec18ac4184af4671a78aedd5554942c8ae105d51a465fa82037949046da01 # v2025.4.22 - env: - MINIO_ROOT_USER: nextcloud - MINIO_ROOT_PASSWORD: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - MINIO_DEFAULT_BUCKETS: nextcloud - ports: - - "9000:9000" - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Nextcloud - env: - OBJECT_STORE: ${{ matrix.key }} - OBJECT_STORE_KEY: nextcloud - OBJECT_STORE_SECRET: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= - run: | - composer install - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password - php -f tests/enable_all.php - - - name: Wait for S3 - run: | - sleep 10 - curl -f -m 1 --retry-connrefused --retry 10 --retry-delay 10 http://localhost:9000/minio/health/ready - - - name: PHPUnit - run: composer run test:db -- --log-junit junit.xml - - - name: S3 logs - if: always() - run: | - cat data/nextcloud.log - docker ps -a - docker ps -aq | while read container ; do IMAGE=$(docker inspect --format='{{.Config.Image}}' $container); echo $IMAGE; docker logs $container; echo "\n\n" ; done - - - object-store-primary-summary: - runs-on: ubuntu-latest-low - needs: [changes,object-store-primary-tests-minio] - - if: always() - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.object-store-primary-tests-minio.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-oci.yml b/.github/workflows/phpunit-oci.yml deleted file mode 100644 index 79ccc0f880202..0000000000000 --- a/.github/workflows/phpunit-oci.yml +++ /dev/null @@ -1,162 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: PHPUnit OCI - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-oci-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-oci: - runs-on: ubuntu-latest - - needs: changes - if: ${{ needs.changes.outputs.src != 'false' && github.repository_owner != 'nextcloud-gmbh' }} - - strategy: - fail-fast: false - matrix: - include: - - oracle-versions: '18' - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - oracle-versions: '21' - php-versions: '8.3' - - oracle-versions: '23' - php-versions: '8.4' - - oracle-versions: '23' - php-versions: '8.5' - - name: Oracle ${{ matrix.oracle-versions }} (PHP ${{ matrix.php-versions }}) - database tests - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - oracle: - image: ghcr.io/gvenzl/oracle-${{ matrix.oracle-versions < 23 && 'xe' || 'free' }}:${{ matrix.oracle-versions }} - - # Provide passwords and other environment variables to container - env: - ORACLE_PASSWORD: oracle - - # Forward Oracle port - ports: - - 1521:1521 - - # Provide healthcheck script options for startup - options: >- - --health-cmd healthcheck.sh - --health-interval 20s - --health-timeout 10s - --health-retries 10 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, oci8 - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: Set up Nextcloud - run: | - mkdir data - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=oci --database-name=${{ matrix.oracle-versions < 23 && 'XE' || 'FREE' }} --database-host=127.0.0.1 --database-port=1521 --database-user=system --database-pass=oracle --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: PHPUnit - run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} - - - name: Upload db code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.db.xml - flags: phpunit-oci - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-oci - - - name: Run repair steps - run: | - ./occ maintenance:repair --include-expensive - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-oci] - - if: always() - - name: phpunit-oci-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-oci.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-pgsql.yml b/.github/workflows/phpunit-pgsql.yml deleted file mode 100644 index 56651fbae99b5..0000000000000 --- a/.github/workflows/phpunit-pgsql.yml +++ /dev/null @@ -1,162 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: PHPUnit PostgreSQL - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-pgsql-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-pgsql: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.2'] - # To keep the matrix smaller we ignore PostgreSQL versions in between as we already test the minimum and the maximum - postgres-versions: ['14'] - include: - - php-versions: '8.3' - postgres-versions: '18' - coverage: ${{ github.event_name != 'pull_request' }} - - php-versions: '8.4' - postgres-versions: '18' - - php-versions: '8.5' - postgres-versions: '18' - - name: PostgreSQL ${{ matrix.postgres-versions }} (PHP ${{ matrix.php-versions }}) - database tests - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - postgres: - image: ghcr.io/nextcloud/continuous-integration-postgres-${{ matrix.postgres-versions }}:latest # zizmor: ignore[unpinned-images] - ports: - - 4444:5432/tcp - env: - POSTGRES_USER: root - POSTGRES_PASSWORD: rootpassword - POSTGRES_DB: nextcloud - options: --mount type=tmpfs,destination=/var/lib/postgresql --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: composer i - - - name: Set up Nextcloud - env: - DB_PORT: 4444 - run: | - mkdir data - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: PHPUnit database tests - run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} - - - name: Upload db code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.db.xml - flags: phpunit-postgres - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-postgres - - - name: Run repair steps - run: | - ./occ maintenance:repair --include-expensive - - - name: Print logs - if: always() - run: | - cat data/nextcloud.log - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-pgsql] - - if: always() - - name: phpunit-pgsql-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-pgsql.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml deleted file mode 100644 index 78b693430ca5c..0000000000000 --- a/.github/workflows/phpunit-sqlite.yml +++ /dev/null @@ -1,147 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: PHPUnit SQLite - -on: - pull_request: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -concurrency: - group: phpunit-sqlite-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - permissions: - contents: read - pull-requests: read - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - '**/tests/**' - - 'vendor/**' - - 'vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - phpunit-sqlite: - runs-on: ubuntu-latest - - needs: changes - if: needs.changes.outputs.src != 'false' - - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4', '8.5'] - include: - - php-versions: '8.2' - coverage: ${{ github.event_name != 'pull_request' }} - - name: SQLite (PHP ${{ matrix.php-versions }}) - - services: - cache: - image: ghcr.io/nextcloud/continuous-integration-redis:latest # zizmor: ignore[unpinned-images] - ports: - - 6379:6379/tcp - options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout server - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php ${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - timeout-minutes: 5 - with: - php-version: ${{ matrix.php-versions }} - # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: ${{ matrix.coverage && 'xdebug' || 'none' }} - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up dependencies - run: | - sudo apt-get update - sudo apt-get install -y ghostscript - composer i - - - name: Set up Nextcloud - run: | - mkdir data - cp tests/redis.config.php config/ - cp tests/preseed-config.php config/config.php - ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin - php -f tests/enable_all.php - - - name: Nextcloud debug information - run: ./occ app:list && echo "======= System config =======" && ./occ config:list system - - - name: PHPUnit database tests - run: composer run test:db -- --log-junit junit.xml ${{ matrix.coverage && '--coverage-clover ./clover.db.xml' || '' }} tests/lib/Preview/PostscriptTest.php - - - name: Upload db code coverage - if: ${{ !cancelled() && matrix.coverage }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 - with: - files: ./clover.db.xml - flags: phpunit-sqlite - - - name: Upload test results - if: ${{ !cancelled() }} - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 - with: - flags: phpunit-sqlite - - - name: Print logs - if: always() - run: | - gs --version - cat /etc/ImageMagick-6/policy.xml - cat data/nextcloud.log - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, phpunit-sqlite] - - if: always() - - name: phpunit-sqlite-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-sqlite.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000000000..371a687e0eab6 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: Playwright Tests + +on: + pull_request: + branches: [ master ] + +permissions: + contents: read + +jobs: + playwright-tests: + timeout-minutes: 60 + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2] + shardTotal: [2] + outputs: + node-version: ${{ steps.versions.outputs.node-version }} + package-manager-version: ${{ steps.versions.outputs.package-manager-version }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Read package.json + uses: nextcloud-libraries/parse-package-engines-action@122ae05d4257008180a514e1ddeb0c1b9d094bdd # v0.1.0 + id: versions + + - name: Set up node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: ${{ steps.versions.outputs.node-version }} + + - name: Set up npm + run: npm i -g 'npm@${{ steps.versions.outputs.package-manager-version }}' + + - name: Install dependencies and build + run: | + npm ci + npm run build --if-present + + - name: Install Playwright browsers + run: npx playwright install --with-deps + + - name: Run Playwright tests + run: npm run playwright -- --shard='${{ matrix.shardIndex }}/${{ matrix.shardTotal }}' + + - name: Upload blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: blob-report-${{ matrix.shardIndex }} + path: blob-report + retention-days: 1 + + merge-reports: + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() }} + needs: [playwright-tests] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: ${{ needs.playwright-tests.outputs.node-version }} + + - name: Set up npm + run: npm i -g 'npm@${{ needs.playwright-tests.outputs.package-manager-version }}' + + - name: Install dependencies + run: npm ci + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html,github ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 7 + + - name: Show the logs + run: | + echo 'To view the report:' + echo ' 1. Extract the folder from the zip file' + echo ' 2. run "npx playwright show-report name-of-my-extracted-playwright-report"' + + summary: + permissions: + contents: none + runs-on: ubuntu-latest-low + needs: [playwright-tests] + + if: always() + + name: playwright-test-summary + + steps: + - name: Summary status + run: if ${{ needs.playwright-tests.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/pr-feedback.yml b/.github/workflows/pr-feedback.yml deleted file mode 100644 index f4c0477ce714f..0000000000000 --- a/.github/workflows/pr-feedback.yml +++ /dev/null @@ -1,55 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization - -# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-FileCopyrightText: 2023 Marcel Klehr -# SPDX-FileCopyrightText: 2023 Joas Schilling <213943+nickvergessen@users.noreply.github.com> -# SPDX-FileCopyrightText: 2023 Daniel Kesselberg -# SPDX-FileCopyrightText: 2023 Florian Steffens -# SPDX-License-Identifier: MIT - -name: 'Ask for feedback on PRs' -on: - schedule: - - cron: '30 1 * * *' - -permissions: - contents: read - pull-requests: write - -jobs: - pr-feedback: - if: ${{ github.repository_owner == 'nextcloud' }} - runs-on: ubuntu-latest - steps: - - name: The get-github-handles-from-website action - uses: marcelklehr/get-github-handles-from-website-action@06b2239db0a48fe1484ba0bfd966a3ab81a08308 # v1.0.1 - id: scrape - with: - website: 'https://nextcloud.com/team/' - - - name: Get blocklist - id: blocklist - run: | - blocklist=$(curl https://raw.githubusercontent.com/nextcloud/.github/master/non-community-usernames.txt | paste -s -d, -) - echo "blocklist=$blocklist" >> "$GITHUB_OUTPUT" - - - uses: nextcloud/pr-feedback-action@f0cab224dea8e1f282f9451de322f323c78fc7a5 # main - with: - feedback-message: | - Hello there, - Thank you so much for taking the time and effort to create a pull request to our Nextcloud project. - - We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process. - - Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6 - - Thank you for contributing to Nextcloud and we hope to hear from you soon! - - (If you believe you should not receive this message, you can add yourself to the [blocklist](https://github.com/nextcloud/.github/blob/master/non-community-usernames.txt).) - days-before-feedback: 14 - start-date: '2025-06-12' - exempt-authors: '${{ steps.blocklist.outputs.blocklist }},${{ steps.scrape.outputs.users }}' - exempt-bots: true diff --git a/.github/workflows/rector-apply.yml b/.github/workflows/rector-apply.yml deleted file mode 100644 index bdcac534b7c0f..0000000000000 --- a/.github/workflows/rector-apply.yml +++ /dev/null @@ -1,70 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Apply rector changes - -on: - workflow_dispatch: - schedule: - # At 14:30 on Sundays - - cron: '30 14 * * 0' - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - php-versions: [ '8.2' ] - - name: rector-apply - - steps: - - name: Checkout - id: checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - ref: ${{ github.event.repository.default_branch }} - - - name: Set up php${{ matrix.php-versions }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - with: - php-version: ${{ matrix.php-versions }} - extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite - coverage: none - ini-file: development - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install dependencies - run: | - composer remove nextcloud/ocp --dev --no-scripts - composer i - git restore lib/composer/composer - - - name: Rector - run: composer run rector - - - name: Create Pull Request - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - commit-message: 'refactor: Apply rector changes' - committer: GitHub - author: nextcloud-command - signoff: true - branch: automated/noid/rector-changes - title: 'Apply rector changes' - labels: | - technical debt - 3. to review diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml deleted file mode 100644 index 735cb94974335..0000000000000 --- a/.github/workflows/rector.yml +++ /dev/null @@ -1,83 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: AGPL-3.0-or-later -name: Rector - -on: - pull_request: - -permissions: - contents: read - -concurrency: - group: rector-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src}} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/lib/**' - - '**/tests/**' - - '**/vendor-bin/**' - - '.php-cs-fixer.dist.php' - - 'composer.json' - - 'composer.lock' - - '**.php' - - strict: - runs-on: ubuntu-latest - - needs: changes - if: ${{ needs.changes.outputs.src != 'false' && github.event_name != 'push' && github.repository_owner != 'nextcloud-gmbh' }} - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - with: - php-version: '8.2' - extensions: apcu,ctype,curl,dom,fileinfo,ftp,gd,imagick,intl,json,ldap,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Composer install - run: composer i - - - name: Rector - run: composer run rector:strict - - - name: Show changes - if: always() - run: git diff --exit-code -- . ':!lib/composer' - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, strict] - - if: always() - - name: rector-summary - - steps: - - name: Summary status - run: if ${{ needs.changes.outputs.src != 'false' && needs.strict.result != 'success' }}; then exit 1; fi diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml deleted file mode 100644 index 3f485f875f7ec..0000000000000 --- a/.github/workflows/reuse.yml +++ /dev/null @@ -1,27 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization - -# SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. -# -# SPDX-License-Identifier: CC0-1.0 - -name: REUSE Compliance Check - -on: [pull_request] - -permissions: - contents: read - -jobs: - reuse-compliance-check: - runs-on: ubuntu-latest-low - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: REUSE Compliance Check - uses: fsfe/reuse-action@676e2d560c9a403aa252096d99fcab3e1132b0f5 # v6.0.0 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 80f841e13e67e..0000000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Close stale issues - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * *" - -permissions: - contents: read - -jobs: - stale: - runs-on: ubuntu-latest - - if: ${{ github.repository_owner != 'nextcloud-gmbh' }} - - permissions: - issues: write - - steps: - - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v9 - with: - repo-token: ${{ secrets.COMMAND_BOT_PAT }} - stale-issue-message: > - This issue has been automatically marked as stale because it has not had - recent activity and seems to be missing some essential information. - It will be closed if no further activity occurs. Thank you - for your contributions. - stale-issue-label: 'stale' - only-labels: 'needs info' - exempt-issue-labels: '1. to develop,2. developing,3. to review,4. to release,security' - days-before-stale: 30 - days-before-close: 14 - # debug-only: true - diff --git a/.github/workflows/static-code-analysis.yml b/.github/workflows/static-code-analysis.yml deleted file mode 100644 index 5155e481ea207..0000000000000 --- a/.github/workflows/static-code-analysis.yml +++ /dev/null @@ -1,234 +0,0 @@ -# SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Psalm static code analysis - -on: - pull_request: - push: - branches: - - main - - master - - stable* - paths: - - '.github/workflows/static-code-analysis.yml' - - '**.php' - -permissions: - contents: read - -concurrency: - group: static-code-analysis-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - changes: - runs-on: ubuntu-latest-low - - outputs: - src: ${{ steps.changes.outputs.src }} - - steps: - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 - id: changes - continue-on-error: true - with: - filters: | - src: - - '.github/workflows/**' - - '3rdparty/**' - - '**/appinfo/**' - - '**/lib/**' - - '**/templates/**' - - 'vendor/**' - - 'vendor-bin/**' - - 'composer.json' - - 'composer.lock' - - '**.php' - - static-code-analysis: - runs-on: ubuntu-latest - - needs: changes - if: ${{ needs.changes.outputs.src != 'false' && github.event_name != 'push' && github.repository_owner != 'nextcloud-gmbh' }} - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - submodules: true - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: '8.2' - extensions: apcu,ctype,curl,dom,fileinfo,ftp,gd,imagick,intl,json,ldap,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Composer install - run: composer i - - - name: Psalm - run: composer run psalm -- --threads=1 --monochrome --no-progress --output-format=github --update-baseline - - - name: Show potential changes in Psalm baseline - if: always() - run: git diff --exit-code -- . ':!lib/composer' - - static-code-analysis-security: - runs-on: ubuntu-latest - - needs: changes - if: ${{ needs.changes.outputs.src != 'false' && github.repository_owner != 'nextcloud-gmbh' }} - - permissions: - security-events: write - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - submodules: true - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: '8.2' - extensions: ctype,curl,dom,fileinfo,ftp,gd,imagick,intl,json,ldap,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip - coverage: none - - - name: Composer install - run: composer i - - - name: Psalm taint analysis - run: composer run psalm:security -- --threads=1 --monochrome --no-progress --output-format=github --update-baseline --report=results.sarif - - - name: Show potential changes in Psalm baseline - if: always() - run: git diff --exit-code -- . ':!lib/composer' - - - name: Upload Security Analysis results to GitHub - if: always() - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3 - with: - sarif_file: results.sarif - - static-code-analysis-ocp: - runs-on: ubuntu-latest - - needs: changes - if: ${{ needs.changes.outputs.src != 'false' && github.event_name != 'push' && github.repository_owner != 'nextcloud-gmbh' }} - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - submodules: true - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: '8.2' - extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Composer install - run: composer i - - - name: Psalm - run: composer run psalm:ocp -- --threads=1 --monochrome --no-progress --output-format=github --update-baseline - - - name: Show potential changes in Psalm baseline - if: always() - run: git diff --exit-code -- . ':!lib/composer' - - static-code-analysis-ncu: - runs-on: ubuntu-latest - - needs: changes - if: ${{ needs.changes.outputs.src != 'false' && github.event_name != 'push' && github.repository_owner != 'nextcloud-gmbh' }} - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - submodules: true - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - timeout-minutes: 5 - with: - php-version: '8.2' - extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Composer install - run: composer i - - - name: Psalm - run: composer run psalm:ncu -- --threads=1 --monochrome --no-progress --output-format=github - - static-code-analysis-strict: - runs-on: ubuntu-latest - - needs: changes - if: ${{ needs.changes.outputs.src != 'false' && github.event_name != 'push' && github.repository_owner != 'nextcloud-gmbh' }} - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - submodules: true - - - name: Set up php - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f #v2.37.0 - with: - php-version: '8.2' - extensions: ctype,curl,dom,fileinfo,gd,imagick,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Composer install - run: composer i - - - name: Psalm - run: composer run psalm:strict -- --threads=1 --monochrome --no-progress --output-format=github - - summary: - permissions: - contents: none - runs-on: ubuntu-latest-low - needs: [changes, static-code-analysis, static-code-analysis-security, static-code-analysis-ocp, static-code-analysis-ncu, static-code-analysis-strict] - - if: always() - - name: static-code-analysis-summary - - steps: - - name: Summary status - run: | - if ${{ needs.changes.outputs.src != 'false' && ( - needs.static-code-analysis-security.result != 'success' || - (github.event_name != 'push' && ( - needs.static-code-analysis.result != 'success' || - needs.static-code-analysis-ocp.result != 'success' || - needs.static-code-analysis-ncu.result != 'success' || - needs.static-code-analysis-strict.result != 'success' - )) - ) }}; then - exit 1 - fi diff --git a/.github/workflows/update-cacert-bundle.yml b/.github/workflows/update-cacert-bundle.yml deleted file mode 100644 index 7c28bd87baef9..0000000000000 --- a/.github/workflows/update-cacert-bundle.yml +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Update CA certificate bundle - -on: - workflow_dispatch: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -jobs: - update-ca-certificate-bundle: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - branches: ['master', 'stable33', 'stable32', 'stable31', 'stable30', 'stable29', 'stable28', 'stable27', 'stable26', 'stable25', 'stable24', 'stable23', 'stable22'] - - name: update-ca-certificate-bundle-${{ matrix.branches }} - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - ref: ${{ matrix.branches }} - submodules: true - - - name: Download CA certificate bundle from curl - run: curl --etag-compare build/ca-bundle-etag.txt --etag-save build/ca-bundle-etag.txt --output resources/config/ca-bundle.crt https://curl.se/ca/cacert.pem - - - name: Create Pull Request - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - commit-message: 'fix(security): Update CA certificate bundle' - committer: GitHub - author: nextcloud-command - signoff: true - branch: 'automated/noid/${{ matrix.branches }}-update-ca-cert-bundle' - title: '[${{ matrix.branches }}] fix(security): Update CA certificate bundle' - body: | - Auto-generated update of CA certificate bundle from [https://curl.se/docs/caextract.html](https://curl.se/docs/caextract.html) - labels: | - dependencies - 3. to review - reviewers: ChristophWurst, miaulalala, nickvergessen diff --git a/.github/workflows/update-code-signing-crl.yml b/.github/workflows/update-code-signing-crl.yml deleted file mode 100644 index 9704aa12cfde3..0000000000000 --- a/.github/workflows/update-code-signing-crl.yml +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Update code signing revocation list - -on: - workflow_dispatch: - schedule: - - cron: "5 2 * * *" - -permissions: - contents: read - -jobs: - update-code-signing-crl: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - branches: ['master', 'stable33', 'stable32', 'stable31', 'stable30', 'stable29', 'stable28', 'stable27', 'stable26', 'stable25', 'stable24', 'stable23', 'stable22'] - - name: update-code-signing-crl-${{ matrix.branches }} - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - ref: ${{ matrix.branches }} - submodules: true - - - name: Download CRL file from Appstore repository - run: curl --output resources/codesigning/root.crl https://raw.githubusercontent.com/nextcloud/appstore/master/nextcloudappstore/certificate/nextcloud.crl - - - name: Verify CRL is from CRT - run: openssl crl -verify -in resources/codesigning/root.crl -CAfile resources/codesigning/root.crt -noout - - - name: Create Pull Request - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - commit-message: 'fix(security): Update code signing revocation list' - committer: GitHub - author: nextcloud-command - signoff: true - branch: 'automated/noid/${{ matrix.branches }}-update-code-signing-crl' - title: '[${{ matrix.branches }}] fix(security): Update code signing revocation list' - body: | - Auto-generated update of code signing revocation list from [Appstore](https://github.com/nextcloud/appstore/commits/master/nextcloudappstore/certificate/nextcloud.crl) - labels: | - dependencies - 3. to review - reviewers: mgallien, miaulalala, nickvergessen diff --git a/.github/workflows/update-min-supported-desktop.yml b/.github/workflows/update-min-supported-desktop.yml deleted file mode 100644 index e2726906defcb..0000000000000 --- a/.github/workflows/update-min-supported-desktop.yml +++ /dev/null @@ -1,128 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Update min supported desktop version - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * 1" - -permissions: - contents: read - -jobs: - update-minimum-supported-desktop-version: - runs-on: ubuntu-latest-low - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - submodules: true - - - name: Download desktop client version file from 5 years ago - id: download - run: | - # Create a temporary directory for the downloaded file - mkdir -p tmp - - # Download the version file from the provided URL - VERSION_FILE_URL="https://github.com/nextcloud/desktop/raw/@%7B5.years.ago%7D/VERSION.cmake" - - # Download the file using curl - curl -s -L "$VERSION_FILE_URL" -o tmp/VERSION.cmake - - if [ ! -f "tmp/VERSION.cmake" ]; then - echo "Failed to download VERSION.cmake file" - exit 1 - fi - - echo "VERSION_FILE=tmp/VERSION.cmake" >> $GITHUB_OUTPUT - echo "Downloaded version file to tmp/VERSION.cmake" - - - name: Extract version info - id: extract-version - run: | - # Path to the downloaded version file - VERSION_FILE="${{ steps.download.outputs.VERSION_FILE }}" - - # Extract major, minor, patch versions - MAJOR=$(grep "VERSION_MAJOR" $VERSION_FILE | grep -o '[0-9]\+') - MINOR=$(grep "VERSION_MINOR" $VERSION_FILE | grep -o '[0-9]\+') - PATCH=$(grep "VERSION_PATCH" $VERSION_FILE | grep -o '[0-9]\+') - - # Construct the version string - VERSION="$MAJOR.$MINOR.$PATCH" - - # Validate format: xx.xx.xx where each x is a digit - if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Error: Extracted version '$VERSION' does not match required format (xx.xx.xx)" - exit 1 - fi - - rm -rf tmp - - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - echo "Extracted Version: $VERSION" - - - name: Update files with new version ${{ steps.extract-version.outputs.VERSION }} - id: update-files - run: | - VERSION="${{ steps.extract-version.outputs.VERSION }}" - - # Define the files to update - DAV_FILE="apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php" - CONFIG_FILE="config/config.sample.php" - - # Check if files exist - if [ ! -f "$DAV_FILE" ]; then - echo "Error: DAV file not found at $DAV_FILE" - exit 1 - fi - - if [ ! -f "$CONFIG_FILE" ]; then - echo "Error: Config file not found at $CONFIG_FILE" - exit 1 - fi - - # Update the DAV file - replace the version in the specific line - sed -i "s/\('minimum\.supported\.desktop\.version', '\)[0-9]\+\.[0-9]\+\.[0-9]\+'/\1$VERSION'/g" "$DAV_FILE" - echo "Updated $DAV_FILE" - - # Update the config sample file - PREV_VERSION=$(grep "'minimum.supported.desktop.version'" "$CONFIG_FILE" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') - sed -i "s/Defaults to \`\`$PREV_VERSION\`\`/Defaults to \`\`$VERSION\`\`/" "$CONFIG_FILE" - sed -i "s/'minimum\.supported\.desktop\.version' => '[0-9]\+\.[0-9]\+\.[0-9]\+'/'minimum.supported.desktop.version' => '$VERSION'/g" "$CONFIG_FILE" - echo "Updated $CONFIG_FILE" - - # Check if any changes were made - if [ -n "$(git diff "$DAV_FILE" "$CONFIG_FILE")" ]; then - echo "CHANGES_MADE=true" >> $GITHUB_OUTPUT - echo "Changes were made to the files" - git diff "$DAV_FILE" "$CONFIG_FILE" - else - echo "CHANGES_MADE=false" >> $GITHUB_OUTPUT - echo "No changes were needed (versions might already be up to date)" - fi - - - name: Create Pull Request - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 - if: steps.update-files.outputs.CHANGES_MADE == 'true' - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - commit-message: "chore: Update minimum supported desktop version" - committer: GitHub - author: nextcloud-command - signoff: true - branch: "automated/noid/update-min-supported-desktop-version" - title: "chore: Update minimum supported desktop version to ${{ steps.extract-version.outputs.VERSION }}" - base: "master" - body: | - Auto-generated update of the minimum supported desktop version using last supported version. - https://github.com/nextcloud/desktop/blob/@%7B5.years.ago%7D/VERSION.cmake - labels: | - client: 💻 desktop - automated - 3. to review - reviewers: '@nextcloud/desktop' diff --git a/.github/workflows/update-stable-titles.yml b/.github/workflows/update-stable-titles.yml deleted file mode 100644 index 8fe443f027c7c..0000000000000 --- a/.github/workflows/update-stable-titles.yml +++ /dev/null @@ -1,69 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Update PRs titles on stable branches - -on: - pull_request: - types: [opened, edited] - branches: - - "stable*" - -concurrency: - group: stable-pr-title-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - update-pr-title: - runs-on: ubuntu-latest-low - permissions: - pull-requests: write - contents: read - - steps: - - name: Wait for potential title edits - run: sleep 15 - - - name: Get PR details and update title - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - - const baseBranch = pr.base.ref; - const currentTitle = pr.title; - - // Check if this is a stable branch - // Should not happen as we only trigger on stable* branches 🤷‍♀️ - if (!baseBranch.startsWith('stable')) { - console.log(`Not a stable branch: ${baseBranch}`); - return; - } - - const prefix = `[${baseBranch}]`; - - // Check if title already has the correct prefix and no other stable tags - const correctTagRegex = new RegExp(`^\\[${baseBranch}\\]\\s*`); - const hasOtherStableTags = /\[stable[\d.]*\]/.test(currentTitle.replace(correctTagRegex, '')); - - if (correctTagRegex.test(currentTitle) && !hasOtherStableTags) { - console.log(`Title already has correct prefix only: ${currentTitle}`); - return; - } - - // Remove all stable tags and add the correct one - const cleanTitle = currentTitle.replace(/\[stable[\d.]*\]\s*/g, '').trim(); - const newTitle = `${prefix} ${cleanTitle}`; - - console.log(`Updating title from: "${currentTitle}" to: "${newTitle}"`); - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - title: newTitle, - }); diff --git a/.gitignore b/.gitignore index 87035b7f67546..a53c847f4a1da 100644 --- a/.gitignore +++ b/.gitignore @@ -145,7 +145,9 @@ Vagrantfile # Tests - auto-generated files /data-autotest +/playwright-report /results.sarif +/test-results /tests/.phpunit.cache /tests/.phpunit.result.cache /tests/coverage* diff --git a/cypress/e2e/theming/a11y-color-contrast.cy.ts b/cypress/e2e/theming/a11y-color-contrast.cy.ts deleted file mode 100644 index 53796707b1cad..0000000000000 --- a/cypress/e2e/theming/a11y-color-contrast.cy.ts +++ /dev/null @@ -1,166 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -const themesToTest = ['light', 'dark', 'light-highcontrast', 'dark-highcontrast'] - -const testCases = { - 'Main text': { - foregroundColors: [ - 'color-main-text', - // 'color-text-light', deprecated - // 'color-text-lighter', deprecated - 'color-text-maxcontrast', - ], - backgroundColors: [ - 'color-main-background', - 'color-background-hover', - 'color-background-dark', - // 'color-background-darker', this should only be used for elements not for text - ], - }, - 'blurred background': { - foregroundColors: [ - 'color-main-text', - 'color-text-maxcontrast-blur', - ], - backgroundColors: [ - 'color-main-background-blur', - ], - }, - Primary: { - foregroundColors: [ - 'color-primary-text', - ], - backgroundColors: [ - // 'color-primary-default', this should only be used for elements not for text! - // 'color-primary-hover', this should only be used for elements and not for text! - 'color-primary', - ], - }, - 'Primary light': { - foregroundColors: [ - 'color-primary-light-text', - ], - backgroundColors: [ - 'color-primary-light', - 'color-primary-light-hover', - ], - }, - 'Primary element': { - foregroundColors: [ - 'color-primary-element-text', - 'color-primary-element-text-dark', - ], - backgroundColors: [ - 'color-primary-element', - 'color-primary-element-hover', - ], - }, - 'Primary element light': { - foregroundColors: [ - 'color-primary-element-light-text', - ], - backgroundColors: [ - 'color-primary-element-light', - 'color-primary-element-light-hover', - ], - }, - 'Severity information texts': { - foregroundColors: [ - 'color-error-text', - 'color-warning-text', - 'color-success-text', - 'color-info-text', - ], - backgroundColors: [ - 'color-main-background', - 'color-background-hover', - ], - }, - // only most important severity colors are supported on the blur - 'Severity information on blur': { - foregroundColors: [ - 'color-error-text', - 'color-success-text', - ], - backgroundColors: [ - 'color-main-background-blur', - ], - }, -} - -/** - * Create a wrapper element with color and background set - * - * @param foreground The foreground color (css variable without leading --) - * @param background The background color - */ -function createTestCase(foreground: string, background: string) { - const wrapper = document.createElement('div') - wrapper.style.padding = '14px' - wrapper.style.color = `var(--${foreground})` - wrapper.style.backgroundColor = `var(--${background})` - if (background.includes('blur')) { - wrapper.style.backdropFilter = 'var(--filter-background-blur)' - } - - const testCase = document.createElement('div') - testCase.innerText = `${foreground} ${background}` - testCase.setAttribute('data-cy-testcase', '') - - wrapper.appendChild(testCase) - return wrapper -} - -describe('Accessibility of Nextcloud theming colors', () => { - for (const theme of themesToTest) { - context(`Theme: ${theme}`, () => { - before(() => { - cy.createRandomUser().then(($user) => { - // set user theme - cy.runOccCommand(`user:setting -- '${$user.userId}' theming enabled-themes '[\\"${theme}\\"]'`) - cy.login($user) - cy.visit('/') - cy.injectAxe({ axeCorePath: 'node_modules/axe-core/axe.min.js' }) - }) - }) - - beforeEach(() => { - cy.document().then((doc) => { - // Unset background image and thus use background-color for testing blur background (images do not work with axe-core) - doc.body.style.backgroundImage = 'unset' - - const root = doc.querySelector('#content') - if (root === null) { - throw new Error('No test root found') - } - root.innerHTML = '' - }) - }) - - for (const [name, { backgroundColors, foregroundColors }] of Object.entries(testCases)) { - context(`Accessibility of CSS color variables for ${name}`, () => { - for (const foreground of foregroundColors) { - for (const background of backgroundColors) { - it(`color contrast of ${foreground} on ${background}`, () => { - cy.document().then((doc) => { - const element = createTestCase(foreground, background) - const root = doc.querySelector('#content') - - expect(root).not.to.be.undefined - - root!.appendChild(element) - - cy.checkA11y('[data-cy-testcase]', { - runOnly: ['color-contrast'], - }) - }) - }) - } - } - }) - } - }) - } -}) diff --git a/cypress/e2e/theming/admin-settings_background.cy.ts b/cypress/e2e/theming/admin-settings_background.cy.ts deleted file mode 100644 index e4ec661486fe2..0000000000000 --- a/cypress/e2e/theming/admin-settings_background.cy.ts +++ /dev/null @@ -1,379 +0,0 @@ -/*! - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/e2e-test-server/cypress' -import { NavigationHeader } from '../../pages/NavigationHeader.ts' -import { - defaultBackground, - defaultPrimary, - pickColor, - validateBodyThemingCss, - validateUserThemingDefaultCss, -} from './themingUtils.ts' - -const admin = new User('admin', 'admin') - -describe('Remove the default background and restore it', { testIsolation: false }, function() { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .should('exist') - .scrollIntoView() - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') - - cy.findByRole('checkbox', { name: /remove background image/i }) - .should('exist') - .should('not.be.checked') - .check({ force: true }) - - cy.wait('@removeBackground') - cy.wait('@cssLoaded') - - cy.window() - .should(() => validateBodyThemingCss(defaultPrimary, null)) - cy.waitUntil(() => cy.window().then((win) => { - const backgroundPlain = getComputedStyle(win.document.body).getPropertyValue('--image-background') - return backgroundPlain !== '' - })) - }) - - it('Screenshot the login page and validate login page', function() { - cy.logout() - cy.visit('/') - - cy.window() - .should(() => validateBodyThemingCss(defaultPrimary, null)) - cy.screenshot() - }) - - it('Undo theming settings and validate login page again', function() { - cy.resetAdminTheming() - cy.visit('/') - - cy.window() - .should(() => validateBodyThemingCss()) - cy.screenshot() - }) -}) - -describe('Remove the default background with a custom background color', function() { - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .should('exist') - .scrollIntoView() - }) - - it('Change the background color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') - - pickColor(cy.findByRole('button', { name: /Background color/ })) - .then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.wait('@cssLoaded') - - cy.window() - .should(() => validateBodyThemingCss( - defaultPrimary, - defaultBackground, - selectedColor, - )) - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - - cy.findByRole('checkbox', { name: /remove background image/i }) - .should('exist') - .should('not.be.checked') - .check({ force: true }) - cy.wait('@removeBackground') - }) - - it('Screenshot the login page and validate login page', function() { - cy.logout() - cy.visit('/') - - cy.window() - .should(() => validateBodyThemingCss(defaultPrimary, null, selectedColor)) - cy.screenshot() - }) - - it('Undo theming settings and validate login page again', function() { - cy.resetAdminTheming() - cy.visit('/') - - cy.window() - .should(() => validateBodyThemingCss()) - cy.screenshot() - }) -}) - -describe('Remove the default background with a bright color', function() { - const navigationHeader = new NavigationHeader() - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.resetUserTheming(admin) - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .should('exist') - .scrollIntoView() - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - cy.findByRole('checkbox', { name: /remove background image/i }) - .check({ force: true }) - cy.wait('@removeBackground') - }) - - it('Change the background color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') - - pickColor(cy.findByRole('button', { name: /Background color/ }), 4) - .then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.wait('@cssLoaded') - - cy.window() - .should(() => validateBodyThemingCss(defaultPrimary, null, selectedColor)) - }) - - it('See the header being inverted', function() { - cy.waitUntil(() => navigationHeader - .getNavigationEntries() - .find('img') - .then((el) => { - let ret = true - el.each(function() { - ret = ret && window.getComputedStyle(this).filter === 'invert(1)' - }) - return ret - })) - }) -}) - -describe('Disable user theming and enable it back', function() { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .should('exist') - .scrollIntoView() - }) - - it('Disable user background theming', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('disableUserTheming') - - cy.findByRole('checkbox', { name: /Disable user theming/ }) - .should('exist') - .and('not.be.checked') - .check({ force: true }) - - cy.wait('@disableUserTheming') - }) - - it('Login as user', function() { - cy.logout() - cy.createRandomUser().then((user) => { - cy.login(user) - }) - }) - - it('User cannot not change background settings', function() { - cy.visit('/settings/user/theming') - cy.contains('Customization has been disabled by your administrator').should('exist') - }) -}) - -describe('The user default background settings reflect the admin theming settings', function() { - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - after(function() { - cy.resetAdminTheming() - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .should('exist') - .scrollIntoView() - }) - - it('Change the default background', function() { - cy.intercept('*/apps/theming/ajax/uploadImage').as('setBackground') - cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') - - cy.fixture('image.jpg', null).as('background') - cy.get('input[type="file"][name="background"]') - .should('exist') - .selectFile('@background', { force: true }) - - cy.wait('@setBackground') - cy.wait('@cssLoaded') - - cy.window() - .should(() => validateBodyThemingCss( - defaultPrimary, - '/apps/theming/image/background?v=', - null, - )) - }) - - it('Change the background color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - cy.intercept('*/apps/theming/theme/default.css?*').as('cssLoaded') - - pickColor(cy.findByRole('button', { name: /Background color/ })) - .then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.wait('@cssLoaded') - - cy.window() - .should(() => validateBodyThemingCss( - defaultPrimary, - '/apps/theming/image/background?v=', - selectedColor, - )) - }) - - it('Login page should match admin theming settings', function() { - cy.logout() - cy.visit('/') - - cy.window() - .should(() => validateBodyThemingCss( - defaultPrimary, - '/apps/theming/image/background?v=', - selectedColor, - )) - }) - - it('Login as user', function() { - cy.createRandomUser().then((user) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .scrollIntoView() - }) - - it('Default user background settings should match admin theming settings', function() { - cy.findByRole('button', { name: 'Default background' }) - .should('exist') - .and('have.attr', 'aria-pressed', 'true') - - cy.window() - .should(() => validateUserThemingDefaultCss( - selectedColor, - '/apps/theming/image/background?v=', - )) - }) -}) - -describe('The user default background settings reflect the admin theming settings with background removed', function() { - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - after(function() { - cy.resetAdminTheming() - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .should('exist') - .scrollIntoView() - }) - - it('Remove the default background', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('removeBackground') - cy.findByRole('checkbox', { name: /remove background image/i }) - .check({ force: true }) - cy.wait('@removeBackground') - }) - - it('Login page should match admin theming settings', function() { - cy.logout() - cy.visit('/') - - cy.window() - .should(() => validateBodyThemingCss(defaultPrimary, null)) - }) - - it('Login as user', function() { - cy.createRandomUser().then((user) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .scrollIntoView() - }) - - it('Default user background settings should match admin theming settings', function() { - cy.findByRole('button', { name: 'Default background' }) - .should('exist') - .and('have.attr', 'aria-pressed', 'true') - - cy.window() - .should(() => validateUserThemingDefaultCss(defaultPrimary, null)) - }) -}) diff --git a/cypress/e2e/theming/admin-settings_branding.cy.ts b/cypress/e2e/theming/admin-settings_branding.cy.ts deleted file mode 100644 index 314a7e2224b6f..0000000000000 --- a/cypress/e2e/theming/admin-settings_branding.cy.ts +++ /dev/null @@ -1,219 +0,0 @@ -/*! - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/e2e-test-server/cypress' - -const admin = new User('admin', 'admin') - -describe('Admin theming: Setting custom project URLs', function() { - this.beforeEach(() => { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - cy.visit('/settings/admin/theming') - cy.intercept('POST', '**/apps/theming/ajax/updateStylesheet').as('updateTheming') - }) - - it('Setting the web link', () => { - cy.findByRole('textbox', { name: /web link/i }) - .and('have.attr', 'type', 'url') - .as('input') - .scrollIntoView() - cy.get('@input') - .should('be.visible') - .type('{selectAll}http://example.com/path?query#fragment{enter}') - - cy.wait('@updateTheming') - - cy.logout() - - cy.visit('/') - cy.contains('a', 'Nextcloud') - .should('be.visible') - .and('have.attr', 'href', 'http://example.com/path?query#fragment') - }) - - it('Setting the legal notice link', () => { - cy.findByRole('textbox', { name: /legal notice link/i }) - .should('exist') - .and('have.attr', 'type', 'url') - .as('input') - .scrollIntoView() - cy.get('@input') - .type('http://example.com/path?query#fragment{enter}') - - cy.wait('@updateTheming') - - cy.logout() - - cy.visit('/') - cy.contains('a', /legal notice/i) - .should('be.visible') - .and('have.attr', 'href', 'http://example.com/path?query#fragment') - }) - - it('Setting the privacy policy link', () => { - cy.findByRole('textbox', { name: /privacy policy link/i }) - .should('exist') - .as('input') - .scrollIntoView() - cy.get('@input') - .should('have.attr', 'type', 'url') - .type('http://privacy.local/path?query#fragment{enter}') - - cy.wait('@updateTheming') - - cy.logout() - - cy.visit('/') - cy.contains('a', /privacy policy/i) - .should('be.visible') - .and('have.attr', 'href', 'http://privacy.local/path?query#fragment') - }) -}) - -describe('Admin theming: Web link corner cases', function() { - this.beforeEach(() => { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - cy.visit('/settings/admin/theming') - cy.intercept('POST', '**/apps/theming/ajax/updateStylesheet').as('updateTheming') - }) - - it('Already URL encoded', () => { - cy.findByRole('textbox', { name: /web link/i }) - .and('have.attr', 'type', 'url') - .as('input') - .scrollIntoView() - cy.get('@input') - .should('be.visible') - .type('{selectAll}http://example.com/%22path%20with%20space%22{enter}') - - cy.wait('@updateTheming') - - cy.logout() - - cy.visit('/') - cy.contains('a', 'Nextcloud') - .should('be.visible') - .and('have.attr', 'href', 'http://example.com/%22path%20with%20space%22') - }) - - it('URL with double quotes', () => { - cy.findByRole('textbox', { name: /web link/i }) - .and('have.attr', 'type', 'url') - .as('input') - .scrollIntoView() - cy.get('@input') - .should('be.visible') - .type('{selectAll}http://example.com/"path"{enter}') - - cy.wait('@updateTheming') - - cy.logout() - - cy.visit('/') - cy.contains('a', 'Nextcloud') - .should('be.visible') - .and('have.attr', 'href', 'http://example.com/%22path%22') - }) - - it('URL with double quotes and already encoded', () => { - cy.findByRole('textbox', { name: /web link/i }) - .and('have.attr', 'type', 'url') - .as('input') - .scrollIntoView() - cy.get('@input') - .should('be.visible') - .type('{selectAll}http://example.com/"the%20path"{enter}') - - cy.wait('@updateTheming') - - cy.logout() - - cy.visit('/') - cy.contains('a', 'Nextcloud') - .should('be.visible') - .and('have.attr', 'href', 'http://example.com/%22the%20path%22') - }) -}) - -describe('Admin theming: Change the login fields then reset them', function() { - const name = 'ABCdef123' - const url = 'https://example.com' - const slogan = 'Testing is fun' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: /^Theming/ }) - .should('exist') - .scrollIntoView() - }) - - it('Change the name field', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('updateFields') - - // Name - cy.findByRole('textbox', { name: 'Name' }) - .should('be.visible') - .type(`{selectall}${name}{enter}`) - cy.wait('@updateFields') - - // Url - cy.findByRole('textbox', { name: 'Web link' }) - .should('be.visible') - .type(`{selectall}${url}{enter}`) - cy.wait('@updateFields') - - // Slogan - cy.findByRole('textbox', { name: 'Slogan' }) - .should('be.visible') - .type(`{selectall}${slogan}{enter}`) - cy.wait('@updateFields') - }) - - it('Ensure undo button presence', function() { - cy.findAllByRole('button', { name: /undo changes/i }) - .should('have.length', 3) - }) - - it('Validate login screen changes', function() { - cy.logout() - cy.visit('/') - - cy.get('[data-login-form-headline]').should('contain.text', name) - cy.get('footer p a').should('have.text', name) - cy.get('footer p a').should('have.attr', 'href', url) - cy.get('footer p').should('contain.text', `– ${slogan}`) - }) - - it('Undo theming settings', function() { - cy.login(admin) - cy.visit('/settings/admin/theming') - cy.findAllByRole('button', { name: /undo changes/i }) - .each((button) => { - cy.intercept('*/apps/theming/ajax/undoChanges').as('undoField') - cy.wrap(button).click() - cy.wait('@undoField') - }) - cy.logout() - }) - - it('Validate login screen changes again', function() { - cy.visit('/') - - cy.get('[data-login-form-headline]').should('not.contain.text', name) - cy.get('footer p a').should('not.have.text', name) - cy.get('footer p a').should('not.have.attr', 'href', url) - cy.get('footer p').should('not.contain.text', `– ${slogan}`) - }) -}) diff --git a/cypress/e2e/theming/admin-settings_colors.cy.ts b/cypress/e2e/theming/admin-settings_colors.cy.ts deleted file mode 100644 index 6651c3a4714ca..0000000000000 --- a/cypress/e2e/theming/admin-settings_colors.cy.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*! - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/e2e-test-server/cypress' -import { - defaultBackground, - defaultPrimary, - pickColor, - validateBodyThemingCss, -} from './themingUtils.ts' - -const admin = new User('admin', 'admin') - -describe('Change the primary color and reset it', function() { - let selectedColor = '' - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the admin theming section', function() { - cy.visit('/settings/admin/theming') - cy.findByRole('heading', { name: 'Background and color' }) - .should('exist') - .scrollIntoView() - }) - - it('Change the primary color', function() { - cy.intercept('*/apps/theming/ajax/updateStylesheet').as('setColor') - - pickColor(cy.findByRole('button', { name: /Primary color/ })) - .then((color) => { - selectedColor = color - }) - - cy.wait('@setColor') - cy.waitUntil(() => validateBodyThemingCss( - selectedColor, - defaultBackground, - defaultPrimary, - )) - }) - - it('Screenshot the login page and validate login page', function() { - cy.logout() - cy.visit('/') - - cy.waitUntil(() => validateBodyThemingCss( - selectedColor, - defaultBackground, - defaultPrimary, - )) - cy.screenshot() - }) - - it('Undo theming settings and validate login page again', function() { - cy.resetAdminTheming() - cy.visit('/') - - cy.waitUntil(validateBodyThemingCss) - cy.screenshot() - }) -}) diff --git a/cypress/e2e/theming/admin-settings_default-app.cy.ts b/cypress/e2e/theming/admin-settings_default-app.cy.ts deleted file mode 100644 index a11928d21a770..0000000000000 --- a/cypress/e2e/theming/admin-settings_default-app.cy.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/e2e-test-server/cypress' -import { NavigationHeader } from '../../pages/NavigationHeader.ts' - -const admin = new User('admin', 'admin') - -after(() => cy.resetAdminTheming()) - -describe('Admin theming set default apps', () => { - const navigationHeader = new NavigationHeader() - - before(function() { - // Just in case previous test failed - cy.resetAdminTheming() - cy.login(admin) - }) - - it('See the current default app is the dashboard', () => { - // check default route - cy.visit('/') - cy.url().should('match', /apps\/dashboard/) - - // Also check the top logo link - navigationHeader.logo().click() - cy.url().should('match', /apps\/dashboard/) - }) - - it('See the default app settings', () => { - cy.visit('/settings/admin/theming') - - cy.get('.settings-section').contains('Navigation bar settings').should('exist') - getDefaultAppSwitch().should('exist') - getDefaultAppSwitch().scrollIntoView() - }) - - it('Toggle the "use custom default app" switch', () => { - getDefaultAppSwitch().should('not.be.checked') - cy.findByRole('region', { name: 'Global default app' }) - .should('not.exist') - - getDefaultAppSwitch().check({ force: true }) - getDefaultAppSwitch().should('be.checked') - cy.findByRole('region', { name: 'Global default app' }) - .should('exist') - }) - - it('See the default app combobox', () => { - cy.findByRole('region', { name: 'Global default app' }) - .should('exist') - .findByRole('combobox') - .as('defaultAppSelect') - .scrollIntoView() - - cy.get('@defaultAppSelect') - .findByText('Dashboard') - .should('be.visible') - cy.get('@defaultAppSelect') - .findByText('Files') - .should('be.visible') - }) - - it('See the default app order selector', () => { - cy.findByRole('region', { name: 'Global default app' }) - .should('exist') - cy.findByRole('list', { name: 'Navigation bar app order' }) - .should('exist') - .findAllByRole('listitem') - .should('have.length', 2) - .then((elements) => { - const appIDs = elements.map((idx, el) => el.innerText.trim()).get() - expect(appIDs).to.deep.eq(['Dashboard', 'Files']) - }) - }) - - it('Change the default app', () => { - cy.findByRole('list', { name: 'Navigation bar app order' }) - .should('exist') - .as('appOrderSelector') - .scrollIntoView() - - cy.get('@appOrderSelector') - .findAllByRole('listitem') - .filter((_, e) => !!e.innerText.match(/Files/i)) - .findByRole('button', { name: 'Move up' }) - .as('moveFilesUpButton') - - cy.get('@moveFilesUpButton').should('be.visible') - cy.get('@moveFilesUpButton').click() - cy.get('@moveFilesUpButton').should('not.exist') - }) - - it('See the default app is changed', () => { - cy.findByRole('list', { name: 'Navigation bar app order' }) - .findAllByRole('listitem') - .then((elements) => { - const appIDs = elements.map((idx, el) => el.innerText.trim()).get() - expect(appIDs).to.deep.eq(['Files', 'Dashboard']) - }) - - // Check the redirect to the default app works - cy.request({ url: '/', followRedirect: false }).then((response) => { - expect(response.status).to.eq(302) - expect(response).to.have.property('headers') - expect(response.headers.location).to.contain('/apps/files') - }) - }) - - it('Toggle the "use custom default app" switch back to reset the default apps', () => { - cy.visit('/settings/admin/theming') - getDefaultAppSwitch().scrollIntoView() - - getDefaultAppSwitch().should('be.checked') - getDefaultAppSwitch().uncheck({ force: true }) - getDefaultAppSwitch().should('be.not.checked') - - // Check the redirect to the default app works - cy.request({ url: '/', followRedirect: false }).then((response) => { - expect(response.status).to.eq(302) - expect(response).to.have.property('headers') - expect(response.headers.location).to.contain('/apps/dashboard') - }) - }) -}) - -function getDefaultAppSwitch() { - return cy.findByRole('checkbox', { name: 'Use custom default app' }) -} diff --git a/cypress/e2e/theming/themingUtils.ts b/cypress/e2e/theming/themingUtils.ts deleted file mode 100644 index 59487920e586a..0000000000000 --- a/cypress/e2e/theming/themingUtils.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import { colord } from 'colord' - -export const defaultPrimary = '#00679e' -export const defaultBackground = 'jo-myoung-hee-fluid.webp' - -/** - * Check if a CSS variable is set to a specific color - * - * @param variable Variable to check - * @param expectedColor Color that is expected - */ -export function validateCSSVariable(variable: string, expectedColor: string) { - const value = window.getComputedStyle(Cypress.$('body').get(0)).getPropertyValue(variable) - console.debug(`${variable}, is: ${colord(value).toHex()} expected: ${expectedColor}`) - return colord(value).isEqual(expectedColor) -} - -/** - * Validate the current page body css variables - * - * @param expectedColor the expected primary color - * @param expectedBackground the expected background - * @param expectedBackgroundColor the expected background color (null to ignore) - */ -export function validateBodyThemingCss(expectedColor = defaultPrimary, expectedBackground: string | null = defaultBackground, expectedBackgroundColor: string | null = defaultPrimary) { - // We must use `Cypress.$` here as any assertions (get is an assertion) is not allowed in wait-until's check function, see documentation - const guestBackgroundColor = Cypress.$('body').css('background-color') - const guestBackgroundImage = Cypress.$('body').css('background-image') - - const isValidBackgroundColor = expectedBackgroundColor === null || colord(guestBackgroundColor).isEqual(expectedBackgroundColor) - const isValidBackgroundImage = !expectedBackground - ? guestBackgroundImage === 'none' - : guestBackgroundImage.includes(expectedBackground) - - console.debug({ - isValidBackgroundColor, - isValidBackgroundImage, - guestBackgroundColor: colord(guestBackgroundColor).toHex(), - guestBackgroundImage, - }) - - return isValidBackgroundColor && isValidBackgroundImage && validateCSSVariable('--color-primary', expectedColor) -} - -/** - * Check background color of element - * - * @param element JQuery element to check - * @param color expected color - */ -export function expectBackgroundColor(element: JQuery, color: string) { - expect(colord(element.css('background-color')).toHex()).equal(colord(color).toHex()) -} - -/** - * Validate the user theming default select option css - * - * @param expectedColor the expected color - * @param expectedBackground the expected background - */ -export function validateUserThemingDefaultCss(expectedColor = defaultPrimary, expectedBackground: string | null = defaultBackground) { - const backgroundImage = Cypress.$('body').css('background-image') - const backgroundColor = Cypress.$('body').css('background-color') - - const isValidBackgroundImage = !expectedBackground - ? (backgroundImage === 'none' || Cypress.$('body').css('background-image') === 'none') - : backgroundImage.includes(expectedBackground) - - console.debug({ - colorPickerOptionColor: colord(backgroundColor).toHex(), - expectedColor, - isValidBackgroundImage, - backgroundImage, - }) - - return isValidBackgroundImage && colord(backgroundColor).isEqual(expectedColor) -} - -/** - * @param trigger - The color picker trigger - * @param index - The color index to pick, if not provided a random one will be picked - */ -export function pickColor(trigger: Cypress.Chainable, index?: number): Cypress.Chainable { - // Pick one of the first 8 options - const randColour = index ?? Math.floor(Math.random() * 8) - - let oldColor = '' - trigger.as('trigger').then(($el) => { - oldColor = $el.css('background-color') - }) - - cy.get('@trigger').scrollIntoView() - cy.get('@trigger').click({ force: true }) - - // Click on random color - cy.get('.color-picker__simple-color-circle').eq(randColour).click() - - // Wait for color change - cy.get('@trigger') - .should(($el) => $el.css('background-color') !== oldColor) - - cy.findByRole('button', { name: /Choose/i }).click() - - // Get the selected color from the color preview block - return cy.get('@trigger').then(($el) => $el.css('background-color')) -} diff --git a/cypress/e2e/theming/user-settings_app-order.cy.ts b/cypress/e2e/theming/user-settings_app-order.cy.ts deleted file mode 100644 index 3db1a17ac4ee2..0000000000000 --- a/cypress/e2e/theming/user-settings_app-order.cy.ts +++ /dev/null @@ -1,282 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import type { User } from '@nextcloud/e2e-test-server/cypress' - -import { NavigationHeader } from '../../pages/NavigationHeader.ts' -import { SettingsAppOrderList } from '../../pages/SettingsAppOrderList.ts' -import { installTestApp, uninstallTestApp } from '../../support/commonUtils.ts' - -before(() => uninstallTestApp()) - -describe('User theming set app order', () => { - const navigationHeader = new NavigationHeader() - const appOrderList = new SettingsAppOrderList() - let user: User - - before(() => { - cy.resetAdminTheming() - // Create random user for this test - cy.createRandomUser().then(($user) => { - user = $user - cy.login($user) - }) - }) - - after(() => cy.deleteUser(user)) - - it('See the app order settings', () => { - visitAppOrderSettings() - }) - - it('See that the dashboard app is the first one', () => { - const appOrder = ['Dashboard', 'Files'] - appOrderList.assertAppOrder(appOrder) - - // Check the top app menu order - navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) - }) - - it('Change the app order', () => { - appOrderList.interceptAppOrder() - appOrderList.getAppOrderList() - .scrollIntoView() - appOrderList.getUpButtonForApp('Files') - .should('be.visible') - .click() - appOrderList.waitForAppOrderUpdate() - - appOrderList.assertAppOrder(['Files', 'Dashboard']) - }) - - it('See the app menu order is changed', () => { - cy.reload() - const appOrder = ['Files', 'Dashboard'] - appOrderList.getAppOrderList() - .scrollIntoView() - appOrderList.assertAppOrder(appOrder) - - // Check the top app menu order - navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) - }) -}) - -describe('User theming set app order with default app', () => { - const appOrderList = new SettingsAppOrderList() - const navigationHeader = new NavigationHeader() - let user: User - - before(() => { - cy.resetAdminTheming() - // install a third app - installTestApp() - // set files as default app - cy.runOccCommand('config:system:set --value \'files\' defaultapp') - - // Create random user for this test - cy.createRandomUser().then(($user) => { - user = $user - cy.login($user) - }) - }) - - after(() => { - cy.deleteUser(user) - uninstallTestApp() - }) - - it('See files is the default app', () => { - // Check the redirect to the default app works - cy.request({ url: '/', followRedirect: false }).then((response) => { - expect(response.status).to.eq(302) - expect(response).to.have.property('headers') - expect(response.headers.location).to.contain('/apps/files') - }) - }) - - it('See the app order settings: files is the first one', () => { - visitAppOrderSettings() - - const appOrder = ['Files', 'Dashboard', 'Test App 2', 'Test App'] - appOrderList.getAppOrderList() - .scrollIntoView() - appOrderList.assertAppOrder(appOrder) - }) - - it('Can not change the default app', () => { - appOrderList.getUpButtonForApp('Files').should('not.exist') - appOrderList.getDownButtonForApp('Files').should('not.exist') - appOrderList.getUpButtonForApp('Dashboard').should('not.exist') - // but can move down - appOrderList.getDownButtonForApp('Dashboard').should('be.visible') - }) - - it('Can see the correct buttons for other apps', () => { - appOrderList.getUpButtonForApp('Test App 2').should('be.visible') - appOrderList.getDownButtonForApp('Test App 2').should('be.visible') - appOrderList.getUpButtonForApp('Test App').should('be.visible') - appOrderList.getDownButtonForApp('Test App').should('not.exist') - }) - - it('Change the order of the other apps', () => { - appOrderList.interceptAppOrder() - appOrderList.getUpButtonForApp('Test App').click() - appOrderList.waitForAppOrderUpdate() - appOrderList.getUpButtonForApp('Test App').click() - appOrderList.waitForAppOrderUpdate() - - // Can't get up anymore, files is enforced as default app - appOrderList.getUpButtonForApp('Test App').should('not.exist') - - // Check the app order settings UI - appOrderList.assertAppOrder(['Files', 'Test App', 'Dashboard', 'Test App 2']) - }) - - it('See the app menu order is changed', () => { - cy.reload() - - const appOrder = ['Files', 'Test App', 'Dashboard', 'Test App 2'] - // Check the top app menu order - navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) - }) -}) - -describe('User theming app order list accessibility', () => { - const appOrderList = new SettingsAppOrderList() - let user: User - - before(() => { - cy.resetAdminTheming() - installTestApp() - // Create random user for this test - cy.createRandomUser().then(($user) => { - user = $user - cy.login($user) - }) - }) - - after(() => { - uninstallTestApp() - cy.deleteUser(user) - }) - - it('click the first button', () => { - visitAppOrderSettings() - appOrderList.interceptAppOrder() - appOrderList.getDownButtonForApp('Dashboard') - .should('be.visible') - .scrollIntoView() - appOrderList.getDownButtonForApp('Dashboard') - .focus() - appOrderList.getDownButtonForApp('Dashboard') - .click() - appOrderList.waitForAppOrderUpdate() - }) - - it('see the same app kept the focus', () => { - appOrderList.getDownButtonForApp('Dashboard').should('have.focus') - }) - - it('click the last button', () => { - appOrderList.interceptAppOrder() - appOrderList.getUpButtonForApp('Dashboard') - .should('be.visible') - .focus() - appOrderList.getUpButtonForApp('Dashboard').click() - appOrderList.waitForAppOrderUpdate() - }) - - it('see the same app kept the focus', () => { - appOrderList.getUpButtonForApp('Dashboard').should('not.exist') - appOrderList.getDownButtonForApp('Dashboard').should('have.focus') - }) -}) - -describe('User theming reset app order', () => { - const appOrderList = new SettingsAppOrderList() - const navigationHeader = new NavigationHeader() - let user: User - - before(() => { - cy.resetAdminTheming() - // Create random user for this test - cy.createRandomUser().then(($user) => { - user = $user - cy.login($user) - }) - }) - - after(() => cy.deleteUser(user)) - - it('See that the dashboard app is the first one', () => { - visitAppOrderSettings() - - const appOrder = ['Dashboard', 'Files'] - appOrderList.assertAppOrder(appOrder) - - // Check the top app menu order - navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) - }) - - it('See the reset button is disabled', () => { - appOrderList.getResetButton() - .scrollIntoView() - appOrderList.getResetButton() - .should('be.disabled') - }) - - it('Change the app order', () => { - appOrderList.interceptAppOrder() - appOrderList.getUpButtonForApp('Files') - .should('be.visible') - .click() - appOrderList.waitForAppOrderUpdate() - - appOrderList.assertAppOrder(['Files', 'Dashboard']) - }) - - it('See the reset button is no longer disabled', () => { - appOrderList.getResetButton() - .scrollIntoView() - appOrderList.getResetButton() - .should('be.visible') - .and('be.enabled') - }) - - it('Reset the app order', () => { - cy.intercept('GET', '/ocs/v2.php/core/navigation/apps').as('loadApps') - appOrderList.interceptAppOrder() - appOrderList.getResetButton().click({ force: true }) - - cy.wait('@updateAppOrder') - .its('request.body') - .should('have.property', 'configValue', '[]') - cy.wait('@loadApps') - }) - - it('See the app order is restored', () => { - const appOrder = ['Dashboard', 'Files'] - appOrderList.assertAppOrder(appOrder) - // Check the top app menu order - navigationHeader.getNavigationEntries() - .each((entry, index) => expect(entry).contain.text(appOrder[index]!)) - }) - - it('See the reset button is disabled again', () => { - appOrderList.getResetButton() - .should('be.disabled') - }) -}) - -function visitAppOrderSettings() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: /Navigation bar settings/ }) - .should('exist') - .scrollIntoView() -} diff --git a/cypress/e2e/theming/user-settings_background.cy.ts b/cypress/e2e/theming/user-settings_background.cy.ts deleted file mode 100644 index 639de55f571f6..0000000000000 --- a/cypress/e2e/theming/user-settings_background.cy.ts +++ /dev/null @@ -1,262 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/e2e-test-server/cypress' -import { NavigationHeader } from '../../pages/NavigationHeader.ts' -import { defaultPrimary, pickColor, validateBodyThemingCss } from './themingUtils.ts' - -const admin = new User('admin', 'admin') - -describe('User default background settings', function() { - before(function() { - cy.resetAdminTheming() - cy.resetUserTheming(admin) - cy.createRandomUser().then((user: User) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: /Appearance and accessibility settings/ }) - .should('be.visible') - }) - - it('Default is selected on new users', function() { - cy.findByRole('button', { name: 'Default background', pressed: true }) - .should('exist') - .scrollIntoView() - }) -}) - -describe('User select shipped backgrounds and remove background', function() { - before(function() { - cy.createRandomUser().then((user: User) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: /Background and color/ }) - .should('exist') - .scrollIntoView() - }) - - it('Select a shipped background', function() { - const background = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg' - const backgroundName = 'Background picture of a red-ish butterfly wing under microscope' - cy.intercept('*/apps/theming/background/shipped').as('setBackground') - - // Select background - cy.findByRole('button', { name: backgroundName, pressed: false }) - .click() - cy.findByRole('button', { name: backgroundName, pressed: true }) - .should('be.visible') - - // Validate changed background and primary - cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss('#a53c17', background, '#652e11')) - }) - - it('Select a bright shipped background', function() { - const background = 'bernie-cetonia-aurata-take-off-composition.jpg' - const backgroundName = 'Montage of a cetonia aurata bug that takes off with white background' - cy.intercept('*/apps/theming/background/shipped').as('setBackground') - - cy.findByRole('button', { name: backgroundName, pressed: false }) - .click() - cy.findByRole('button', { name: backgroundName, pressed: true }) - .should('be.visible') - - // Validate changed background and primary - cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss('#56633d', background, '#dee0d3')) - }) -}) - -describe('User select a custom color', function() { - before(function() { - cy.createRandomUser().then((user: User) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: /Background and color/ }) - .should('exist') - .scrollIntoView() - }) - - it('Select a custom color', function() { - cy.intercept('*/apps/theming/background/color').as('clearBackground') - - // Clear background - pickColor(cy.findByRole('button', { name: 'Plain background' }), 7) - - // Validate clear background - cy.wait('@clearBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#3794ac')) - }) -}) - -describe('User select a bright custom color and remove background', function() { - const navigationHeader = new NavigationHeader() - - before(function() { - cy.createRandomUser().then((user: User) => { - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: /Background and color/ }) - .should('exist') - .scrollIntoView() - }) - - it('Remove background', function() { - cy.intercept('*/apps/theming/background/color').as('clearBackground') - - // Clear background - pickColor(cy.findByRole('button', { name: 'Plain background' }), 4) - - // Validate clear background - cy.wait('@clearBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#ddcb55')) - }) - - it('See the header being inverted', function() { - cy.waitUntil(() => navigationHeader.getNavigationEntries().find('img').then((el) => { - let ret = true - el.each(function() { - ret = ret && window.getComputedStyle(this).filter === 'invert(1)' - }) - return ret - })) - }) - - it('Select another but non-bright shipped background', function() { - const background = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg' - const backgroundName = 'Background picture of a red-ish butterfly wing under microscope' - cy.intercept('*/apps/theming/background/shipped').as('setBackground') - - // Select background - cy.findByRole('button', { name: backgroundName, pressed: false }) - .click() - cy.findByRole('button', { name: backgroundName, pressed: true }) - .should('be.visible') - - // Validate changed background and primary - cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss('#a53c17', background, '#652e11')) - }) - - it('See the header NOT being inverted this time', function() { - cy.waitUntil(() => navigationHeader.getNavigationEntries().find('img').then((el) => { - let ret = true - el.each(function() { - ret = ret && window.getComputedStyle(this).filter === 'none' - }) - return ret - })) - }) -}) - -describe('User select a custom background', function() { - const image = 'image.jpg' - before(function() { - cy.createRandomUser().then((user: User) => { - cy.uploadFile(user, image, 'image/jpeg') - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: /Background and color/ }) - .should('exist') - .scrollIntoView() - }) - - it('Select a custom background', function() { - cy.intercept('*/apps/theming/background/custom').as('setBackground') - - // Pick background - cy.findByRole('button', { name: 'Custom background' }).click() - cy.findByRole('dialog') - .should('be.visible') - .findAllByRole('row') - .contains(image) - .click() - cy.findByRole('button', { name: 'Select background' }).click() - - // Wait for background to be set - cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, 'apps/theming/background?v=', '#2f2221')) - }) -}) - -describe('User changes settings and reload the page', function() { - const image = 'image.jpg' - - before(function() { - cy.createRandomUser().then((user: User) => { - cy.uploadFile(user, image, 'image/jpeg') - cy.login(user) - }) - }) - - it('See the user background settings', function() { - cy.visit('/settings/user/theming') - cy.findByRole('heading', { name: /Background and color/ }) - .should('exist') - .scrollIntoView() - }) - - it('Select a custom background', function() { - cy.intercept('*/apps/theming/background/custom').as('setBackground') - - // Pick background - cy.findByRole('button', { name: 'Custom background' }).click() - cy.findByRole('dialog') - .should('be.visible') - .findAllByRole('row') - .contains(image) - .click() - cy.findByRole('button', { name: 'Select background' }).click() - - // Wait for background to be set - cy.wait('@setBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, 'apps/theming/background?v=', '#2f2221')) - }) - - it('Select a custom background color', function() { - cy.intercept('*/apps/theming/background/color').as('clearBackground') - - // Clear background - pickColor(cy.findByRole('button', { name: 'Plain background' }), 5) - - // Validate clear background - cy.wait('@clearBackground') - cy.waitUntil(() => validateBodyThemingCss(defaultPrimary, null, '#a5b872')) - }) - - it('Select a custom primary color', function() { - cy.intercept('/ocs/v2.php/apps/provisioning_api/api/v1/config/users/theming/primary_color').as('setPrimaryColor') - - pickColor(cy.findByRole('button', { name: 'Primary color' }), 2) - - cy.wait('@setPrimaryColor') - cy.waitUntil(() => validateBodyThemingCss('#c98879', null, '#a5b872')) - }) - - it('Reload the page and validate persistent changes', function() { - cy.reload() - cy.waitUntil(() => validateBodyThemingCss('#c98879', null, '#a5b872')) - }) -}) diff --git a/package-lock.json b/package-lock.json index 2157cd7c771f4..f96e9c205398e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "@nextcloud/stylelint-config": "^3.2.1", "@nextcloud/typings": "^1.10.0", "@nextcloud/vite-config": "^2.5.2", + "@playwright/test": "^1.59.1", "@testing-library/cypress": "^10.1.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/vue": "^8.1.0", @@ -3060,6 +3061,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -13384,6 +13401,53 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", diff --git a/package.json b/package.json index 83c22a8d7e42a..84e39a6c4f955 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "lint": "eslint --suppressions-location build/eslint-baseline.json --no-error-on-unmatched-pattern ./cypress", "postlint": "build/demi.sh lint", "lint:fix": "build/demi.sh lint:fix", + "playwright": "playwright test", + "playwright:install": "playwright install chromium-headless-shell", "sass": "sass --style compressed --load-path core/css core/css/ $(for cssdir in $(find apps -mindepth 2 -maxdepth 2 -name \"css\"); do if ! $(git check-ignore -q $cssdir); then printf \"$cssdir \"; fi; done)", "sass:icons": "node build/icons.mjs", "sass:watch": "sass --watch --load-path core/css core/css/ $(for cssdir in $(find apps -mindepth 2 -maxdepth 2 -name \"css\"); do if ! $(git check-ignore -q $cssdir); then printf \"$cssdir \"; fi; done)", @@ -76,6 +78,7 @@ "@nextcloud/stylelint-config": "^3.2.1", "@nextcloud/typings": "^1.10.0", "@nextcloud/vite-config": "^2.5.2", + "@playwright/test": "^1.59.1", "@testing-library/cypress": "^10.1.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/vue": "^8.1.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000000..07478057bd467 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './tests/playwright/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.CI ? [['blob'], ['dot'], ['github']] : 'html', + use: { + baseURL: 'http://localhost:8042/index.php/', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + ], + webServer: { + command: 'node tests/playwright/start-nextcloud-server.js', + env: { + NEXTCLOUD_PORT: '8042', + }, + stderr: 'pipe', + stdout: 'pipe', + gracefulShutdown: { + signal: 'SIGTERM', + timeout: 10000, + }, + reuseExistingServer: !process.env.CI, + timeout: 5 * 60 * 1000, + wait: { + stdout: /Nextcloud is now ready to use/, + }, + }, +}) diff --git a/tests/playwright/e2e/theming/a11y-color-contrast.spec.ts b/tests/playwright/e2e/theming/a11y-color-contrast.spec.ts new file mode 100644 index 0000000000000..576ba6a96fbe4 --- /dev/null +++ b/tests/playwright/e2e/theming/a11y-color-contrast.spec.ts @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { resolve } from 'node:path' +import { runOcc } from '@nextcloud/e2e-test-server/docker' +import { createRandomUser, login } from '@nextcloud/e2e-test-server/playwright' +import { expect, test } from '@playwright/test' + +const themesToTest = ['light', 'dark', 'light-highcontrast', 'dark-highcontrast'] + +const testCases = { + 'Main text': { + foregroundColors: ['color-main-text', 'color-text-maxcontrast'], + backgroundColors: ['color-main-background', 'color-background-hover', 'color-background-dark'], + }, + 'blurred background': { + foregroundColors: ['color-main-text', 'color-text-maxcontrast-blur'], + backgroundColors: ['color-main-background-blur'], + }, + Primary: { + foregroundColors: ['color-primary-text'], + backgroundColors: ['color-primary'], + }, + 'Primary light': { + foregroundColors: ['color-primary-light-text'], + backgroundColors: ['color-primary-light', 'color-primary-light-hover'], + }, + 'Primary element': { + foregroundColors: ['color-primary-element-text', 'color-primary-element-text-dark'], + backgroundColors: ['color-primary-element', 'color-primary-element-hover'], + }, + 'Primary element light': { + foregroundColors: ['color-primary-element-light-text'], + backgroundColors: ['color-primary-element-light', 'color-primary-element-light-hover'], + }, + 'Severity information texts': { + foregroundColors: ['color-error-text', 'color-warning-text', 'color-success-text', 'color-info-text'], + backgroundColors: ['color-main-background', 'color-background-hover'], + }, + 'Severity information on blur': { + foregroundColors: ['color-error-text', 'color-success-text'], + backgroundColors: ['color-main-background-blur'], + }, +} + +for (const theme of themesToTest) { + test(`Accessibility of Nextcloud theming colors: ${theme}`, async ({ page, context }) => { + const user = await createRandomUser() + const failures: string[] = [] + + try { + await runOcc(['user:setting', '--', user.userId, 'theming', 'enabled-themes', `["${theme}"]`]) + await login(context.request, user) + await page.goto('') + + await page.addScriptTag({ path: resolve(process.cwd(), 'node_modules/axe-core/axe.min.js') }) + + for (const [groupName, { foregroundColors, backgroundColors }] of Object.entries(testCases)) { + for (const foreground of foregroundColors) { + for (const background of backgroundColors) { + await page.evaluate(({ foregroundValue, backgroundValue }) => { + document.body.style.backgroundImage = 'unset' + const root = document.querySelector('#content') + if (!root) { + throw new Error('No test root found') + } + + root.innerHTML = '' + + const wrapper = document.createElement('div') + wrapper.style.padding = '14px' + wrapper.style.color = `var(--${foregroundValue})` + wrapper.style.backgroundColor = `var(--${backgroundValue})` + if (backgroundValue.includes('blur')) { + wrapper.style.backdropFilter = 'var(--filter-background-blur)' + } + + const testCase = document.createElement('div') + testCase.innerText = `${foregroundValue} ${backgroundValue}` + testCase.setAttribute('data-cy-testcase', '') + + wrapper.append(testCase) + root.append(wrapper) + }, { + foregroundValue: foreground, + backgroundValue: background, + }) + + const axeResult = await page.evaluate(async () => { + const axe = (window as any).axe + if (!axe) { + throw new Error('axe is not loaded') + } + + return axe.run('[data-cy-testcase]', { + runOnly: { + type: 'rule', + values: ['color-contrast'], + }, + }) + }) + + if (axeResult.violations.length > 0) { + failures.push(`${groupName}: ${foreground} on ${background}`) + } + } + } + } + } finally { + await runOcc(['user:delete', user.userId]) + } + + expect(failures).toEqual([]) + }) +} diff --git a/tests/playwright/e2e/theming/admin-settings-background.spec.ts b/tests/playwright/e2e/theming/admin-settings-background.spec.ts new file mode 100644 index 0000000000000..fae954c3373d6 --- /dev/null +++ b/tests/playwright/e2e/theming/admin-settings-background.spec.ts @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createRandomUser, login } from '@nextcloud/e2e-test-server/playwright' +import { runOcc } from '@nextcloud/e2e-test-server/docker' +import { expect } from '@playwright/test' +import { test } from '../../support/fixtures/admin-theming-page.ts' +import { resolve } from 'node:path' +import { defaultBackground, defaultPrimary, getBodyThemingSnapshot, pickColor } from '../../support/utils/theming.ts' + +test.describe('Admin theming background settings', () => { + test.describe.configure({ mode: 'serial' }) + + test.beforeEach(async ({ adminThemingPage, page }) => { + await adminThemingPage.reset() + await adminThemingPage.open() + if (await adminThemingPage.disableUserThemingCheckbox().isChecked()) { + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.disableUserThemingCheckbox().uncheck({ force: true }), + ]) + } + }) + + test('Remove default background and restore it', async ({ adminThemingPage, page, context }) => { + await expect(adminThemingPage.backgroundAndColorHeading()).toBeVisible() + if (await adminThemingPage.removeBackgroundImageCheckbox().isChecked()) { + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.removeBackgroundImageCheckbox().uncheck({ force: true }), + ]) + } + + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.removeBackgroundImageCheckbox().check({ force: true }), + ]) + + await page.goto('/index.php/logout') + await page.goto('/index.php/login') + await expect.poll(async () => (await getBodyThemingSnapshot(page)).backgroundImage).toBe('none') + + await adminThemingPage.reset() + await page.goto('settings/admin/theming') + await expect(adminThemingPage.backgroundAndColorHeading()).toBeVisible() + }) + + test('Disable user theming', async ({ adminThemingPage, page, context }) => { + await expect(adminThemingPage.disableUserThemingCheckbox()).not.toBeChecked() + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.disableUserThemingCheckbox().check({ force: true }), + ]) + + const user = await createRandomUser() + try { + await login(context.request, user) + await page.goto('settings/user/theming') + await expect(page.getByText('Customization has been disabled by your administrator')).toBeVisible() + } finally { + await runOcc(['user:delete', user.userId]) + } + }) + + test('Remove default background with custom color', async ({ adminThemingPage, page, context }) => { + await expect(adminThemingPage.backgroundAndColorHeading()).toBeVisible() + const backgroundColorButton = page.getByRole('button', { name: /Background color/ }) + const selectedColor = await pickColor(page, backgroundColorButton, 2) + expect(selectedColor).toBeTruthy() + + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.removeBackgroundImageCheckbox().check({ force: true }), + ]) + + await page.goto('/index.php/logout') + await page.goto('/index.php/login') + await expect.poll(async () => (await getBodyThemingSnapshot(page)).backgroundImage).toBe('none') + }) + + test('User default background reflects admin custom background and color', async ({ adminThemingPage, page, context }) => { + const imagePath = resolve(process.cwd(), 'cypress/fixtures/image.jpg') + + await page.locator('input[type="file"][name="background"]').setInputFiles(imagePath) + await page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/uploadImage') && response.request().method() === 'POST') + + const backgroundColorButton = page.getByRole('button', { name: /Background color/ }) + await pickColor(page, backgroundColorButton, 1) + await page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST') + + await page.goto('/index.php/logout') + const user = await createRandomUser() + try { + await login(context.request, user) + await page.goto('settings/user/theming') + await expect(page.getByRole('button', { name: 'Default background' })).toHaveAttribute('aria-pressed', 'true') + const snapshot = await getBodyThemingSnapshot(page) + expect(snapshot.backgroundImage).toContain('/apps/theming/image/background?v=') + } finally { + await runOcc(['user:delete', user.userId]) + } + }) + + test('User default background reflects admin removed background', async ({ adminThemingPage, page, context }) => { + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.removeBackgroundImageCheckbox().check({ force: true }), + ]) + + await page.goto('/index.php/logout') + const user = await createRandomUser() + try { + await login(context.request, user) + await page.goto('settings/user/theming') + await expect(page.getByRole('button', { name: 'Default background' })).toHaveAttribute('aria-pressed', 'true') + await expect.poll(async () => (await getBodyThemingSnapshot(page)).backgroundImage).toBe('none') + } finally { + await runOcc(['user:delete', user.userId]) + } + }) +}) diff --git a/tests/playwright/e2e/theming/admin-settings-branding.spec.ts b/tests/playwright/e2e/theming/admin-settings-branding.spec.ts new file mode 100644 index 0000000000000..d78409a26d035 --- /dev/null +++ b/tests/playwright/e2e/theming/admin-settings-branding.spec.ts @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { User } from '@nextcloud/e2e-test-server' +import { expect } from '@playwright/test' +import { test } from '../../support/fixtures/admin-theming-page.ts' + +const admin = new User('admin', 'admin') + +test.describe('Admin theming branding settings', () => { + test.describe.configure({ mode: 'serial' }) + + test.beforeEach(async ({ adminThemingPage }) => { + await adminThemingPage.reset() + await adminThemingPage.open() + }) + + test('Set project links and verify persisted values', async ({ adminThemingPage, page }) => { + await expect(adminThemingPage.webLinkInput()).toHaveAttribute('type', 'url') + await expect(adminThemingPage.legalNoticeLinkInput()).toHaveAttribute('type', 'url') + await expect(adminThemingPage.privacyPolicyLinkInput()).toHaveAttribute('type', 'url') + + await adminThemingPage.webLinkInput().fill('http://example.com/path?query#fragment') + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.webLinkInput().press('Enter'), + ]) + + await adminThemingPage.legalNoticeLinkInput().fill('http://example.com/legal?query#fragment') + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.legalNoticeLinkInput().press('Enter'), + ]) + + await adminThemingPage.privacyPolicyLinkInput().fill('http://privacy.local/path?query#fragment') + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.privacyPolicyLinkInput().press('Enter'), + ]) + + await page.reload() + await expect(adminThemingPage.webLinkInput()).toHaveValue('http://example.com/path?query#fragment') + await expect(adminThemingPage.legalNoticeLinkInput()).toHaveValue('http://example.com/legal?query#fragment') + await expect(adminThemingPage.privacyPolicyLinkInput()).toHaveValue('http://privacy.local/path?query#fragment') + }) + + test('Set and undo login fields', async ({ adminThemingPage, page }) => { + const name = 'ABCdef123' + const url = 'https://example.com' + const slogan = 'Testing is fun' + + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.nameInput().fill(name), + ]) + await adminThemingPage.nameInput().press('Enter') + + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.webLinkInput().fill(url), + ]) + await adminThemingPage.webLinkInput().press('Enter') + + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + adminThemingPage.sloganInput().fill(slogan), + ]) + await adminThemingPage.sloganInput().press('Enter') + + await expect(adminThemingPage.undoChangesButtons()).toHaveCount(3) + + for (let index = 0; index < 3; index++) { + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/undoChanges') && response.request().method() === 'POST'), + adminThemingPage.undoChangesButtons().first().click(), + ]) + } + await expect(adminThemingPage.undoChangesButtons()).toHaveCount(0) + }) + + test('Web link corner cases', async ({ adminThemingPage, page }) => { + await setUrlFieldAndWait(page, adminThemingPage.webLinkInput(), 'http://example.com/%22path%20with%20space%22') + await page.reload() + await expect(adminThemingPage.webLinkInput()).toHaveValue('http://example.com/%22path%20with%20space%22') + + await setUrlFieldAndWait(page, adminThemingPage.webLinkInput(), 'http://example.com/"path"') + await page.reload() + await expect(adminThemingPage.webLinkInput()).toHaveValue('http://example.com/%22path%22') + + await setUrlFieldAndWait(page, adminThemingPage.webLinkInput(), 'http://example.com/"the%20path"') + await page.reload() + await expect(adminThemingPage.webLinkInput()).toHaveValue('http://example.com/%22the%20path%22') + }) +}) + +async function setUrlFieldAndWait(page: import('@playwright/test').Page, locator: import('@playwright/test').Locator, value: string) { + await locator.fill(value) + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/apps/theming/ajax/updateStylesheet') && response.request().method() === 'POST'), + locator.press('Enter'), + ]) +} diff --git a/tests/playwright/e2e/theming/admin-settings-colors.spec.ts b/tests/playwright/e2e/theming/admin-settings-colors.spec.ts new file mode 100644 index 0000000000000..9becf0aef0369 --- /dev/null +++ b/tests/playwright/e2e/theming/admin-settings-colors.spec.ts @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { expect } from '@playwright/test' +import { test } from '../../support/fixtures/admin-theming-page.ts' +import { pickColor } from '../../support/utils/theming.ts' + +test.beforeEach(async ({ adminThemingPage }) => { + await adminThemingPage.reset() + await adminThemingPage.open() +}) + +test('Change the primary color and reset it', async ({ adminThemingPage, page }) => { + await page.getByRole('heading', { name: 'Background and color' }).scrollIntoViewIfNeeded() + + const primaryColorButton = page.getByRole('button', { name: /Primary color/ }) + const updateStylesheetResponse = page.waitForResponse((response) => { + return response.url().includes('/apps/theming/ajax/updateStylesheet') + && response.request().method() === 'POST' + }) + await pickColor(page, primaryColorButton, 3) + expect(await updateStylesheetResponse).toBeTruthy() + + await page.goto('settings/admin/theming') + await adminThemingPage.reset() + await page.goto('settings/admin/theming') + await expect(page.getByRole('heading', { name: 'Background and color' })).toBeVisible() +}) diff --git a/tests/playwright/e2e/theming/admin-settings-default-app.spec.ts b/tests/playwright/e2e/theming/admin-settings-default-app.spec.ts new file mode 100644 index 0000000000000..b82a1fa5316f8 --- /dev/null +++ b/tests/playwright/e2e/theming/admin-settings-default-app.spec.ts @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { expect } from '@playwright/test' +import { runOcc } from '@nextcloud/e2e-test-server/docker' +import { test } from '../../support/fixtures/admin-theming-page.ts' +import { NavigationHeaderPage } from '../../support/sections/NavigationHeaderPage.ts' + +test.describe('Admin theming set default apps', () => { + // we need serial mode to reset the default app setting after each test + // and to restore the default app to dashboard at the end of the tests. + // Otherwise, the tests would influence each other and lead to random failures (race condition when run in parallel). + test.describe.configure({ mode: 'serial' }) + + test.beforeEach(async ({ adminThemingPage, page, context }) => { + await runOcc(['config:system:set', 'defaultapp', '--value', 'dashboard']) + await adminThemingPage.reset() + await page.goto('') + }) + + test.afterAll(async () => { + await runOcc(['config:system:set', 'defaultapp', '--value', 'dashboard']) + }) + + test('See the current default app is the dashboard', async ({ page }) => { + const navigationHeader = new NavigationHeaderPage(page) + + await expect(page).toHaveURL(/apps\/dashboard/) + await navigationHeader.logo().click() + await expect(page).toHaveURL(/apps\/dashboard/) + }) + + test('Can configure and switch the default app to files', async ({ adminThemingPage }) => { + await adminThemingPage.open() + await expect(adminThemingPage.defaultAppSwitch()).toBeVisible() + if (await adminThemingPage.defaultAppSwitch().isChecked()) { + await adminThemingPage.defaultAppSwitch().uncheck({ force: true }) + } + await expect(adminThemingPage.defaultAppSwitch()).not.toBeChecked() + + await adminThemingPage.defaultAppSwitch().check({ force: true }) + await expect(adminThemingPage.defaultAppSwitch()).toBeChecked() + await expect(adminThemingPage.defaultAppRegion()).toBeVisible() + + await expect(adminThemingPage.defaultAppSelect().getByText('Dashboard')).toBeVisible() + await expect(adminThemingPage.defaultAppSelect().getByText('Files')).toBeVisible() + + await expect(adminThemingPage.appOrderEntries()).toHaveCount(2) + await expect(adminThemingPage.appOrderEntries().nth(0)).toContainText('Dashboard') + await expect(adminThemingPage.appOrderEntries().nth(1)).toContainText('Files') + + await adminThemingPage.moveUpButton('Files').click() + await expect(adminThemingPage.moveUpButton('Files')).toHaveCount(0) + await expect(adminThemingPage.appOrderEntries().nth(0)).toContainText('Files') + await expect(adminThemingPage.appOrderEntries().nth(1)).toContainText('Dashboard') + + await adminThemingPage.defaultAppSwitch().uncheck({ force: true }) + await expect(adminThemingPage.defaultAppSwitch()).not.toBeChecked() + await expect(adminThemingPage.defaultAppRegion()).toHaveCount(0) + }) +}) diff --git a/tests/playwright/e2e/theming/user-settings-app-order.spec.ts b/tests/playwright/e2e/theming/user-settings-app-order.spec.ts new file mode 100644 index 0000000000000..305334635d687 --- /dev/null +++ b/tests/playwright/e2e/theming/user-settings-app-order.spec.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { expect } from '@playwright/test' +import { test } from '../../support/fixtures/random-user-session.ts' +import { NavigationHeaderPage } from '../../support/sections/NavigationHeaderPage.ts' +import { UserThemingPage } from '../../support/sections/UserThemingPage.ts' + +test('User can change personal app order', async ({ page }) => { + const userThemingPage = new UserThemingPage(page) + const navigationHeader = new NavigationHeaderPage(page) + + await userThemingPage.open() + + await expect(userThemingPage.appOrderEntries()).toHaveCount(2) + await expect(userThemingPage.appOrderEntries().nth(0)).toContainText('Dashboard') + await expect(userThemingPage.appOrderEntries().nth(1)).toContainText('Files') + + await expect(navigationHeader.navigationEntries().nth(0)).toContainText('Dashboard') + await expect(navigationHeader.navigationEntries().nth(1)).toContainText('Files') + + const initialFirstEntry = await userThemingPage.appOrderEntries().nth(0).innerText() + if (/Dashboard/i.test(initialFirstEntry)) { + const moveUpButton = userThemingPage.appEntry('Files').locator('button[aria-label="Move up"]').first() + if (await moveUpButton.count() > 0) { + await moveUpButton.evaluate((element) => { + (element as HTMLButtonElement).click() + }) + } + } + + const currentOrder = (await userThemingPage.appOrderEntries().allInnerTexts()).map((entry) => entry.trim()) + expect(currentOrder).toContain('Dashboard') + expect(currentOrder).toContain('Files') + + await page.reload() + const reloadedOrder = (await userThemingPage.appOrderEntries().allInnerTexts()).map((entry) => entry.trim()) + expect(reloadedOrder).toContain('Dashboard') + expect(reloadedOrder).toContain('Files') + await expect(navigationHeader.navigationEntries().nth(0)).toContainText(reloadedOrder[0]!) + await expect(navigationHeader.navigationEntries().nth(1)).toContainText(reloadedOrder[1]!) +}) diff --git a/tests/playwright/e2e/theming/user-settings-background.spec.ts b/tests/playwright/e2e/theming/user-settings-background.spec.ts new file mode 100644 index 0000000000000..eec6c53b8e5c6 --- /dev/null +++ b/tests/playwright/e2e/theming/user-settings-background.spec.ts @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { expect } from '@playwright/test' +import { test } from '../../support/fixtures/random-user-session.ts' +import { getBodyThemingSnapshot, pickColor } from '../../support/utils/theming.ts' + +test('User can configure background and plain color', async ({ page }) => { + await page.goto('settings/user/theming') + await page.getByRole('heading', { name: 'Background and color' }).waitFor({ state: 'visible' }) + + await expect(page.getByRole('button', { name: 'Default background', pressed: true })).toBeVisible() + + const darkBackground = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg' + const darkBackgroundName = 'Background picture of a red-ish butterfly wing under microscope' + await page.getByRole('button', { name: darkBackgroundName, pressed: false }).click() + await expect(page.getByRole('button', { name: darkBackgroundName, pressed: true })).toBeVisible() + await expect.poll(async () => (await getBodyThemingSnapshot(page)).backgroundImage).toContain(darkBackground) + + const brightBackground = 'bernie-cetonia-aurata-take-off-composition.jpg' + const brightBackgroundName = 'Montage of a cetonia aurata bug that takes off with white background' + await page.getByRole('button', { name: brightBackgroundName, pressed: false }).click() + await expect(page.getByRole('button', { name: brightBackgroundName, pressed: true })).toBeVisible() + await expect.poll(async () => (await getBodyThemingSnapshot(page)).backgroundImage).toContain(brightBackground) + + const plainBackgroundButton = page.getByRole('button', { name: 'Plain background' }) + await pickColor(page, plainBackgroundButton, 7) + await expect.poll(async () => (await getBodyThemingSnapshot(page)).backgroundImage).toBe('none') + + await page.reload() + await expect.poll(async () => (await getBodyThemingSnapshot(page)).backgroundImage).toBe('none') +}) diff --git a/tests/playwright/start-nextcloud-server.js b/tests/playwright/start-nextcloud-server.js new file mode 100644 index 0000000000000..d1baf6fa8be36 --- /dev/null +++ b/tests/playwright/start-nextcloud-server.js @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { configureNextcloud, runExec, runOcc, startNextcloud, stopNextcloud, waitOnNextcloud } from '@nextcloud/e2e-test-server/docker' +import { existsSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..') + +function getMounts() { + const mounts = { + '3rdparty': resolve(rootDir, '3rdparty'), + apps: resolve(rootDir, 'apps'), + core: resolve(rootDir, 'core'), + cypress: resolve(rootDir, 'cypress'), + dist: resolve(rootDir, 'dist'), + lib: resolve(rootDir, 'lib'), + ocs: resolve(rootDir, 'ocs'), + 'ocs-provider': resolve(rootDir, 'ocs-provider'), + resources: resolve(rootDir, 'resources'), + tests: resolve(rootDir, 'tests'), + 'console.php': resolve(rootDir, 'console.php'), + 'cron.php': resolve(rootDir, 'cron.php'), + 'index.php': resolve(rootDir, 'index.php'), + occ: resolve(rootDir, 'occ'), + 'public.php': resolve(rootDir, 'public.php'), + 'remote.php': resolve(rootDir, 'remote.php'), + 'status.php': resolve(rootDir, 'status.php'), + 'version.php': resolve(rootDir, 'version.php'), + } + + return Object.fromEntries(Object.entries(mounts).filter(([, path]) => existsSync(path))) +} + +async function start() { + const port = Number.parseInt(process.env.NEXTCLOUD_PORT ?? '8042', 10) + const ip = await startNextcloud(process.env.BRANCH, false, { + mounts: getMounts(), + exposePort: port, + forceRecreate: true, + }) + + await runExec(['mkdir', '-p', 'apps-cypress']) + await runExec(['cp', 'cypress/fixtures/app.config.php', 'config']) + + await waitOnNextcloud(ip) + await configureNextcloud() + await runOcc(['config:system:set', 'appstoreenabled', '--value', 'false', '--type', 'boolean']) + + process.stdout.write('Nextcloud is now ready to use\n') +} + +async function stop() { + process.stderr.write('Stopping Nextcloud server…\n') + await stopNextcloud() + process.exit(0) +} + +process.on('SIGTERM', stop) +process.on('SIGINT', stop) + +await start() + +while (true) { + await new Promise((resolvePromise) => setTimeout(resolvePromise, 5000)) +} diff --git a/tests/playwright/support/fixtures/admin-session.ts b/tests/playwright/support/fixtures/admin-session.ts new file mode 100644 index 0000000000000..4ee9fcf87a93c --- /dev/null +++ b/tests/playwright/support/fixtures/admin-session.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { User } from '@nextcloud/e2e-test-server' +import { login } from '@nextcloud/e2e-test-server/playwright' +import { test as baseTest } from '@playwright/test' + +const admin = new User('admin', 'admin') + +export const test = baseTest.extend({ + page: async ({ page, context }, use) => { + await login(context.request, admin) + await use(page) + }, +}) diff --git a/tests/playwright/support/fixtures/admin-theming-page.ts b/tests/playwright/support/fixtures/admin-theming-page.ts new file mode 100644 index 0000000000000..f2d232915e721 --- /dev/null +++ b/tests/playwright/support/fixtures/admin-theming-page.ts @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { test as adminSessionTest } from './admin-session.ts' +import { AdminThemingPage } from '../sections/AdminThemingPage.ts' + +export const test = adminSessionTest.extend<{ adminThemingPage: AdminThemingPage }>({ + adminThemingPage: async ({ page }, use) => { + const adminThemingPage = new AdminThemingPage(page) + await use(adminThemingPage) + }, +}) diff --git a/tests/playwright/support/fixtures/random-user-session.ts b/tests/playwright/support/fixtures/random-user-session.ts new file mode 100644 index 0000000000000..8b3951a17aabe --- /dev/null +++ b/tests/playwright/support/fixtures/random-user-session.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { runOcc } from '@nextcloud/e2e-test-server/docker' +import { createRandomUser, login } from '@nextcloud/e2e-test-server/playwright' +import { test as baseTest } from '@playwright/test' + +export const test = baseTest.extend({ + page: async ({ page, context }, use) => { + const user = await createRandomUser() + await login(context.request, user) + + await use(page) + + await runOcc(['user:delete', user.userId]) + }, +}) diff --git a/tests/playwright/support/sections/AdminThemingPage.ts b/tests/playwright/support/sections/AdminThemingPage.ts new file mode 100644 index 0000000000000..44520c5a640d7 --- /dev/null +++ b/tests/playwright/support/sections/AdminThemingPage.ts @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Locator, Page } from '@playwright/test' + +export class AdminThemingPage { + constructor(private readonly page: Page) {} + + async open() { + await this.page.goto('settings/admin/theming') + await this.page.getByText('Navigation bar settings').waitFor({ state: 'visible' }) + } + + /** + * Resets the admin theming settings to default using HTTP request. + * + * @param request - The APIRequestContext to perform the request with admin credentials. + */ + async reset() { + const tokenResponse = await this.page.request.get('/csrftoken', { + failOnStatusCode: true, + }) + const requestToken = (await tokenResponse.json()).token + + const response = await this.page.request.post('/apps/theming/ajax/undoAllChanges', { + headers: { + requesttoken: requestToken, + }, + }) + + if (!response.ok) { + throw new Error(`Failed to reset theming settings (${response.status})`) + } + } + + defaultAppSwitch(): Locator { + return this.page.getByRole('checkbox', { name: 'Use custom default app' }) + } + + defaultAppRegion(): Locator { + return this.page.getByRole('region', { name: 'Global default app' }) + } + + defaultAppSelect(): Locator { + return this.defaultAppRegion().getByRole('combobox') + } + + appOrderList(): Locator { + return this.page.getByRole('list', { name: 'Navigation bar app order' }) + } + + appOrderEntries(): Locator { + return this.appOrderList().getByRole('listitem') + } + + appEntry(name: string): Locator { + return this.appOrderEntries().filter({ hasText: name }) + } + + moveUpButton(appName: string): Locator { + return this.appEntry(appName).getByRole('button', { name: 'Move up' }) + } + + backgroundAndColorHeading(): Locator { + return this.page.getByRole('heading', { name: 'Background and color' }) + } + + webLinkInput(): Locator { + return this.page.getByRole('textbox', { name: /web link/i }) + } + + legalNoticeLinkInput(): Locator { + return this.page.getByRole('textbox', { name: /legal notice link/i }) + } + + privacyPolicyLinkInput(): Locator { + return this.page.getByRole('textbox', { name: /privacy policy link/i }) + } + + nameInput(): Locator { + return this.page.getByRole('textbox', { name: 'Name' }) + } + + sloganInput(): Locator { + return this.page.getByRole('textbox', { name: 'Slogan' }) + } + + undoChangesButtons(): Locator { + return this.page.getByRole('button', { name: /undo changes/i }) + } + + removeBackgroundImageCheckbox(): Locator { + return this.page.getByRole('checkbox', { name: /remove background image/i }) + } + + disableUserThemingCheckbox(): Locator { + return this.page.getByRole('checkbox', { name: /disable user theming/i }) + } +} diff --git a/tests/playwright/support/sections/NavigationHeaderPage.ts b/tests/playwright/support/sections/NavigationHeaderPage.ts new file mode 100644 index 0000000000000..543fccd830480 --- /dev/null +++ b/tests/playwright/support/sections/NavigationHeaderPage.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Locator, Page } from '@playwright/test' + +export class NavigationHeaderPage { + constructor(private readonly page: Page) {} + + private get header(): Locator { + return this.page.locator('header#header') + } + + logo(): Locator { + return this.header.locator('#nextcloud') + } + + navigation(): Locator { + return this.header.getByRole('navigation', { name: 'Applications menu' }) + } + + navigationEntries(): Locator { + return this.navigation().getByRole('listitem') + } +} diff --git a/tests/playwright/support/sections/UserThemingPage.ts b/tests/playwright/support/sections/UserThemingPage.ts new file mode 100644 index 0000000000000..a67b98ec9edd7 --- /dev/null +++ b/tests/playwright/support/sections/UserThemingPage.ts @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Locator, Page } from '@playwright/test' + +export class UserThemingPage { + constructor(private readonly page: Page) {} + + async open() { + await this.page.goto('settings/user/theming') + await this.page.getByRole('heading', { name: /Navigation bar settings/ }).waitFor({ state: 'visible' }) + } + + appOrderList(): Locator { + return this.page.getByRole('list', { name: 'Navigation bar app order' }) + } + + appOrderEntries(): Locator { + return this.appOrderList().getByRole('listitem') + } + + appEntry(name: string): Locator { + return this.appOrderEntries().filter({ hasText: name }) + } + + moveUpButton(appName: string): Locator { + return this.appEntry(appName).getByRole('button', { name: 'Move up', includeHidden: true }) + } +} diff --git a/tests/playwright/support/utils/theming.ts b/tests/playwright/support/utils/theming.ts new file mode 100644 index 0000000000000..300e19e8851c3 --- /dev/null +++ b/tests/playwright/support/utils/theming.ts @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Locator, Page } from '@playwright/test' + +import { expect } from '@playwright/test' + +export const defaultPrimary = '#00679e' +export const defaultBackground = 'jo-myoung-hee-fluid.webp' + +export async function getBodyThemingSnapshot(page: Page) { + return page.evaluate(() => { + const styles = getComputedStyle(document.body) + return { + primary: styles.getPropertyValue('--color-primary').trim(), + backgroundColor: styles.backgroundColor, + backgroundImage: styles.backgroundImage, + } + }) +} + +export async function expectBodyThemingCss(page: Page, expected: { + primary?: string + background?: string | null + backgroundColor?: string | null +}) { + await expect.poll(async () => { + const snapshot = await getBodyThemingSnapshot(page) + const expectedPrimary = expected.primary ?? defaultPrimary + const normalizedPrimary = await normalizeColor(page, snapshot.primary) + const normalizedExpectedPrimary = await normalizeColor(page, expectedPrimary) + + const expectedBackgroundColor = expected.backgroundColor ?? defaultPrimary + const normalizedBackground = expectedBackgroundColor === null + ? null + : await normalizeColor(page, expectedBackgroundColor) + + const expectedBackground = expected.background === undefined ? defaultBackground : expected.background + + const validPrimary = normalizedPrimary === normalizedExpectedPrimary + const validBackgroundColor = normalizedBackground === null || snapshot.backgroundColor === normalizedBackground + const validBackgroundImage = expectedBackground === null + ? snapshot.backgroundImage === 'none' + : snapshot.backgroundImage.includes(expectedBackground) + + return validPrimary && validBackgroundColor && validBackgroundImage + }, { + timeout: 10000, + message: 'Expected body theming CSS to match expected values', + }).toBeTruthy() +} + +export async function expectPrimaryColor(page: Page, expectedColor: string) { + const normalizedExpectedPrimary = await normalizeColor(page, expectedColor) + + await expect.poll(async () => { + const snapshot = await getBodyThemingSnapshot(page) + return normalizeColor(page, snapshot.primary) + }, { + timeout: 10000, + message: 'Expected primary color CSS variable to match', + }).toBe(normalizedExpectedPrimary) +} + +export async function pickColor(page: Page, trigger: Locator, index: number) { + const oldColor = await trigger.evaluate((element) => getComputedStyle(element as HTMLElement).backgroundColor) + + await trigger.click({ force: true }) + await page.locator('.color-picker__simple-color-circle').nth(index).click() + await page.getByRole('button', { name: /Choose/i }).click() + + await expect.poll(async () => { + return trigger.evaluate((element) => getComputedStyle(element as HTMLElement).backgroundColor) + }).not.toBe(oldColor) + + return trigger.evaluate((element) => getComputedStyle(element as HTMLElement).backgroundColor) +} + +async function normalizeColor(page: Page, color: string) { + return page.evaluate((value) => { + const element = document.createElement('div') + element.style.color = value + document.body.append(element) + const normalized = getComputedStyle(element).color + element.remove() + return normalized + }, color) +}