Skip to content

ci: run tests and linting on pull requests - #12

Merged
pannoury merged 3 commits into
mainfrom
ci/add-pr-checks-and-linting
Aug 8, 2026
Merged

ci: run tests and linting on pull requests#12
pannoury merged 3 commits into
mainfrom
ci/add-pr-checks-and-linting

Conversation

@pannoury

@pannoury pannoury commented Aug 8, 2026

Copy link
Copy Markdown
Member

Why

Nothing ran on pull requests. Tests only executed inside the publish workflows, after a merge to main — so a broken change was caught while it was being released, not before. There was also no linting anywhere.

What changed

A new CI workflow runs on every pull request and on pushes to main. It reuses the release workflow's paths-filter, so a PR that only touches python/ doesn't spin up Node and PHP runners. A final ci job aggregates the three and is the one to mark as a required check in branch protection — it stays green when a language is skipped for being untouched.

Per language:

Lint Static analysis Tests
JavaScript oxlint tsc --noEmit vitest on Node 20, 22
Python ruff check + ruff format --check mypy, strict pytest on 3.9, 3.13
PHP PHP_CodeSniffer (PSR-12) PHPStan level 8 PHPUnit on 8.1, 8.4

Lint and type checks run once per language, on the newest runtime — older versions only need to prove the tests pass.

On the JavaScript linter

typescript-eslint is the obvious choice and it does not work here: its peer range is typescript >=4.8.4 <6.1.0, and this package is on TypeScript 7. npm install refuses outright. oxlint has no TypeScript peer dependency, covers the same correctness rules, and runs in milliseconds. If the repo ever moves back to TypeScript 5.x, swapping to typescript-eslint is a small change.

no-await-in-loop is disabled — the retry loop awaits sequentially by design. require-await is disabled on EmailService.send with a comment, because async there is what turns its argument validation into a rejected promise rather than a synchronous throw.

Fixes found along the way

The JavaScript build was already broken on main. tsup --dts loads rollup-plugin-dts, which reads the TypeScript compiler API and crashes on TypeScript 7 (Cannot read properties of undefined (reading 'useCaseSensitiveFileNames')). Since npm run build only ran during publish, this would have failed the next release. Declarations now come from tsc --emitDeclarationOnly via a tsconfig.build.json that excludes test files. Verified by type-checking a consumer against the emitted dist/ under node16 resolution.

A PHP test asserted the wrong thing. testAcceptsArrayRecipients asserted on the mocked response body, which is fixed by the test itself — it passed no matter how recipients were serialised. It now asserts on the request payload, which is what the test is named for.

Baseline cleanup

Bringing three linters to zero touched a fair number of files, all mechanical:

  • Python: docstrings on the remaining __init__ methods, Optional[X]X | None (safe: every module has from __future__ import annotations), and explicit types where strict mypy could not follow Any through the transport.
  • PHP: non-empty-string on the HTTP method parameter, array value types on test helpers, \CurlHandle on the header callback, and three PSR-12 brace placements.

Verified locally

Python 60 passed, ruff and mypy clean. JavaScript 55 passed, oxlint, tsc --noEmit and build clean. PHP 56 passed / 86 assertions, phpcs and PHPStan clean.

Adds a CI workflow that runs on every pull request and on pushes to main.
It uses the same paths-filter as the release workflow, so only the language
directories a PR touches are exercised, and aggregates into a single `ci`
check that can be made required for branch protection.

Each language now has lint, static analysis and tests:

- JavaScript: oxlint, `tsc --noEmit`, build, vitest, on Node 20 and 22.
  typescript-eslint was not an option — its peer range stops at TypeScript
  <6.1.0 and this package is on TypeScript 7.
- Python: ruff (lint + format check) and mypy in strict mode, tests on 3.9
  and 3.13. Lint and type checks run once, on the newest runtime.
- PHP: PHP_CodeSniffer against PSR-12 and PHPStan at level 8, on PHP 8.1
  and 8.4.

Getting to a clean baseline required a handful of code changes: docstrings
on the remaining `__init__` methods, `Optional[X]` rewritten as `X | None`,
narrower types on the PHP transport and test helpers, and a couple of
brace-placement fixes.

Also fixes the JavaScript build, which was already failing on main: tsup's
`--dts` flag loads rollup-plugin-dts, which cannot read TypeScript 7's
compiler API. Declarations are now emitted by `tsc` directly, via a build
tsconfig that excludes the test files.

One test assertion changed: testAcceptsArrayRecipients asserted on the
mocked response rather than the request, so it passed regardless of how
recipients were serialised. It now checks the sent payload.
@pannoury
pannoury merged commit 3ab5d49 into main Aug 8, 2026
8 checks passed
@pannoury
pannoury deleted the ci/add-pr-checks-and-linting branch August 8, 2026 15:33
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