Skip to content

ci: bump actions/github-script from 7 to 8 #565

ci: bump actions/github-script from 7 to 8

ci: bump actions/github-script from 7 to 8 #565

Workflow file for this run

name: CI
on:
push:
branches: [trunk]
pull_request:
branches: [trunk]
env:
DOTNET_VERSION: '10.0.x'
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_NOLOGO: true
PYTHON_VERSION: '3.11'
jobs:
changes:
name: Detect Non-Test Changes
runs-on: ubuntu-latest
outputs:
non_test_changes: ${{ steps.filter.outputs.non_test_changes }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine diff range
id: diff
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
git fetch origin ${{ github.base_ref }} --depth=1
echo "range=origin/${{ github.base_ref }}...${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "range=${{ github.event.before }}...${{ github.sha }}" >> $GITHUB_OUTPUT
fi
- name: Check for non-test changes
id: filter
run: |
CHANGED_FILES=$(git diff --name-only "${{ steps.diff.outputs.range }}" || true)
NON_TEST_FILES=$(echo "$CHANGED_FILES" | grep -Ev '^(tests/|test/)' | sed '/^$/d' || true)
if [ -z "$NON_TEST_FILES" ]; then
echo "non_test_changes=false" >> $GITHUB_OUTPUT
else
echo "non_test_changes=true" >> $GITHUB_OUTPUT
fi
echo "Changed files:"
echo "$CHANGED_FILES"
echo ""
echo "Non-test files:"
echo "$NON_TEST_FILES"
build:
name: Build & Format Check
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Instruction Sync (Claude/Codex)
run: bash scripts/check-instructions-sync.sh
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore
run: dotnet restore Honua.sln
- name: Build (warnings as errors)
run: dotnet build Honua.sln --no-restore --configuration Release /p:TreatWarningsAsErrors=true
- name: Format Check
run: dotnet format Honua.sln --verify-no-changes --verbosity diagnostic
test-all:
name: Test Suite (.NET + Python + JS)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest
needs: build
timeout-minutes: 35
env:
TESTCONTAINERS_RYUK_DISABLED: false
TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX: ""
HONUA_TEST_CONFIGURATION: Release
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: tests/python/requirements.txt
- name: Install Localstack CLI (awslocal)
run: |
python -m pip install --upgrade pip
python -m pip install --user awscli-local
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: tests/js/package-lock.json
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore
run: dotnet restore Honua.sln
- name: Create Test Results Directory
run: mkdir -p ./tests/TestResults
- name: Pre-pull Docker images for faster tests
run: |
docker pull postgis/postgis:16-3.4-alpine || true
docker pull postgis/postgis:18-3.6 || true
- name: Start emulator containers (Azurite + Localstack)
run: |
docker compose -f .devcontainer/docker-compose.emulators.yml up -d
for i in {1..30}; do
if (echo > /dev/tcp/localhost/4566) >/dev/null 2>&1 \
&& (echo > /dev/tcp/localhost/10000) >/dev/null 2>&1; then
exit 0
fi
sleep 2
done
echo "Emulators failed to start."
docker compose -f .devcontainer/docker-compose.emulators.yml ps
exit 1
- name: Run .NET Tests
timeout-minutes: 25
run: |
dotnet build tests/Honua.Admin.Playwright/Honua.Admin.Playwright.csproj \
--configuration Release
pwsh tests/Honua.Admin.Playwright/bin/Release/net10.0/playwright.ps1 install
dotnet test Honua.sln \
--no-restore \
--configuration Release \
--logger "trx;LogFileName=all-test-results.trx" \
--logger "console;verbosity=minimal" \
--results-directory ./tests/TestResults \
--blame-hang-timeout 8m \
--collect:"XPlat Code Coverage" \
--settings tests/coverlet.runsettings \
-- RunConfiguration.MaxCpuCount=0
- name: Install Python test dependencies
run: |
python -m pip install --upgrade pip
pip install -r tests/python/requirements.txt
- name: Run Python Integration Tests (OGC + FeatureServer)
run: pytest --tb=short
working-directory: tests/python
- name: Install JavaScript test dependencies
run: npm ci
working-directory: tests/js
- name: TypeScript type check (JavaScript tests)
run: npm run typecheck
working-directory: tests/js
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: ./tests/TestResults/**/coverage.cobertura.xml
flags: combined
name: honua-server-coverage
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
timeout-minutes: 3
with:
name: all-test-results
path: tests/TestResults/
retention-days: 7
js-integration-tests:
name: JavaScript Integration Tests
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest
needs: build
timeout-minutes: 15
services:
postgres:
image: postgis/postgis:16-3.4-alpine
env:
POSTGRES_USER: honua
POSTGRES_PASSWORD: honua
POSTGRES_DB: honua_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: tests/js/package-lock.json
- name: Restore .NET packages
run: dotnet restore Honua.sln
- name: Build Honua Server
run: dotnet build src/Honua.Server --configuration Release --no-restore
- name: Initialize test database
run: |
PGPASSWORD=honua psql -h localhost -U honua -d honua_test -c "CREATE EXTENSION IF NOT EXISTS postgis;"
env:
PGPASSWORD: honua
- name: Seed FeatureServer metadata
run: |
cat <<'SQL' > /tmp/honua-js-seed.sql
CREATE SCHEMA IF NOT EXISTS honua;
CREATE TABLE IF NOT EXISTS honua.services (
service_name VARCHAR(64) PRIMARY KEY,
description TEXT NOT NULL DEFAULT '',
srid INT NOT NULL DEFAULT 4326,
max_record_count INT NOT NULL DEFAULT 1000,
supported_formats TEXT[] NOT NULL DEFAULT '{JSON,GeoJSON}',
capabilities TEXT[] NOT NULL DEFAULT '{Query,Extract}',
service_extent GEOMETRY,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS honua.layers (
layer_id SERIAL PRIMARY KEY,
layer_name TEXT NOT NULL,
description TEXT,
table_name TEXT NOT NULL,
geometry_type TEXT NOT NULL,
srid INT NOT NULL DEFAULT 4326,
extent GEOMETRY(POLYGON, 4326),
min_scale DOUBLE PRECISION,
max_scale DOUBLE PRECISION,
default_visibility BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS honua.service_layers (
service_name VARCHAR(64) NOT NULL REFERENCES honua.services(service_name) ON DELETE CASCADE,
layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE,
layer_order INT NOT NULL,
PRIMARY KEY (service_name, layer_id),
UNIQUE (service_name, layer_order)
);
CREATE TABLE IF NOT EXISTS honua.layer_fields (
layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE,
field_name VARCHAR(64) NOT NULL,
field_type VARCHAR(32) NOT NULL,
field_order INT NOT NULL,
max_length INT,
nullable BOOLEAN NOT NULL DEFAULT TRUE,
default_value TEXT,
description TEXT,
PRIMARY KEY (layer_id, field_name)
);
CREATE TABLE IF NOT EXISTS honua.attachments (
id BIGSERIAL PRIMARY KEY,
feature_id BIGINT NOT NULL,
layer_id INT NOT NULL,
filename TEXT NOT NULL,
content_type TEXT NOT NULL,
size BIGINT NOT NULL CHECK (size >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
storage_path TEXT NOT NULL,
keywords TEXT
);
CREATE TABLE IF NOT EXISTS honua.relationships (
id SERIAL PRIMARY KEY,
layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE,
relationship_id INT NOT NULL,
name TEXT NOT NULL,
related_layer_id INT NOT NULL REFERENCES honua.layers(layer_id) ON DELETE CASCADE,
relationship_type TEXT NOT NULL,
origin_foreign_key TEXT NOT NULL,
destination_foreign_key TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT relationships_layer_relationship_unique UNIQUE(layer_id, relationship_id),
CONSTRAINT relationships_valid_ids CHECK(layer_id >= 0 AND related_layer_id >= 0 AND relationship_id > 0),
CONSTRAINT relationships_valid_fields CHECK(
LENGTH(name) > 0 AND LENGTH(name) <= 128 AND
LENGTH(relationship_type) > 0 AND LENGTH(relationship_type) <= 64 AND
LENGTH(origin_foreign_key) > 0 AND LENGTH(origin_foreign_key) <= 128 AND
LENGTH(destination_foreign_key) > 0 AND LENGTH(destination_foreign_key) <= 128
),
CONSTRAINT relationships_valid_type CHECK(
relationship_type IN ('esriRelRoleOrigin', 'esriRelRoleDestination', 'esriRelRoleAny')
)
);
CREATE TABLE IF NOT EXISTS features (
objectid BIGSERIAL PRIMARY KEY,
layer_id INT NOT NULL,
geometry GEOMETRY,
attributes JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_service_layers_service_name ON honua.service_layers(service_name);
CREATE INDEX IF NOT EXISTS idx_service_layers_layer_id ON honua.service_layers(layer_id);
CREATE INDEX IF NOT EXISTS idx_layer_fields_layer_id ON honua.layer_fields(layer_id);
CREATE INDEX IF NOT EXISTS idx_relationships_layer_id ON honua.relationships(layer_id);
CREATE INDEX IF NOT EXISTS idx_relationships_related_layer_id ON honua.relationships(related_layer_id);
CREATE INDEX IF NOT EXISTS idx_features_layer_id ON features(layer_id);
CREATE INDEX IF NOT EXISTS idx_features_geometry ON features USING GIST(geometry);
CREATE INDEX IF NOT EXISTS idx_features_attributes ON features USING GIN(attributes);
INSERT INTO honua.services (
service_name,
description,
srid,
max_record_count,
supported_formats,
capabilities,
service_extent
)
VALUES (
'test_service',
'Test Feature Service',
4326,
1000,
ARRAY['JSON', 'GeoJSON'],
ARRAY['Query', 'Extract', 'Create', 'Update', 'Delete'],
ST_MakeEnvelope(-122.5, 37.7, -122.35, 37.84, 4326)
)
ON CONFLICT (service_name) DO UPDATE SET
description = EXCLUDED.description,
srid = EXCLUDED.srid,
max_record_count = EXCLUDED.max_record_count,
supported_formats = EXCLUDED.supported_formats,
capabilities = EXCLUDED.capabilities,
service_extent = EXCLUDED.service_extent,
updated_at = NOW();
INSERT INTO honua.layers (
layer_id,
layer_name,
description,
table_name,
geometry_type,
srid,
extent,
default_visibility
)
VALUES (
0,
'Test Layer',
'Default layer for integration tests',
'features',
'Point',
4326,
ST_MakeEnvelope(-122.5, 37.7, -122.35, 37.84, 4326),
true
)
ON CONFLICT (layer_id) DO UPDATE SET
layer_name = EXCLUDED.layer_name,
description = EXCLUDED.description,
table_name = EXCLUDED.table_name,
geometry_type = EXCLUDED.geometry_type,
srid = EXCLUDED.srid,
extent = EXCLUDED.extent,
default_visibility = EXCLUDED.default_visibility;
INSERT INTO honua.layer_fields (
layer_id,
field_name,
field_type,
field_order,
max_length,
nullable,
default_value,
description
)
VALUES
(0, 'objectid', 'Integer', 0, NULL, false, NULL, 'Object ID'),
(0, 'name', 'String', 1, 255, true, NULL, 'Name'),
(0, 'description', 'String', 2, 1024, true, NULL, 'Description'),
(0, 'shape', 'Geometry', 3, NULL, true, NULL, 'Geometry')
ON CONFLICT (layer_id, field_name) DO NOTHING;
INSERT INTO honua.layer_fields (
layer_id,
field_name,
field_type,
field_order,
max_length,
nullable,
default_value,
description
)
VALUES
(0, 'status', 'String', 4, 64, true, NULL, 'Status'),
(0, 'count', 'Integer', 5, NULL, true, NULL, 'Count'),
(0, 'ratio', 'Double', 6, NULL, true, NULL, 'Ratio'),
(0, 'active', 'Boolean', 7, NULL, true, NULL, 'Active flag'),
(0, 'created_at', 'DateTime', 8, NULL, true, NULL, 'Created timestamp'),
(0, 'event_date', 'Date', 9, NULL, true, NULL, 'Event date'),
(0, 'event_time', 'Time', 10, NULL, true, NULL, 'Event time'),
(0, 'uid', 'Uuid', 11, NULL, true, NULL, 'Unique identifier'),
(0, 'tags', 'Json', 12, NULL, true, NULL, 'Tag array'),
(0, 'numbers', 'Json', 13, NULL, true, NULL, 'Number array')
ON CONFLICT (layer_id, field_name) DO NOTHING;
INSERT INTO honua.service_layers (
service_name,
layer_id,
layer_order
)
VALUES ('test_service', 0, 0)
ON CONFLICT (service_name, layer_id) DO NOTHING;
SQL
PGPASSWORD=honua psql -v ON_ERROR_STOP=1 -h localhost -U honua -d honua_test -f /tmp/honua-js-seed.sql
- name: Start Honua Server
run: |
dotnet run --project src/Honua.Server --configuration Release --no-build &
sleep 10
curl --retry 10 --retry-delay 2 --retry-connrefused http://localhost:5000/health || exit 1
env:
ConnectionStrings__DefaultConnection: "Host=localhost;Database=honua_test;Username=honua;Password=honua"
ASPNETCORE_URLS: "http://localhost:5000"
Security__ConnectionEncryption__MasterKey: "test-master-key-that-is-at-least-32-characters-long-for-security"
Security__ConnectionEncryption__Salt: "dGVzdC1zYWx0LWZvci1lbmNyeXB0aW9uLXRlc3RpbmctcHVycG9zZXM="
- name: Install JavaScript test dependencies
run: npm ci
working-directory: tests/js
- name: Run JavaScript Integration Tests
run: npm test
working-directory: tests/js
env:
HONUA_BASE_URL: http://localhost:5000
HONUA_SERVICE_ID: test_service
HONUA_LAYER_ID: '0'
llm-architecture-review:
name: LLM Architecture Review
runs-on: ubuntu-latest
needs: [build, test-all, aot-build] # Run after core tests complete
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]'
outputs:
assessment: ${{ steps.llm-analysis.outputs.assessment }}
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get Changed Files
id: changed-files
run: |
# Get ALL changed files for comprehensive review (acceptance criteria, issue linking)
ALL_CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...${{ github.sha }})
# Get C# files separately for architecture-specific analysis
CS_CHANGED_FILES=$(echo "$ALL_CHANGED_FILES" | grep -E '\.(cs|csproj)$' || echo "")
if [ -z "$ALL_CHANGED_FILES" ]; then
echo "No files changed, skipping review"
echo "skip_review=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
echo "$ALL_CHANGED_FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "cs_files<<EOF" >> $GITHUB_OUTPUT
echo "$CS_CHANGED_FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "skip_review=false" >> $GITHUB_OUTPUT
# Show what we found
echo "All changed files:"
echo "$ALL_CHANGED_FILES"
echo ""
echo "C# files (for architecture analysis):"
echo "$CS_CHANGED_FILES"
- name: Setup Python
if: steps.changed-files.outputs.skip_review == 'false'
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install Dependencies
if: steps.changed-files.outputs.skip_review == 'false'
run: |
python -m pip install --upgrade pip
# Only install OpenAI if API key is available
if [ -n "${{ secrets.OPENAI_API_KEY }}" ]; then
pip install openai
fi
- name: LLM Architecture Analysis
if: steps.changed-files.outputs.skip_review == 'false'
id: llm-analysis
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_BASE_REF: ${{ github.base_ref }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_PR_NUMBER: ${{ github.event.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
REVIEW_CHUNK_LINES: 300
REVIEW_MAX_FILES: 50
run: |
python scripts/architecture-review.py
- name: Post Review Comment
if: steps.changed-files.outputs.skip_review == 'false'
uses: actions/github-script@v8
env:
ANALYSIS_OUTPUT: ${{ steps.llm-analysis.outputs.analysis }}
ASSESSMENT_OUTPUT: ${{ steps.llm-analysis.outputs.assessment }}
with:
script: |
const analysis = process.env.ANALYSIS_OUTPUT;
const assessment = process.env.ASSESSMENT_OUTPUT;
let assessmentIcon = '✅';
let assessmentColor = 'green';
if (assessment === 'NEEDS_ATTENTION') {
assessmentIcon = '⚠️';
assessmentColor = 'orange';
} else if (assessment === 'BLOCKING_ISSUES') {
assessmentIcon = '🚫';
assessmentColor = 'red';
}
const body = `## 🤖 LLM Architecture Review
${assessmentIcon} **Assessment: ${assessment}**
${analysis}
---
*Automated architectural analysis powered by OpenAI GPT-4*
*This review focuses on architectural patterns and design decisions*
*Human review still recommended for complex changes*`;
// Check if we already have a review comment
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const existingComment = comments.data.find(comment =>
comment.body.includes('🤖 LLM Architecture Review')
);
if (existingComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: body
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
}
aot-build:
name: AOT Build Verification
if: ${{ (github.event_name != 'pull_request' || github.event.pull_request.user.login != 'dependabot[bot]') && needs.changes.outputs.non_test_changes == 'true' }}
runs-on: ubuntu-latest
needs: [build, changes] # Run in parallel with tests
timeout-minutes: 8
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore
run: dotnet restore Honua.sln
- name: Publish AOT
run: |
dotnet publish \
--configuration Release \
--runtime linux-x64 \
--self-contained \
-p:PublishAot=true \
-p:StripSymbols=true \
-o ./publish
working-directory: src/Honua.Server
- name: Verify Binary Size
run: |
SIZE=$(stat --printf="%s" ./publish/Honua.Server 2>/dev/null || echo "0")
SIZE_MB=$((SIZE / 1024 / 1024))
echo "Binary size: ${SIZE_MB}MB"
# Warn if over 100MB (target from MVP_PLAN)
if [ "$SIZE_MB" -gt 100 ]; then
echo "::warning::AOT binary ${SIZE_MB}MB exceeds 100MB target"
fi
working-directory: src/Honua.Server
# Architecture gate job that blocks merge if LLM review finds blocking issues
architecture-gate:
name: Architecture Gate
needs: llm-architecture-review
runs-on: ubuntu-latest
if: needs.llm-architecture-review.outputs.assessment == 'BLOCKING_ISSUES'
steps:
- name: Block PR on Architectural Violations
run: |
echo "::error::Architecture review found BLOCKING_ISSUES that must be resolved before merge."
echo "::error::Please review the LLM Architecture Review comment and address all critical violations."
echo "::error::Common blocking issues: dependency violations, controller usage, AOT incompatibility, missing docs."
exit 1