Skip to content

fix(composer-update): extract update logic to script + fix no-widen abort + tests#31

Merged
oxyc merged 1 commit into
masterfrom
fix/composer-update-extract-script
May 21, 2026
Merged

fix(composer-update): extract update logic to script + fix no-widen abort + tests#31
oxyc merged 1 commit into
masterfrom
fix/composer-update-extract-script

Conversation

@oxyc
Copy link
Copy Markdown
Member

@oxyc oxyc commented May 21, 2026

Why

Two issues in composer-update, plus the testing you asked about.

1. master is currently broken (regression from #30)

#30 added explanatory comments to the inline "Update packages" run: block, which pushed it past GitHub's 21000-char compiled-expression limit. Every scheduled scan now fails before running:

The template is not valid. generoi/github-actions/v2/composer-update/action.yml
(Line: 107, Col: 12): Exceeded max expression length 21000

(Seen when re-triggering the solarplexius scan after pointing v2 at #30.)

Fix: extract the ~470-line block into composer-update/scripts/update.sh, invoked as bash "$GITHUB_ACTION_PATH/scripts/update.sh". Inputs pass through env (PACKAGES, VULNS_JSON); the script sets set -eo pipefail to mirror the composite shell exactly. This removes the inline-size ceiling for good and makes the logic testable. Net behavior is unchanged — only 3 expression refs existed in the block (github.action_path$GITHUB_ACTION_PATH, inputs.packages$PACKAGES, inputs.vulns_json→ existing env).

2. The original no-widen abort (the bug #30 set out to fix)

When every constraint-blocked package is in extra.vuln-scan.no-widen, the widen-targets dedupe runs grep -v '^$' over empty input → exit 1 → under pipefail the step dies and no PR is raised, even when other packages (e.g. symfony CVEs) updated cleanly. Guarded with || true (same on find_direct_ancestors, identical empty-result property). This is the real fix that #30 intended but couldn't deliver because of #1.

3. Tests (per request)

  • composer-update/tests/no-widen-still-creates-pr.test.sh — drives the real update.sh with a fake composer/git (no network), asserting a safe package still updates and changed=true is emitted while a no-widen package is skipped without aborting.
  • Verified it fails on the pre-fix logic (reproduces the solarplexius abort) and passes after.
  • .github/workflows/composer-update-tests.yml runs composer-update/tests/*.test.sh on changes to the action. The extraction makes adding more cases straightforward.

Note

My apologies — the size regression in #30 was mine (the verbose comments). This supersedes it and hardens against recurrence. After merge, v2 needs re-pointing to include this commit.

🤖 Generated with Claude Code

…abort

Two problems, one root area:

1. The inline "Update packages" run block grew past GitHub's 21000-char
   compiled-expression limit (`The template is not valid ... Exceeded max
   expression length 21000`), so the action failed to even start — breaking
   every repo's scheduled scan. Extract the ~470-line block to
   composer-update/scripts/update.sh, invoked via
   `bash "$GITHUB_ACTION_PATH/scripts/update.sh"`. Inputs pass through env
   (PACKAGES, VULNS_JSON); the script keeps `set -eo pipefail` to mirror the
   composite shell. No more inline-expression size ceiling, and the logic is
   now unit-testable.

2. When every constraint-blocked package is in extra.vuln-scan.no-widen, the
   widen-targets dedupe ran `grep -v '^$'` over empty input, which exits 1 and
   (under pipefail) aborted the step — dropping the PR even when other packages
   updated cleanly. Guard with `|| true` (same fix on find_direct_ancestors,
   whose empty-result grep had the identical property).

Add a regression test (composer-update/tests/no-widen-still-creates-pr.test.sh)
that drives the real script with a fake composer and asserts a safe package
still updates while a no-widen package is skipped without aborting. Verified
it fails on the pre-fix logic and passes after. Wire it into CI via
.github/workflows/composer-update-tests.yml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxyc oxyc merged commit 2e72ae9 into master May 21, 2026
1 check passed
@oxyc oxyc deleted the fix/composer-update-extract-script branch May 21, 2026 12:07
oxyc added a commit that referenced this pull request May 29, 2026
… (+ test suite) (#35)

* test(composer-update): unit + integration tests; extract helpers to lib.sh

The composer-update logic had no tests and was fragile (every recent fix —
#20#31 — was a production-only discovery). Add a real test suite and make
the logic testable.

- Extract the helper functions (build_pkg_arg, build_widen_arg,
  find_direct_ancestors, loosen_constraint, is_still_vulnerable,
  expand_args_for, get_lock_version) from update.sh into a sourceable
  scripts/lib.sh. update.sh sources it; behavior is unchanged (the existing
  no-widen integration test still passes).

- Unit tests for the PHP helpers (the fragile version-range logic):
  - compute-min-safe-constraints: exclusive/inclusive bounds, missing version
    components, multi-range selection, no-entry fall-throughs.
  - is-still-vulnerable: in/out of range, multi-range, junk-input fail-safe.

- Unit tests for the bash helpers, each tied to the edge case it guards:
  #27 build_pkg_arg trailing newline, #29 build_widen_arg caret widen,
  #28 loosen_constraint, #22/#26 find_direct_ancestors BFS + expand_args_for.

- Integration tests driving the real update.sh with a fake composer:
  #24 downgrade revert, #21 dev-* revert, #23 per-package retry isolation,
  #20 no-widen honored as a JSON array. (Plus the existing no-widen test.)

- Fix surfaced by the tests: compute-min-safe-constraints emitted `[]` for an
  empty result, but update.sh string-indexes that map with `jq '.[$pkg]'`,
  which errors on a JSON array under `set -eo pipefail`. Encode as `{}`.

- CI: composer-update-tests.yml now sets up PHP and runs tests/run.sh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(composer-update): inclusive-bound (<=) safe-version derivation skips 4-segment hotfixes

compute-min-safe-constraints derived ~X.Y.(Z+1) for a <=X.Y.Z advisory.
That skips a 4-segment hotfix like X.Y.Z.1, which is >X.Y.Z but
<X.Y.(Z+1): the constraint matched no published version, the update
no-opped, and the vuln went unpatched (no PR opened).

Real case: suomentyokalu / seo-by-rank-math, advisory <=1.0.271, fixed
in 1.0.271.1. The derived ~1.0.272 could not resolve.

Emit >X.Y.Z,<X.(Y+1).0 instead — a strict-greater lower bound that
matches the hotfix while still excluding the vulnerable boundary X.Y.Z,
still minor-capped. Teach loosen_constraint() and build_widen_arg() the
new range shape (major-cap widen/loosen, boundary stays excluded).

Tests: regression case asserting <=1.0.271 -> >1.0.271,<1.1.0 plus
Semver checks that 1.0.271.1 is reachable and 1.0.271 is excluded;
updated existing <= assertions; helper-fn coverage for the range shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: test <test@example.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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