Release: merge development into beta - #289
Open
github-actions[bot] wants to merge 13 commits into
Open
Conversation
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.
Contributor
Author
Quality Report — ConductionNL/openbuild @
|
| 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.
Contributor
Author
Quality Report — ConductionNL/openbuild @
|
| 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
Contributor
Author
Quality Report — ConductionNL/openbuild @
|
| 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.
…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
Contributor
Author
Quality Report — ConductionNL/openbuild @
|
| 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.
#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>
Contributor
Author
Quality Report — ConductionNL/openbuild @
|
| 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.
Contributor
Author
Quality Report — ConductionNL/openbuild @
|
| 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.