diff --git a/.coverage-baseline b/.coverage-baseline new file mode 100644 index 000000000..b513d914c --- /dev/null +++ b/.coverage-baseline @@ -0,0 +1 @@ +57.39 diff --git a/.forgejo/workflows/app-tests-live.yml b/.forgejo/workflows/app-tests-live.yml deleted file mode 100644 index 00f11120e..000000000 --- a/.forgejo/workflows/app-tests-live.yml +++ /dev/null @@ -1,50 +0,0 @@ -# app-tests-live.yml — openbuild caller for the LIVE-NC reusable workflow. -# -# Boots a real, seeded Nextcloud (db + NC + openregister deployed/enabled + -# openbuild deployed/enabled), then runs the deep Playwright e2e -# (tests/e2e/workflows/) and the Newman API-contract suite against it as HARD -# gates — a PR can no longer go green while those flows are broken. -# -# This is the Forgejo/Codeberg counterpart to openregister's app-tests-live.yml -# reference rig, generalised per GAP-2 (arm CI live-gating). The reusable -# tests-live.yml is byte-identical across the fleet; only these inputs differ. -# Additive — sits alongside app-tests.yml (bare gates: phpunit-unit, l10n) and -# the release / pre-merge workflows, none of which are touched. -# -# Gate status for openbuild (see TESTING-CI-ROLLOUT.md for the evidence table): -# deep-e2e: true — OR object API by register slug, run-id-prefixed -# newman: true — 38 assertions, 0 fail live (isolated) - -name: app-tests-live - -on: - pull_request: - branches: - - development - - main - - beta - push: - branches: - - development - - main - workflow_dispatch: - -permissions: - contents: read - -jobs: - live: - uses: ./.forgejo/workflows/tests-live.yml - with: - app-id: openbuild - is-openregister: false - run-e2e: true - run-newman: true - # NON-GATING (GAP-5): runs the visual-regression project + uploads - # snapshots/diffs as an artifact. Committed baselines are dev-container - # native, so they won't byte-match the CI Linux runner until regenerated - # in-CI (see tests-live.yml run-visual caveat) — continue-on-error. - run-visual: true - # Feature apps use the per-app Newman entrypoint (self-seeding collection), - # not openregister's multi-collection orchestrator. - newman-entrypoint: tests/integration/run-newman.sh diff --git a/.forgejo/workflows/app-tests.yml b/.forgejo/workflows/app-tests.yml index b525e941a..595bd8129 100644 --- a/.forgejo/workflows/app-tests.yml +++ b/.forgejo/workflows/app-tests.yml @@ -28,6 +28,7 @@ permissions: jobs: tests: uses: ./.forgejo/workflows/tests.yml + secrets: inherit with: app-id: openbuild run-e2e: ${{ github.event.inputs.run-e2e == 'true' }} diff --git a/.forgejo/workflows/documentation.yml b/.forgejo/workflows/documentation.yml deleted file mode 100644 index 5aa671755..000000000 --- a/.forgejo/workflows/documentation.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Publish docs - -# Docs deploy ONLY from the dedicated `documentation` branch — decoupled from main/development -# so doc edits never trigger releases and code releases never trigger doc builds. No cron. -on: - push: - branches: [documentation] - pull_request: - branches: [documentation] - workflow_dispatch: - -jobs: - build: - uses: Conduction/.github/.forgejo/workflows/documentation-build.yml@main - with: - source-folder: docs - secrets: inherit - - deploy: - needs: build - if: github.event_name != 'pull_request' - uses: Conduction/.github/.forgejo/workflows/documentation-deploy.yml@main - with: - cf-project-name: openbuild-docs - secrets: inherit diff --git a/.forgejo/workflows/pre-merge-check-strict.yaml b/.forgejo/workflows/pre-merge-check-strict.yaml deleted file mode 100644 index baca99bc6..000000000 --- a/.forgejo/workflows/pre-merge-check-strict.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# Pre-merge quality gate — enforced lint + phpcs + all Hydra gates on every PR. -# Required status check on protected branches. -# -# Runner/container mirror the proven release-semrel workflow: codeberg-medium + -# official php:8.3-cli + a base-tooling step. The old code.forgejo.org/oci/ci-php:8.3 -# image 404s ("manifest unknown"), which fast-failed every run at container-pull. -# -# The gate runs `composer lint` + `composer phpcs` directly: check:strict's -# psalm/phpstan/phpmd/test:all are wrapped in `|| echo skipping` so they never -# affect pass/fail (ADR-022 parks static analysis), and running them on the medium -# runner OOMs it. lint+phpcs is the identical enforced gate, fast and deterministic. - -name: pre-merge-check-strict - -on: - pull_request: - branches: - - development - - main - - beta - -jobs: - quality-gates: - runs-on: codeberg-medium - container: - image: php:8.3-cli - timeout-minutes: 15 - steps: - - name: Install base tooling - run: | - apt-get update - apt-get install -y --no-install-recommends \ - git curl ca-certificates gnupg jq unzip zip \ - libzip-dev libpng-dev python3 nodejs - docker-php-ext-install -j"$(nproc)" zip gd - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - - - name: Checkout PR - uses: https://github.com/actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install composer deps - run: composer install --no-interaction --no-progress --prefer-dist --ignore-platform-reqs - - - name: Run lint + phpcs (the enforced gate) - run: | - composer lint - composer phpcs - - - name: Clone Hydra (for gate runner) - uses: https://github.com/actions/checkout@v4 - with: - repository: Conduction/hydra - ref: development - path: .hydra - - - name: Run all Hydra gates (diff-scoped per ADR-020) - run: | - git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} || true - bash .hydra/scripts/run-hydra-gates.sh --scope-to-diff --base origin/${{ github.base_ref }} . - - - name: Gate-19 e2e coverage report (informational) - if: always() - run: | - python3 .hydra/scripts/lib/check_e2e_coverage.py . --mode report || true diff --git a/.forgejo/workflows/release-beta.yml b/.forgejo/workflows/release-beta.yml deleted file mode 100644 index 8aa163370..000000000 --- a/.forgejo/workflows/release-beta.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Beta Release - -on: - push: - branches: [beta] - workflow_dispatch: - -jobs: - release: - uses: Conduction/.github/.forgejo/workflows/release-beta.yml@main - with: - app-name: openbuilt - secrets: - CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }} - NEXTCLOUD_SIGNING_KEY: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} - NEXTCLOUD_SIGNING_CERT: ${{ secrets.NEXTCLOUD_SIGNING_CERT }} - NEXTCLOUD_APPSTORE_TOKEN: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} diff --git a/.forgejo/workflows/release-stable.yml b/.forgejo/workflows/release-stable.yml deleted file mode 100644 index e5dd6a3b5..000000000 --- a/.forgejo/workflows/release-stable.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Stable Release - -on: - push: - branches: [main] - workflow_dispatch: - -jobs: - release: - uses: Conduction/.github/.forgejo/workflows/release-stable.yml@main - with: - app-name: openbuilt - secrets: - CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }} - NEXTCLOUD_SIGNING_KEY: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} - NEXTCLOUD_SIGNING_CERT: ${{ secrets.NEXTCLOUD_SIGNING_CERT }} - NEXTCLOUD_APPSTORE_TOKEN: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} diff --git a/.forgejo/workflows/tests-live.yml b/.forgejo/workflows/tests-live.yml deleted file mode 100644 index d1a3ed637..000000000 --- a/.forgejo/workflows/tests-live.yml +++ /dev/null @@ -1,327 +0,0 @@ -# tests-live.yml — reusable LIVE-NC feature-test workflow (PHASE-5 NC-in-CI). -# -# This is the GATING counterpart to tests.yml: where tests.yml runs the bare -# layers (phpunit-unit, l10n) that need no service container, this workflow -# boots a real, seeded Nextcloud and runs the two layers that DO need one — -# the deep Playwright e2e (tests/e2e/workflows/) and the Newman API-contract -# suite — so a PR can no longer go green while those flows are broken. -# -# It generalises openregister's reference rig -# (.github/workflows/api-test-coverage.yml + .github/docker-compose.ci.yml), -# which already boots NC + Postgres, deploys + enables OpenRegister, waits for -# the API and runs Newman. The only per-app knobs are `app-id` and whether the -# app is itself openregister (the data backend) or a feature app that needs -# openregister enabled ALONGSIDE it. -# -# Per-app PREREQUISITE (the common seeding step): -# The deep e2e + Newman suites must be SELF-SEEDING — each spec/collection -# creates the OR register + schema + objects it asserts on in its -# beforeAll/setUp and tears them down in afterAll/teardown. openregister's -# tests/e2e/workflows/object-lifecycle-workflows.spec.ts and the Newman -# collections already do this. A feature app whose suites assume a -# pre-imported OR register-config must EITHER add an `occ` seed step here -# (import the app's register-config, e.g. via its Repair step or -# `occ :import-config`) OR make the suites self-seed. Until one of -# those is true, keep run-e2e/run-newman false for that app. -# -# Runner label `docker` (needs a docker daemon to boot the compose stack) + -# the short `https://code.forgejo.org/actions/...@v4` `uses:` form follow the -# fleet convention. Additive — does not touch tests.yml, pre-merge-check-strict -# or any release workflow. - -name: tests-live - -on: - workflow_call: - inputs: - app-id: - description: "App id (matches package.json name + composer namespace + custom_apps dir)." - required: true - type: string - node-version: - required: false - type: string - default: "20" - php-version: - required: false - type: string - default: "8.3" - is-openregister: - description: "True when app-id IS openregister (the data backend). Feature apps leave this false; openregister is enabled alongside them." - required: false - type: boolean - default: false - run-e2e: - description: "Run the deep Playwright e2e (tests/e2e/workflows/) against the live NC. GATING when true." - required: false - type: boolean - default: false - run-newman: - description: "Run the Newman API-contract suite against the live NC. GATING when true." - required: false - type: boolean - default: false - run-visual: - description: >- - Run the Playwright visual-regression project (tests/e2e/visual/, GAP-5) - against the live NC. NON-GATING by design (continue-on-error). PLATFORM - CAVEAT: PNG baselines are host-font/GPU specific, so committed - dev-container baselines will NOT byte-match a CI Linux runner. On the - first CI run the baselines must be regenerated in-CI (download the - uploaded visual-snapshots artifact and commit it) before this step can - be made gating. Until then it reports diffs as an artifact only. - required: false - type: boolean - default: false - newman-entrypoint: - description: "Path to the Newman runner. openregister uses the orchestrator; feature apps use tests/integration/run-newman.sh." - required: false - type: string - default: "tests/newman/run-all.sh" - newman-collections: - description: "COLLECTIONS subset passed to the orchestrator (self-seeding domains only; excludes fixtures that assume dev-container state)." - required: false - type: string - default: "crud graphql relations auth-matrix error-matrix referential-integrity" - -permissions: - contents: read - -jobs: - # --------------------------------------------------------------------------- - # LIVE GATE — boot seeded NC, deploy OR (+ the app), run deep e2e + Newman. - # - # A single job boots the stack once and runs both suites against it (cheaper - # than two stacks; both are read-mostly + self-seeding so they don't collide - # — e2e prefixes its fixtures with a run-id, Newman teardown-cleans its own). - # --------------------------------------------------------------------------- - live-nc: - name: Live NC e2e + Newman (${{ inputs.app-id }}) - if: ${{ inputs.run-e2e || inputs.run-newman || inputs.run-visual }} - runs-on: docker - timeout-minutes: 40 - permissions: - contents: read - env: - APP_ID: ${{ inputs.app-id }} - COMPOSE_FILE: .github/docker-compose.ci.yml - steps: - - name: Checkout - uses: https://code.forgejo.org/actions/checkout@v4 - - - name: Set up PHP - uses: https://github.com/shivammathur/setup-php@v2 - with: - php-version: ${{ inputs.php-version }} - tools: composer:v2 - coverage: none - - - name: Set up Node.js - uses: https://code.forgejo.org/actions/setup-node@v4 - with: - node-version: ${{ inputs.node-version }} - - - name: Install composer deps (production) - run: composer install --no-dev --no-interaction --no-progress --prefer-dist - - - name: Install npm deps + Newman - run: | - npm ci --no-audit --no-fund || npm install --no-audit --no-fund - npm install -g newman - - - name: Boot CI stack (db + Nextcloud) - # Reuses openregister's reference compose (db + NC, named volume only). - # A feature app that does not ship its own compose should commit a copy - # of openregister/.github/docker-compose.ci.yml. - run: docker compose -f "$COMPOSE_FILE" up -d - - - name: Wait for Nextcloud to be installed - run: | - for i in $(seq 1 60); do - if docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ status" 2>/dev/null | grep -q "installed: true"; then - echo "Nextcloud installed and ready" - break - fi - echo "Waiting for Nextcloud installation... ($i/60)" - sleep 5 - done - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ status" - - - name: Deploy OpenRegister (data backend) - # The app under test stores its objects in OpenRegister, so OR must be - # deployed+enabled regardless of which app we're testing. When the app - # UNDER TEST *is* openregister, this single deploy covers it. - run: | - if [ "${{ inputs.is-openregister }}" = "true" ]; then - OR_SRC="." - else - # Feature apps build against a sibling openregister checkout in the - # same apps-extra tree. CI clones only this repo, so fetch OR's - # release build instead. - OR_SRC="$RUNNER_TEMP/openregister" - git clone --depth 1 https://codeberg.org/Conduction/openregister.git "$OR_SRC" - ( cd "$OR_SRC" && composer install --no-dev --no-interaction --no-progress --prefer-dist ) - fi - docker exec nextcloud mkdir -p /var/www/html/custom_apps/openregister - tar --exclude='.git' --exclude='node_modules' --exclude='.claude' \ - --exclude='tests/e2e/playwright-report' --exclude='tests/e2e/test-results' \ - -C "$OR_SRC" -cf - . \ - | docker exec -i nextcloud tar -xf - -C /var/www/html/custom_apps/openregister - docker exec nextcloud chown -R www-data:www-data /var/www/html/custom_apps/openregister - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ app:enable openregister" - - - name: Deploy app under test - # Skipped when the app IS openregister (already deployed above). - if: ${{ inputs.is-openregister == false }} - run: | - docker exec nextcloud mkdir -p "/var/www/html/custom_apps/$APP_ID" - tar --exclude='.git' --exclude='node_modules' --exclude='.claude' \ - --exclude='tests/e2e/playwright-report' --exclude='tests/e2e/test-results' \ - -cf - . \ - | docker exec -i nextcloud tar -xf - -C "/var/www/html/custom_apps/$APP_ID" - docker exec nextcloud chown -R www-data:www-data "/var/www/html/custom_apps/$APP_ID" - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ app:enable $APP_ID" - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ app:list" | grep "$APP_ID" - - - name: Seed OR register-config (per-app prerequisite) - # SELF-SEEDING suites need nothing here. An app whose deep-e2e / Newman - # assumes a pre-imported register-config should import it now, e.g.: - # docker exec nextcloud su -s /bin/bash www-data -c \ - # "php /var/www/html/occ $APP_ID:import-config" # if the app ships one - # or trigger the app's Repair step (most Conduction apps import their - # register via lib/Repair/Initialize*.php on app:enable — already done - # by the enable step above). Left as an explicit, documented hook. - run: | - echo "Seeding: openregister's own suites are self-seeding (fixtures create" - echo "register+schema+objects per run-id, torn down in afterAll/teardown)." - echo "Feature apps: add the register-config import here (see step comment)." - - - name: Wait for the API to respond - # app:enable returns before the repair/magic-mapping step + PHP-FPM - # opcache warm-up finish; poll the OR registers endpoint until it - # returns a real HTTP status (not connection-level 000). - run: | - for i in $(seq 1 60); do - code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 \ - -u admin:admin \ - http://localhost:8080/index.php/apps/openregister/api/registers \ - 2>/dev/null | tr -d '\n') - if [ -n "$code" ] && [ ${#code} -eq 3 ] \ - && [ "$code" -ge 100 ] 2>/dev/null && [ "$code" -lt 500 ]; then - echo "OpenRegister API responding (HTTP $code, attempt $i)" - sleep 5 - break - fi - echo "Waiting for API... (attempt $i/60, last='$code')" - sleep 2 - done - - - name: Mint admin storageState + run deep e2e (GATING) - # The app's playwright global-setup logs into NC once and persists the - # cookie jar to tests/e2e/.auth/admin.json (the storageState the - # workflows spec consumes). We run ONLY tests/e2e/workflows here — the - # deep, data-dependent layer — against the live, self-seeded NC. - if: ${{ inputs.run-e2e }} - env: - NEXTCLOUD_URL: http://localhost:8080 - NC_ADMIN_USER: admin - NC_ADMIN_PASS: admin - OR_USER: admin - OR_PASS: admin - CI: "true" - run: | - npx playwright install --with-deps chromium - npx playwright test tests/e2e/workflows - - - name: Run visual-regression project (NON-GATING — GAP-5) - # Visual baselines are rendered against the local dev container; a CI - # Linux runner uses a different font stack + GPU so the committed PNGs - # will not byte-match here. This step is therefore NON-GATING - # (continue-on-error) and exists to (a) surface visual diffs as an - # artifact and (b) let a maintainer regenerate CI-native baselines: - # download the `visual-snapshots-` artifact from a run with - # PLAYWRIGHT_UPDATE=1 and commit it, then drop continue-on-error to - # make the step gating in the CI environment. - if: ${{ inputs.run-visual }} - continue-on-error: true - env: - NEXTCLOUD_URL: http://localhost:8080 - NC_ADMIN_USER: admin - NC_ADMIN_PASS: admin - OR_USER: admin - OR_PASS: admin - CI: "true" - run: | - npx playwright install --with-deps chromium - if [ "${PLAYWRIGHT_UPDATE:-0}" = "1" ]; then - echo "Regenerating CI-native visual baselines (--update-snapshots)…" - npx playwright test --project visual --update-snapshots || true - else - npx playwright test --project visual || true - fi - - - name: Upload visual snapshots + diffs (NON-GATING) - if: ${{ inputs.run-visual }} - continue-on-error: true - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: visual-snapshots-${{ inputs.app-id }} - path: | - tests/e2e/visual/**/*-snapshots/** - tests/e2e/test-results/** - retention-days: 14 - if-no-files-found: ignore - - - name: Run Newman API-contract suite (GATING) - if: ${{ inputs.run-newman }} - env: - BASE_URL: http://localhost:8080 - ADMIN_USER: admin - ADMIN_PASSWORD: admin - CONTAINER_NAME: nextcloud - NEWMAN_RUNNER: host - FAIL_FAST: "0" - COLLECTIONS: ${{ inputs.newman-collections }} - run: bash "${{ inputs.newman-entrypoint }}" - - - name: Upload Playwright report - if: always() - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: playwright-report-${{ inputs.app-id }} - path: | - tests/e2e/playwright-report/ - tests/e2e/test-results/ - retention-days: 14 - if-no-files-found: ignore - - - name: Upload Newman reports - if: always() - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: newman-reports-${{ inputs.app-id }} - path: | - tests/newman/reports/ - newman-*.json - retention-days: 14 - if-no-files-found: ignore - - - name: Collect docker logs on failure - if: failure() - run: | - mkdir -p ci-logs - docker compose -f "$COMPOSE_FILE" logs --no-color > ci-logs/docker-compose.log 2>&1 || true - docker exec nextcloud cat /var/www/html/data/nextcloud.log > ci-logs/nextcloud.log 2>&1 || true - - - name: Upload docker logs - if: failure() - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: ci-logs-${{ inputs.app-id }} - path: ci-logs/ - retention-days: 14 - if-no-files-found: ignore - - - name: Tear down CI stack - if: always() - run: docker compose -f "$COMPOSE_FILE" down -v diff --git a/.forgejo/workflows/tests.yml b/.forgejo/workflows/tests.yml index 88b5833a2..a20f10756 100644 --- a/.forgejo/workflows/tests.yml +++ b/.forgejo/workflows/tests.yml @@ -74,7 +74,11 @@ jobs: apt-get update apt-get install -y --no-install-recommends \ git curl ca-certificates gnupg jq unzip zip \ - libzip-dev libpng-dev python3 nodejs + libzip-dev libpng-dev python3 + # Node is required by actions/checkout@v4 (a JS action) which runs + # inside this php:8.3-cli container; the stock image ships no node. + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y --no-install-recommends nodejs docker-php-ext-install -j"$(nproc)" zip gd curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer @@ -87,13 +91,8 @@ jobs: - name: Run unit suite (phpunit-unit.xml) # unit-gating=false reports failures without failing the job, for apps # carrying pre-existing tests/Unit debt (see TESTING-CI-ROLLOUT.md). - # Gating is done in-shell, NOT via `continue-on-error: ${{ }}` — Forgejo - # Actions cannot unmarshal an expression into that bool field, which made - # the whole workflow fail to parse (cannot unmarshal !!str into bool). - run: | - ./vendor/bin/phpunit --configuration phpunit-unit.xml --no-coverage --colors=never && exit 0 - [ "${{ inputs.unit-gating }}" = "false" ] && { echo "unit-gating off — not failing the job"; exit 0; } - exit 1 + continue-on-error: false + run: ./vendor/bin/phpunit --configuration phpunit-unit.xml --no-coverage --colors=never # --------------------------------------------------------------------------- # HARD GATE 2 — l10n extraction-drift check (no NC needed; pure Node). @@ -110,6 +109,30 @@ jobs: - name: Assert every t() source string is in l10n/en.json run: node tests/l10n/check-l10n.js + # --------------------------------------------------------------------------- + # HARD GATE 3 — frontend unit suite (Vitest, OFFLINE; no NC needed). + # + # Runs the pure-logic Vitest suite under tests/vitest/** (Pinia store + # state transitions, util/formatter calc, form-validation mappers, and any + # offline component mounts). These need no DOM/NC runtime — @nextcloud/* and + # @conduction/nextcloud-vue are aliased to deterministic stubs in + # vitest.config.js. Always gating. + # --------------------------------------------------------------------------- + frontend-unit: + name: Frontend unit (Vitest — ${{ inputs.app-id }}) + runs-on: codeberg-medium + container: + image: node:${{ inputs.node-version }} + steps: + - name: Checkout + uses: https://github.com/actions/checkout@v4 + + - name: Install npm deps + run: npm ci --legacy-peer-deps || npm install --legacy-peer-deps + + - name: Run Vitest unit suite + run: npm run test:unit + # --------------------------------------------------------------------------- # COVERAGE GATE A — PHPUnit COVERAGE RATCHET (PCOV clover line coverage). # Fails a PR that drops backend coverage below tests/.coverage-baseline.json @@ -122,16 +145,18 @@ jobs: runs-on: codeberg-medium container: image: php:8.3-cli - # Soft-gating (unit-gating=false) is applied in-shell on the ratchet step, - # NOT via `continue-on-error: ${{ }}` — Forgejo can't unmarshal an - # expression into that bool and the workflow fails to parse. + continue-on-error: false steps: - name: Install base tooling run: | apt-get update apt-get install -y --no-install-recommends \ git curl ca-certificates gnupg jq unzip zip \ - libzip-dev libpng-dev python3 nodejs + libzip-dev libpng-dev python3 + # Node is required by actions/checkout@v4 (a JS action) which runs + # inside this php:8.3-cli container; the stock image ships no node. + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y --no-install-recommends nodejs docker-php-ext-install -j"$(nproc)" zip gd curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer @@ -160,9 +185,7 @@ jobs: - name: Coverage ratchet (fail on drop) run: | test -f coverage/clover.xml || exit 0 - bash tests/coverage-ratchet.sh phpunit coverage/clover.xml && exit 0 - [ "${{ inputs.unit-gating }}" = "false" ] && { echo "unit-gating off — not failing on coverage drop"; exit 0; } - exit 1 + bash tests/coverage-ratchet.sh phpunit coverage/clover.xml # --------------------------------------------------------------------------- # COVERAGE GATE B — FRONTEND COVERAGE RATCHET (Vitest v8, src/** line coverage). @@ -184,19 +207,21 @@ jobs: VITEST_VER="$(node -e "console.log(require('./node_modules/vitest/package.json').version)")" npm install --no-save --legacy-peer-deps "@vitest/coverage-v8@${VITEST_VER}" - - name: Run Vitest WITH coverage (json-summary over src/**) + - name: Run Vitest coverage + ratchet (honors each app's own --coverage.include) run: | - npx vitest run --coverage --coverage.provider=v8 \ - --coverage.reporter=json-summary --coverage.reporter=text-summary \ - --coverage.include='src/**' \ - --coverage.reportsDirectory=coverage-vitest || true - test -f coverage-vitest/coverage-summary.json \ - || { echo "no coverage-summary.json — vitest coverage unavailable; skipping ratchet"; exit 0; } - - - name: Coverage ratchet (fail on drop) - run: | - test -f coverage-vitest/coverage-summary.json || exit 0 - bash tests/coverage-ratchet.sh vitest coverage-vitest/coverage-summary.json + # Use the app's OWN coverage script so its per-app --coverage.include is + # honored (apps keep frontend JS under src/** OR js/**; hardcoding src/** + # here makes js/**-based apps measure 0 files -> "Unknown" -> ratchet exit 2). + if npm run 2>/dev/null | grep -qE '(^|[[:space:]])test:coverage-ratchet([[:space:]]|$)'; then + npm run test:coverage-ratchet + elif npm run 2>/dev/null | grep -qE '(^|[[:space:]])test:coverage([[:space:]]|$)'; then + npm run test:coverage + test -f coverage-vitest/coverage-summary.json \ + && bash tests/coverage-ratchet.sh vitest coverage-vitest/coverage-summary.json \ + || { echo "no coverage-summary.json — vitest coverage unavailable; skipping ratchet"; exit 0; } + else + echo "no frontend coverage script for this app; skipping ratchet"; exit 0 + fi # --------------------------------------------------------------------------- # SCAFFOLD — deep e2e (Playwright tests/e2e/workflows/). Opt-in via run-e2e. diff --git a/.github/workflows/branch-protection.yml b/.github/workflows/branch-protection.yml index afeee63d4..d878721fe 100644 --- a/.github/workflows/branch-protection.yml +++ b/.github/workflows/branch-protection.yml @@ -5,6 +5,11 @@ on: branches: [main, beta] jobs: - check: - uses: Conduction/.github/.github/workflows/branch-protection.yml@main + # Job id must stay `branch-protection` so the check reports as + # `branch-protection / check-branch`, which is the context name the org + # ruleset requires. GitHub names a reusable-workflow context + # ` / `, so renaming this job silently + # detaches the required status check and leaves PRs permanently pending. + branch-protection: + uses: ConductionNL/.github/.github/workflows/branch-protection.yml@main secrets: inherit \ No newline at end of file diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 06d18e362..55cba7f83 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -9,18 +9,71 @@ on: jobs: quality: - uses: Conduction/.github/.github/workflows/quality.yml@main + uses: ConductionNL/.github/.github/workflows/quality.yml@main with: app-name: openbuild php-version: "8.3" php-test-versions: '["8.3", "8.4"]' - nextcloud-test-refs: '["stable31", "stable32", "stable33"]' + # stable31 is REMOVED, not "dropped for coverage". `additional-apps` below + # installs openregister, which declares min-version="32" and therefore + # cannot install on NC31 — `occ app:enable openregister` fails with only a + # ::warning::, so the run continues WITHOUT its data layer and every + # /apps/openregister/... call returns Nextcloud's HTML 404 page. A stable31 + # leg was testing an impossible configuration, so its red said nothing. + # Newman, Playwright and journeydoc-capture all pin `fromJSON(...)[0]`, so + # the FIRST entry has to be a version openregister can load. + nextcloud-test-refs: '["stable32", "stable33"]' enable-psalm: true enable-phpstan: true enable-phpmetrics: true enable-frontend: true enable-eslint: true enable-phpunit: true + # Run the Hydra mechanical quality gates against this PR's diff. + # + # Deliberately no count here: the gate set is the composer package + # `conduction/hydra-gates` and that package is the single source of truth + # for which gates exist and how many actually report. A digit written into + # prose goes stale silently and then misrepresents the coverage this repo + # has. See https://github.com/ConductionNL/.github/tree/main/hydra-gates + # + # Without this, wiring the gates into `composer check:strict` would have + # been a LOCAL-ONLY change: nothing in this repository's CI runs + # check:strict — there is no workflow that invokes it — so every PR would + # have gone green with the gates never having executed. A gate that only + # runs on a developer's machine is not a gate. + enable-hydra-gates: true + # No `hydra-gates-ref` here on purpose. The shared workflow defaults it + # to @main, and this workflow is itself consumed at @main, so the two + # sides move together and a gate fix reaches this repo without a commit + # in this repo. A pin is a silent expiry date: 22 repos sat on v1.0.1 and + # 16 gates were dead fleet-wide while every one reported PASS (.github#159), + # and a default flipped at @main later reached those old runners and made + # them red on gates they had no subject matter for (.github#173). + # To hold this repo still for a specific reason, set the input explicitly + # and say why — it is still honoured. To roll back for everyone, revert on + # ConductionNL/.github main. + # + # THIRD CAUSE, and the one that is failing this repo RIGHT NOW + # (.github#177): quality.yml@main began executing three gate helpers BY + # NAME — check_spec_anchors.py, check_form_labels.py and + # check_license_triangle.py — which exist in NO tag before v1.5.0. + # Verified by DIRECTORY LISTING of each tag, not by per-file lookups: + # those answered "present" uniformly across v1.0.0..v1.5.0, and the + # uniformity across independent inputs was the tell that the instrument + # was wrong. So the Hydra Gates job here fails at "Verify the pinned gates + # package satisfies this workflow", before a single gate runs, with the + # workflow's own words: "This is NOT a code-quality finding about your + # repository." Removing the pin is the repair. + # + # Unpinning also picks up v1.5.1's push scoping (.github#179): on a push to + # `development`, `origin/development` IS `HEAD`, so the diff was empty by + # construction — <= v1.4.0 passed over it (permanently green) and v1.5.0 + # refused with exit 99 (permanently red). The scope is now + # `github.event.before...HEAD`, what the push actually changed. + # `enable-axe` is deliberately still NOT set — a vanilla Nextcloud 34 + # reports serious/critical violations on core's OWN routes that DOM + # scoping does not remove. Enabling axe is a separate decision. # Newman stays disabled while the openbuild#33 fixes are in flight. # The collection bugs in this PR are fixed (uuid `@self.id` extraction, # `export-job` slug correction, hello-world `@self.slug` fallback). But @@ -39,11 +92,187 @@ jobs: # File those as their own issues; once both are unblocked, flip this # back to `true` and re-evaluate. enable-newman: false - database: sqlite + # Was `sqlite`. Measured on run 31018480923: it is what made the E2E seed + # fail before a single spec ran — + # + # Failed to seed hello-world fixture: … SQLSTATE[HY000]: General error: + # 1 no such table: information_schema.columns + # + # OpenRegister's MagicTableHandler reads `information_schema.columns` to + # decide whether an object table needs creating/updating. Postgres and + # MySQL both ship that view; SQLite does not have it at all. This is the + # same family as documented Newman blocker #2 below (`no such function: + # REGEXP`, also from OR's MagicMapper, also SQLite-only) — OpenRegister + # simply does not run on SQLite, and every job in this file that boots it + # was running on a database it does not support. + # + # `pgsql` is the shared workflow's own default, which is why it did not + # need writing here at all; the repos whose E2E is green (opencatalogi, + # doriath) set no `database:` and get it. Stated explicitly rather than + # deleted so the next person reads the reason instead of re-adding the + # override. The `database` input also feeds PHPUnit, Newman and the + # journeydoc capture, so those move to Postgres with it. + database: pgsql newman-seed-command: 'php occ app:disable openbuild && php occ app:enable openbuild' # OpenRegister `development` carries the runtime-schema-API (auto-create # Register on application-type imports — OR PR #1464) and the lifecycle # engine (ObjectTransitionedEvent) that OpenBuild's repair steps + the # Newman publish step need; `main` predates both. See openbuild#29. - additional-apps: '[{"repo":"Conduction/openregister","app":"openregister","ref":"development"}]' + # + # DOCUDESK IS HERE BECAUSE THREE E2E SPECS ASSERT AGAINST ITS API AND + # THERE WAS NO WAY FOR THEM TO PASS WITHOUT IT. + # + # `tests/e2e/spec-coverage/docudesk-document-templates.spec.ts` drives the + # builder's Documents section: attach a template, preview it, and warn + # when the attached template was deleted. All three go through Docudesk's + # own REST surface (`GET/POST /apps/docudesk/api/templates`, + # `GET /apps/docudesk/api/templates/{id}`, + # `POST /apps/docudesk/api/templates/{id}/preview`). With Docudesk absent + # every one of those is a Nextcloud router 404, and the run said so + # plainly: `[globalSetup] docudesk not installed — template fixtures + # skipped`, then three failures reading `Expected: 200 / Received: 404`. + # That is not an app defect and no code change in this repository could + # have fixed it — the dependency was simply not installed. + # + # `tests/e2e/global-setup.ts` already knows what to do once it IS: it + # configures Docudesk's `template_register`/`template_schema` through + # Docudesk's own `POST /api/settings` and seeds the two template fixtures + # the specs attach to, and it degrades to a log line when Docudesk is + # missing. So this input is the whole fix. + # + # `ref: main` — Docudesk has no `development` branch (verified by listing + # the repo's branches; it publishes `main` and `beta` only), and `main` + # carries every route above. + # + # Only the API is exercised. `additional-apps` clones and runs `composer + # install` but never builds an app's frontend, so Docudesk's own JS bundle + # is absent here — which is fine, because no spec in this suite opens a + # Docudesk page. + additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"},{"repo":"ConductionNL/docudesk","app":"docudesk","ref":"main"}]' enable-sbom: true + + # Integration Tests (Newman) stays OFF here, deliberately, and unlike the + # gates above this is not a default nobody chose. The `enable-newman: + # false` further up carries two named, still-live upstream blockers + # (OpenRegister's `React\Async\await()` not autoloading in the CI install, + # and SQLite lacking `REGEXP` for OR's MagicMapper). Nineteen collections + # are committed and would all meet those two failures — a guaranteed red + # whose cause is already written down, which teaches nothing the comment + # does not already say. Flipped back on in the commit that closes them. + + # ── Frontend Check legs ────────────────────────────────────────────── + # `frontend-checks` defaults to `[]`, and an empty list means the shared + # workflow emits NO "Frontend Check" job at all — so these two validators + # ran nowhere while the run still looked complete. Both are self-contained + # `node` scripts, which is what a leg has to be. + # `test:l10n` was measured FAILING on this tree (source strings missing + # from l10n/en.json, e.g. "Widgets on this page"). `check:manifest` could + # not be measured locally — it `require`s `ajv/dist/2020`, which only + # exists after the leg's own `npm ci`, so CI is the first place it gets a + # real verdict. + # `test` / `test:unit` are NOT listed: "Frontend Tests (unit)" runs them. + frontend-checks: '["check:manifest", "test:l10n"]' + + # ── Coverage ratchet ───────────────────────────────────────────────── + # `enable-coverage-guard` defaults to FALSE, which is why both + # "Coverage Baseline Protection" and "Coverage Baseline Check" have only + # ever reported `skipped`. It needs two inputs this repo did not have, + # both added in this commit: `scripts/coverage-guard.php` (byte-identical + # to openregister's) and `.coverage-baseline` = 57.39, this repo's own + # measured coverage (8018 of 13971 statements) read from clover.xml in the + # `coverage-report` artifact of run 30911188960. + enable-coverage-guard: true + + # ── E2E Tests (Playwright) ─────────────────────────────────────────── + # `enable-playwright` defaults to FALSE and this file never set it, so + # the shared workflow's `E2E Tests (Playwright)` job has reported + # `skipped` on every run this repository has ever produced — while 50 + # committed spec files sat in `tests/e2e/`. Unlike `enable-newman: false` + # above, that was not a decision anyone recorded; it is simply a default + # nobody chose, and a skipped job is indistinguishable from a passing one + # in the run summary. Turning it on is the whole point of this change. + # + # `playwright-test-path` is NOT cosmetic here. The workflow picks its + # config as `"${playwright-test-path}/playwright.config.ts"`, falling + # back to the ROOT `playwright.config.ts`, and then runs it with NO + # `--project` — so every project in whichever config it lands on runs. + # The root config declares three, and two of them are documented as + # unable to pass on a CI runner: `docs-capture` (journeydoc; the separate + # `Journeydoc Capture` job owns it) and `visual` (PNG baselines that, + # per that config's own header, "a CI Linux runner will not byte-match"). + # Pointing at `tests/e2e` makes the FIRST lookup hit + # `tests/e2e/playwright.config.ts`, added in this commit, which declares + # only the `chromium` regression project and writes its report/traces to + # the app root — the paths the workflow's upload steps actually look at. + # The root config is untouched and still drives local runs. + # + # OpenRegister is already checked out for every job via `additional-apps` + # above, which the specs need: OpenBuild's applications, versions and + # schemas are OpenRegister objects. + enable-playwright: true + playwright-test-path: tests/e2e + + # The instance setup the suite cannot do for itself on CI. + # `tests/e2e/global-setup.ts` does it locally, but it reaches `occ` by + # resolving the DOCKER CONTAINER publishing the port under test — and it + # deliberately refuses to guess when it cannot resolve one (guessing is + # how it used to seed a different instance than it asserted against). + # On this runner Nextcloud is `php -S`, not a container, so those steps + # log a warning and no-op. `tests/e2e/ci-seed.sh` is the documented + # override path; the step runs with cwd = the Nextcloud server root, so + # the app-relative path below is what `occ` sees. + # + # It was a two-command inline string until run 31030663352 measured what + # that leaves undone. Read the script's header for the full reasoning; the + # short version is five things, each of which fails SILENTLY: + # + # 1. htaccess.IgnoreFrontController=true. THE root cause of the first + # run's failures. openbuild builds its router as + # `createWebHistory(generateUrl('/apps/openbuild'))`, and + # `generateUrl()` prefixes `/index.php` unless the page reports + # `modRewriteWorking`. A freshly `maintenance:install`ed instance + # does not set it, so the router base becomes + # `/index.php/apps/openbuild` while every spec navigates to the + # pretty `/apps/openbuild/...`. Those disagree, vue-router matches + # nothing, and the SPA mounts and renders perfectly — on the + # DASHBOARD. No 404, no console error. That is why 68 of the 83 + # tests the first run reached failed on a non-Dashboard selector + # (`.agents-page`, `.ob-app-card`, `.automations-page`, + # `.page-designer__left`), while the only UI tests that passed were + # builder-host's first two — whose inner router is based at + # `/apps/openbuild/builder/`, the exact URL the spec opens, so + # its DEFAULT route is the page being asserted. builder-host's third + # test, the one that needs the `/messages/new` SUB-path to survive, + # failed with the rest. The CI router already serves pretty URLs + # (the workflow's own front-controller check proves it), so this + # states something TRUE about the instance rather than working + # around anything. + # 2. The register + schema import. `occ app:enable openbuild` runs + # `InitializeSettings`, which imports with NO user session — so OR's + # RBAC denies it and the repair step downgrades the throw to a + # warning. Done here explicitly over the admin HTTP API, FORCED, + # and then VERIFIED against the 15 schema slugs read out of the repo. + # 3. ratelimit.protection.enabled=false — the app-creation wizard + # carries `#[UserRateLimit(limit: 10, period: 3600)]`, and the suite + # creates far more than ten apps (every `ensureApp()` in + # tests/e2e/support/appFixture.ts goes through that endpoint). Past + # the tenth create every later spec fails on a 429 that has nothing + # to do with what it asserts. The limit is correct product behaviour + # and is NOT changed — it is switched off for this throwaway test + # instance using Nextcloud's own supported flag. + # 4. openbuild:seed-hello-world-fixture — the deterministic + # `hello-world` virtual app the legacy specs assert against. + # Production no longer seeds it (the SeedHelloWorld repair step was + # retired with the versioned-model migration), so the harness seeds + # it. Idempotent; a test/dev-only occ command. Its exit code covers + # "already present", so the script also proves the fixture exists + # over HTTP. + # 5. The bundle. A missing frontend bundle does not 404 on Nextcloud — + # it serves the HTML error page with HTTP 200 and `text/html`, which + # every status-code check in the pipeline reads as success. Gated on + # the SERVED response's content type actually being JavaScript. + # + # Deliberately NOT `|| true`: if the instance cannot be provisioned, the + # specs fail on missing content and the cause is a wall of selector + # timeouts. Better to fail here, once, where the reason is printed. + playwright-seed-command: 'bash apps/openbuild/tests/e2e/ci-seed.sh' diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index bd2be16ac..f77729f15 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,6 +8,6 @@ on: jobs: deploy: - uses: Conduction/.github/.github/workflows/documentation.yml@main + uses: ConductionNL/.github/.github/workflows/documentation.yml@main with: cname: openbuild.conduction.nl diff --git a/.github/workflows/exporter-e2e.yml b/.github/workflows/exporter-e2e.yml index c4d176845..e3cbea903 100644 --- a/.github/workflows/exporter-e2e.yml +++ b/.github/workflows/exporter-e2e.yml @@ -19,6 +19,8 @@ permissions: jobs: exporter-e2e: runs-on: ubuntu-latest + # Observed over 30 runs: max 0.8 min. + timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index b726ba7a7..74c2106a4 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -12,7 +12,7 @@ on: jobs: triage: - uses: Conduction/.github/.github/workflows/issue-triage.yml@main + uses: ConductionNL/.github/.github/workflows/issue-triage.yml@main with: app-name: openbuild backlog-existing: ${{ github.event_name == 'workflow_dispatch' && inputs.backlog-existing || false }} diff --git a/.github/workflows/openspec-sync.yml b/.github/workflows/openspec-sync.yml index 1a2264fe1..7dac1a7a6 100644 --- a/.github/workflows/openspec-sync.yml +++ b/.github/workflows/openspec-sync.yml @@ -8,7 +8,7 @@ on: jobs: sync: - uses: Conduction/.github/.github/workflows/openspec-sync.yml@main + uses: ConductionNL/.github/.github/workflows/openspec-sync.yml@main with: app-name: openbuild secrets: diff --git a/.github/workflows/pull-request-lint-check.yaml b/.github/workflows/pull-request-lint-check.yaml index fa2776ba0..4a02c7797 100644 --- a/.github/workflows/pull-request-lint-check.yaml +++ b/.github/workflows/pull-request-lint-check.yaml @@ -10,6 +10,8 @@ on: jobs: lint-check: runs-on: ubuntu-latest + # Observed fleet-wide over 176 runs: median 0.6 min, max 1.4 min. + timeout-minutes: 15 steps: - name: Checkout repository diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 093fc9241..84cc4cbac 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -6,7 +6,7 @@ on: jobs: release: - uses: Conduction/.github/.github/workflows/release-beta.yml@main + uses: ConductionNL/.github/.github/workflows/release-beta.yml@main with: app-name: openbuild secrets: inherit diff --git a/.github/workflows/release-development.yml b/.github/workflows/release-development.yml new file mode 100644 index 000000000..21ad991c1 --- /dev/null +++ b/.github/workflows/release-development.yml @@ -0,0 +1,36 @@ +# Publishes an installable build of the development branch. +# +# Beta and stable reach people through the Nextcloud app store. Development +# reached nobody: releases only fire on a push to beta or main, so the +# newest installable build was months behind the branch and trying out +# unreleased work meant building it yourself. +# +# This publishes a GitHub prerelease with a .tar.gz on every push to +# development. The App Versions app reads a repository's releases from the +# forge API and installs from that asset, and it already trusts +# github:ConductionNL/* by default, so nothing needs configuring on the +# Nextcloud side. +# +# Deliberately never uploaded to the app store: the shared workflow skips +# that step for this channel. A dev build is for people who asked for one. +name: Development Release + +on: + push: + branches: [development] + workflow_dispatch: + +# A push during a running build supersedes it. Without this, a busy morning +# produces a queue of releases that are obsolete before they finish, and +# the tag each one cuts sticks around. +concurrency: + group: development-release + cancel-in-progress: true + +jobs: + release: + uses: ConductionNL/.github/.github/workflows/release-beta.yml@main + with: + app-name: openbuild + channel: dev + secrets: inherit diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml index b45b49a8e..5d7ca19e9 100644 --- a/.github/workflows/release-stable.yml +++ b/.github/workflows/release-stable.yml @@ -6,7 +6,7 @@ on: jobs: release: - uses: Conduction/.github/.github/workflows/release-stable.yml@main + uses: ConductionNL/.github/.github/workflows/release-stable.yml@main with: app-name: openbuild secrets: inherit diff --git a/.github/workflows/sync-to-beta.yml b/.github/workflows/sync-to-beta.yml index 6081fe871..979a37346 100644 --- a/.github/workflows/sync-to-beta.yml +++ b/.github/workflows/sync-to-beta.yml @@ -6,5 +6,5 @@ on: jobs: sync: - uses: Conduction/.github/.github/workflows/sync-to-beta.yml@main + uses: ConductionNL/.github/.github/workflows/sync-to-beta.yml@main secrets: inherit diff --git a/.gitignore b/.gitignore index 3604dd7bf..59de202d5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,12 @@ /.phpunit.cache .phpunit.cache/ -/node_modules/ -/website/node_modules/ -/website/.docusaurus/ +# Un-anchored: these must match at any depth. The root-anchored /website/ and +# /docusaurus/ rules stopped matching when the docs moved to docs/, which let +# 40k files (559 MB) of node_modules be committed in 0c0d29b6f. +node_modules/ +.docusaurus/ +docs/build/ /js/ /custom_apps/ /config/ @@ -98,3 +101,9 @@ openspec/test-site-results/**/*.webp tests/e2e/.auth/ /coverage-vitest/ /coverage/ + +# Nested hydra tooling repo (separate repo + secrets) — never track here +.hydra/ + +# docs build artefacts (never track) +docs/node_modules/ diff --git a/.npmrc b/.npmrc index 3942d3489..e4e8835b7 100644 --- a/.npmrc +++ b/.npmrc @@ -2,4 +2,4 @@ # 24h ago. Compromised first-party-Conduction packages are excluded via # Dependabot cooldown (.github/dependabot.yml); for fresh @conduction/* # releases, override per-install with `npm install --min-release-age=0`. -min-release-age=1 +min-release-age=0 diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e7643e8..431ee5105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,171 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.0] - 2026-08-05 + +### Fixed +- **Every app OpenBuild has ever generated was born declaring the wrong licence.** + The embedded template snapshot's `appinfo/info.xml` hardcoded + `agpl` while the very same file's description read "Free and + open source under the EUPL-1.2 license". It now reads `{{license}}`, + so the `license` value the export already carried end to end + (`ExportJobService` → `RunExportJob` → `PlaceholderResolver`, defaulting to + `EUPL-1.2` at all three layers) finally reaches the file that declares it, and + a caller who picks a different licence gets the licence they picked. Verified + against the Nextcloud appstore schema + (`https://apps.nextcloud.com/schema/apps/info.xsd`): the `licence` enumeration + **does** include `EUPL-1.2`. It does **not** include `eupl`. + +### Added +- `ExporterEndToEndTest::testGeneratedAppDeclaresTheRequestedLicence()` — asserts + a really-exported app's `appinfo/info.xml` declares `EUPL-1.2` and does not + declare `agpl`, and that `src/manifest.json` and `composer.json` agree. Shown + to fail against the pre-fix snapshot before it was made to pass. Nothing in the + suite covered the generated app's licence declaration until now: the existing + unresolved-placeholder assertion matches `/\{\{[a-zA-Z]+\}\}/`, and a hardcoded + wrong value contains no placeholder. + +### Changed +- `lib/Resources/template/.snapshot-meta.json` and `docs/releasing.md` now record + that the embedded template is a **fork**, not a snapshot, and that the + documented `rsync -a --delete` refresh is unsafe to run as written — it would + revert OpenBuild-only fixes (including "the generated app could not be built at + all", #39) and swap OpenBuild's `{{token}}` placeholder dialect for upstream's + `{APP_NAME}` dialect, which `PlaceholderResolver` does not resolve and no test + would catch. `docs/releasing.md` also now records that the "CI drift check" it + describes **does not exist**. +- `lib/Resources/template/.path-manifest.txt` no longer lists `.snapshot-meta.json`; + the regeneration command in `docs/releasing.md` excludes it, so the checked-in + manifest disagreed with its own generator. + +## [0.8.0] - 2026-07-25 + +### Changed +- **Theme picker now consumes nldesign's published catalogue** + (theme-picker-consumes-nldesign) — bumps `@conduction/nextcloud-vue` to + `^1.0.0-beta.221`, which ships `useScopedTheme()` and wires `CnAppRoot` to + self-apply `manifest.runtime.theme`. `ThemePickerDialog.vue` collapses its + old three-tier admin/probe/free-text catalogue fallback to a single + `useScopedTheme().listTokenSets()` call against nldesign's real non-admin + `GET /api/token-sets` endpoint, and adds a warn-only WCAG contrast preview + via `evaluateContrast()` that never blocks Save. Live theme preview now + retargets the page-designer's sandboxed live-preview-pane `CnAppRoot` + instance instead of a separate OpenBuild-owned applier. + +### Removed +- `src/composables/useAppTheme.js` — OpenBuild's own scoped-CSS + `:root`-rewriter and injector; `CnAppRoot`'s own `useScopedTheme` watcher + now owns runtime theme application end-to-end, with zero OpenBuild-side + wiring in `BuilderHost.vue` or `PageDesignerHost.vue`. +- `src/services/manifestValidation/theme.js` — OpenBuild's own + `runtime.theme` shape validator; `@conduction/nextcloud-vue`'s + `validateManifest()` (schema 2.21.0, `$defs/runtimeTheme`) is now the + single source for this validation. + +## [0.7.7] - 2026-07-24 + +### Added +- **Runtime group-scoped access** (runtime-group-scoped-access) — a manifest + `menu[]`/`pages[]` entry may declare a `permission: "group:"`; the + runtime resolves the caller's Nextcloud group context server-side and + `ManifestResolverService::filterManifestForCaller()` strips any entry the + caller does not hold the permission for from the manifest response BEFORE + it leaves the server — the authoritative gate, not client-side hiding. + Admins and callers with an owner/editor role on the Application see the + manifest unfiltered. A group-scoped dashboard page is promoted to the + landing position for members who satisfy it, falling back to the default + dashboard otherwise. `PermissionGroupField.vue` adds a group picker to the + menu-item and page editors. Client-side `CnAppNav` filtering mirrors the + server decision as defense in depth, not the only defense. Documented + boundary: this hides navigation only — object-level access for the + underlying data remains OpenRegister schema `authorization`'s job. + +- **Agent workspace** (agent-workspace) — named, tool-scoped AI agents + layered on the existing `ai-copilot` plan/execute engine (ADR-022 + consume-not-rebuild): an `Agent` (instructions, an explicit subset of the + eight `OpenBuildToolProvider` tools, `maxActionsPerRun`) is never a wider + capability surface than the bare copilot — enforced server-side as a + narrowed intersection of the existing eight-tool catalogue on every + plan/execute request, never trusted from the client. +- Transparent per-run log (`AgentRun`): every plan+execute/discard turn + persists the prompt, plan, every tool call's arguments and result, and the + outcome (`applied`/`rolled-back`/`discarded`/`plan-rejected`) — the Retool + tool-chip transparency pattern, addressing the market-wide "trust gap" + evidence directly. +- `AgentsPage.vue` (CRUD list), `AgentEditDialog.vue`, and a run-history + view (`AgentRunHistory.vue`) restricted server-side to owners/editors of + the agent's parent Application; `CopilotPanel.vue` gains optional + `agentId`/`name`/`instructions`/`enabledTools` props, fully + backwards-compatible with the existing bare-copilot surfaces. +- No autonomous/automation-triggered agent runs in v1 — an agent acts only + inside a human-initiated chat turn. +- **Component blocks** (component-blocks) — capture a configured widget, or + a selected multi-widget page section, from the page designer into a + named, reusable `ComponentBlock` (new `componentBlock` OR schema, + `lib/Settings/register.d/60-component-blocks.json`). +- Block-library panel (`NcAppSidebar`) in the page designer listing every + org-wide block, filterable by category, with insert support. +- Insert deep-copies the fragment and mints fresh widget ids, so repeated + insertions never collide and editing the source block never affects an + already-inserted copy. +- Schema-dependency remap prompt (`BlockRemapDialog.vue`) on a cross-app + insert whose schemas don't exact-match — never a silent guess, never a + silently dropped binding. +- Blocks export/import as standalone JSON. +- Template-catalogue gallery gains a "Blocks" filter alongside full-app + templates. + +## [0.7.5] - 2026-07-24 + +### Added +- **Document-generation automation action** (automation-document-action) — a + new `generateDocument` action kind on `object-created`/`object-updated`/ + `object-deleted`/`lifecycle-transition` triggers, compiling to no + compile-time artifact (Docudesk's `correspondence/generate` route is + stateless) and dispatching at trigger-fire time through a new + `DocumentGenerationListener` → `DocumentGenerationService`. +- `DocumentGenerationService` calls Docudesk's existing, Newman-pinned + `POST /apps/docudesk/api/correspondence/generate` route — never a + `OCA\DocuDesk\*` PHP class import — impersonating the owning + Application's owner (via the existing `JobOwnerImpersonator`) for the + duration of exactly one internal call, authenticated with a short-lived + Nextcloud login token minted through `OC\Authentication\Token\IProvider` + and invalidated immediately after use. +- Three output modes: `attach` (writes the generated document to Nextcloud + Files and sets a `{ "ref": "" }` reference on the triggering + object's `generatedDocument` field), `download-link` (a short-lived, + ~24h signed URL served by the new `GeneratedDocumentController` from + OpenBuild's own app-private storage — never the user's Files tree), and + `notify` (reuses the existing `RuleActionDispatcher` send-notification + path; must be paired with `attach` and/or `download-link`). +- `AutomationEditDialog` gains the `generateDocument` action editor: a + Docudesk template picker (via the new shared `useDocudeskTemplates.js` + composable, also adopted by `DocumentTemplateAttachmentDialog` so the + template-list fetch has exactly one implementation) and an output-mode + multi-select, disabled with a missing-app hint when Docudesk is absent. +- Compile-time validation (`AutomationCompilerService`): `templateId` + required, `output` a known non-empty set, `notify` never alone, and a + fail-closed `UnsupportedAutomationCombinationException` naming the + missing `docudesk` dependency when Docudesk is not installed. + +## [0.7.4] - 2026-07-23 + +### Added +- **Approval automation action** (automation-approval-steps) — a new `approval` + action kind on `object-created`/`object-updated`/`object-deleted`/ + `lifecycle-transition` triggers, group-only assignee, compiling to an + OpenRegister `ApprovalChain` instantiated against the trigger object + (consume-not-rebuild, ADR-022 — no new approval engine in OpenBuild). +- On-approve/on-reject follow-up actions, composed from the same typed-action + vocabulary (send-notification/object-op/webhook), dispatched by a typed + listener on OR's `ApprovalStepApprovedEvent`/`ApprovalStepRejectedEvent`. +- **"My approvals" runtime widget** — lists the viewer's pending approval + steps (filtered client-side by NC group membership) with approve/reject + actions calling OpenRegister's `/api/approval-steps` endpoints directly. +- `AutomationsController::status()` and the dry-run test panel now report + `approvalState: none|pending|approved|rejected` for automations carrying an + `approval` action. + ## [0.5.40] - 2026-06-26 ### Added diff --git a/appinfo/info.xml b/appinfo/info.xml index 6df987686..1e40b5109 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -6,7 +6,7 @@ OpenBuild Citizen-developer app builder for Nextcloud — compose apps from registers, connectors, workflows, and documents without code. App builder voor Nextcloud — stel apps samen uit registers, connectors, processen en documenten zonder code. - - - 0.5.40 - agpl + 0.10.0 + EUPL-1.2 Conduction OpenBuild @@ -40,6 +40,17 @@ Vrij en open source onder de EUPL-1.2-licentie. https://codeberg.org/Conduction/openbuild/raw/branch/main/img/app-store.svg + openregister + @@ -94,6 +105,7 @@ Vrij en open source onder de EUPL-1.2-licentie. OCA\OpenBuild\Repair\MigrateAppOverridesToHybrid OCA\OpenBuild\Repair\PopulateApplicationPermissions OCA\OpenBuild\Repair\SeedApplicationTemplates + OCA\OpenBuild\Repair\FlagRegistryTokenSensitive @@ -104,5 +116,6 @@ Vrij en open source onder de EUPL-1.2-licentie. OCA\OpenBuild\Command\SeedHelloWorldFixture + OCA\OpenBuild\Command\PublishTemplates diff --git a/appinfo/routes.php b/appinfo/routes.php index a2756c71a..35bf3aae3 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -22,6 +22,15 @@ // Must precede the {slug} + collection routes so it does not shadow them. ['name' => 'applicationCreation#wizard', 'url' => '/api/applications/wizard', 'verb' => 'POST'], + // First-time-setup contract (openbuild-first-time-setup, ADR-042) — the + // fleet-wide CnSetupWizard endpoints. Admin-only via + // #[AuthorizedAdminSetting] on each controller method (CSRF enforced). + // The run-action step seeds the bundled ApplicationTemplate records + // idempotently. Specific-first, before the SPA catch-all (ADR-016/029). + ['name' => 'setup#status', 'url' => '/api/setup/status', 'verb' => 'GET'], + ['name' => 'setup#saveConfig', 'url' => '/api/setup/config', 'verb' => 'POST'], + ['name' => 'setup#runAction', 'url' => '/api/setup/action/{actionId}', 'verb' => 'POST'], + // RBAC-filtered Application list (openbuild-rbac REQ-OBRBAC-002 / REQ-OBR-007). // OR's schema-level read rule is a coarse group ACL — not a row-level filter on the // Application's `permissions` block — so the editor list MUST go through this @@ -107,11 +116,9 @@ // Owner-only full delete (Application + versions + per-version registers + routes). ['name' => 'applicationPublish#destroy', 'url' => '/api/applications/{appUuid}', 'verb' => 'DELETE', 'requirements' => ['appUuid' => '[a-f0-9-]{8,}']], - // Standalone runtime page for a published virtual app. The slug pattern - // excludes slashes, so ONLY the bare /builder/{slug} matches here — the - // designer sub-routes (/builder/{slug}/pages, /schemas) fall through to - // the SPA catch-all. Placed before the catch-all (Routes::standard - // appends it) so this specific page wins. + // Standalone runtime page for a published virtual app — the bare + // /builder/{slug} (no sub-path). Placed before the catch-all + // (Routes::standard appends it) so this specific page wins. ['name' => 'dashboard#builder', 'url' => '/builder/{slug}', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], // Same page with a trailing slash — browsers and pasted links often add one. @@ -121,28 +128,79 @@ // builder()) — the AppHost Routes::standard() guard throws on duplicate names. ['name' => 'dashboard#builderSlash', 'url' => '/builder/{slug}/', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + // Reserved OpenBuild designer sub-paths (openbuild-deep-links #100 fix). + // `pages`, `schemas`, `schemas/{schemaId}` and `walkthrough` are + // OpenBuild's OWN designer surfaces (src/manifest.json: PageDesigner, + // SchemaDesignerList, SchemaDesigner, WalkthroughDesigner) — matched by + // the SPA's OWN vue-router (main.js) before its BuilderHost wildcard. + // They must keep serving the OpenBuild SPA shell (dashboard#builderDesigner + // renders the same page as catchAll()), NOT the standalone virtual-app + // runtime that dashboard#builderPath now serves below. MUST precede + // builderPath so this more-specific literal alternation wins + // (NC/Symfony route matching is order-sensitive, first-match-wins). + // + // MAINTENANCE: this alternation duplicates the /builder/:slug/* designer + // pages declared in src/manifest.json. When you add a designer surface + // there, extend this `designerPath` requirement too, or the new URL + // silently falls through to the runtime (builderPath). RoutesTest + // (testManifestDesignerRoutesAllResolveToTheDesigner) derives the list + // from src/manifest.json and FAILS if the two drift, so the omission is + // caught in CI rather than by a user report. + ['name' => 'dashboard#builderDesigner', 'url' => '/builder/{slug}/{designerPath}', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]', 'designerPath' => 'pages|schemas|schemas/[^/]+|walkthrough']], + + // ANY OTHER /builder/{slug}/... sub-path is a page defined by the + // DEPLOYED virtual app's OWN manifest (openbuild-deep-links #100). + // Direct navigation (fresh load / refresh / bookmark) previously fell + // through to the SPA catch-all — the wrong shell, nesting the app + // inside OpenBuild's own chrome/router instead of letting the app's + // own client-side router (builder.js, history mode) resolve it, the + // way clicking within the app already does. `path` allows slashes + // (requirement '.*', same trick as the SPA catch-all's `.+`) so + // nested app pages (e.g. /tenders/{id}) deep-link correctly too. + ['name' => 'dashboard#builderPath', 'url' => '/builder/{slug}/{path}', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]', 'path' => '.*']], + // Icon-serving endpoints (openbuild-nextcloud-nav REQ-OBICON-002 / REQ-OBICON-003). - // Both are #[NoAdminRequired] on the controller. The dark route uses a longer - // URL pattern ("{slug}-dark.svg") that is unambiguous — it cannot shadow the - // light route because slugs are kebab-case [a-z0-9-] and never end in "-dark". - // Placed before the SPA catch-all; after exports so slug patterns don't collide. - ['name' => 'icon#iconLight', 'url' => '/icons/{slug}.svg', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + // Both are #[NoAdminRequired] on the controller. ORDER MATTERS: iconDark's + // pattern ("{slug}-dark.svg") is a SUBSET of iconLight's ("{slug}.svg") because + // {slug} matches hyphens — so "foo-dark.svg" matches BOTH (iconDark with + // slug="foo", OR iconLight with slug="foo-dark"). Routes match in registration + // order (first wins), so iconDark MUST come first; otherwise every dark-icon + // request is captured by the light route (slug="foo-dark" → no such app → light + // default) and dark icons never resolve. Light requests ("foo.svg") lack the + // "-dark.svg" suffix so they never match iconDark — dark-first is safe (assumes + // no app slug itself ends in "-dark"). Placed before the SPA catch-all; after + // exports so slug patterns don't collide. ['name' => 'icon#iconDark', 'url' => '/icons/{slug}-dark.svg', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + ['name' => 'icon#iconLight', 'url' => '/icons/{slug}.svg', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], // Export pipeline (Phase-2 graduation). ['name' => 'exports#submit', 'url' => '/api/applications/{slug}/exports', 'verb' => 'POST', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], ['name' => 'exports#download', 'url' => '/api/exports/{uuid}/download', 'verb' => 'GET'], // Business-rules engine (spec business-rules-engine REQ-BRE-006 / REQ-BRE-004). - // All three carry #[NoAdminRequired] on the controller; multi-tenant isolation - // is enforced server-side in RuleEngineService (a slug owned by another tenant - // resolves to 404 — no IDOR). evaluate/test-all are POST so they cannot collide + // All three carry #[NoAdminRequired] on the controller; resolution goes + // through searchObjectsBySlug (schema RBAC applied). `openbuild` is a + // system-wide register, so this is NOT per-owner/per-org read isolation + // (writes stay admin-gated at the schema). evaluate/test-all are POST so they cannot collide // with the GET SPA catch-all; the GET schema route's `/schema` suffix makes it // strictly more specific than `/{path}`. Slugs are kebab-case. ['name' => 'rules#evaluate', 'url' => '/api/rules/{ruleSetSlug}/evaluate', 'verb' => 'POST', 'requirements' => ['ruleSetSlug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], ['name' => 'rules#schema', 'url' => '/api/rules/{ruleSetSlug}/schema', 'verb' => 'GET', 'requirements' => ['ruleSetSlug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], ['name' => 'rules#testAll', 'url' => '/api/rules/{ruleSetSlug}/test-all', 'verb' => 'POST', 'requirements' => ['ruleSetSlug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + // Automation designer (spec automation-designer REQ-AUTD-005/006/007/008). + // Thin, value-adding routes only — CRUD on the `automation` object itself + // stays on OR REST per ADR-022; these five uuid-addressed routes are the + // security boundary (AutomationsController enforces RBAC via + // PermissionResolver before any compile side effect, no admin bypass). + // All POST except the read-only `status` GET; uuid requirement guards + // against a kebab-case slug accidentally matching another route. + ['name' => 'automations#compile', 'url' => '/api/automations/{uuid}/compile', 'verb' => 'POST', 'requirements' => ['uuid' => '[a-f0-9-]{8,}']], + ['name' => 'automations#enable', 'url' => '/api/automations/{uuid}/enable', 'verb' => 'POST', 'requirements' => ['uuid' => '[a-f0-9-]{8,}']], + ['name' => 'automations#disable', 'url' => '/api/automations/{uuid}/disable', 'verb' => 'POST', 'requirements' => ['uuid' => '[a-f0-9-]{8,}']], + ['name' => 'automations#dryRun', 'url' => '/api/automations/{uuid}/dry-run', 'verb' => 'POST', 'requirements' => ['uuid' => '[a-f0-9-]{8,}']], + ['name' => 'automations#status', 'url' => '/api/automations/{uuid}/status', 'verb' => 'GET', 'requirements' => ['uuid' => '[a-f0-9-]{8,}']], + // App-override store-and-serve (openbuild-inline-edit-persistence, // spec app-override-persistence). Per-instance shared manifest delta for // an EXISTING fleet app, keyed by `appId`. GET returns the raw stored @@ -175,6 +233,55 @@ ['name' => 'store#search', 'url' => '/api/store/templates', 'verb' => 'GET'], ['name' => 'store#install', 'url' => '/api/store/templates/{slug}/install', 'verb' => 'POST', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + // GitHub shop source (github-shop-catalogue REQ-GHSC-005 / REQ-GHSC-006). + // Both #[NoAdminRequired] with an in-body 401 guard; search is an + // instance-shared read, install parses the repo via AppRepoParser then + // reuses ApplicationsController::installFromTemplateArray. Specific-first, + // before the engine-appended SPA catch-all. + ['name' => 'shop#githubSearch', 'url' => '/api/shop/github/search', 'verb' => 'GET'], + ['name' => 'shop#githubInstall', 'url' => '/api/shop/github/install', 'verb' => 'POST'], + + // GitHub owner round-trip (github-app-sync REQ-GHAS-001..004). All four + // #[NoAdminRequired] with a per-object owner guard (status viewer-readable). + // The trailing `/github/{action}` literal disambiguates from the slug-based + // CRUD + versions routes above; `{slug}` carries the kebab-case constraint. + // Registered specific-first before the SPA catch-all. + ['name' => 'gitHubSync#link', 'url' => '/api/applications/{slug}/github/link', 'verb' => 'POST', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + ['name' => 'gitHubSync#push', 'url' => '/api/applications/{slug}/github/push', 'verb' => 'POST', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + ['name' => 'gitHubSync#pull', 'url' => '/api/applications/{slug}/github/pull', 'verb' => 'POST', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + ['name' => 'gitHubSync#status', 'url' => '/api/applications/{slug}/github/status', 'verb' => 'GET', 'requirements' => ['slug' => '[a-z0-9][a-z0-9-]*[a-z0-9]']], + + // AI copilot / prompt-to-app (spec `ai-copilot` REQ-OBAIC-001/002/004), + // extended with optional agent-scoping (spec `agent-workspace`). All + // four #[NoAdminRequired]; per-object RBAC (existing-app owners/ + // editors, hybrid-app rejection, agent resolution) is enforced inside + // CopilotService, not via a route attribute. `plan` performs zero + // builder writes; `execute` re-validates and dispatches through + // OpenBuildToolProvider::invokeTool(); `discard` only ever runs for + // the agent-scoped chat surface (logs a discarded AgentRun). + // Specific-first, before the engine-appended SPA catch-all. + ['name' => 'copilot#health', 'url' => '/api/copilot/health', 'verb' => 'GET'], + ['name' => 'copilot#plan', 'url' => '/api/copilot/plan', 'verb' => 'POST'], + ['name' => 'copilot#execute', 'url' => '/api/copilot/execute', 'verb' => 'POST'], + ['name' => 'copilot#discard', 'url' => '/api/copilot/discard', 'verb' => 'POST'], + + // Agent run-history (spec `agent-workspace`). #[NoAdminRequired] with a + // per-object owners/editors guard enforced inside AgentsController — + // AgentRun rows are NEVER served through the generic OpenRegister REST + // surface (no row-level RBAC there; see AgentsController docblock). + // Agent CRUD itself rides OR's generic REST surface (ADR-022), mirroring + // AutomationsController's posture for the `automation` object. + ['name' => 'agents#runs', 'url' => '/api/agents/{uuid}/runs', 'verb' => 'GET'], + + // Anonymous download-link resolver for the `generateDocument` + // automation action's `download-link` output mode + // (automation-document-action, `GeneratedDocumentController`). + // `#[PublicPage]` — the random token IS the authorization. + // `/api/generated-documents/` is disjoint from every other route in + // this file so ordering relative to them is immaterial; declared + // before the SPA catch-all. + ['name' => 'generatedDocument#download', 'url' => '/api/generated-documents/{token}', 'verb' => 'GET'], + // NB: the SPA catch-all (dashboard#catchAll) is appended by // \OCA\OpenRegister\AppHost\Routes::standard() — do NOT add it here. ] diff --git a/composer.json b/composer.json index 054f02086..df925cd2d 100644 --- a/composer.json +++ b/composer.json @@ -22,10 +22,18 @@ "OCA\\OpenRegister\\": "tests/Stubs/" } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/ConductionNL/.github.git", + "no-api": true + } + ], "require": { "php": "^8.3" }, "require-dev": { + "conduction/hydra-gates": "^1.0", "cyclonedx/cyclonedx-php-composer": "^6.2", "edgedesign/phpqa": "^1.27", "nextcloud/coding-standard": "^1.4", @@ -47,15 +55,17 @@ "phpcs": "./vendor/bin/phpcs --standard=phpcs.xml", "phpcs:fix": "./vendor/bin/phpcbf --standard=phpcs.xml", "phpcs:output": "./vendor/bin/phpcs --standard=phpcs.xml --report=json lib/ 2>/dev/null | tail -1 > phpcs-output.json", - "phpmd": "./vendor/bin/phpmd lib text phpmd.xml --exclude '*/Resources/template/*' --baseline-file phpmd.baseline.xml || echo 'PHPMD not installed, skipping...'", + "phpmd": "E=0; ./vendor/bin/phpmd lib text phpmd.xml --exclude '*/Resources/template/*' --baseline-file phpmd.baseline.xml || E=$?; ./vendor/bin/phpmd lib text phpmd-unusedparams.xml --exclude '*/Resources/template/*' --baseline-file phpmd.baseline.xml || E=$?; exit $E", "phpmetrics": "./vendor/bin/phpmetrics --report-html=phpmetrics lib/", - "psalm": "./vendor/bin/psalm --threads=1 --no-cache || echo 'Psalm not installed, skipping...'", - "phpstan": "./vendor/bin/phpstan analyse --memory-limit=1G || echo 'PHPStan not installed, skipping...'", - "test:unit": "./vendor/bin/phpunit --colors=always || echo 'Tests require Nextcloud environment, skipping...'", - "test:all": "./vendor/bin/phpunit --colors=always || echo 'Tests require Nextcloud environment, skipping...'", + "psalm": "./vendor/bin/psalm --threads=1 --no-cache", + "phpstan": "./vendor/bin/phpstan analyse --memory-limit=1G", + "test:unit": "./vendor/bin/phpunit --colors=always --no-coverage", + "test:all": "./vendor/bin/phpunit --colors=always --no-coverage", "check": "E=0; for CMD in lint phpcs psalm test:unit; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", "check:full": "E=0; for CMD in lint phpcs psalm phpstan test:all; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", - "check:strict": "E=0; for CMD in lint phpcs phpmd psalm phpstan test:all; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", + "gates": "hydra-gates --app-dir .", + "gates:full": "hydra-gates --app-dir . --full", + "check:strict": "E=0; G=0; for CMD in lint phpcs phpmd psalm phpstan test:all gates; do echo; echo \"=== $CMD ===\"; composer $CMD; R=$?; if [ $R -ne 0 ]; then E=1; if [ \"$CMD\" = gates ]; then G=$R; fi; fi; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED — read the gate COVERAGE block above before treating this as complete.\"; else echo \"SOME CHECKS FAILED (see above)\"; if [ $G -eq 99 ]; then echo \" gates: COULD NOT RUN (exit 99) — a configuration error, NOT a clean run. Nothing was gated.\"; elif [ $G -ne 0 ]; then echo \" gates: $G gate(s) failed — run 'composer gates' directly; its exit code is the failure COUNT.\"; fi; fi; exit $E", "fix": [ "@cs:fix" ], diff --git a/composer.lock b/composer.lock index d47aa1b42..7c64f8a1d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e22f033670a4dfa25bad1c7ebed2dd4c", + "content-hash": "dd14a5bb9499a702283c257023f27736", "packages": [], "packages-dev": [ { @@ -464,6 +464,66 @@ ], "time": "2024-05-06T16:37:16+00:00" }, + { + "name": "conduction/hydra-gates", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/ConductionNL/.github.git", + "reference": "f4d97563dec05b9296b18f9ab986d50ba9ce90e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ConductionNL/.github/zipball/f4d97563dec05b9296b18f9ab986d50ba9ce90e9", + "reference": "f4d97563dec05b9296b18f9ab986d50ba9ce90e9", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "bin": [ + "hydra-gates/bin/hydra-gates" + ], + "type": "library", + "extra": { + "hydra-gates": { + "runner": "hydra-gates/scripts/run-hydra-gates.sh", + "helpers": "hydra-gates/scripts/lib", + "schemas": "hydra-gates/scripts/schemas" + } + }, + "scripts": { + "gates": [ + "hydra-gates/bin/hydra-gates" + ], + "test:package": [ + "bash hydra-gates/tests/test-hydra-gates-bin.sh" + ] + }, + "license": [ + "EUPL-1.2" + ], + "authors": [ + { + "name": "Conduction", + "homepage": "https://conduction.nl" + } + ], + "description": "Hydra's mechanical quality gates, packaged so any repo can run them against its own diff. The exit code is the failure COUNT.", + "homepage": "https://github.com/ConductionNL/.github/tree/main/hydra-gates", + "keywords": [ + "conduction", + "gates", + "nextcloud", + "quality", + "static-analysis" + ], + "support": { + "issues": "https://github.com/ConductionNL/.github/issues", + "docs": "https://github.com/ConductionNL/.github/blob/main/hydra-gates/README.md" + }, + "time": "2026-08-03T19:02:26+00:00" + }, { "name": "consolidation/annotated-command", "version": "4.10.4", @@ -3807,18 +3867,19 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "e19b0f27b204991af95a5fefad00630bc3e376ae" + "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e19b0f27b204991af95a5fefad00630bc3e376ae", - "reference": "e19b0f27b204991af95a5fefad00630bc3e376ae", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3c9ad688ad8826203588ec49363f73f4deb590c1", + "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", "adaptcms/adaptcms": "<=1.3", - "admidio/admidio": "<=5.0.6", + "adawolfa/isdoc": "<1.4.3|>=1.5,<1.5.1|>=1.6,<1.6.1", + "admidio/admidio": "<=5.0.11", "adodb/adodb-php": "<=5.22.9", "aheinze/cockpit": "<2.2", "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", @@ -3829,12 +3890,14 @@ "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", "aimeos/aimeos-laravel": "==2021.10", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "aimeos/pagible": "<0.10.4", "airesvsg/acf-to-rest-api": "<=3.1", "akaunting/akaunting": "<2.1.13", "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", "alextselegidis/easyappointments": "<=1.5.2", "alexusmai/laravel-file-manager": "<=3.3.1", "algolia/algoliasearch-magento-2": "<=3.16.1|>=3.17.0.0-beta1,<=3.17.1", + "almirhodzic/nova-toggle-5": "<1.3", "alt-design/alt-redirect": "<1.6.4", "altcha-org/altcha": "<1.3.1", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", @@ -3850,8 +3913,10 @@ "aoe/restler": "<1.7.1", "apache-solr-for-typo3/solr": "<2.8.3", "apereo/phpcas": "<1.6", - "api-platform/core": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", + "api-platform/core": "<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8", "api-platform/graphql": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", + "api-platform/hal": ">=4,<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8", + "api-platform/json-api": ">=4,<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8", "appwrite/server-ce": "<=1.2.1", "arc/web": "<3", "area17/twill": "<1.2.5|>=2,<2.5.3", @@ -3862,29 +3927,28 @@ "athlon1600/youtube-downloader": "<=4", "aureuserp/aureuserp": "<1.3.0.0-beta1", "austintoddj/canvas": "<=3.4.2", - "auth0/auth0-php": ">=3.3,<8.18", - "auth0/login": "<7.20", - "auth0/symfony": "<=5.5", - "auth0/wordpress": "<=5.4", - "automad/automad": "<2.0.0.0-alpha5", + "auth0/auth0-php": ">=3.3,<=8.18", + "auth0/login": "<=7.20", + "auth0/symfony": "<=5.8", + "auth0/wordpress": "<=5.5", + "automad/automad": "<=2.0.0.0-beta27", "automattic/jetpack": "<9.8", - "avideo/avideo": "<=26", "awesome-support/awesome-support": "<=6.0.7", - "aws/aws-sdk-php": "<3.368", + "aws/aws-sdk-php": "<=3.371.3", "ayacoo/redirect-tab": "<2.1.2|>=3,<3.1.7|>=4,<4.0.5", - "azuracast/azuracast": "<=0.23.3", + "azuracast/azuracast": "<=0.23.5", "b13/seo_basics": "<0.8.2", "backdrop/backdrop": "<=1.32", - "backpack/crud": "<3.4.9", + "backpack/crud": "<4.0.63|>=4.1,<4.1.69|>=5,<5.0.13", "backpack/filemanager": "<2.0.2|>=3,<3.0.9", "bacula-web/bacula-web": "<9.7.1", "badaso/core": "<=2.9.11", - "bagisto/bagisto": "<2.3.10", + "bagisto/bagisto": "<=2.3.15", "barrelstrength/sprout-base-email": "<1.2.7", "barrelstrength/sprout-forms": "<3.9", "barryvdh/laravel-translation-manager": "<0.6.8", "barzahlen/barzahlen-php": "<2.0.1", - "baserproject/basercms": "<=5.1.1", + "baserproject/basercms": "<=5.2.2", "bassjobsen/bootstrap-3-typeahead": ">4.0.2", "bbpress/bbpress": "<2.6.5", "bcit-ci/codeigniter": "<3.1.3", @@ -3892,6 +3956,7 @@ "bedita/bedita": "<4", "bednee/cooluri": "<1.0.30", "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billabear/billabear": "<=2025.01.03", "billz/raspap-webgui": "<3.3.6", "binarytorch/larecipe": "<2.8.1", "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", @@ -3912,13 +3977,14 @@ "bytefury/crater": "<6.0.2", "cachethq/cachet": "<2.5.1", "cadmium-org/cadmium-cms": "<=0.4.9", - "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|>=5.2.10,<5.2.12|==5.3", + "cakephp/authentication": "<3.3.6|>=4,<4.1.1", + "cakephp/cakephp": "<4.5.11|>=4.6,<4.6.4|>=5,<5.1.7|>=5.2,<5.2.13|>=5.3,<5.3.6", "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", "cardgate/magento2": "<2.0.33", "cardgate/woocommerce": "<=3.1.15", - "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cart2quote/module-quotation": ">=4.1.6,<4.4.6|>=5,<5.4.4", "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", - "cartalyst/sentry": "<=2.1.6", + "cartalyst/sentry": "<2.1.7", "catfan/medoo": "<1.7.5", "causal/oidc": "<4", "cecil/cecil": "<7.47.1", @@ -3927,41 +3993,42 @@ "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", "chrome-php/chrome": "<1.14", - "ci4-cms-erp/ci4ms": "<0.28.5", + "ci4-cms-erp/ci4ms": "<=0.31.8", "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", "ckeditor/ckeditor": "<4.25", "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", "co-stack/fal_sftp": "<0.2.6", - "cockpit-hq/cockpit": "<2.13.5", - "code16/sharp": "<9.11.1", + "cockpit-hq/cockpit": "<=2.14", + "code16/sharp": "<9.22.3", "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.10", - "codeigniter4/framework": "<4.6.2", + "codeigniter4/framework": "<4.7.2", "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", "codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5", "commerceteam/commerce": ">=0.9.6,<0.9.9", "components/jquery": ">=1.0.3,<3.5", - "composer/composer": "<1.10.27|>=2,<2.2.26|>=2.3,<2.9.3", - "concrete5/concrete5": "<9.4.8", + "composer/composer": "<2.2.29|>=2.3,<2.10.2", + "concrete5/concrete5": "<9.5.2", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", - "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.13.56|>=5,<5.3.38|>=5.4.0.0-RC1-dev,<5.6.1", + "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<5.3.48|>=5.4,<5.7.9", "contao/core": "<3.5.39", - "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", + "contao/core-bundle": "<5.3.48|>=5.4,<5.7.9", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", - "coreshop/core-shop": "<4.1.9", + "coreshop/core-shop": "<4.1.9|==5", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", + "cotonti/cotonti": "<=1", "couleurcitron/tarteaucitron-wp": "<0.3", "cpsit/typo3-mailqueue": "<0.4.5|>=0.5,<0.5.2", "craftcms/aws-s3": ">=2.0.2,<=2.2.4", "craftcms/azure-blob": ">=2.0.0.0-beta1,<=2.1", - "craftcms/cms": "<=4.17.5|>=5,<=5.9.11", - "craftcms/commerce": ">=4,<4.11|>=5,<5.6", + "craftcms/cms": "<4.18|>=5,<5.10", + "craftcms/commerce": ">=4,<=4.11.1|>=5,<=5.6.4", "craftcms/composer": ">=4.0.0.0-RC1-dev,<=4.10|>=5.0.0.0-RC1-dev,<=5.5.1", "craftcms/craft": ">=3.5,<=4.16.17|>=5.0.0.0-RC1-dev,<=5.8.21", "craftcms/google-cloud": ">=2.0.0.0-beta1,<=2.2", @@ -3979,11 +4046,12 @@ "david-garcia/phpwhois": "<=4.3.1", "dbrisinajumi/d2files": "<1", "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta", + "dedoc/scramble": ">=0.13.2,<0.13.22", "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", "dev-lancer/minecraft-motd-parser": "<=1.0.5", - "devcode-it/openstamanager": "<=2.9.8", + "devcode-it/openstamanager": "<=2.10.1", "devgroup/dotplant": "<2020.09.14-dev", "digimix/wp-svg-upload": "<=1", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", @@ -4000,8 +4068,8 @@ "doctrine/mongodb-odm": "<1.0.2", "doctrine/mongodb-odm-bundle": "<3.0.1", "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<21.0.3", - "dompdf/dompdf": "<2.0.4", + "dolibarr/dolibarr": "<=23.0.2", + "dompdf/dompdf": "<3.1.6", "doublethreedigital/guest-entries": "<3.1.2", "dreamfactory/df-core": "<1.0.4", "drupal-pattern-lab/unified-twig-extensions": "<=0.1", @@ -4015,7 +4083,7 @@ "drupal/commerce_alphabank_redirect": "<1.0.3", "drupal/commerce_eurobank_redirect": "<2.1.1", "drupal/config_split": "<1.10|>=2,<2.0.2", - "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.4.9|>=10.5,<10.5.6|>=11,<11.1.9|>=11.2,<11.2.8", + "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.5.10|>=10.6,<10.6.9|>=11,<11.2.12|>=11.3,<11.3.10", "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", "drupal/currency": "<3.5", "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", @@ -4042,10 +4110,11 @@ "drupal/umami_analytics": "<1.0.1", "duncanmcclean/guest-entries": "<3.1.2", "dweeves/magmi": "<=0.7.24", + "easycorp/easyadmin-bundle": ">=4,<4.29.10|>=5,<5.0.13", "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.3.1", "ecodev/newsletter": "<=4", "ectouch/ectouch": "<=2.7.2", - "egroupware/egroupware": "<23.1.20260113|>=26.0.20251208,<26.0.20260113", + "egroupware/egroupware": "<23.1.20260601|>=26.0.20251208,<26.5.20260507", "elefant/cms": "<2.0.7", "elgg/elgg": "<3.3.24|>=4,<4.0.5", "elijaa/phpmemcacheadmin": "<=1.3", @@ -4057,6 +4126,7 @@ "erusev/parsedown": "<1.7.2", "ether/logs": "<3.0.4", "evolutioncms/evolution": "<=3.2.3", + "evoweb/sf-register": "<13.2.4|>=14,<14.0.2", "exceedone/exment": "<4.4.3|>=5,<5.0.3", "exceedone/laravel-admin": "<2.2.3|==3", "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", @@ -4079,15 +4149,16 @@ "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<2025.81", + "facturascripts/facturascripts": "<=2026.2", "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", "fenom/fenom": "<=2.12.1", - "filament/actions": ">=3.2,<3.2.123", - "filament/filament": ">=4,<4.3.1", - "filament/infolists": ">=3,<3.2.115", - "filament/tables": ">=3,<3.2.115|>=4,<4.8.5|>=5,<5.3.5", + "filament/actions": ">=3.2,<3.2.123|>=4,<=4.11.3|>=5,<=5.6.3", + "filament/filament": ">=3,<=3.3.51|>=4,<4.11.5|>=5,<5.6.5", + "filament/forms": ">=3,<=3.3.52", + "filament/infolists": ">=3,<3.2.115|>=4,<=4.11.4|>=5,<=5.6.4", + "filament/tables": ">=3,<=3.3.50|>=4,<=4.11.4|>=5,<=5.6.4", "filegator/filegator": "<7.8", "filp/whoops": "<2.1.13", "fineuploader/php-traditional-server": "<=1.2.2", @@ -4095,13 +4166,14 @@ "fisharebest/webtrees": "<=2.1.18", "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6", - "flarum/core": "<1.8.10", + "flarum/core": "<=1.8.15|>=2.0.0.0-beta1,<=2.0.0.0-beta8", "flarum/flarum": "<0.1.0.0-beta8", "flarum/framework": "<1.8.10", "flarum/mentions": "<1.6.3", "flarum/nicknames": "<1.8.3", "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", "flarum/tags": "<=0.1.0.0-beta13", + "flightphp/core": "<3.18.1", "floriangaerber/magnesium": "<0.3.1", "fluidtypo3/vhs": "<5.1.1", "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", @@ -4120,19 +4192,22 @@ "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "friendsoftypo3/tt-address": "<8.1.2|>=9,<9.1.1|>=10,<10.0.1", "froala/wysiwyg-editor": "<=4.3", "frosh/adminer-platform": "<2.2.1", - "froxlor/froxlor": "<=2.3.3", + "froxlor/froxlor": "<2.3.7", "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=7.1.0.0-RC4", + "funadmin/funadmin": "<=7.1.0.0-RC6", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "georgringer/news": "<1.3.3", + "georgringer/news": "<10.0.4|>=11,<11.4.4|>=12,<12.3.2|>=13,<13.0.2|>=14,<14.0.3", "geshi/geshi": "<=1.0.9.1", "getformwork/formwork": "<=2.3.3", - "getgrav/grav": "<1.11.0.0-beta1", - "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<=5.2.1", + "getgrav/grav": "<=2.0.0.0-RC8", + "getgrav/grav-plugin-api": "<1.0.0.0-beta15", + "getgrav/grav-plugin-form": "<9.1", + "getkirby/cms": "<=4.9.3|>=5,<=5.4.3", "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", @@ -4141,16 +4216,18 @@ "globalpayments/php-sdk": "<2", "goalgorilla/open_social": "<12.3.11|>=12.4,<12.4.10|>=13.0.0.0-alpha1,<13.0.0.0-alpha11", "gogentooss/samlbase": "<1.2.7", - "google/protobuf": "<3.4", + "goodoneuz/pay-uz": "<=2.2.24", + "google/protobuf": "<4.33.6", "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", "gp247/core": "<1.1.24", "gree/jose": "<2.2.1", "gregwar/rst": "<1.0.3", - "grumpydictator/firefly-iii": "<6.1.17|>=6.4.23,<=6.5", + "grumpydictator/firefly-iii": "<=6.6.2", "gugoan/economizzer": "<=0.9.0.0-beta1", - "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/guzzle": "<7.15.1", + "guzzlehttp/guzzle-services": "<1.5.4", "guzzlehttp/oauth-subscriber": "<0.8.1", - "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "guzzlehttp/psr7": "<2.12.3", "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", "handcraftedinthealps/goodby-csv": "<1.4.3", "harvesthq/chosen": "<1.8.7", @@ -4161,6 +4238,7 @@ "hjue/justwriting": "<=1", "hov/jobfair": "<1.0.13|>=2,<2.0.2", "httpsoft/http-message": "<1.0.12", + "hybridauth/hybridauth": "<=3.12.2", "hyn/multi-tenant": ">=5.6,<5.7.2", "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.25|>=5,<5.0.3", "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21", @@ -4178,6 +4256,7 @@ "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/mail": ">=9,<12.60|>=13,<13.10", "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", "imdbphp/imdbphp": "<=5.1.1", "impresscms/impresscms": "<=1.4.5", @@ -4189,10 +4268,13 @@ "innologi/typo3-appointments": "<2.0.6", "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", - "ipl/web": "<0.10.1", + "intercom/intercom-php": "==5.0.2", + "invoiceninja/invoiceninja": "<5.13.4", + "ipl/web": "<=0.10.2|>=0.11,<=0.13", "islandora/crayfish": "<4.1", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", + "j0k3r/graby": "<=2.5", "jackalope/jackalope-doctrine-dbal": "<1.7.4", "jambagecom/div2007": "<0.10.2", "james-heinrich/getid3": "<1.9.21", @@ -4200,6 +4282,8 @@ "jasig/phpcas": "<1.3.3", "jbartels/wec-map": "<3.0.3", "jcbrand/converse.js": "<3.3.3", + "jleehr/canto-saas-api": "<=2", + "joedolson/my-calendar": "<3.7.7", "joelbutcher/socialstream": "<5.6|>=6,<6.2", "johnbillion/query-monitor": "<3.20.4", "johnbillion/wp-crontrol": "<1.16.2|>=1.17,<1.19.2", @@ -4219,29 +4303,32 @@ "juzaweb/cms": "<=3.4.2", "jweiland/events2": "<8.3.8|>=9,<9.0.6", "jweiland/kk-downloader": "<1.2.2", + "kantorge/yaffa": "<=2", "kazist/phpwhois": "<=4.2.6", "kelvinmo/simplejwt": "<=1.1", "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", - "khodakhah/nodcms": "<=3", - "kimai/kimai": "<=2.50", + "khodakhah/nodcms": "<=3.4.1", + "kimai/kimai": "<2.59", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", - "knplabs/knp-snappy": "<=1.4.2", + "knplabs/knp-snappy": "<=1.7", "kohana/core": "<3.3.3", "koillection/koillection": "<1.6.12", - "krayin/laravel-crm": "<=1.3", + "krayin/laravel-crm": "<=2.2", "kreait/firebase-php": ">=3.2,<3.8.1", "kumbiaphp/kumbiapp": "<=1.1.1", "la-haute-societe/tcpdf": "<6.2.22", + "laktak/hjson": "<2.3", "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", "laminas/laminas-http": "<2.14.2", "lara-zeus/artemis": ">=1,<=1.0.6", "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", "laravel/fortify": "<1.11.1", - "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1", + "laravel/framework": "<12.61.1|>=13,<13.12", "laravel/laravel": ">=5.4,<5.4.22", + "laravel/passport": ">=13,<13.7.1", "laravel/pulse": "<1.3.1", "laravel/reverb": "<1.7", "laravel/socialite": ">=1,<2.0.10", @@ -4255,10 +4342,10 @@ "leantime/leantime": "<3.3", "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", "libreform/libreform": ">=2,<=2.0.8", - "librenms/librenms": "<26.2", + "librenms/librenms": "<26.3", "liftkit/database": "<2.13.2", "lightsaml/lightsaml": "<1.3.5", - "limesurvey/limesurvey": "<6.15.4", + "limesurvey/limesurvey": "<=7.0.0.0-beta1", "livehelperchat/livehelperchat": "<=3.91", "livewire-filemanager/filemanager": "<=1.0.4", "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.6.4", @@ -4281,20 +4368,23 @@ "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", "manogi/nova-tiptap": "<=3.2.6", - "mantisbt/mantisbt": "<2.27.2", + "mantisbt/mantisbt": "<=2.28.3", "marcwillmann/turn": "<0.3.3", + "markhuot/craftql": "<=1.3.7", "marshmallow/nova-tiptap": "<5.7", "matomo/matomo": "<1.11", "matyhtf/framework": "<3.0.6", - "mautic/core": "<5.2.10|>=6,<6.0.8|>=7.0.0.0-alpha,<7.0.1", + "mautic/core": "<5.2.11|>=6,<6.0.9|>=7,<7.1.2", "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", "mautic/grapes-js-builder-bundle": ">=4,<4.4.18|>=5,<5.2.9|>=6,<6.0.7", "maximebf/debugbar": "<1.19", + "mckenziearts/livewire-markdown-editor": "<1.3", "mdanter/ecc": "<2", "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", "mediawiki/cargo": "<3.8.3", "mediawiki/core": "<1.39.5|==1.40", "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", + "mediawiki/maps": "<12.1.3", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "mehrwert/phpmyadmin": "<3.2", @@ -4312,7 +4402,10 @@ "mikehaertl/php-shellcommand": "<1.6.1", "mineadmin/mineadmin": "<=3.0.9", "miniorange/miniorange-saml": "<1.4.3", + "miraheze/ts-portal": "<=33", "mittwald/typo3_forum": "<1.2.1", + "mix/mix": ">=2,<=2.2.17", + "mmc/ceselector": "<3.0.3|>=4,<4.0.2|>=5,<5.0.1|>=6,<6.0.1", "mobiledetect/mobiledetectlib": "<2.8.32", "modx/revolution": "<=3.1", "mojo42/jirafeau": "<4.4", @@ -4325,6 +4418,7 @@ "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", "mpdf/mpdf": "<=7.1.7", + "mtdowling/jmespath.php": "<2.9.1", "munkireport/comment": "<4", "munkireport/managedinstalls": "<2.6", "munkireport/munki_facts": "<1.5", @@ -4332,6 +4426,7 @@ "munkireport/softwareupdate": "<1.6", "mustache/mustache": ">=2,<2.14.1", "mwdelaney/wp-enable-svg": "<=0.2", + "nabeel/phpvms": "<7.0.6", "namshi/jose": "<2.2", "nasirkhan/laravel-starter": "<11.11", "nategood/httpful": "<1", @@ -4351,20 +4446,20 @@ "nilsteampassnet/teampass": "<3.1.3.1-dev", "nitsan/ns-backup": "<13.0.1", "nonfiction/nterchange": "<4.1.1", - "notrinos/notrinos-erp": "<=0.7", + "notrinos/notrinos-erp": "<=1", "noumo/easyii": "<=0.9", "novaksolutions/infusionsoft-php-sdk": "<1", "novosga/novosga": "<=2.2.12", - "nukeviet/nukeviet": "<4.5.02", + "nukeviet/nukeviet": "<4.6.00", "nyholm/psr7": "<1.6.1", "nystudio107/craft-seomatic": "<3.4.12", "nzedb/nzedb": "<0.8", "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", "october/backend": "<1.1.2", "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", - "october/october": "<3.7.5", - "october/rain": "<1.0.472|>=1.1,<1.1.2", - "october/system": "<=3.7.12|>=4,<=4.0.11", + "october/october": "<3.7.14|>=4,<4.1.10", + "october/rain": "<=3.7.13|>=4,<=4.1.9", + "october/system": "<3.7.16|>=4,<4.1.16", "oliverklee/phpunit": "<3.5.15", "omeka/omeka-s": "<4.0.3", "onelogin/php-saml": "<2.21.1|>=3,<3.8.1|>=4,<4.3.1", @@ -4372,9 +4467,9 @@ "open-web-analytics/open-web-analytics": "<1.8.1", "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.16.1", + "openmage/magento-lts": "<=20.17", "opensolutions/vimbadmin": "<=3.0.15", - "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7", + "opensource-workshop/connect-cms": "<1.41.1|>=2,<2.41.1", "orchid/platform": ">=8,<14.43", "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", @@ -4383,8 +4478,10 @@ "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", - "oxid-esales/oxideshop-ce": "<=7.0.5", + "oxid-esales/oxideshop-ce": "<4.5|>=6,<6.14.4", + "oxid-esales/oxideshop-metapackage-ce": ">=6,<6.5.5", "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", + "oxid-esales/smarty-component": "<1.0.1", "packbackbooks/lti-1-3-php-library": "<5", "padraic/humbug_get_contents": "<1.1.2", "pagarme/pagarme-php": "<3", @@ -4393,6 +4490,7 @@ "paragonie/random_compat": "<2", "paragonie/sodium_compat": "<1.24|>=2,<2.5", "passbolt/passbolt_api": "<4.6.2", + "paymenter/paymenter": "<=1.5.4", "paypal/adaptivepayments-sdk-php": "<=3.9.2", "paypal/invoice-sdk-php": "<=3.9", "paypal/merchant-sdk-php": "<3.12", @@ -4405,70 +4503,76 @@ "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", "personnummer/personnummer": "<3.0.2", "ph7software/ph7builder": "<=17.9.1", - "phanan/koel": "<5.1.4", + "phanan/koel": "<=9.7", + "pheditor/pheditor": "<2.0.8", "phenx/php-svg-lib": "<0.5.2", "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", "php-mod/curl": "<2.3.2", - "phpbb/phpbb": "<3.3.11", + "php-standard-library/h2": ">=6.1,<6.1.2|>=6.2,<6.2.1", + "php-standard-library/php-standard-library": ">=6.1,<6.1.2|>=6.2,<6.2.1", + "phpbb/phpbb": "<3.3.16|==4.0.0.0-alpha1", "phpems/phpems": ">=6,<=6.1.3", "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<5.2.2", - "phpmyfaq/phpmyfaq": "<=4.0.16", + "phpmyfaq/phpmyfaq": "<4.1.4", "phpoffice/common": "<0.2.9", "phpoffice/math": "<=0.2", "phpoffice/phpexcel": "<=1.8.2", - "phpoffice/phpspreadsheet": "<1.30|>=2,<2.1.12|>=2.2,<2.4|>=3,<3.10|>=4,<5", + "phpoffice/phpspreadsheet": "<=1.30.5|>=2,<=2.1.17|>=2.2,<=2.4.6|>=3,<=3.10.6|>=4,<=5.8", "phppgadmin/phppgadmin": "<=7.13", - "phpseclib/phpseclib": "<=2.0.51|>=3,<=3.0.49", + "phpseclib/phpseclib": "<=2.0.54|>=3,<=3.0.53", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", - "phpunit/phpunit": "<8.5.52|>=9,<9.6.33|>=10,<10.5.62|>=11,<11.5.50|>=12,<12.5.8", + "phpunit/phpunit": "<8.5.52|>=9,<9.6.33|>=10,<10.5.62|>=11,<11.5.50|>=12,<12.5.8|>=12.5.21,<12.5.22|>=13.1.5,<13.1.6", "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", "phraseanet/phraseanet": "==4.0.3", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<=1.7.15|>=2.0.0.0-RC1-dev,<=2.2.2", + "pimcore/admin-ui-classic-bundle": "<1.7.18|>=2.0.0.0-RC1-dev,<=2.3.5", "pimcore/customer-management-framework-bundle": "<4.2.1", "pimcore/data-hub": "<1.2.4", "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", "pimcore/demo": "<10.3", "pimcore/ecommerce-framework-bundle": "<1.0.10", "pimcore/perspective-editor": "<1.5.1", - "pimcore/pimcore": "<=11.5.14.1|>=12,<12.3.3", + "pimcore/pimcore": "<=12.3.8|>=2026.1,<2026.1.3", "pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1", "piwik/piwik": "<1.11", "pixelfed/pixelfed": "<0.12.5", "plotly/plotly.js": "<2.25.2", "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<5.32.1", + "pocketmine/pocketmine-mp": "<5.42.1", "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", + "pontedilana/php-weasyprint": "<=2.5.1", + "poweradmin/poweradmin": "<4.2.5|>=4.3,<4.3.4", "pressbooks/pressbooks": "<5.18", "prestashop/autoupgrade": ">=4,<4.10.1", "prestashop/blockreassurance": "<=5.1.3", "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.2.4|>=9.0.0.0-alpha1,<9.0.3", + "prestashop/prestashop": "<8.2.6|>=9,<9.1.1", "prestashop/productcomments": "<5.0.2", - "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", + "prestashop/ps_checkout": "<5.3", "prestashop/ps_contactinfo": "<=3.3.2", "prestashop/ps_emailsubscription": "<2.6.1", - "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_facetedsearch": "<4.0.4", "prestashop/ps_linklist": "<3.1", "privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.3", - "processwire/processwire": "<=3.0.246", - "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", - "propel/propel1": ">=1,<=1.7.1", + "processwire/processwire": "<=3.0.255", + "propel/propel": ">=2.0.0.0-alpha1,<2.0.0.0-alpha8", + "propel/propel1": ">=1,<1.7.2", "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", - "pterodactyl/panel": "<1.12.1", + "pterodactyl/panel": "<=1.12.4", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", "punktde/pt_extbase": "<1.5.1", "pusher/pusher-php-server": "<2.2.1", + "putyourlightson/craft-sprig": ">=2,<2.15.2|>=3,<3.7.2", "pwweb/laravel-core": "<=0.3.6.0-beta", "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3", "pyrocms/pyrocms": "<=3.9.1", @@ -4477,48 +4581,54 @@ "rainlab/blog-plugin": "<1.4.1", "rainlab/debugbar-plugin": "<3.1", "rainlab/user-plugin": "<=1.4.5", - "ralffreit/mfa-email": "<=2", + "ralffreit/mfa-email": "<1.0.7|==2", "rankmath/seo-by-rank-math": "<=1.0.95", "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<=5.20.1", + "redaxo/source": "<5.21.1", "remdex/livehelperchat": "<4.29", "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1", "reportico-web/reportico": "<=8.1", - "rhukster/dom-sanitizer": "<1.0.7", + "rhukster/dom-sanitizer": "<1.0.10", "rmccue/requests": ">=1.6,<1.8", + "roadiz/documents": "<2.3.42|>=2.4,<2.5.44|>=2.6,<2.6.28|>=2.7,<2.7.9", + "roadiz/openid": "<2.3.43|>=2.5,<2.5.45|>=2.6,<2.6.31|>=2.7,<2.7.18", "robrichards/xmlseclibs": "<3.1.5", "roots/soil": "<4.1", - "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11", + "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11|>=1.7.0.0-beta,<1.7.0.0-RC5-dev", "rudloff/alltube": "<3.0.3", "rudloff/rtmpdump-bin": "<=2.3.1", "s-cart/core": "<=9.0.5", "s-cart/s-cart": "<6.9", + "s9y/serendipity": "<2.6", "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", + "saloonphp/saloon": "<4", "samwilson/unlinked-wikibase": "<1.42", "scheb/two-factor-bundle": "<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", - "setasign/fpdi": "<2.6.4", + "setasign/fpdi": "<2.6.7", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<1.2.1", - "shopware/core": "<6.6.10.15-dev|>=6.7,<6.7.8.1-dev", - "shopware/platform": "<6.6.10.15-dev|>=6.7,<6.7.8.1-dev", + "shopper/cart": "<2.8", + "shopper/framework": "<2.8", + "shopware/core": "<6.6.10.18-dev|>=6.7,<6.7.10.1-dev", + "shopware/platform": "<6.6.10.18-dev|>=6.7,<6.7.10.1-dev", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.17|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev", + "shopware/shopware": "<=6.3.5.2|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev", "shopware/storefront": "<6.6.10.10-dev|>=6.7,<6.7.5.1-dev", "shopxo/shopxo": "<=6.4", - "showdoc/showdoc": "<2.10.4", + "showdoc/showdoc": "<3.8.1", "shuchkin/simplexlsx": ">=1.0.12,<1.1.13", "silverstripe-australia/advancedreports": ">=1,<=2", "silverstripe/admin": "<1.13.19|>=2,<2.1.8", - "silverstripe/assets": ">=1,<1.11.1", - "silverstripe/cms": "<4.11.3", + "silverstripe/assets": "<2.4.5|>=3,<3.1.3", + "silverstripe/cms": "<6.2.1", "silverstripe/comments": ">=1.3,<3.1.1", - "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<5.3.23", + "silverstripe/forum": "<0.6.2|>=0.7,<0.7.4", + "silverstripe/framework": "<6.2.2", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/recipe-cms": ">=4.5,<4.5.3", @@ -4528,13 +4638,15 @@ "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", "silverstripe/subsites": ">=2,<2.6.1", "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", - "silverstripe/userforms": "<3|>=5,<5.4.2", + "silverstripe/userforms": "<6.4.9|>=7,<7.0.7|>=7.1,<7.1.1", + "silverstripe/versioned": "<3.2.1", "silverstripe/versioned-admin": ">=1,<1.11.1", "simogeo/filemanager": "<=2.5", "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19", - "simplesamlphp/saml2-legacy": "<=4.16.15", - "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/saml2": "<=4.20.2|>=5,<5.0.6|>=6,<6.2.1", + "simplesamlphp/saml2-legacy": "<=4.20.2", + "simplesamlphp/simplesamlphp": "<=2.4.6|>=2.5,<=2.5.1", + "simplesamlphp/simplesamlphp-module-casserver": "<=7.0.2", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "simplesamlphp/simplesamlphp-module-openid": "<1", "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", @@ -4546,19 +4658,23 @@ "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", "sjbr/static-info-tables": "<2.3.1", "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", - "slim/slim": "<2.6", + "slim/slim": "<2.6|>=4.4,<=4.15.1", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<8.3.7", + "snipe/snipe-it": "<=8.6.1", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", + "solidinvoice/solidinvoice": "<=2.3.15", "solspace/craft-freeform": "<4.1.29|>=5,<=5.14.6", "soosyze/soosyze": "<=2", "spatie/browsershot": "<5.0.5", "spatie/image-optimizer": "<1.7.3", + "spatie/laravel-medialibrary": "<11.23", + "spatie/schema-org": ">=3.23.1,<3.23.2|>=4,<4.0.2", "spencer14420/sp-php-email-handler": "<1", "spipu/html2pdf": "<5.2.8", "spiral/roadrunner": "<2025.1", + "spomky-labs/otphp": "<11.4.3", "spoon/library": "<1.4.1", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", @@ -4567,14 +4683,14 @@ "starcitizentools/short-description": ">=4,<4.0.1", "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<5.73.14|>=6,<6.7.1", + "statamic/cms": "<5.74|>=6,<6.20.3", "stormpath/sdk": "<9.9.99", - "studio-42/elfinder": "<=2.1.64", + "studio-42/elfinder": "<=2.1.67", "studiomitte/friendlycaptcha": "<0.1.4", "subhh/libconnect": "<7.0.8|>=8,<8.1", "sukohi/surpass": "<1", "sulu/form-bundle": ">=2,<2.5.3", - "sulu/sulu": "<1.6.44|>=2,<2.5.25|>=2.6,<2.6.9|>=3.0.0.0-alpha1,<3.0.0.0-alpha3", + "sulu/sulu": "<=2.6.22|>=3,<=3.0.5", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", "svewap/a21glossary": "<=0.4.10", @@ -4584,50 +4700,65 @@ "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", "sylius/grid-bundle": "<1.10.1", + "sylius/mollie-plugin": "<2.2.8|>=3,<3.2.4|>=3.3,<3.3.1", "sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2", "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.9.12|>=1.10,<1.10.16|>=1.11,<1.11.17|>=1.12,<=1.12.22|>=1.13,<=1.13.14|>=1.14,<=1.14.17|>=2,<=2.0.15|>=2.1,<=2.1.11|>=2.2,<=2.2.2", + "sylius/sylius": "<1.9.12|>=1.10,<1.10.16|>=1.11,<1.11.17|>=1.12,<=1.12.22|>=1.13,<=1.13.14|>=1.14,<=1.14.17|>=2,<2.0.18|>=2.1,<2.1.15|>=2.2,<2.2.6", + "symbiote/silverstripe-advancedworkflow": "<6.4.5|>=7,<7.1.3|>=7.2,<7.2.1", "symbiote/silverstripe-multivaluefield": ">=3,<3.1", "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", "symbiote/silverstripe-seed": "<6.0.3", "symbiote/silverstripe-versionedfiles": "<=2.0.3", "symfont/process": ">=0", - "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/cache": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/dom-crawler": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", - "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", - "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", - "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/html-sanitizer": ">=6.1,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", + "symfony/http-client": ">=4.3,<5.4.53|>=6,<6.4.15|>=7,<7.1.8", + "symfony/http-foundation": "<5.4.50|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6|>=7.4,<7.4.12|>=8,<8.0.12", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/json-path": ">=7.3,<7.4.12|>=8,<8.0.12", + "symfony/lox24-notifier": ">=7.1,<7.4.12|>=8,<8.0.12", + "symfony/mailer": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/mailjet-mailer": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/mailomat-mailer": ">=7.2,<7.4.13|>=8,<8.0.13", + "symfony/mailtrap-mailer": ">=7.2,<7.4.12|>=8,<8.0.12", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", - "symfony/mime": ">=4.3,<4.3.8", + "symfony/mime": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/monolog-bridge": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill": ">=1,<1.10|>=1.17.1,<1.38.1", + "symfony/polyfill-intl-idn": ">=1.17.1,<1.38.1", "symfony/polyfill-php55": ">=1,<1.10", "symfony/process": "<5.4.51|>=6,<6.4.33|>=7,<7.1.7|>=7.3,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/routing": ">=2,<2.0.19", - "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/routing": "<5.4.53|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", + "symfony/runtime": ">=5.3,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/security-http": "<5.4.53|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.51|>=6,<6.4.33|>=7,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", + "symfony/symfony": "<5.4.53|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", "symfony/translation": ">=2,<2.0.17", - "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", - "symfony/ux-autocomplete": "<2.11.2", - "symfony/ux-live-component": "<2.25.1", + "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8|>=6.4.24,<6.4.40", + "symfony/twilio-notifier": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/ux-autocomplete": "<2.36|>=3,<3.1", + "symfony/ux-icons": ">=2.17,<2.36.1|>=3,<3.2", + "symfony/ux-live-component": "<2.36|>=3,<3.1", + "symfony/ux-toolkit": ">=2.32,<2.36.1|>=3,<3.2", "symfony/ux-twig-component": "<2.25.1", "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", - "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4|>=7.2.9,<7.4.12|>=8,<8.0.12", "symfony/webhook": ">=6.3,<6.3.8", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symfony/yaml": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symphonycms/symphony-2": "<2.6.4", "t3/dce": "<0.11.5|>=2.2,<2.6.2", "t3g/svg-sanitizer": "<1.0.3", @@ -4638,45 +4769,50 @@ "tecnickcom/tcpdf": "<6.8", "terminal42/contao-tablelookupwizard": "<3.3.5", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.3", + "thelia/thelia": ">=2.0.0.0-beta1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<4.0.18|>=4.1.0.0-alpha,<=4.1.0.0-beta2", + "thorsten/phpmyfaq": "<4.1.4", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", - "tinymce/tinymce": "<7.2", + "tinymce/tinymce": "<7.9.3|>=8,<8.5.1", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", "tltneon/lgsl": "<7", "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "tomasnorre/crawler": "<11.0.13|>=12,<12.0.11", "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", "torrentpier/torrentpier": "<=2.8.8", - "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tpwd/ke_search": "<5.6.2|>=6,<6.6.1|>=7,<7.0.1", "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", - "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", - "typicms/core": "<16.1.7", + "twig/cssinliner-extra": "<3.26", + "twig/intl-extra": "<3.26", + "twig/markdown-extra": "<3.26", + "twig/twig": "<3.27", + "typicms/core": "<12.0.5|>=13,<13.0.9|>=14,<14.0.27|>=15,<15.0.29|>=16,<16.1.7", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-backend": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", - "typo3/cms-core": "<=8.7.56|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-core": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-felogin": ">=4.2,<4.2.3", - "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-filelist": ">=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", + "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1|>=8,<8.7.23|>=9,<9.5.4", + "typo3/cms-form": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.5", "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", - "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", "typo3/cms-lowlevel": ">=11,<=11.5.41", "typo3/cms-recordlist": ">=11,<11.5.48", - "typo3/cms-recycler": ">=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-recycler": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-redirects": ">=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", "typo3/cms-scheduler": ">=11,<=11.5.41", @@ -4684,7 +4820,7 @@ "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11", "typo3/cms-workspaces": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", + "typo3/html-sanitizer": "<2.3.2", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", @@ -4700,7 +4836,7 @@ "uvdesk/core-framework": "<=1.1.1", "vanilla/safecurl": "<0.9.2", "verbb/comments": "<1.5.5", - "verbb/formie": "<=2.1.43", + "verbb/formie": "<3.1.28", "verbb/image-resizer": "<2.0.9", "verbb/knock-knock": "<1.2.8", "verot/class.upload.php": "<=2.1.6", @@ -4714,15 +4850,20 @@ "wallabag/wallabag": "<2.6.11", "wanglelecc/laracms": "<=1.0.3", "wapplersystems/a21glossary": "<=0.4.10", - "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9|>=5.2,<5.2.4", - "web-auth/webauthn-lib": ">=4.5,<4.9|>=5.2,<5.2.4", - "web-auth/webauthn-symfony-bundle": ">=5.2,<5.2.4", + "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9|>=5.2,<5.2.4|>=5.3,<5.3.1", + "web-auth/webauthn-lib": ">=4.5,<5.3.5", + "web-auth/webauthn-symfony-bundle": "<5.3.4", "web-feet/coastercms": "==5.5", + "web-token/jwt-bundle": "<3.4.10|>=4,<4.0.7|>=4.1,<4.1.7", + "web-token/jwt-experimental": "<4.1.7", + "web-token/jwt-framework": "<4.1.7", + "web-token/jwt-library": "<3.4.10|>=4,<4.0.7|>=4.1,<4.1.7", "web-tp3/wec_map": "<3.0.3", "webbuilders-group/silverstripe-kapost-bridge": "<0.4", "webcoast/deferred-image-processing": "<1.0.2", "webklex/laravel-imap": "<5.3", "webklex/php-imap": "<5.3", + "webonyx/graphql-php": "<=15.32.2", "webpa/webpa": "<3.1.2", "webreinvent/vaahcms": "<=2.3.1", "wikibase/wikibase": "<=1.39.3", @@ -4734,24 +4875,27 @@ "winter/wn-system-module": "<1.2.4", "wintercms/winter": "<=1.2.3", "wireui/wireui": "<1.19.3|>=2,<2.1.3", + "wnx/laravel-backup-restore": "<=1.9.3", "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", "wp-cli/wp-cli": ">=0.12,<2.5", - "wp-graphql/wp-graphql": "<=1.14.5", + "wp-coding-standards/wpcs": ">=0.14.1,<3.4.1", + "wp-graphql/wp-graphql": "<=2.6", "wp-premium/gravityforms": "<2.4.21", "wpanel/wpanel4-cms": "<=4.3.1", "wpcloud/wp-stateless": "<3.2", "wpglobus/wpglobus": "<=1.9.6", "wpmetabox/meta-box": "<5.11.2", - "wwbn/avideo": "<=26", + "wwbn/avideo": "<=29", "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", - "yeswiki/yeswiki": "<=4.5.4", + "yansongda/pay": "<=3.7.19", + "yeswiki/yeswiki": "<4.6.6", "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": "<1.1.31", - "yiisoft/yii2": "<2.0.52", + "yiisoft/yii2": "<2.0.55", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-dev": "<=2.0.45", @@ -4841,7 +4985,7 @@ "type": "tidelift" } ], - "time": "2026-03-20T22:08:23+00:00" + "time": "2026-08-01T00:01:24+00:00" }, { "name": "sebastian/cli-parser", @@ -5866,16 +6010,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.5", + "version": "3.13.6", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/4c378e1a528ea066890fc2397cbdd2f94eb2fc91", + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91", "shasum": "" }, "require": { @@ -5941,7 +6085,7 @@ "type": "thanks_dev" } ], - "time": "2025-11-04T16:30:35+00:00" + "time": "2026-08-06T00:17:32+00:00" }, { "name": "symfony/config", diff --git a/docs/ai-copilot.md b/docs/ai-copilot.md new file mode 100644 index 000000000..5d3df705f --- /dev/null +++ b/docs/ai-copilot.md @@ -0,0 +1,97 @@ +--- +sidebar_position: 8 +description: The AI copilot turns a natural-language brief into a reviewable, approvable builder plan — the prompt-to-app path in OpenBuild. +--- + +# AI Copilot + +The AI copilot is OpenBuild's prompt-to-app surface: describe the app you +want in a sentence or two, review the concrete operations the AI proposes, +and approve before anything is created or changed. It never mutates your +app silently. + +## What it does + +- **In the creation wizard** — Step 1 offers a **Generate with AI** button. + Describe the app, review the proposed schemas, pages, and menu items, then + confirm to create the app and jump straight into it. +- **In the page designer** — a toolbar toggle opens a chat-style side panel + scoped to the app and version you're editing. Ask it to add a page, a + widget, or a menu item; it proposes the operations plus a before/after + manifest diff. Approve to apply, or discard — nothing happens until you + say so. + +## The plan / review / approve model + +1. **Plan** — your brief is sent to the configured AI provider with a + constrained prompt that only knows about the eight builder operations + OpenBuild's MCP tools already expose (create app, promote version, upsert + schema, upsert page, add widget, upsert menu item, list apps, get + manifest). The AI's reply is parsed into a plan: a short summary plus an + ordered list of steps. **Nothing is written during this step.** +2. **Review** — every step is validated against that operation's argument + schema, and cap-checked so no proposed change would blow past a + manifest's size or page/widget/menu-item limits. The predicted manifest + is shown as a diff. If validation fails, Approve stays disabled and you + see why. +3. **Approve** — only on your explicit action does OpenBuild execute the + plan. Execution runs through the exact same handler code the builder's + MCP tools use — the same permission checks, the same locking, the same + caps. There is no separate, less-checked path for AI-driven changes. + +## Atomicity guarantee + +An approved plan is applied step by step. If any step fails partway +through, OpenBuild restores every manifest it had touched to its +pre-plan snapshot, and deletes an application the plan itself created (so +you're never left with a half-built app you didn't ask for). This is +compensation-based, not a database transaction — OpenRegister has no +cross-object transactions — so the guarantee is precisely scoped: **a +failed plan leaves no plan-created state behind.** Every write still goes +through the same locked, validated handler path, so nothing is ever +silently corrupted; worst case is a visible, deletable draft you can +remove by hand. + +## Provider setup (admins) + +The copilot rides Nextcloud's built-in **Task Processing** API, so it +works with whatever text-generation provider you've configured for your +instance — a local model, an EU-hosted one, or one of the bundled +Nextcloud AI apps. OpenBuild never talks to a vendor directly and never +names a model. + +- Requires **Nextcloud 30 or newer** (Task Processing shipped in NC 30). +- Configure a `TextToText` provider under **Administration settings → Artificial + intelligence**. +- The AI Chat Companion (the free-form assistant available elsewhere in + Nextcloud) shares the same provider configuration but is a different, + independent surface — the copilot's deterministic plan/approve flow is + specific to OpenBuild. + +### Degradation without a provider + +When no provider is configured (or the server predates NC 30), the copilot +is simply absent: the wizard's "Generate with AI" button and the builder's +panel toggle are both hidden. Nextcloud administrators additionally see a +small hint in the wizard pointing at the AI provider settings; everyone +else sees no trace of the feature at all. + +## Permissions + +- **Editing an existing app**: you need an owner or editor role on that + app — the same bar as any other builder write. Nextcloud administrators + get the same audited bypass the builder's MCP tools already have. +- **Creating a new app**: any authenticated user can generate and confirm a + new app; you become its owner, exactly like the manual creation wizard. +- **Hybrid apps** (installed real apps OpenBuild layers customisation on + top of) are out of scope for the copilot entirely — it only edits virtual + apps built from scratch in OpenBuild. + +## What it will not do + +- It never applies a change without your explicit approval — there is no + "auto-apply" mode and no autonomous multi-turn agent loop. +- It cannot generate or run arbitrary code — every proposed step is one of + the fixed, allow-listed builder operations. +- It cannot touch an installed real app's manifest (hybrid apps are + rejected as a target). diff --git a/docs/automation-designer.md b/docs/automation-designer.md new file mode 100644 index 000000000..874b9e2c7 --- /dev/null +++ b/docs/automation-designer.md @@ -0,0 +1,137 @@ + + +# Automations + +The Automations page is the unified "when X happens, do Y" surface: one +citizen-developer composes a **trigger**, an optional **condition**, and one +or more **actions** without needing to know which of OpenBuild's four +declarative dialects actually executes it. Nothing new is invented — every +automation compiles to an existing primitive (the notifications dialect, +lifecycle actions, `manifest.schedules[]`, or the business-rules engine). + +An automation is one stored declarative object (schema `automation` on the +shared `openbuild` register): trigger + condition + actions + a `provenance` +block listing exactly which compiled artifacts it produced. List, edit, +enable/disable, dry-run and delete it as one unit from the Automations page +(`/automations`, reached from within an app's builder — it has no top-level +menu entry, mirroring the Business rules page). + +## What compiles to what + +| Trigger ↓ / Action → | Send notification | Run synchronization | Object-op | Webhook | Require approval | Generate document | +| --- | --- | --- | --- | --- | --- | --- | +| Object created / updated / deleted | ✅ notifications dialect entry | ⛔ v1.1 | ⛔ v1.1 | ⛔ v1.1 | ✅ OR `ApprovalChain` | ✅ owner-impersonated Docudesk call | +| Lifecycle transition | ✅ notifications dialect entry | ⛔ v1.1 | ✅ lifecycle `related-object-upsert` action | ✅ lifecycle `webhook-dispatch` action | ✅ OR `ApprovalChain` | ✅ owner-impersonated Docudesk call | +| Schedule | ⛔ v1.1 | ✅ `manifest.schedules[]` entry | ⛔ v1.1 | ⛔ v1.1 | ⛔ no bound object | ⛔ no bound object | +| Manual | ✅ rules backend | ⛔ v1.1 (no verified "run now" API — see below) | ✅ rules backend | ✅ rules backend | ⛔ no bound object | ⛔ no bound object | + +**Require approval** compiles to an OpenRegister `ApprovalChain` (one step, +group-only assignee) instantiated against the fired object's uuid at +trigger-fire time; on-approve/on-reject follow-up actions dispatch through +the same typed-action vocabulary as the top-level actions above. + +**Generate document** picks a Docudesk template and one or more output modes +(`attach` — writes the rendered document to Nextcloud Files and sets a +`{ "ref": "" }` reference on the object; `download-link` — a +short-lived, ~24h signed download URL; `notify` — a notification, must be +paired with `attach` and/or `download-link`). Compiles to no persisted +artifact (Docudesk's generate route is stateless) — a listener calls +Docudesk's existing `correspondence/generate` route at trigger-fire time, +impersonating the Application owner's session, never importing a Docudesk +PHP class. Disabled with a missing-app hint when Docudesk is not installed. +Both **Require approval** and **Generate document** need a concrete fired +object to act on, so neither is expressible on `schedule`/`manual` triggers. + +A blocked (⛔) combination is refused **in the editor**, with a message +naming the unsupported combination — nothing is ever silently dropped or +partially compiled. + +**Conditions** (a FEEL expression, or a reference to an existing rule set) +are v1-supported only on the **manual** trigger — the rules engine is the +only existing primitive that evaluates FEEL. A condition on any other +trigger is blocked the same way. + +> **Deviation from the original design table:** `manual` + +> `run-synchronization` is blocked in this release. No primitive to invoke an +> OpenConnector synchronization on demand exists anywhere in OpenBuild today +> (the only existing trigger for a sync run is the scheduled-tasks +> reconciler) — see `lib/Service/AutomationCompilerService.php`'s class +> docblock for the full rationale. This is a documented v1.1 follow-up, not a +> silent gap. + +## Provenance and drift + +Every compiled artifact's id/key carries an `aut-` prefix (rule sets +use `aut-`, since rule-set slugs are shared platform-wide). The +automation's `provenance` block records exactly which artifacts the last +compile produced plus a content hash. This makes compilation: + +- **Deterministic** — the same automation definition always compiles to the + same artifacts. +- **Idempotent** — recompiling an unchanged automation is a no-op. +- **Reversible** — deleting the automation removes exactly the + provenance-listed artifacts and nothing else; a hand-authored entry on the + same schema (a key without the `aut-` prefix) is never touched. + +Opening the Automations page recomputes each row's drift status by comparing +the live artifacts against the stamped hash. A hand-edit to a compiled +artifact (e.g. tweaking a schedules entry directly in the page designer) +shows a **drift** badge; **Recompile (overwrite)** restores it — the +automation definition always wins. + +## Enable / disable + +Disabling an automation recompiles with every artifact's own enabled switch +turned off (a notification entry's `enabled: false`, a schedules entry's +`enabled: false`, a rule's `actief: false`); a lifecycle-transition action has +no per-action enabled flag, so it is removed from the transition's +`actions[]` while `provenance` retains it for a cheap re-enable. Artifacts +never disappear from storage while disabled — re-enabling is just another +recompile. + +## Dry-run + +The test panel (mirrors the business-rules test sandbox) compiles the +automation **in-memory** to its rules-backend representation — regardless of +its actual trigger — and evaluates it through the same rules engine with +`dryRun: true`. This gives a single, uniform preview surface for every +matrix cell without a persisted rule set (an event- or schedule-triggered +automation never has one) and without ever dispatching a real side effect. + +## RBAC + +Authoring, dry-running and enabling on a non-production version requires the +caller to be an **owner or editor** on the parent Application. Enabling an +automation on the version currently set as the Application's **production** +version requires an **owner** — mirroring the version-promotion posture, +with no Nextcloud-admin bypass. Every check runs before any compile side +effect; a rejected call never touches a compiled artifact. Automation object +CRUD (create/edit/delete) itself goes through OpenRegister's REST surface, not +this controller — the compile/enable/disable boundary is the security +boundary, not the raw object write. + +## Runtime API + +| Method | Endpoint | Purpose | +| --- | --- | --- | +| POST | `/api/automations/{uuid}/compile` | Recompile in place (upsert artifacts). | +| POST | `/api/automations/{uuid}/enable` | Turn every compiled artifact on. | +| POST | `/api/automations/{uuid}/disable` | Turn every compiled artifact off (stays in place). | +| POST | `/api/automations/{uuid}/dry-run` | Evaluate via the rules engine, no side effects. | +| GET | `/api/automations/{uuid}/status` | Recompute drift against the live artifacts. | + +CRUD on the automation object itself is OpenRegister's generic REST surface: +`/apps/openregister/api/objects/openbuild/automation`. + +## Relationship to the specialist editors + +The Automations page is not a replacement for the Business rules page, the +Schedules section of the page designer, or the schema designer's lifecycle +editor — those remain the power-user surfaces for their own dialects and can +still be hand-edited directly (drift on a compiled artifact is expected and +surfaced, not an error). See [Business rules engine](./business-rules-engine.md) +for the FEEL subset, hit policies and audit trail the manual-trigger backend +inherits unchanged. diff --git a/docs/business-rules-engine.md b/docs/business-rules-engine.md index a11ce4f5e..1ae9e25f0 100644 --- a/docs/business-rules-engine.md +++ b/docs/business-rules-engine.md @@ -105,3 +105,14 @@ job (7-day interval) purges logs past the 90-day retention window. To query the audit trail for compliance, list `rule-execution-log` objects in the `openbuild` register filtered by `ruleSetId` and time window. + +## Automation designer + +A `manual`-trigger automation composed on the [Automations page](./automation-designer.md) +compiles to exactly this engine: a namespaced RuleSet (`aut-`) plus one +`condition-action` rule, evaluated and dry-run through the same +`RuleEngineService` — inheriting the audit trail, PII masking and 500 ms +soft timeout documented above unchanged. The Automations page is the +citizen-developer composer for the common "when X happens, do Y" case; this +page's DecisionTable / ConditionActionRule editors remain the power-user +surface for hand-authoring rule sets directly. diff --git a/docs/features.json b/docs/features.json index a0873b053..903730ad9 100644 --- a/docs/features.json +++ b/docs/features.json @@ -1,80 +1,148 @@ [ { - "slug": "app-icon-management", - "title": "app-icon-management", - "summary": "Lets an operator brand each published OpenBuild virtual app with per-app SVG icons (light + dark) so the published app surfaces with its own identity in the Nextcloud top bar and in OpenBuild's own card grid. Adds top-level `icon` / `iconDark` ref fields to the `Application` schema (sibling to `slug`, `name`, `manifest`, `permissions`), thin icon-serving endpoints with a clear fallback chain, and the upload / preview / remove UX on the Application detail page — all routed through OR's existing files-attached-to-object mechanism (ADR-001) so no new openbuild-side file storage is introduced.", - "docsUrl": "openspec/specs/app-icon-management/spec.md" + "slug": "openbuild-runtime", + "title": "Build apps without code", + "summary": "You compose an app as a manifest and run it live, with no deployment step.", + "status": "stable", + "docsUrl": "openspec/specs/openbuild-runtime/spec.md", + "title_nl": "Bouw apps zonder code", + "summary_nl": "Je stelt een app samen als een manifest en draait hem direct live, zonder uitrol." }, { - "slug": "app-nav-entries", - "title": "app-nav-entries", - "summary": "@e2e exclude mixed spec — INavigationManager registration, RBAC visibility closure, per-request re-evaluation and group-wildcard filtering are PHP boot-time contracts verified by PHPUnit/Newman; published app top-bar appearance requires a published app + boot-cycle which is not reproducible in isolation in Playwright without a lifecycle trigger that is itself part of the app; scenarios tested by playwright UI are covered in the openbuild-runtime spec tests", - "docsUrl": "openspec/specs/app-nav-entries/spec.md" + "slug": "openbuild-schema-designer", + "title": "Design your data model visually", + "summary": "You define entities, fields and relations without writing JSON Schema.", + "status": "stable", + "docsUrl": "openspec/specs/openbuild-schema-designer/spec.md", + "title_nl": "Ontwerp je datamodel visueel", + "summary_nl": "Je legt entiteiten, velden en relaties vast zonder JSON Schema te schrijven." }, { - "slug": "application-creation-wizard", - "title": "application-creation-wizard", - "summary": "Replaces the legacy single-form \"Add Application\" dialog with a four-step wizard that provisions the full ADR-002 chain in one atomic backend call: an `Application` row + N `ApplicationVersion` rows + N per-version registers (named `openbuild-{appSlug}-{versionSlug}`), each pre-seeded with the default `hello-message` schema and the default manifest. Supports `single | dev-prod | dev-staging-prod | custom` presets, enforces unique slugs per chain (leading-`_` reserved for openbuild system use), provides full rollback on any provisioning failure, sets the caller as sole owner, and retires install-time auto-seed (`SeedHelloWorld` does not return) — fresh installs are empty until the admin runs the wizard.", - "docsUrl": "openspec/specs/application-creation-wizard/spec.md" + "slug": "openbuild-page-designer", + "title": "Build pages and forms visually", + "summary": "You create list, detail, form and dashboard screens yourself.", + "status": "stable", + "docsUrl": "openspec/specs/openbuild-page-designer/spec.md", + "title_nl": "Bouw pagina's en formulieren visueel", + "summary_nl": "Je maakt zelf lijst-, detail-, formulier- en dashboardschermen." }, { - "slug": "application-detail-overview", - "title": "application-detail-overview", - "summary": "Replaces the generic `CnDetailPage` main area on `/applications/:objectId` with a purpose-built maintainer cockpit registered as the `headerComponent` on the `VirtualAppDetail` page entry. Renders six stacked rows — hero strip (icon, name, description, status, role, production semver), version pill tabs (chain order, production starred, non-authorised hidden, Promote affordance on each non-terminal pill), a 7d/30d/90d window toggle, a four-card KPI grid (active users, object count, files count, audit events), an activity-graph card, and a five-card structural-widget grid (Register / Schemas / Groups / Pages / Menu) that deep-links into the existing builder views and OpenRegister. Consumes the insights endpoint owned by `application-insights` for KPI + activity data.", - "docsUrl": "openspec/specs/application-detail-overview/spec.md" + "slug": "business-rules-engine", + "title": "Automate decisions with rules", + "summary": "You set conditions and decision tables, with no scripting.", + "status": "stable", + "docsUrl": "openspec/specs/business-rules-engine/spec.md", + "title_nl": "Automatiseer beslissingen met regels", + "summary_nl": "Je stelt voorwaarden en beslistabellen in, zonder te scripten." }, { - "slug": "application-insights", - "title": "application-insights", - "summary": "@e2e exclude pure-backend API/aggregation spec — all scenarios are REST endpoint contracts and service-layer aggregation logic verified by Newman/PHPUnit; no UI surface in this spec", - "docsUrl": "openspec/specs/application-insights/spec.md" + "slug": "openbuild-exporter", + "title": "Export to a real Nextcloud app", + "summary": "You take your app as a clean package, so there is no lock-in.", + "status": "stable", + "docsUrl": "openspec/specs/openbuild-exporter/spec.md", + "title_nl": "Exporteer naar een echte Nextcloud-app", + "summary_nl": "Je neemt je app mee als schoon pakket, dus geen lock-in." + }, + { + "slug": "openbuild-rbac", + "title": "Control who sees what", + "summary": "You grant access per app, per group and per person.", + "status": "stable", + "docsUrl": "openspec/specs/openbuild-rbac/spec.md", + "providedBy": "openregister", + "title_nl": "Bepaal wie wat ziet", + "summary_nl": "Je geeft toegang per app, per groep en per persoon." }, { "slug": "application-versions", - "title": "application-versions", - "summary": "@e2e exclude pure-backend schema/lifecycle/CRUD spec — all scenarios are OR REST contracts and service-layer logic verified by Newman/PHPUnit; no UI surface in this spec", - "docsUrl": "openspec/specs/application-versions/spec.md" + "title": "Version, promote and roll back", + "summary": "You move an app from dev to production and undo a change.", + "status": "stable", + "docsUrl": "openspec/specs/application-versions/spec.md", + "title_nl": "Versioneer, promoot en draai terug", + "summary_nl": "Je tilt een app van dev naar productie en draait een wijziging terug." + }, + { + "slug": "application-creation-wizard", + "title": "Set up an app in four steps", + "summary": "The wizard provisions environments and data in one go.", + "status": "stable", + "docsUrl": "openspec/specs/application-creation-wizard/spec.md", + "title_nl": "Zet een app in vier stappen op", + "summary_nl": "De wizard richt in een keer omgevingen en data in." }, { - "slug": "deep-link-registration", - "title": "deep-link-registration", - "summary": "@e2e exclude pure-backend event-listener spec — listener registration, event wiring, URL template, and short-circuit guards verified by PHPUnit; no UI surface testable via Playwright (deep-link integration requires OR to dispatch events, not exercisable in isolation)", - "docsUrl": "openspec/specs/deep-link-registration/spec.md" + "slug": "application-insights", + "title": "See how each app is used", + "summary": "You track users, objects and activity per app over time.", + "status": "stable", + "docsUrl": "openspec/specs/application-insights/spec.md", + "title_nl": "Zie hoe elke app gebruikt wordt", + "summary_nl": "Je volgt gebruikers, objecten en activiteit per app door de tijd." }, { - "slug": "green-field-migration", - "title": "green-field-migration", - "summary": "@e2e exclude pure-backend migration spec — destructive repair step, idempotency guard, logging, and OR API calls verified by PHPUnit; no UI surface in this spec", - "docsUrl": "openspec/specs/green-field-migration/spec.md" + "slug": "unified-app-model", + "title": "Extend an installed app", + "summary": "You layer your own changes over a fleet app without forking it.", + "status": "stable", + "docsUrl": "openspec/specs/unified-app-model/spec.md", + "title_nl": "Breid een geinstalleerde app uit", + "summary_nl": "Je legt je eigen wijzigingen over een fleet-app zonder te forken." }, { - "slug": "openbuild-schema-designer", - "title": "openbuild-schema-designer", - "summary": "Ships the visual Schema Designer that gives non-technical authors direct authoring power over the data model of their virtual app — replacing the deploy-time `lib/Settings/{app}_register.json` pattern. Scoped to the current virtual app's register namespace, the designer composes a JSON Schema body with declarative `x-openregister-*` extension blocks (lifecycle, aggregations, calculations, notifications, relations, widgets) through typed sub-editors. Every behaviour-shaping field is declarative — no free-text PHP, no JavaScript callbacks, no service-class references; the editor is code, but the product is declarative (canonical ADR-031 example). Persists via OR's runtime schema CRUD endpoint (chain spec `openregister-runtime-schema-api`), surfaces confirm-before-destructive flows for delete-field / delete-schema, and runs live client-side validation that disables Save until the staged change is valid.", - "docsUrl": "openspec/specs/openbuild-schema-designer/spec.md" + "slug": "app-icon-management", + "title": "Brand each app", + "summary": "You give every app its own icon, light and dark.", + "status": "stable", + "docsUrl": "openspec/specs/app-icon-management/spec.md", + "title_nl": "Geef elke app een eigen merk", + "summary_nl": "Je geeft elke app een eigen icoon, licht en donker." + }, + { + "slug": "openbuild-remote-template-store", + "title": "Install templates from a remote catalogue", + "summary": "You pull curated apps from a shared store into your own instance.", + "status": "beta", + "docsUrl": "openspec/specs/openbuild-remote-template-store/spec.md", + "title_nl": "Installeer sjablonen uit een externe catalogus", + "summary_nl": "Je haalt samengestelde apps uit een gedeelde winkel binnen je eigen omgeving." }, { - "slug": "openbuild-template-catalogue", - "title": "openbuild-template-catalogue", - "summary": "Ships the starter-template gallery that turns OpenBuild's competitor-parity \"day-one templates\" promise into a working surface. Declares an `ApplicationTemplate` schema, seeds four Conduction-curated templates (permit-tracker, stakeholder-consultation, employee-onboarding, incident-reporter) via an idempotent repair step, renders a filterable gallery view, and one-click clones a chosen template into a new draft Application — namespacing every cloned companion schema under the new Application's slug to avoid collisions, recording the source template + version on `templateOrigin` for traceability, and redirecting straight into the page editor for customisation. Clones are one-shot snapshots (no back-propagation); curated templates are read-only via UI; gallery and seed content are fully i18n'd (nl/en minimum).", - "docsUrl": "openspec/specs/openbuild-template-catalogue/spec.md" + "slug": "nldesign-theme-selection", + "title": "Match your government theme", + "summary": "You apply NL Design styling per app for a consistent public look.", + "status": "beta", + "docsUrl": "openspec/specs/nldesign-theme-selection/spec.md", + "title_nl": "Sluit aan op je overheidshuisstijl", + "summary_nl": "Je past per app NL Design-styling toe voor een consistente publieke uitstraling." }, { - "slug": "openbuild-version-snapshots", - "title": "openbuild-version-snapshots", - "summary": "@e2e exclude pure-backend spec — snapshot retirement, OR time-travel rollback, and diff endpoint contracts verified by Newman/PHPUnit; no independent UI surface (UI rollback/diff surface covered by version-routing-ui Playwright tests)", - "docsUrl": "openspec/specs/openbuild-version-snapshots/spec.md" + "slug": "docudesk-document-templates", + "title": "Generate documents and attach workflows", + "summary": "You add DocuDesk templates and Procest steps to an app.", + "status": "beta", + "docsUrl": "openspec/specs/docudesk-document-templates/spec.md", + "title_nl": "Genereer documenten en koppel workflows", + "summary_nl": "Je voegt DocuDesk-sjablonen en Procest-stappen toe aan een app." }, { - "slug": "version-promotion", - "title": "version-promotion", - "summary": "@e2e exclude mixed spec — backend promotion endpoint + strategy logic verified by Newman/PHPUnit; UI dialog scenarios (PromoteVersionDialog) covered by version-routing-ui spec Playwright tests", - "docsUrl": "openspec/specs/version-promotion/spec.md" + "slug": "openconnector-api-sources", + "title": "Connect external API data", + "summary": "You read and write line-of-business systems from your app.", + "status": "soon", + "docsUrl": "openspec/specs/openconnector-api-sources/spec.md", + "title_nl": "Koppel externe API-data", + "summary_nl": "Je leest en schrijft vanuit je app in bedrijfssystemen." }, { - "slug": "version-routing", - "title": "version-routing", - "summary": "@e2e exclude mixed spec — server-side RBAC gate, ManifestResolverService, and store/composable contracts verified by Newman/PHPUnit + Vitest; UI-visible version routing behavior covered by version-routing-ui spec Playwright tests", - "docsUrl": "openspec/specs/version-routing/spec.md" + "slug": "ai-agent-mcp-bridge", + "title": "Expose apps to AI agents", + "summary": "You let assistants act on your apps over MCP.", + "status": "soon", + "docsUrl": "openspec/specs/ai-agent-mcp-bridge/spec.md", + "providedBy": "openregister", + "title_nl": "Open je apps voor AI-agents", + "summary_nl": "Je laat assistenten via MCP handelen op je apps." } ] diff --git a/docs/form-logic-authoring.md b/docs/form-logic-authoring.md new file mode 100644 index 000000000..c15ef7f96 --- /dev/null +++ b/docs/form-logic-authoring.md @@ -0,0 +1,114 @@ + + +# Form logic — steps, conditions, and validation + +The page designer's form-page sub-editor lets you build multi-step +wizards, conditional field visibility, and per-field validation rules +visually — no raw JSON required. This page covers the three sections +added to the **Form page** editor, plus the one editor-only behaviour +that keeps every save valid. + +> Rendering these shapes at runtime (`CnFormPage`) requires a +> `@conduction/nextcloud-vue` build that contains the +> `manifest-form-logic` leaf (`1.0.0-beta.173`+, off the `beta` dist-tag). +> Authoring works regardless — the additive manifest keys survive either +> way — but a pre-leaf renderer shows the form flat and always-visible +> until the app's nextcloud-vue dependency is upgraded. + +## Steps + +Open a **form** page in the designer and expand the **Steps** fieldset +(below **Fields**). Each step has: + +- a **title** (required, shown in the step indicator at runtime), +- an optional **description**, +- a stable **id** — auto-derived as a kebab-case slug from the title, but + editable if you type your own, +- an ordered list of **field keys** assigned to that step. + +Use **+ Add step** to create a step, then assign existing fields to it +from the **Unassigned fields** pool above the step list (pick a key in +the step's select, click **Assign**). Removing a field from a step +returns its key to the pool — field *definitions* are never touched by +the Steps section; only which step a key belongs to changes. + +If you leave a page with no steps at all, it renders as a single flat +form — exactly like before this feature existed. Deleting the last +remaining step removes the `steps` key entirely rather than leaving an +empty array, so the page falls back to the same single-step state. + +### The "every field needs a step" rule + +Once a page has *any* steps, the runtime requires every declared field +to belong to **exactly one** step — an unassigned field would silently +never render, and a field assigned to two steps would render twice. The +Steps section shows the current unassigned pool live, with a note that +those keys are automatically appended to the **last** step when you +save. There is no silent renderer fallback for this — the manifest +either satisfies the rule or fails validation — so the auto-assign-on-save +behaviour is what keeps your save from ever producing an invalid +manifest, even if you forget to place a newly-added field into a step +before clicking Save. + +## Conditions + +Expand a field's row (click **Details**) to open its **Conditions** +section. Pick: + +- **Field** — another field on the same form (the field you're editing + is excluded from the list), +- **Op** — `eq`, `neq`, `gt`, `gte`, `lt`, or `lte` (defaults to `eq`, + which is left out of the saved manifest since it's the default), +- **Value** — typed `true` / `false` and plain numbers are saved as + boolean/number so ordering comparisons (`gt`, `lt`, …) work correctly; + anything else is saved as the literal text you typed. + +Clearing the field picker (or clicking **Clear**) removes the condition. + +If a condition was authored directly in the Raw JSON tab using the +advanced `endpoint` or `source` shapes (a condition resolved against a +same-origin URL or an OpenRegister query, rather than another field on +the form), the Conditions section shows a read-only note — **"Advanced +condition — edit in Raw JSON"** — and never rewrites it. Editing +anything else on that field leaves the advanced condition untouched. + +## Validation + +The same field details area has a **Validation** section: + +- **Required** — the field must be filled in, +- **Min** / **Max** — a length bound for text fields, a value bound for + number fields, +- **Pattern** — a regular expression the value must match. An + expression that doesn't compile is flagged inline immediately and is + never saved, so you can't accidentally write a broken pattern. +- **Message** — a custom message shown for whichever rule fails. + +Older form pages sometimes carry validation as flat `required` / +`pattern` keys directly on the field (rather than the structured +`validation` object above). The Validation section reads those as a +starting point, but **only writes the structured object once you make an +edit in that field's own Validation section** — at that point the flat +keys on that field are replaced. Fields you never touch keep their flat +keys exactly as they were; this is opt-in, per field, never a bulk +rewrite of the whole form. + +## The dangling-reference warning + +Deleting a field that's still referenced by another field's condition, +or by a step, does **not** silently break anything or cascade-delete the +reference. Instead, you'll see an immediate warning ("Condition +references removed field '…'" / a similar note on the step) right where +the stale reference lives, so you can decide what to do — re-add the +field, or edit the condition/step yourself. The reference stays in the +manifest until you resolve it. + +## See also + +- [OpenBuild Runtime](./openbuild-runtime.md) — how a virtual app renders + end to end. +- [OpenBuild RBAC](./openbuild-rbac.md) — who can edit a page's form + logic. diff --git a/docs/github-store.md b/docs/github-store.md new file mode 100644 index 000000000..a8c5520ea --- /dev/null +++ b/docs/github-store.md @@ -0,0 +1,112 @@ +# GitHub store — publish and install apps + +The **GitHub store** lets you publish an OpenBuild app to a GitHub repository and +discover and install apps that others have published — on top of the built-in +templates and the [remote template store](./template-store.md). + +Unlike the remote template store (which is consume-only), the GitHub store is a +full **round-trip**: build an app, publish it to GitHub, and install it again on +any instance — the app lives in the repository, independent of the instance that +built it. + +## What a published app looks like + +Publishing writes the app to a repository as plain, re-importable files, and tags +the repo with the `openbuild-app` topic (the store's discovery contract): + +| File | Contents | +|------|----------| +| `openbuild-app.json` | App descriptor — slug, name, description, category, `appType`, version, icon refs, and the declared `credentials[]`. | +| `manifest.json` | The `ApplicationVersion` manifest — every page, widget, menu entry, sidebar, and setting. | +| `schemas/.json` | The companion schemas that make up the data model. | +| `README.md` | Generated overview of the app. | + +`AppRepoSerializer` writes this layout deterministically (recursively key-sorted); +`AppRepoParser` reads it back with strict, all-or-nothing validation, so a +malformed repository fails loudly and installs nothing. + +## Credentials — the token never reaches OpenBuild + +Every GitHub call is routed through OpenRegister's **credential broker**. You +store a GitHub personal access token once, in the **Credentials** pane of the +app's user settings; it is kept in **Doriath**, the encrypted credential vault. +OpenBuild never receives the token — it asks the broker to make each GitHub call +(create repo, push commit, set topic, read contents), the token is injected +server-side, host-locked to `api.github.com`, and only the result comes back. + +- Browsing the store is **anonymous** by default (public repos, no credential). +- Passing a credential **upgrades** the call through the broker so you also see + your own private repositories and get a higher rate limit. +- Publishing and pulling always go through the broker. + +Use a GitHub **fine-grained** token with exactly three repository permissions: +**Administration — Read and write** (create the repository, set its topic), +**Contents — Read and write** (push the app's files and commits), and +**Metadata — Read-only** (required). Nothing else is needed; the broker's +allow-rules deny issues, pull-requests, workflows, and webhooks regardless. + +The token owner controls access per credential (which apps may use it) and can +revoke or rotate it in one place — nothing to clean up inside OpenBuild. + +## Publishing an app + +Open the app, choose **Actions → GitHub**, pick a `github` credential, and select +**Publish**. OpenBuild: + +1. Serializes the chosen version to the repo layout. +2. Creates the repository (via the broker) — **public by default** so it is + discoverable in the store's anonymous search; pass `visibility: "private"` to + keep it private. +3. Sets the `openbuild-app` topic and commits the app in one clean commit via the + Git Data API (blob → tree → commit → ref). +4. Records the resulting `commitSha` and repository on the app. + +Re-publishing advances the branch on a new commit — it never force-pushes or +rewrites history. + +## Pulling changes back + +**Pull** fetches a repository ref back into a **new draft `ApplicationVersion`** — +it never touches the production version. A change someone else pushed lands next +to your production version for you to review and promote through the normal +version-promotion flow. + +## Filling the store from GitHub + +Go to **Store → GitHub**. The store searches GitHub for the `openbuild-app` topic +and renders each published app as an installable card built from its +`openbuild-app.json`. Click **Install**, name the new app, and confirm — the +repository is parsed and cloned into a fresh local app through the same seam as +any template (`ApplicationsController::installFromTemplateArray`), so it is an +ordinary editable virtual app, not a locked import. + +## Endpoints + +| Method + path | Purpose | +|---|---| +| `GET /index.php/apps/openbuild/api/shop/github/search?q=&credentialId=` | Search GitHub for `openbuild-app` repos. Anonymous by default; `credentialId` broker-upgrades to include private repos. Returns `{ outcome, cards, brokerCredentialAvailable, brokerUsed, rateLimited }`. | +| `POST /index.php/apps/openbuild/api/shop/github/install` | Install an app from a repo. Body `{ owner, repo, ref?, name?, slug?, credentialId? }` → `201 { uuid, slug, register, companionSchemas }`. | +| `GET /index.php/apps/openbuild/api/applications/{slug}/github/status` | Linked repo, default branch, last pushed/pulled sha, and feature-detection flags (`brokerCredentialAvailable`, `publishAvailable`). Viewer-readable. | +| `POST /index.php/apps/openbuild/api/applications/{slug}/github/link` | Link an app to a repo. Body `{ owner, name, org? }`. Owner-only. | +| `POST /index.php/apps/openbuild/api/applications/{slug}/github/push` | Publish. Body `{ credentialId, versionSlug?, repo?, visibility? }` → `{ outcome, repoUrl, commitSha, branch }`. Owner-only. | +| `POST /index.php/apps/openbuild/api/applications/{slug}/github/pull` | Pull a ref into a new draft version. Body `{ ref, credentialId? }` → `{ outcome, versionUuid, versionSlug, commitSha, sourceRef, status: 'draft', register }`. Owner-only. | + +`outcome` values include `ok`, `not_linked`, `broker_unavailable`, +`broker_denied`, `push_conflict`, `github_rate_limited`, `github_unreachable`. + +## Security + +- All GitHub reads are **fixed-host** to `api.github.com`; callers supply a path, + never a full URL, and the broker host-locks and allow-rule-checks every call. +- The write operations (search, identity, repo-create, ref-update, topic-set) + require the widened `github` provider allow-rules shipped in OpenRegister + (catalogue v1.2.0). Issues, pull-requests, workflows, webhooks, and deletes + stay denied. +- Publish/pull/link/status are **owner-gated** on the app's `permissions` model; + a Nextcloud admin who is not an owner is not auto-granted. +- The credential secret is never returned to the app, the browser, a log line, or + an error message. + +See the OpenSpec changes `github-app-repo-format`, `github-shop-catalogue`, and +`github-app-sync` (OpenBuild) and `github-provider-shop-rules` (OpenRegister) for +the full specification. diff --git a/docs/integrator-guide.md b/docs/integrator-guide.md index f08443acc..51cfea481 100644 --- a/docs/integrator-guide.md +++ b/docs/integrator-guide.md @@ -59,6 +59,45 @@ For further reading on what each step writes through to OR, see [`openbuild-runtime.md`](./openbuild-runtime.md) and the wizard chain spec [`openspec/changes/openbuild-app-creation-wizard/`](../openspec/changes/openbuild-app-creation-wizard/). +## Editing session: undo/redo (page designer & schema designer) + +Both visual designers — the page designer (`/builder/{slug}/pages`) and the +schema designer (`/builder/{slug}/schemas/{schemaId}`) — offer editor-level +undo/redo over your in-flight (unsaved) edits, on top of the toolbar's +Save action: + +- **Reach it three ways:** the toolbar's Undo/Redo buttons (disabled at the + ends of the stack), `Ctrl+Z` (undo) and `Ctrl+Shift+Z` or `Ctrl+Y` (redo), + or the `Cmd` equivalents on macOS. +- **Native text-field undo wins while typing.** Pressing `Ctrl+Z` inside an + ``, `