Skip to content

Release: merge development into beta - #289

Open
github-actions[bot] wants to merge 13 commits into
betafrom
development
Open

Release: merge development into beta#289
github-actions[bot] wants to merge 13 commits into
betafrom
development

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

rubenvdlinde and others added 7 commits August 20, 2026 22:50
Bumps phpstan/phpstan to ^2.0 and conduction/hydra-gates to ^1.8.2 (which
carries the shared phpstan-base.neon fixes for `treatPhpDocTypesAsCertain`
and PHPMD's `@SuppressWarnings` phpDoc.parseError), then removes the code
PHPStan 2 correctly identifies as unreachable. `phpstan analyse` goes from
11 errors to 0; 880 PHPUnit tests stay green and phpcs reports 0 errors.

Dead `method_exists()` back-compat probes (5)
---------------------------------------------
`runAsSystem()`, `lockObject()` and `unlockObject()` are all declared on
`OCA\OpenRegister\Contract\ObjectServiceInterface` — verified against both
the canonical `openregister@development` contract and the copy vendored by
hydra-gates v1.8.2, which is what static analysis resolves here.

The probes were written for back-compat with an OpenRegister release that
predates the elevation/locking API. That release cannot run this code at
all: `openregister@main` ships no `Contract` namespace whatsoever, so the
constructors that type-hint `ObjectServiceInterface` cannot have their
dependencies resolved there. Wherever this code executes, the contract is
the one that declares all three methods, and every fallback branch behind
the probes was unreachable.

Removing them cascades in MigrateToVersionedModel: `$hasSystemContext` was
always true, so `ROW_BLOCKED` was never returned and `STATE_BLOCKED` was
never written. Both constants and the retry prose go with them. This is
backwards compatible — a `blocked` value persisted by an older install is
still not `STATE_DONE`, so the step retries exactly as before.

Other always-true conditions (6)
--------------------------------
- AbstractToolHandler: the lock is acquired in a *separate* try/catch that
  throws on failure, so the later `finally` can only run with the lock
  held; the `$locked` flag could never be false and is now gone.
- AppChannelApplier / ApplicationVersionService: `is_array()` on findAll()'s
  return and `is_object()` on saveObject()'s ObjectEntityInterface.
- AgentsController / ManifestResolverService: `array_values()` on arrays
  only ever appended to with `[]=` (and usort()ed in place) — no-ops.
- SettingsService: `registry_url` is one of CONFIG_KEYS and is always set
  by the loop above, so the `?? ''` was unreachable.

Every removal is annotated in place so the next reader does not
reintroduce the guard.
Bumps [diff](https://github.com/kpdecker/jsdiff) from 5.2.2 to 9.0.0.
- [Changelog](https://github.com/kpdecker/jsdiff/blob/master/release-notes.md)
- [Commits](kpdecker/jsdiff@v5.2.2...v9.0.0)

---
updated-dependencies:
- dependency-name: diff
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [jsdom](https://github.com/jsdom/jsdom) from 24.1.3 to 30.0.1.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](jsdom/jsdom@v24.1.3...v30.0.1)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 30.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
chore(quality): migrate to PHPStan 2 and clear the 11 residual errors
…elopment/jsdom-30.0.1

build(deps-dev): bump jsdom from 24.1.3 to 30.0.1
…elopment/diff-9.0.0

build(deps): bump diff from 5.2.2 to 9.0.0
…al copy

Verbatim copy of ConductionNL/.github@main quality-config/coverage-guard.php.

The shipped 433-line copy predates --deletion-neutral, so deleting
well-tested dead code reads as a coverage drop: on decidesk the same class
of change gave 1782/2324 -> 1763/2305 statements, i.e. 19 deleted and all 19
covered, and the guard failed it as -0.19%. Nothing was less tested.

All nine PHP apps ship this identical stale copy with no local edits, so the
refresh is wholesale rather than patched. The ratchet is NOT disabled or
baselined — a real coverage loss still fails.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/openbuild @ d8a3174

Check PHP Vue Security License Tests
lint ⏭️
phpcs ⏭️
phpmd ⏭️
psalm
phpstan ⏭️
phpmetrics ⏭️
eslint
stylelint
build
composer ⏭️ ⏭️
npm ⏭️
app:check-code ⏭️
info.xml ⏭️
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-20 22:53 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/openbuild @ 971e1ca

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer ✅ 106/106
npm ✅ 626/626
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-20 23:58 UTC

Download the full PDF report from the workflow artifacts.

…on-neutral

chore(ci): refresh coverage-guard.php to the canonical deletion-neutral copy
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/openbuild @ ed29be3

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer ✅ 106/106
npm ✅ 626/626
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-21 01:03 UTC

Download the full PDF report from the workflow artifacts.

Conduction Release Bot and others added 2 commits August 21, 2026 06:19
…t mocks

The library upgrade is trivial -- openbuild uses exactly two of its exports,
`generateUrl` and `imagePath`, and v3 still exports both. Unlike @nextcloud/vue
v9 or @nextcloud/dialogs v7, v3 keeps `main` plus BOTH `import` and `require`
conditions, so there is no resolution work either.

What blocked this PR was 33 failing test files, and they were not the library's
fault.

    Error: [vitest] No "imagePath" export is defined on the
    "@nextcloud/router" mock. Did you forget to return it from "vi.mock"?

57 spec files mock @nextcloud/router, and most declared only what they
personally cared about:

    vi.mock('@nextcloud/router', () => ({ generateUrl: (p) => p }))

But a `vi.mock` factory replaces the module for the WHOLE module graph of that
test, not just for the spec's own imports. Any component reachable from the test
that imports `imagePath` gets a module where it does not exist. src has been
importing `imagePath` all along -- so these mocks have been incomplete since they
were written.

Why it only surfaces now: v2 was resolved as CJS, and vitest models a mocked CJS
module as a proxy that answers `undefined` for anything the factory omitted.
v3 declares `"type": "module"` and is resolved as ESM, where a missing named
export is an error rather than an undefined. The upgrade did not break the
mocks; it stopped hiding that they were broken.

Fixed with `importOriginal` rather than by adding `imagePath`
-------------------------------------------------------------
Every one of the 33 now spreads the real module and overrides only what it
means to stub:

    vi.mock('@nextcloud/router', async (importOriginal) => ({
        ...(await importOriginal()),
        generateUrl: (p) => p,
    }))

Adding `imagePath: () => ...` to each would have fixed today's failure and left
the same trap for the next export any component starts using. Spreading the
original cannot rot: the stub stays deliberate, everything else stays real.
Files that stub more than one function (ThemePickerDialog, the two
`{slug}`-expanding GitHub modals) keep their own overrides on top of the spread.

Measured, not assumed
---------------------
The test COUNT is the part worth reading, not the pass/fail:

    before (router 3, old mocks):  33 files failed,  1125 tests ran
    after:                        141 files passed,  1378 tests ran

Those 33 files errored during collection, so their 253 tests never executed at
all. A file that fails to load reports as one red file, not 253 missing tests --
the count is the only place the difference is visible.

Verified with the commands CI runs
----------------------------------
  npm ci                   rc=0
  npm run build            rc=0, 0 errors (3 pre-existing size warnings)
  npx vitest run           141/141 files, 1378/1378 tests
  npm run lint             rc=0
  npm run stylelint        rc=0
  npx prettier --check     rc=0
  check:manifest, check:gitignore, check:nc-floor, test:l10n   all rc=0

One caveat, stated rather than smoothed over: immediately after the cold
`npm ci` reinstall, one run reported `1 failed | 1377 passed`. Four subsequent
full runs were clean (1378/1378), and I did not capture which test it was before
the log was overwritten, so I cannot attribute it. It is not a mock failure --
those fail at collection and take a whole file with them, which this did not. I
am flagging it as an unidentified one-off rather than calling the suite
deterministic on four green runs.

Closes #246
chore(deps): @nextcloud/router 2 -> 3, and complete 33 incomplete test mocks
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/openbuild @ 9b588a1

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer ✅ 106/106
npm ✅ 625/625
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-21 06:16 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde and others added 2 commits August 21, 2026 09:07
#297)

Dependabot opened #244 to bump @nextcloud/files 3.12.2 -> 4.0.0. The right
answer is not a bump: openbuild does not use this package.

    grep -rn "@nextcloud/files" --include=*.js --include=*.vue \
      --include=*.ts --include=*.json .    # minus node_modules and the lockfile
    package.json:48:  "@nextcloud/files": "^3.12.2"

One hit, and it is the declaration itself. No import in src/, no import in
tests/, no webpack external, no vitest alias.

It is still in the tree, just not as ours
-----------------------------------------
Two dependencies genuinely need it, and they disagree about the major:

    @conduction/nextcloud-vue@2.8.2  -> @nextcloud/files@3.12.2
    @nextcloud/dialogs@7.4.1         -> @nextcloud/files@4.0.0

npm resolves both, nested, and it keeps doing so after this change -- verified
by `npm ls @nextcloud/files` before and after. Removing OUR declaration removes
a claim we were not making good on; it does not remove the package.

That disagreement is also why bumping is the wrong move rather than merely an
unnecessary one. Declaring ^4.0.0 would hoist v4 to the top level while
@conduction/nextcloud-vue still expects v3, which is how an app ends up shipping
a library a consumer was not built against -- the same class of failure the
webpack config already documents at length for @vueuse/core. Declaring nothing
lets each consumer keep the major it was compiled for.

Verified with the commands CI runs
----------------------------------
  npm ci                   rc=0
  npm run build            rc=0, 0 errors
  npx vitest run           141/141 files, 1378/1378 tests
  npm run lint             rc=0
  npm run stylelint        rc=0
  npm run check:manifest   rc=0

1378 is the same total this repo reports on development, so nothing stopped
being collected -- worth checking explicitly, because a module that fails to
resolve takes its whole spec file out of the run and shows up as one red file
rather than as missing tests.

Closes #244

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
… 6, coverage-v8 4) (#298)

Combines dependabot #284 (vitest) and #288 (@vitest/coverage-v8). They cannot
land separately: vitest 4.1.11 peers `@vitest/coverage-v8: 4.1.11` EXACTLY, so
either PR alone produces a tree `npm ci` refuses. The peer chain drags in two
more packages, so this is a four-package move:

    vitest                 ^1.6.1 -> ^4.1.11
    @vitest/coverage-v8    ^1.6.1 -> ^4.1.11   (exact-pinned to vitest)
    vite                   ^5.4.0 -> ^7.3.6    (vitest 4 peers ^6 || ^7 || ^8)
    @vitejs/plugin-vue     ^5.2.1 -> ^6.0.8    (needed for vite 7)

vite 7, not vite 8, deliberately. vite 8.2.2 is the current latest, but this
repo's `.npmrc` sets `min-release-age=2` — a supply-chain cooldown that makes
the lockfile lag newly published versions ON PURPOSE. Pulling a package inside
its cooldown window is how you get a lock that resolves differently in CI than
locally. vite 7.3.6 is well outside it and satisfies vitest 4's peer range.

Five tests failed, all the same latent race
-------------------------------------------
Three in ManifestDiff.spec.js, one in PageDesignerHost.spec.js, one in
ApplicationDetailHeader.spec.js. None is a vitest bug and none needed a
production change — every one is a test seeding component state while that
component's own mounted-hook fetch is still in flight.

ManifestDiff is the clearest. The test mounts, then immediately does:

    await wrapper.setData({ fromBlob: sampleFrom, toBlob: sampleTo })

with a comment claiming this "skips the async fetch". It does not skip it, it
RACES it. The mocked axios resolves `{from: null, to: null}`, and whichever
settles second wins. Under vitest 1 setData won; under vitest 4 the fetch does,
nulls `fromBlob` straight back out, and `diffParts` computes over two empty
strings and returns []. Verified directly rather than guessed — a probe printed
`fromBlob: null` immediately after an awaited setData, while `diffLines` itself
was confirmed to be a working function returning correct hunks.

PageDesignerHost is the same shape wearing different clothes: it arms
`mockRejectedValueOnce` before the mount-time `load()` has settled, so the
mount's own request eats the rejection and the explicit `load()` succeeds.
ApplicationDetailHeader assigns `wrapper.vm.versions` while the mounted hook is
still fetching and assigning that same field.

Fixed by letting the mount settle first (`await flushPromises()` /
`await flush(wrapper)`) before seeding, with a comment at each site saying what
the ordering depends on. These tests were correct-by-accident for three major
versions.

    before: 3 files failed, 5 tests failed, 1373 passed
    after:  141 files passed, 1378 tests passed

The coverage baseline is recalibrated, and that number needs reading carefully
-------------------------------------------------------------------------------
tests/.coverage-baseline.json vitest: 85.18 -> 64.72.

This is NOT 20 points of lost coverage. The suite is identical — 1378 tests
before and after. The instrument changed:

    coverage-v8 v1:  src/App.vue = 235 lines   (exactly its `wc -l`)
    coverage-v8 v4:  src/App.vue = 24 lines
    across src/**:   59,333 -> 10,097 lines, over the same ~200 files

v1 treated every physical line as coverable — blank lines, comments and Vue
template markup included, nearly all of which score covered for free. v4 remaps
through `ast-v8-to-istanbul` to executable lines only. 64.72% of real statements
is a stricter bar than 89.63% of a file's line count, so the ratchet is being
recalibrated, not relaxed. The baseline file carries a `_note` explaining this
so nobody "restores" 85.18 — under v4 it is unreachable and meaningless.

Flagging explicitly for review: this repo does NOT currently run the vitest
ratchet in CI. `frontend-checks` is `["check:manifest", "test:l10n",
"check:gitignore", "check:nc-floor", "format"]`, and `enable-coverage-guard:
true` refers to the separate PHP clover guard (`.coverage-baseline` = 57.39).
So no CI gate changes either way here — but leaving 85.18 in place would make
the ratchet fail instantly the day someone wires it up, which is why it is
corrected rather than left alone.

Verified with the commands CI runs
----------------------------------
  npm ci                        rc=0
  npx vitest run                141/141 files, 1378/1378 tests
  npm run test:coverage         rc=0
  npm run test:coverage-ratchet rc=0 (holds at floor)
  npm run build                 rc=0
  npm run lint                  rc=0
  npm run stylelint             rc=0
  npx prettier --check          rc=0

Closes #284
Closes #288

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/openbuild @ c7d7c2b

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer
npm
app:check-code ⏭️
info.xml
REUSE ⏭️
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-21 07:09 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/openbuild @ 2691b66

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer ✅ 106/106
npm ✅ 625/625
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-21 07:49 UTC

Download the full PDF report from the workflow artifacts.

hydra-gates v1.8.2 -> v1.8.2
nc-vue      2.8.2 -> 2.9.2

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant