Skip to content
Open
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
119 changes: 97 additions & 22 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ jobs:

- uses: actions/setup-node@v6
with:
# No `cache: npm` here — this job caches the resolved node_modules tree directly
# below, and both caches key off the same lockfile, so the npm download cache only
# ever hits when node_modules already hit. It was pure repo cache-quota spend.
node-version: 24
cache: npm

- name: Restore node_modules
id: node-modules
Expand Down Expand Up @@ -153,8 +155,10 @@ jobs:

- uses: actions/setup-node@v6
with:
# No `cache: npm` here — this job caches the resolved node_modules tree directly
# below, and both caches key off the same lockfile, so the npm download cache only
# ever hits when node_modules already hit. It was pure repo cache-quota spend.
node-version: 24
cache: npm

- name: Restore node_modules
id: node-modules
Expand Down Expand Up @@ -258,22 +262,81 @@ jobs:
- name: Apply the fresh target database baseline
run: psql "$DATABASE_URL" -X --set=ON_ERROR_STOP=1 --file=apps/opencrane/prisma/bootstrap/target-baseline.sql

# Restored before the first Nx invocation so the SQL suites replay too. The save half
# is a separate step at the end of the job: actions/cache only writes its cache when the
# job succeeded, and a run that fails late would otherwise leave the next run cold.
- name: Restore the Nx computation cache
uses: actions/cache/restore@v4
with:
path: .nx/cache
key: nx-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.run_id }}
restore-keys: |
nx-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-

# Every suite validates the shared target baseline. A baseline-only change belongs to the
# opencrane app and therefore cannot make the library-owned SQL suites "affected" in Nx.
- name: Run every PostgreSQL authority suite
run: npx nx run-many -t test:sql --parallel=1

- name: Restore the Nx computation cache
- name: Build, test, and lint affected projects
run: npx nx affected -t build test lint

- name: Enforce monorepo dependency boundaries
run: npm run lint:boundaries

- name: Verify API reference and generated client when affected
if: needs.prepare.outputs.api_contract_changed == 'true'
run: |
npx nx run opencrane:build
npm run sync-openapi -w @opencrane/website
git diff --exit-code -- website/public/openapi.json
npx nx run contracts:generate
git diff --exit-code -- libs/contracts/src/generated/api.ts

# Saved even when the job failed. A failing guard says nothing about the build/test/lint
# tasks that already passed, and discarding their results made every later run start cold.
- name: Save the Nx computation cache
if: always()
uses: actions/cache/save@v4
with:
path: .nx/cache
key: nx-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-test-${{ github.run_id }}

storybook:
name: Storybook component contracts
runs-on: ubuntu-latest
needs: prepare
env:
NX_BASE: ${{ needs.prepare.outputs.nx_base }}
NX_HEAD: ${{ needs.prepare.outputs.nx_head }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: actions/setup-node@v6
with:
node-version: 24

- name: Restore node_modules
id: node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-node24-${{ hashFiles('package-lock.json') }}

- name: Install dependencies
if: steps.node-modules.outputs.cache-hit != 'true'
run: npm ci

- name: Restore the Nx computation cache
uses: actions/cache/restore@v4
with:
path: .nx/cache
key: nx-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ github.sha }}
key: nx-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-storybook-${{ github.run_id }}
restore-keys: |
nx-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-

- name: Build, test, and lint affected projects
run: npx nx affected -t build test lint

- name: Detect affected Storybook regression coverage
id: storybook-regression
shell: bash
Expand All @@ -285,10 +348,21 @@ jobs:
echo "required=false" >> "$GITHUB_OUTPUT"
fi

# Keyed on the lockfile, which pins the Playwright version the browser build must match.
- name: Restore the Chromium build
id: playwright-browsers
if: steps.storybook-regression.outputs.required == 'true'
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- name: Install Chromium for affected Storybook regressions
if: steps.storybook-regression.outputs.required == 'true'
run: npx playwright install --with-deps chromium

# --parallel=1 is required, not conservative: test-storybook and test-storybook-visual
# both serve the catalogue on port 4400, so running them at once makes them fight for it.
- name: Build and test affected Storybook component contracts
if: steps.storybook-regression.outputs.required == 'true'
run: npx nx affected -t build-storybook test-storybook test-storybook-visual --base="$NX_BASE" --head="$NX_HEAD" --parallel=1
Expand All @@ -303,17 +377,12 @@ jobs:
if-no-files-found: ignore
retention-days: 7

- name: Enforce monorepo dependency boundaries
run: npm run lint:boundaries

- name: Verify API reference and generated client when affected
if: needs.prepare.outputs.api_contract_changed == 'true'
run: |
npx nx run opencrane:build
npm run sync-openapi -w @opencrane/website
git diff --exit-code -- website/public/openapi.json
npx nx run contracts:generate
git diff --exit-code -- libs/contracts/src/generated/api.ts
- name: Save the Nx computation cache
if: always()
uses: actions/cache/save@v4
with:
path: .nx/cache
key: nx-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-storybook-${{ github.run_id }}

develop_smoke:
name: k3d current-silo smoke test
Expand All @@ -339,8 +408,10 @@ jobs:

- uses: actions/setup-node@v6
with:
# No `cache: npm` here — this job caches the resolved node_modules tree directly
# below, and both caches key off the same lockfile, so the npm download cache only
# ever hits when node_modules already hit. It was pure repo cache-quota spend.
node-version: 24
cache: npm

# The production deploy wrapper resolves and schema-validates the database transition before
# it mutates the cluster. The smoke job is a fresh checkout, so restore the same exact
Expand Down Expand Up @@ -410,8 +481,10 @@ jobs:

- uses: actions/setup-node@v6
with:
# No `cache: npm` here — this job caches the resolved node_modules tree directly
# below, and both caches key off the same lockfile, so the npm download cache only
# ever hits when node_modules already hit. It was pure repo cache-quota spend.
node-version: 24
cache: npm

