Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ca779f7
Move jev4k to Kotlin Multiplatform
pambrose Sep 27, 2026
8c89c87
Simplify the multiplatform code, build and CI
pambrose Sep 27, 2026
3a58868
Keep CIO out of the JVM platform class initializer, and add a code re…
pambrose Sep 28, 2026
9f33416
Gate merges on every CI job and deploy the docs site only on release
pambrose Sep 28, 2026
79269a0
Map every request failure to a JevException, and keep cancellations i…
pambrose Sep 28, 2026
fddf01a
Lower the JSON depth limit to 128 for Windows' 1 MB main-thread stack
pambrose Sep 28, 2026
a4a1e55
Validate the configuration when it's built, without echoing secrets
pambrose Sep 28, 2026
796f6db
Settle the public API for 0.2.0: per-call options, Java reach, serial…
pambrose Sep 28, 2026
aa19116
Tighten DSL validation and response mapping, and report mapping error…
pambrose Sep 28, 2026
b393013
Harden the HTTP layer: no redirects, one Accept, dated retry hints, a…
pambrose Sep 28, 2026
cfd84f0
Close test gaps: shared body parsing, a real environment probe, stric…
pambrose Sep 28, 2026
2597421
Run linuxArm64's tests in CI, and tidy the build and CI configuration
pambrose Sep 28, 2026
604b8ea
Correct the documentation: logging, retry defaults, Node.js, and Java…
pambrose Sep 28, 2026
9680e0f
Fix the website examples so they do what their pages say
pambrose Sep 28, 2026
e61f133
Simplify the review fixes: shared error and send checks, fewer specia…
pambrose Sep 28, 2026
bac77ba
Drop Ktor's ContentNegotiation, date 0.2.0, and correct the release d…
pambrose Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ ktlint_standard_import-ordering = disabled

# The documentation examples line their trailing comments up in a column, which reads better on the docs site
# than ragged comments do. ktlint's no-multi-spaces rule forbids that, so it is off for these files only; the
# library sources in src/main keep it.
[src/test/kotlin/website/*.kt]
# library sources in src/commonMain and the other main source sets keep it.
[src/jvmTest/kotlin/website/*.kt]
ktlint_standard_no-multi-spaces = disabled

[build/generated/**/*]
Expand Down
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Copy to .env (gitignored) and fill in. The com.pambrose.envvar plugin loads these into the
# environment of every Test and JavaExec task, so `make example` and `make live-tests` pick them up.
# environment of every Test and JavaExec task, which are the JVM ones, so `make example` and the JVM half of
# `make live-tests` pick them up. The js, wasmJs and native test tasks see only the shell's environment.
TYPESAFE_API_KEY=ts-...
# Optional overrides; the defaults are https://api.typesafe.ai and jev-latest.
# TYPESAFE_BASE_URL=https://api.typesafe.ai
# TYPESAFE_DEFAULT_MODEL=jev-latest

# Don't put JEV4K_LIVE here. It is what enables LiveSmokeTest, and because this file reaches every test
# Don't put JEV4K_LIVE here. It is what enables LiveSmokeTest, and because this file reaches the JVM test
# task, setting it would turn each `make tests` into a run that spends tokens. `make live-tests` sets it
# for that one invocation, which is the only place it belongs.
3 changes: 2 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# Dependabot rewrites the SHA and the trailing version comment together, so the pins keep receiving fixes.
#
# Gradle dependencies are deliberately not listed here: the version catalog is reviewed with `make versions`
# (the ben-manes plugin), which reports updates without opening pull requests.
# (the ben-manes plugin), which reports updates without opening pull requests. The docs site's website/uv.lock is
# left out for the same reason: `make check-site` reports its updates and `make upgrade-site` applies them.
version: 2
updates:
- package-ecosystem: github-actions
Expand Down
117 changes: 108 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ on:
pull_request:
workflow_dispatch:

# Supersede a PR's earlier run when it is pushed to again; every master commit is still built.
# Supersede a PR's earlier run when it is pushed to again. On master nothing is cancelled, but GitHub keeps only one
# pending run per group, so a burst of pushes can skip a queued middle commit; the latest one is always built.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
Expand All @@ -16,8 +17,11 @@ permissions:
contents: read

jobs:
# Linux runs the jvm, js, wasmJs and linuxX64 tests, and linuxArm64's under QEMU. It can't run the Apple or
# Windows test executables, so the native job below covers those.
build:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# JDK 25 is the toolchain in the catalog.
Expand All @@ -26,15 +30,37 @@ jobs:
distribution: temurin
java-version: 25
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
# setup-gradle caches ~/.gradle but not the Kotlin/Native toolchain that compiling the native targets
# downloads. The toolchain depends only on the Kotlin version, so that is the whole key: an exact hit, or
# a fresh download saved under the new version. No restore-keys, which would carry every older toolchain
# forward into each new entry.
- id: kotlin
shell: bash
run: |
version=$(sed -n 's/^kotlin = "\(.*\)"/\1/p' gradle/libs.versions.toml)
test -n "$version"
echo "version=$version" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.konan
key: konan-${{ runner.os }}-${{ steps.kotlin.outputs.version }}

# build runs check without the tests, and check already includes lintKotlin and detekt.
- name: Compile, lint, and detekt (no tests)
run: ./gradlew clean build -x test
# build runs check, which compiles every target this host supports and runs lintKotlin, detekt, the ABI
# check against api/, and the jvm, js, wasmJs and linuxX64 tests. --continue lets independent tasks run
# after a failure, but Kover's reports depend on jvmTest, so a JVM test failure leaves no coverage to
# upload. The live tests stay disabled: they need JEV4K_LIVE=1 (and LiveSmokeTest a TYPESAFE_API_KEY),
# and neither is set here.
- name: Build, lint, check the ABI, run the tests, and generate the coverage report
run: ./gradlew --continue build koverVerify koverXmlReport koverLog

# --continue lets the Kover report tasks run even if some tests fail, so coverage is still uploaded.
# LiveSmokeTest stays disabled: it needs JEV4K_LIVE=1 and TYPESAFE_API_KEY, neither of which is set here.
- name: Run tests and generate coverage report
run: ./gradlew --continue test koverVerify koverXmlReport koverLog
# linuxArm64 is published but has no Gradle test task, and this runner can't execute its binary. QEMU lets
# Docker run it, so the same make target that tests it locally tests it here; linuxX64's tests already ran
# in the step above.
- uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
with:
platforms: arm64
- name: Run the linuxArm64 tests in Docker
run: make docker-linux-tests LINUX_TEST_TARGETS=linuxArm64:arm64

- name: Upload test reports on failure
if: failure()
Expand Down Expand Up @@ -63,6 +89,7 @@ jobs:
test:
name: Tests on JDK ${{ matrix.java }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
# One JDK failing shouldn't hide the result on the others.
fail-fast: false
Expand All @@ -88,7 +115,7 @@ jobs:
# shortcut rather than a requirement.
- name: Run tests on JDK ${{ matrix.java }}
run: >
./gradlew test -PtestJavaVersion=${{ matrix.java }}
./gradlew jvmTest -PtestJavaVersion=${{ matrix.java }}
"-Porg.gradle.java.installations.fromEnv=JAVA_HOME_${{ matrix.java }}_X64,JAVA_HOME_25_X64"

- name: Upload test reports on failure
Expand All @@ -100,3 +127,75 @@ jobs:
build/reports/tests/
build/test-results/
retention-days: 7

# The native tests Linux can't run: the macOS and the iOS, tvOS and watchOS simulator tests on macOS (Gradle skips
# iosX64Test on the arm64 runner, and a tvOS or watchOS test when the runner has no simulator device for it), and
# the mingwX64 tests on Windows.
native:
name: Native tests on ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
# One OS failing shouldn't hide the result on the other.
fail-fast: false
matrix:
include:
- name: apple
os: macos-latest
tasks: macosArm64Test iosSimulatorArm64Test tvosSimulatorArm64Test watchosSimulatorArm64Test
- name: windows
os: windows-latest
tasks: mingwX64Test
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: 25
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
# setup-gradle caches ~/.gradle but not the Kotlin/Native toolchain that compiling the native targets
# downloads. The toolchain depends only on the Kotlin version, so that is the whole key: an exact hit, or
# a fresh download saved under the new version. No restore-keys, which would carry every older toolchain
# forward into each new entry.
- id: kotlin
shell: bash
run: |
version=$(sed -n 's/^kotlin = "\(.*\)"/\1/p' gradle/libs.versions.toml)
test -n "$version"
echo "version=$version" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.konan
key: konan-${{ runner.os }}-${{ steps.kotlin.outputs.version }}

- name: Run the native tests
run: ./gradlew ${{ matrix.tasks }}

- name: Upload test reports on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-reports-${{ matrix.name }}
path: |
build/reports/tests/
build/test-results/
retention-days: 7

# The one CI check branch protection requires (with the docs site's "docs" check and GitGuardian). It passes only
# when every job above succeeded, so the JDK matrix and both native jobs gate a merge too, and renaming a job or a
# matrix entry can't silently drop a required check.
ci-ok:
if: always()
needs: [ build, test, native ]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Fail unless every CI job succeeded
env:
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
echo "Job results: $RESULTS"
for result in $RESULTS; do [ "$result" = success ] || exit 1; done
59 changes: 45 additions & 14 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
name: Documentation
on:
# Pull requests and pushes to master only build the site, so a broken snippet, link or Dokka setting fails a
# check before it merges.
pull_request:
push:
branches:
- master
# The site is deployed only when a release is published, after its artifacts are on Maven Central, or on a
# manual run, so jev4k.com never shows install instructions for a version Central doesn't have yet. A release
# runs on its tag, so the github-pages environment allows release tags as well as master.
release:
types: [ published ]
workflow_dispatch:

# Least privilege by default; each job asks for what it needs.
permissions:
contents: read

# Deploys share one group and are never cancelled. Build-only runs get a group per ref, so a newer push to a PR
# supersedes its older run, and no build-only run can replace a deploy waiting in the queue.
concurrency:
group: pages
cancel-in-progress: false
group: >-
${{ (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
&& 'pages' || format('docs-{0}', github.ref) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# Build the docs site + KDocs and upload the Pages artifact. Kept separate from
# the deploy job so that recovering a failed deploy means re-running only the
# deploy job (via "Re-run failed jobs"), which does NOT re-upload the artifact.
# actions/deploy-pages hard-fails when a run contains more than one artifact
# named "github-pages", which is what re-running a combined build+deploy job did.
build:
# Build the docs site + KDocs, and upload the Pages artifact when deploying. Kept separate from the deploy job
# so that recovering a failed deploy means re-running only the deploy job (via "Re-run failed jobs"), which does
# NOT re-upload the artifact. actions/deploy-pages hard-fails when a run contains more than one artifact named
# "github-pages", which is what re-running a combined build+deploy job did. Its check, "docs", is required by
# branch protection.
docs:
runs-on: ubuntu-latest
permissions:
contents: read
pages: read
env:
DEPLOY: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' }}
steps:
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
if: env.DEPLOY == 'true'
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# Build the Zensical documentation site from website/uv.lock, the same resolution `make site`
# uses locally. uv reads website/.python-version and installs that Python itself.
# uses locally. uv reads website/.python-version and installs that Python itself. --strict turns
# warnings, such as a link to a missing anchor, into failures.
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
- run: uv run --locked zensical build --clean
- run: uv run --locked zensical build --clean --strict
working-directory: website/jev4k

# Build KDoc API documentation (the toolchain in the catalog is JDK 25).
Expand All @@ -40,20 +56,35 @@ jobs:
distribution: temurin
java-version: 25
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
# Dokka documents every source set, the native ones included, so it needs the Kotlin/Native toolchain;
# setup-gradle doesn't cache it. This only restores ci.yml's cache, under the same key: Dokka finishes
# first and never downloads the compiler's dependencies, so a save here would claim the key with a
# toolchain too incomplete for the build job, which would then never save its own.
- id: kotlin
run: |
version=$(sed -n 's/^kotlin = "\(.*\)"/\1/p' gradle/libs.versions.toml)
test -n "$version"
echo "version=$version" >> "$GITHUB_OUTPUT"
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.konan
key: konan-${{ runner.os }}-${{ steps.kotlin.outputs.version }}
- run: ./gradlew dokkaGeneratePublicationHtml

# Copy KDocs into the Zensical site output; the site's "KDocs" page links to /kdocs/.
- run: cp -r build/dokka/html website/jev4k/site/kdocs

- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
if: env.DEPLOY == 'true'
with:
path: website/jev4k/site

# Deploy the artifact produced by the build job. Deliberately minimal (no
# checkout or build) so re-running just this job re-deploys the existing
# artifact without rebuilding or re-uploading it.
# Deploy the artifact produced by the docs job, for a published release or a manual run only. Deliberately
# minimal (no checkout or build) so re-running just this job re-deploys the existing artifact without
# rebuilding or re-uploading it.
deploy:
needs: build
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
needs: docs
runs-on: ubuntu-latest
permissions:
pages: write
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ secrets/
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea/artifacts/
*.iws
*.iml
*.ipr
Expand Down Expand Up @@ -53,6 +54,10 @@ __pycache__/
### it. This negation is what keeps it in git, because a global *.lock ignore would otherwise hide it.
!website/uv.lock

### The Node.js lockfiles for the js and wasmJs test toolchains are committed too (the same global *.lock ignore
### would hide them). Regenerate them with kotlinUpgradeYarnLock and kotlinWasmUpgradeYarnLock.
!kotlin-js-store/**/yarn.lock

### Zensical build output ###
website/**/site/
website/**/.cache/
Expand Down
Loading
Loading