Skip to content

fix(dart): port nullable referenced field codecs from TypeScript #586

fix(dart): port nullable referenced field codecs from TypeScript

fix(dart): port nullable referenced field codecs from TypeScript #586

Workflow file for this run

name: Go CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
permissions:
contents: read
pull-requests: read
security-events: write
jobs:
# Build and test
# Gating test legs: ubuntu + macos. Runs through the shared workflow, which
# provisions the Node toolchain the generated-client runtime tests need.
test:
uses: xraph/workflows/.github/workflows/go-ci.yml@v1
with:
go-versions: '["1.26"]'
os: '["ubuntu-latest","macos-latest"]'
only-test: true
node-version: "24"
npm-global-packages: "typescript@5.8.2 esbuild@0.28.1"
# `make test` sweeps every module in the repo; CI deliberately tests only
# the root module and leaves the rest to build-all-modules.
prefer-makefile: false
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# Windows is optional and must never block a merge - ci-summary deliberately
# leaves this out of its gate, matching the pre-migration policy.
test-windows:
uses: xraph/workflows/.github/workflows/go-ci.yml@v1
with:
go-versions: '["1.26"]'
os: '["windows-latest"]'
only-test: true
node-version: "24"
npm-global-packages: "typescript@5.8.2 esbuild@0.28.1"
prefer-makefile: false
coverage: false
# Build and vet ALL submodules (extensions, examples, cmd)
build-all-modules:
name: Build All Modules
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
sparse-checkout: |
/*
!docs/
sparse-checkout-cone-mode: false
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
cache-dependency-path: |
**/go.sum
- name: Build and vet all modules
shell: bash
run: |
set -euo pipefail
# cmd/forge requires github.com/xraph/forge at its last PUBLISHED
# version, because its go.mod has to stay free of replace directives
# for `go install ...@latest` to accept it (see the comment at the top
# of that file). Building it standalone would therefore compile against
# the released root module instead of this tree, so any root API this
# branch adds but has not published yet shows up here as "undefined". A
# throwaway workspace restores local resolution for it, exactly as the
# Build CLI job does. Every other module carries its own replace
# directive and needs no help.
#
# The workspace file deliberately lives OUTSIDE the repo: a go.work at
# the root would apply to every module in the loop below, and the ones
# it does not list would fail with "directory prefix . does not contain
# modules listed in go.work". Everything else builds with GOWORK=off so
# each module is still checked standalone.
WORKFILE="${RUNNER_TEMP:-/tmp}/forge-ci.work"
rm -f "$WORKFILE"
GOWORK="$WORKFILE" go work init "$PWD" "$PWD/cmd/forge"
FAILED=0
TOTAL=0
FAILED_MODULES=""
for modfile in $(find . -name "go.mod" -not -path "*/testdata/*" -not -path "./docs/*" | sort); do
dir=$(dirname "$modfile")
mod_name=$(grep -m1 '^module ' "$modfile" | awk '{print $2}')
TOTAL=$((TOTAL + 1))
case "$dir" in
./cmd/forge) export GOWORK="$WORKFILE" ;;
*) export GOWORK=off ;;
esac
echo "::group::Building $dir ($mod_name)"
if ! (cd "$dir" && go build ./... 2>&1); then
echo "::error::Build failed for module: $dir ($mod_name)"
FAILED=$((FAILED + 1))
FAILED_MODULES="$FAILED_MODULES\n - $dir ($mod_name)"
fi
if ! (cd "$dir" && go vet ./... 2>&1); then
echo "::error::Vet failed for module: $dir ($mod_name)"
FAILED=$((FAILED + 1))
FAILED_MODULES="$FAILED_MODULES\n - $dir ($mod_name) [vet]"
fi
echo "::endgroup::"
done
echo ""
echo "====================================="
echo "Module Build Summary: $TOTAL modules checked"
echo "====================================="
if [ "$FAILED" -gt 0 ]; then
echo "FAILED modules ($FAILED failures):$FAILED_MODULES"
exit 1
else
echo "All $TOTAL modules built and vetted successfully."
fi
- name: Test submodules
shell: bash
run: |
set -euo pipefail
# Same published-vs-local resolution problem as the build step above;
# see the comment there for why this workspace exists and why it lives
# outside the repo.
WORKFILE="${RUNNER_TEMP:-/tmp}/forge-ci.work"
rm -f "$WORKFILE"
GOWORK="$WORKFILE" go work init "$PWD" "$PWD/cmd/forge"
FAILED=0
FAILED_MODULES=""
for modfile in $(find . -name "go.mod" -not -path "*/testdata/*" -not -path "./docs/*" -not -path "./go.mod" | sort); do
dir=$(dirname "$modfile")
mod_name=$(grep -m1 '^module ' "$modfile" | awk '{print $2}')
case "$dir" in
./cmd/forge) export GOWORK="$WORKFILE" ;;
*) export GOWORK=off ;;
esac
echo "::group::Testing $dir ($mod_name)"
if ! (cd "$dir" && go test -short -count=1 -timeout=5m ./... 2>&1); then
echo "::error::Tests failed for module: $dir ($mod_name)"
FAILED=$((FAILED + 1))
FAILED_MODULES="$FAILED_MODULES\n - $dir ($mod_name)"
fi
echo "::endgroup::"
done
if [ "$FAILED" -gt 0 ]; then
echo ""
echo "FAILED submodule tests ($FAILED failures):$FAILED_MODULES"
exit 1
fi
# Linting
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
cache-dependency-path: |
**/go.sum
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --timeout=5m --exclude-dirs=bk
continue-on-error: true
# bk/ no longer exists, so the old `grep -v '/bk/'` filter was a no-op.
- name: Run go vet
run: go vet ./...
# Security scanning
security:
name: Security Scan
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Run gosec
run: |
go install github.com/securego/gosec/v2/cmd/gosec@latest
gosec -exclude-dir=bk -exclude-dir=vendor -exclude-dir=examples -fmt=sarif -out=gosec.sarif ./... || true
- name: Fix SARIF file format
if: always()
run: |
chmod +x .github/scripts/fix_sarif.py
python3 .github/scripts/fix_sarif.py
- name: Run govulncheck
continue-on-error: true
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
# - name: Upload SARIF file
# uses: github/codeql-action/upload-sarif@v4
# if: always()
# with:
# sarif_file: gosec.sarif
# Build CLI binary
build-cli:
name: Build CLI
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
- name: Build CLI
# cmd/forge/go.mod deliberately carries NO replace directives, because
# `go install github.com/xraph/forge/cmd/forge@latest` refuses any
# module whose go.mod has them. That means a plain `go build` here
# would compile the CLI against the last PUBLISHED github.com/xraph/forge
# rather than this tree — so a change that broke the root module's API
# would still pass this job. A throwaway workspace restores local
# resolution for CI only, without putting replace directives back into
# the published go.mod. go.work is gitignored, so this file never
# escapes the runner.
run: |
go work init . ./cmd/forge
cd cmd/forge
go build -v -ldflags="-s -w -X main.version=dev-${{ github.sha }}" -o forge .
- name: Test CLI binary
run: |
cd cmd/forge
./forge --version
./forge doctor || true
- name: Upload CLI artifact
uses: actions/upload-artifact@v4
with:
name: forge-cli-${{ github.sha }}
path: cmd/forge/forge
retention-days: 7
deploy-compose:
name: Deploy Compose Lifecycle
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: Create development workspace
run: go work init . ./cmd/forge
- name: Test real deployment and retained data
working-directory: cmd/forge
run: go test -tags integration ./internal/deploy/provider/compose -run TestComposeStackLifecycle -count=1 -v -timeout 22m
deploy-kubernetes:
name: Deploy Kubernetes Lifecycle
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: "**/go.sum"
- name: Install cluster tools
run: |
go install sigs.k8s.io/kind@v0.33.0
mkdir -p "$RUNNER_TEMP/bin"
curl --fail --location https://dl.k8s.io/release/v1.36.1/bin/linux/amd64/kubectl -o "$RUNNER_TEMP/bin/kubectl"
curl --fail --location https://dl.k8s.io/release/v1.36.1/bin/linux/amd64/kubectl.sha256 -o "$RUNNER_TEMP/bin/kubectl.sha256"
cd "$RUNNER_TEMP/bin"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
chmod +x kubectl
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- name: Create development workspace
run: go work init . ./cmd/forge
- name: Test real Kubernetes deployment and retained data
working-directory: cmd/forge
run: go test -tags integration ./internal/deploy/provider/kubernetes -run TestKubernetesStackLifecycle -count=1 -v -timeout 25m
deploy-workbench:
name: Deploy Workbench
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v5
with:
go-version-file: cmd/forge/go.mod
cache-dependency-path: "**/go.sum"
- uses: actions/setup-node@v4
with:
node-version: "24"
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.1.12"
- name: Verify and rebuild embedded interface
working-directory: cmd/forge/internal/deploy/workbench/_ui
run: |
set -euo pipefail
bun install --frozen-lockfile
bun run test
bun run lint
bun run format:check
bun run typecheck
bun run build
test -z "$(git status --porcelain -- dist)"
- name: Build standalone CLI and check server boundaries
working-directory: cmd/forge
env:
GOWORK: "off"
run: |
set -euo pipefail
go build -o "$RUNNER_TEMP/forge-workbench-cli" .
go test -race ./internal/deploy/workbench ./internal/deploy/engine ./internal/deploy/persistence ./internal/deploy/state -count=1 -timeout=3m
- name: Qualify real workbench and SQL authority
working-directory: cmd/forge
env:
GOWORK: "off"
FORGE_DEPLOY_CLI: ${{ runner.temp }}/forge-workbench-cli
FORGE_DEPLOY_REQUIRE_REGISTRY: "1"
run: |
set -euo pipefail
go test -tags integration ./internal/deploy/workbench -run TestWorkbench -count=1 -v -timeout=25m
go test -tags integration ./internal/deploy/persistence -run TestPostgresAuthorityContracts -count=1 -v -timeout=4m
conduit-integration:
name: Conduit Broker and PostgreSQL Integration
runs-on: ubuntu-latest
timeout-minutes: 12
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: conduit
POSTGRES_PASSWORD: conduit_test
POSTGRES_DB: conduit
ports: [5432:5432]
options: >-
--health-cmd "pg_isready -U conduit -d conduit"
--health-interval 5s
--health-timeout 5s
--health-retries 12
redis:
image: redis:7.4-alpine
ports: [6379:6379]
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 12
kafka:
image: apache/kafka:4.1.0
ports: [9092:9092]
env:
KAFKA_NODE_ID: "1"
KAFKA_PROCESS_ROLES: "broker,controller"
KAFKA_LISTENERS: "PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093"
KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://localhost:9092"
KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT"
KAFKA_CONTROLLER_QUORUM_VOTERS: "1@localhost:9093"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1"
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0"
KAFKA_HEAP_OPTS: "-Xms256M -Xmx512M"
options: >-
--health-cmd "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092"
--health-interval 10s
--health-timeout 10s
--health-retries 12
--health-start-period 15s
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v5
with:
go-version-file: extensions/conduit/go.mod
cache-dependency-path: "**/go.sum"
- name: Enable Redis persistence
run: docker exec ${{ job.services.redis.id }} redis-cli CONFIG SET appendonly yes
- name: Test broker faults and transactional effects
working-directory: extensions/conduit
env:
GOWORK: "off"
CONDUIT_REQUIRE_INTEGRATION: "1"
CONDUIT_REQUIRE_PROVIDERS: "1"
CONDUIT_TEST_REDIS: "redis://localhost:6379"
CONDUIT_TEST_KAFKA: "localhost:9092"
CONDUIT_TEST_POSTGRES: "postgres://conduit:conduit_test@localhost:5432/conduit?sslmode=disable"
run: go test -race -count=1 -timeout=8m ./...
# Summary
ci-summary:
name: CI Summary
runs-on: ubuntu-latest
needs:
[
test,
test-windows,
build-all-modules,
lint,
security,
build-cli,
deploy-compose,
deploy-kubernetes,
deploy-workbench,
conduit-integration,
]
if: always()
steps:
- name: Generate summary
env:
TEST_RESULT: ${{ needs.test.result }}
WINDOWS_RESULT: ${{ needs.test-windows.result }}
MODULES_RESULT: ${{ needs.build-all-modules.result }}
LINT_RESULT: ${{ needs.lint.result }}
SECURITY_RESULT: ${{ needs.security.result }}
CLI_RESULT: ${{ needs.build-cli.result }}
CONDUIT_RESULT: ${{ needs.conduit-integration.result }}
KUBERNETES_RESULT: ${{ needs.deploy-kubernetes.result }}
WORKBENCH_RESULT: ${{ needs.deploy-workbench.result }}
COMPOSE_RESULT: ${{ needs.deploy-compose.result }}
run: |
set -uo pipefail
{
echo "# CI Results Summary"
echo
echo "| Job | Result |"
echo "|---|---|"
echo "| Test (ubuntu, macos) | ${TEST_RESULT} |"
echo "| Test (windows, optional) | ${WINDOWS_RESULT} |"
echo "| Build All Modules | ${MODULES_RESULT} |"
echo "| Lint | ${LINT_RESULT} |"
echo "| Security | ${SECURITY_RESULT} |"
echo "| Build CLI | ${CLI_RESULT} |"
echo "| Conduit Integration | ${CONDUIT_RESULT} |"
echo "| Deploy Compose Lifecycle | ${COMPOSE_RESULT} |"
echo "| Deploy Kubernetes Lifecycle | ${KUBERNETES_RESULT} |"
echo "| Deploy Workbench | ${WORKBENCH_RESULT} |"
} >> "$GITHUB_STEP_SUMMARY"
# Windows is explicitly optional and is NOT part of the gate.
failed=0
for entry in "test:${TEST_RESULT}" "build-all-modules:${MODULES_RESULT}" \
"lint:${LINT_RESULT}" "security:${SECURITY_RESULT}" \
"build-cli:${CLI_RESULT}" "deploy-compose:${COMPOSE_RESULT}" \
"deploy-kubernetes:${KUBERNETES_RESULT}" "deploy-workbench:${WORKBENCH_RESULT}" \
"conduit-integration:${CONDUIT_RESULT}"; do
name="${entry%%:*}"; status="${entry##*:}"
if [ "$status" != "success" ]; then
echo "::error::Required CI job '$name' did not succeed (result: $status)"
failed=1
fi
done
if [ "$failed" -ne 0 ]; then
echo "**CI failed** - see the job logs above." >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
if [ "$WINDOWS_RESULT" != "success" ]; then
echo "**Passed**, with optional Windows tests failing." >> "$GITHUB_STEP_SUMMARY"
else
echo "**All CI checks passed**" >> "$GITHUB_STEP_SUMMARY"
fi