Repository navigation
fix(dart): port nullable referenced field codecs from TypeScript #586
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |