Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ jobs:

- uses: gradle/actions/setup-gradle@v6

# The Angular CLI checks `process.version`, so the frontend runs on the Node the frontend
# image builds on rather than whatever the runner ships. `.nvmrc` holds the major for
# this step and for a local `nvm use`; `frontend/Dockerfile` pins the exact tag.
- uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'

# The frontend is bracketed into the Gradle build with plain `Exec` tasks calling
# bun, so bun has to exist before `check` runs. Version pinned to what
# `package.json` declares as its packageManager.
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
distribution: temurin
java-version: '25'
- uses: gradle/actions/setup-gradle@v6
- uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.4.4'
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ may change in any release. See the status note in the README.
turn or one past its deadline leaves the model call running (openai/openai-java#1027; 4.96.0 still does). The hold
moves from Spring AI to openai-java, so Spring AI's own fixes land: Anthropic streaming chunks carry the thinking
text, and the configured OpenAI timeout is honoured again. Renovate proposes openai-java in a pull request of its own.
- **The frontend image builds on Node 26** (`node:26.13.0-alpine`, from 24.22.1). The Angular CLI accepts
`>=26.0.0`; the build output is the same static files, served by the same nginx stage. A root `.nvmrc` names
the major, and CI and the release gate now set up that Node with `actions/setup-node` instead of building on
whatever the runner ships.
- **Angular 22.2.5 (CLI 22.2.6)** and the frontend's other dependencies to their current releases, among them
vitest 5.1, Playwright 1.65 and three majors: jsdom 31, stylelint 18 and stylelint-config-standard 41.
stylelint 18's new `selector-no-invalid` rejects Angular's `::ng-deep` and has no option to accept it, so it is
switched off — it did not exist before, so the check covers what it covered. jsdom 31 answers `none` instead of
`''` for a `grid-template-columns` nothing computed, and the shortlist now reads both as "no grid", which a
browser can answer too. TypeScript stays on 6.0, which is what Angular 22 accepts.
- **Backend: jsoup 1.24.1 and Spotless 8.11.0.** palantir-java-format 2.103.0 reformats four files and stays at
2.98.0 for a commit of its own; Gradle 9.9.0 waits for its `gradle:9.9.0-jdk25` image. openai-java 4.101.0 still
fails the four chat-stop tests, so the hold at 4.65.0 stays.

## [0.5.7] — 2026-10-31

Expand Down
5 changes: 5 additions & 0 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ tasks.withType<Test>().configureEach {
rootProject.file(".github/workflows/release.yml"),
).withPropertyName("bunPins")
.withPathSensitivity(PathSensitivity.RELATIVE)
// `NodePinTest` holds `.nvmrc` to the Dockerfile's Node major; the other three are above.
inputs
.files(rootProject.file(".nvmrc"))
.withPropertyName("nodePins")
.withPathSensitivity(PathSensitivity.RELATIVE)
inputs
.files(rootProject.fileTree("docs") { include("**/*.md") })
.withPropertyName("documentation")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2026 Marcello Muscara (codeministry)
*
* Licensed under the Apache License, Version 2.0. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0
*/
package de.codeministry.leadgen.config;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;

/**
* The Node the frontend runs on is named twice: the exact tag of the {@code node:...} build
* stage in {@code frontend/Dockerfile}, and the major in the root {@code .nvmrc}, which CI and
* the release gate read through {@code actions/setup-node}. Nothing else ties them together,
* so an Angular bump that moves the image's tag would leave the gate testing on the old major
* without a word. Same shape as {@link BunPinTest}.
*
* <p>The files are declared as inputs of the {@code test} task in {@code backend/build.gradle.kts},
* so an edit re-runs this.
*/
class NodePinTest {

private static final Path ROOT = ConfigFixtures.repositoryRoot();

private static final Pattern IMAGE_MAJOR =
Pattern.compile("FROM --platform=\\$BUILDPLATFORM node:([0-9]+)\\.[0-9.]+-");

private static final Pattern NODE_VERSION_FILE = Pattern.compile("node-version-file:\\s*'\\.nvmrc'");

@Test
void nvmrcNamesTheMajorTheFrontendImageBuildsOn() {
Matcher image = IMAGE_MAJOR.matcher(read(ROOT.resolve("frontend/Dockerfile")));
assertThat(image.find()).as("a node build stage in frontend/Dockerfile").isTrue();

assertThat(read(ROOT.resolve(".nvmrc")).strip())
.as(".nvmrc against node:%s in frontend/Dockerfile", image.group(1))
.isEqualTo(image.group(1));
}

@Test
void bothGatesSetUpTheNodeNamedInNvmrc() {
for (String workflow : List.of(".github/workflows/ci.yml", ".github/workflows/release.yml")) {
assertThat(NODE_VERSION_FILE.matcher(read(ROOT.resolve(workflow))).find())
.as("%s sets up Node from .nvmrc", workflow)
.isTrue();
}
}

private static String read(Path file) {
try {
return Files.readString(file, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
1 change: 1 addition & 0 deletions frontend/.stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"declaration-property-value-disallowed-list": {
"/^(transition|animation)(-duration|-delay)?$/": ["/\\b[1-9][0-9]*m?s\\b/"]
},
"selector-no-invalid": null,
"selector-pseudo-element-no-unknown": [
true,
{
Expand Down
5 changes: 3 additions & 2 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# below the CLI's minimum. So this stage runs on the Node the CLI wants and carries the bun
# that `packageManager` pins, both by exact tag. When an Angular bump raises the CLI's Node
# floor (`engines.node` in node_modules/@angular/cli/package.json), the Node tag here moves
# with it; CI builds on the runner's Node and will not notice. `BunPinTest` holds the bun
# with it, and so does the major in `.nvmrc`, which is what CI and the release gate run on
# (`actions/setup-node`) and nothing checks against this tag. `BunPinTest` holds the bun
# tag equal to `packageManager` and the two workflows.
# `$BUILDPLATFORM`: the build output is static files, the same for every architecture, so the
# stage runs once on the runner's own platform. Built per target, the arm64 pass ran under
Expand All @@ -13,7 +14,7 @@
# `bun install` died with exit 255.
FROM --platform=$BUILDPLATFORM oven/bun:1.4.4-alpine AS bun

FROM --platform=$BUILDPLATFORM node:24.22.1-alpine AS build
FROM --platform=$BUILDPLATFORM node:26.13.0-alpine AS build
COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun
WORKDIR /app
COPY frontend/package.json frontend/bun.lock ./
Expand Down
Loading
Loading