- name: Restore node_modules
id: node-modules
Expand All @@ -432,12 +505,13 @@ jobs:
build-and-push:
name: Build and publish affected images
runs-on: ubuntu-latest
needs: [prepare, test, develop_smoke, image_smoke]
needs: [prepare, test, storybook, develop_smoke, image_smoke]
if: >-
${{
always() &&
needs.prepare.outputs.has_deployables == 'true' &&
needs.test.result == 'success' &&
(needs.storybook.result == 'success' || needs.storybook.result == 'skipped') &&
(needs.develop_smoke.result == 'success' || needs.develop_smoke.result == 'skipped') &&
(needs.image_smoke.result == 'success' || needs.image_smoke.result == 'skipped')
}}
Expand Down Expand Up @@ -484,13 +558,14 @@ jobs:
publish-develop-smoke-images:
name: Publish develop smoke image (${{ matrix.project }})
runs-on: ubuntu-latest
needs: [prepare, test, develop_smoke, image_smoke, build-and-push]
needs: [prepare, test, storybook, develop_smoke, image_smoke, build-and-push]
if: >-
${{
always() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/develop' &&
needs.test.result == 'success' &&
(needs.storybook.result == 'success' || needs.storybook.result == 'skipped') &&
needs.develop_smoke.result == 'success' &&
(needs.image_smoke.result == 'success' || needs.image_smoke.result == 'skipped') &&
(needs['build-and-push'].result == 'success' || needs['build-and-push'].result == 'skipped')
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified apps/_infra/deploy-k8s/charts/opencrane-channel-proxy-0.8.0.tgz
Binary file not shown.
Binary file modified apps/_infra/deploy-k8s/charts/opencrane-memory-gateway-0.8.0.tgz
Binary file not shown.
Binary file modified apps/_infra/deploy-k8s/charts/opencrane-server-0.8.0.tgz
Binary file not shown.
65 changes: 53 additions & 12 deletions apps/_infra/deploy-k8s/platform/tests/develop-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ SMOKE_AFFECTED_PROJECTS="${SMOKE_AFFECTED_PROJECTS-all}"
SMOKE_BASE_SHA="${SMOKE_BASE_SHA:-}"
SMOKE_REGISTRY="${SMOKE_REGISTRY:-ghcr.io/elewa-git}"
SMOKE_STORAGE_MODE="${SMOKE_STORAGE_MODE:-full}"
# How many images to build or pull at once. Three keeps the network and disk busy without
# starving the two cores a hosted runner gives us; raise it on a bigger machine.
SMOKE_BUILD_CONCURRENCY="${SMOKE_BUILD_CONCURRENCY:-3}"
KEY_DIR=""
CSI_DIR=""
IMAGE_PREPARATION_PID=""
Expand Down Expand Up @@ -201,18 +204,56 @@ _prepare_image()
fi
}

# Pending image jobs and the file each one's output is collected into.
_PREPARE_PIDS=()
_PREPARE_LOGS=()

# Wait for the images started so far, then replay their output one image at a time.
# Concurrent builds interleave their progress lines, so each job writes to its own
# file and it is printed whole — the log stays readable when a build fails.
_await_image_batch()
{
local index status=0
for index in "${!_PREPARE_PIDS[@]}"; do
if ! wait "${_PREPARE_PIDS[$index]}"; then
status=1
fi
cat "${_PREPARE_LOGS[$index]}"
rm -f "${_PREPARE_LOGS[$index]}"
done
_PREPARE_PIDS=()
_PREPARE_LOGS=()
return "$status"
}

# Every image is independent, so prepare several at once. Run one at a time they were by far
# the longest phase of the smoke. The fan-out stays capped because the runner's Docker daemon
# is small — all five at once would make them fight for the same two cores.
_prepare_images()
{
_prepare_image opencrane opencrane/opencrane-server:develop-smoke \
opencrane-server apps/opencrane/deploy/Dockerfile
_prepare_image opencrane-ui opencrane/opencrane-ui:develop-smoke \
opencrane-ui apps/opencrane-ui/deploy/Dockerfile
_prepare_image channel-proxy opencrane/channel-proxy:develop-smoke \
opencrane-channel-proxy apps/channel-proxy/deploy/Dockerfile
_prepare_image memory-gateway opencrane/memory-gateway:develop-smoke \
opencrane-memory-gateway apps/memory-gateway/deploy/Dockerfile
_prepare_image artifact-service opencrane/artifact-service:develop-smoke \
opencrane-artifact-service apps/artifact-service/deploy/Dockerfile
local -a specs=(
"opencrane opencrane/opencrane-server:develop-smoke opencrane-server apps/opencrane/deploy/Dockerfile"
"opencrane-ui opencrane/opencrane-ui:develop-smoke opencrane-ui apps/opencrane-ui/deploy/Dockerfile"
"channel-proxy opencrane/channel-proxy:develop-smoke opencrane-channel-proxy apps/channel-proxy/deploy/Dockerfile"
"memory-gateway opencrane/memory-gateway:develop-smoke opencrane-memory-gateway apps/memory-gateway/deploy/Dockerfile"
"artifact-service opencrane/artifact-service:develop-smoke opencrane-artifact-service apps/artifact-service/deploy/Dockerfile"
)
local spec log status=0

for spec in "${specs[@]}"; do
log="$(mktemp)"
_PREPARE_LOGS+=("$log")
# Word splitting is intended: a spec is four fields, none of which contains a space.
# shellcheck disable=SC2086
( _prepare_image $spec ) >"$log" 2>&1 &
_PREPARE_PIDS+=("$!")
if [[ "${#_PREPARE_PIDS[@]}" -ge "$SMOKE_BUILD_CONCURRENCY" ]]; then
_await_image_batch || status=1
fi
done

_await_image_batch || status=1
return "$status"
}

_create_database_credentials()
Expand Down Expand Up @@ -405,8 +446,8 @@ if [[ "$SMOKE_STORAGE_MODE" != "fast" && "$SMOKE_STORAGE_MODE" != "full" ]]; the
fi

# Image preparation is the longest independent lane. Start it before k3d so cluster creation and
# external-controller readiness consume the same wall-clock time without fanning out five builds
# against the runner's small Docker daemon.
# external-controller readiness consume the same wall-clock time. The lane builds a few images at
# a time (SMOKE_BUILD_CONCURRENCY) rather than all of them, so it never swamps the small daemon.
_prepare_images &
IMAGE_PREPARATION_PID=$!

Expand Down
2 changes: 1 addition & 1 deletion apps/agent-controller/deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ FROM node:22-bookworm-slim AS build

WORKDIR /app
COPY package.json package-lock.json tsconfig.json nx.json ./
COPY libs libs
COPY apps/agent-controller/package.json apps/agent-controller/project.json apps/agent-controller/tsconfig.json apps/agent-controller/
RUN npm ci
COPY libs libs

COPY apps/agent-controller/src apps/agent-controller/src
RUN npx nx run agent-controller:build
Expand Down
2 changes: 1 addition & 1 deletion apps/agent-controller/helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ name: opencrane-agent-controller
description: App-owned named-template library for the personal-runtime workload controller.
type: library
version: 0.8.0
appVersion: "0.8.0"
appVersion: "0.8.1"
2 changes: 1 addition & 1 deletion apps/agent-controller/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencrane/agent-controller",
"version": "0.8.0",
"version": "0.8.1",
"private": true,
"license": "AGPL-3.0-or-later",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion apps/agent-controller/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "agent-controller",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"metadata": { "release": { "adaptedVersion": "0.8.0" } },
"metadata": { "release": { "adaptedVersion": "0.8.1" } },
"sourceRoot": "apps/agent-controller/src",
"tags": ["type:app", "layer:entrypoint", "scope:agent-controller"],
"targets": {
Expand Down
2 changes: 1 addition & 1 deletion apps/artifact-preprocessor/deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ FROM node:22-bookworm-slim AS build

WORKDIR /app
COPY package.json package-lock.json tsconfig.json nx.json ./
COPY libs libs
COPY apps/artifact-preprocessor/package.json apps/artifact-preprocessor/project.json apps/artifact-preprocessor/tsconfig.json apps/artifact-preprocessor/
RUN npm ci
COPY libs libs

COPY apps/artifact-preprocessor/src apps/artifact-preprocessor/src
RUN npx nx run artifact-preprocessor:build
Expand Down
2 changes: 1 addition & 1 deletion apps/artifact-preprocessor/helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ name: opencrane-artifact-preprocessor
description: App-owned named-template library for the isolated PDF preprocessing worker.
type: library
version: 0.8.0
appVersion: "0.8.0"
appVersion: "0.8.1"
2 changes: 1 addition & 1 deletion apps/artifact-preprocessor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencrane/artifact-preprocessor",
"version": "0.8.0",
"version": "0.8.1",
"private": true,
"license": "AGPL-3.0-or-later",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion apps/artifact-preprocessor/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "artifact-preprocessor",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"metadata": { "release": { "adaptedVersion": "0.8.0" } },
"metadata": { "release": { "adaptedVersion": "0.8.1" } },
"sourceRoot": "apps/artifact-preprocessor/src",
"tags": ["type:app", "layer:entrypoint", "scope:artifacts"],
"targets": {
Expand Down
2 changes: 1 addition & 1 deletion apps/artifact-scanner/deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
FROM node:22-bookworm-slim@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436 AS build
WORKDIR /app
COPY package.json package-lock.json tsconfig.json nx.json ./
COPY libs libs
COPY apps/artifact-scanner/package.json apps/artifact-scanner/project.json apps/artifact-scanner/tsconfig.json apps/artifact-scanner/
RUN npm ci
COPY libs libs
COPY apps/artifact-scanner/src apps/artifact-scanner/src
RUN npx nx run artifact-scanner:build

Expand Down
2 changes: 1 addition & 1 deletion apps/artifact-scanner/helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ name: opencrane-artifact-scanner
description: App-owned named-template library for the isolated malware-scanning worker.
type: library
version: 0.8.0
appVersion: "0.8.0"
appVersion: "0.8.1"
2 changes: 1 addition & 1 deletion apps/artifact-scanner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencrane/artifact-scanner",
"version": "0.8.0",
"version": "0.8.1",
"private": true,
"license": "AGPL-3.0-or-later",
"type": "module",
Expand Down
Loading
Loading