chore(deps-dev): bump hono from 4.12.10 to 4.12.25 #405
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: Release | |
| on: | |
| push: | |
| branches: | |
| - master | |
| pull_request: | |
| branches: | |
| - '*' | |
| workflow_dispatch: | |
| concurrency: ${{ github.workflow }}-${{ github.ref }} | |
| jobs: | |
| check-deps: | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '22.x' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Check for mature dependency updates | |
| run: npx dry-aged-deps --check | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| opensearch_version: ['1.3.20', '2.19.5'] | |
| services: | |
| opensearch: | |
| image: opensearchproject/opensearch:${{ matrix.opensearch_version }} | |
| env: | |
| discovery.type: single-node | |
| ES_JAVA_OPTS: '-Xms1g -Xmx1g' | |
| plugins.security.disabled: 'true' | |
| # Required for OpenSearch 2.12+ to skip the demo-config installer when | |
| # the security plugin is disabled. No-op on 1.3.20 (env var unknown | |
| # to that version's entrypoint). Without this, the 2.x entrypoint | |
| # bails out demanding OPENSEARCH_INITIAL_ADMIN_PASSWORD even though | |
| # plugins.security.disabled is set. | |
| DISABLE_INSTALL_DEMO_CONFIG: 'true' | |
| ports: | |
| - 9200:9200 | |
| options: >- | |
| --health-cmd "curl -sf http://localhost:9200" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 20 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '22.x' | |
| - name: Cache npm dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.npm | |
| key: npm-${{ hashFiles('package-lock.json') }} | |
| restore-keys: | | |
| npm- | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build | |
| run: npm run build | |
| - name: Cache G-NAF zip | |
| uses: actions/cache@v4 | |
| id: gnaf-cache | |
| with: | |
| path: target/gnaf/*.zip | |
| key: gnaf-zip-${{ hashFiles('service/address-service.js') }} | |
| restore-keys: | | |
| gnaf-zip- | |
| - name: Prepare OT test fixture | |
| run: | | |
| # Download G-NAF if not cached | |
| npm run build | |
| if [ ! -f target/gnaf/*.zip ]; then | |
| node -e " | |
| require('@babel/register'); | |
| require('@babel/polyfill'); | |
| require('./service/address-service').fetchGnafFile().then(f => console.log('Downloaded:', f)); | |
| " | |
| fi | |
| # Extract only OT + Authority Code files into a slim fixture | |
| GNAF_ZIP=$(ls target/gnaf/*.zip | head -1) | |
| FIXTURE_DIR=target/gnaf-fixture | |
| mkdir -p "$FIXTURE_DIR" | |
| unzip -o -j "$GNAF_ZIP" '*/Authority Code/*' -d "$FIXTURE_DIR/Authority Code/" 2>/dev/null || true | |
| unzip -o -j "$GNAF_ZIP" '*/Standard/OT_*' -d "$FIXTURE_DIR/Standard/" 2>/dev/null || true | |
| echo "Fixture contents:" | |
| find "$FIXTURE_DIR" -type f | head -20 | |
| - name: Run tests (no geo) | |
| env: | |
| GNAF_TEST_FIXTURE_DIR: target/gnaf-fixture | |
| run: npm run test:nogeo | |
| - name: Run tests (geo) | |
| env: | |
| GNAF_TEST_FIXTURE_DIR: target/gnaf-fixture | |
| # ADDRESSR_ENABLE_GEO=1 matches the production EB env at deploy/main.tf | |
| # so this step closes the previous coverage gap where the geo code | |
| # path (loadSiteGeo / loadDefaultGeo) only ran in production. No | |
| # external API: geo data comes from the G-NAF dataset itself, so the | |
| # OT fixture above is sufficient. | |
| run: npm run test:geo | |
| release: | |
| if: github.ref == 'refs/heads/master' | |
| needs: build-and-test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '22.x' | |
| - uses: actions/checkout@v5 | |
| - name: Install dependencies | |
| run: | | |
| npm ci | |
| - name: Create Release Pull Request or Publish to npm | |
| id: changesets | |
| uses: changesets/action@v1.4.5 | |
| with: | |
| publish: npm run turbo:ci:publish | |
| version: npm run turbo:ci:version | |
| commit: 'chore: release' | |
| title: 'chore: release' | |
| createGithubReleases: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| - name: Deploy new version | |
| if: steps.changesets.outputs.published == 'true' | |
| uses: devcontainers/ci@v0.3 | |
| env: | |
| TF_VAR_elastic_host: ${{ secrets.TF_VAR_elastic_host }} | |
| TF_VAR_elastic_password: ${{ secrets.TF_VAR_elastic_password }} | |
| TF_VAR_elastic_username: ${{ secrets.TF_VAR_elastic_username }} | |
| # ADR 029 amendment 2026-04-29: distinct creds for v2 to fix P028's | |
| # silent 401 (v2 master user no longer aliases v1's; cutover still | |
| # flips only var.elastic_host). | |
| TF_VAR_elastic_v2_username: ${{ secrets.TF_VAR_ELASTIC_V2_USERNAME }} | |
| TF_VAR_elastic_v2_password: ${{ secrets.TF_VAR_ELASTIC_V2_PASSWORD }} | |
| TF_VAR_aws_secret_key: ${{ secrets.TF_VAR_aws_secret_key }} | |
| TF_VAR_aws_access_key: ${{ secrets.TF_VAR_aws_access_key }} | |
| TF_VAR_proxy_auth_header: ${{ secrets.TF_VAR_proxy_auth_header }} | |
| TF_VAR_proxy_auth_value: ${{ secrets.TF_VAR_proxy_auth_value }} | |
| # ADR 032 / P042 — Cloudflare provider + worker module credentials. | |
| # Token requires Workers Scripts Edit + Workers Routes Edit + Workers | |
| # Secrets Edit scopes on the addressr account/zone. RapidAPI key is | |
| # the existing dashboard value (no rotation during the P042 cutover, | |
| # per ADR 032 Scope). Sourced via 1P Voder per reference_addressr_secrets. | |
| TF_VAR_cloudflare_api_token: ${{ secrets.TF_VAR_cloudflare_api_token }} | |
| TF_VAR_cloudflare_account_id: ${{ secrets.TF_VAR_cloudflare_account_id }} | |
| TF_VAR_cloudflare_zone_id: ${{ secrets.TF_VAR_cloudflare_zone_id }} | |
| TF_VAR_cloudflare_rapidapi_key: ${{ secrets.TF_VAR_cloudflare_rapidapi_key }} | |
| TF_TOKEN_app_terraform_io: ${{ secrets.TERRAFORM_CLOUD_TOKEN }} | |
| with: | |
| runCmd: | | |
| npm run deploy:prod | |
| env: | | |
| TF_IN_AUTOMATION=1 | |
| TF_VAR_elasticapp=mountainpass-addressr | |
| TF_VAR_elastic_host | |
| TF_VAR_elastic_password | |
| TF_VAR_elastic_username | |
| TF_VAR_elastic_v2_username | |
| TF_VAR_elastic_v2_password | |
| TF_VAR_aws_secret_key | |
| TF_VAR_aws_access_key | |
| TF_VAR_proxy_auth_header | |
| TF_VAR_proxy_auth_value | |
| TF_VAR_cloudflare_api_token | |
| TF_VAR_cloudflare_account_id | |
| TF_VAR_cloudflare_zone_id | |
| TF_VAR_cloudflare_rapidapi_key | |
| TF_TOKEN_app_terraform_io | |
| - name: Wait for deployment to stabilize | |
| if: steps.changesets.outputs.published == 'true' | |
| run: sleep 120 | |
| - name: Smoke test production | |
| if: steps.changesets.outputs.published == 'true' | |
| run: | | |
| set -e | |
| echo "Testing health endpoint..." | |
| curl -sf --retry 3 --retry-delay 10 https://backend.addressr.io/health | |
| echo "" | |
| echo "Testing /api-docs is reachable without gateway auth (ADR 024 allowlist)..." | |
| curl -sf --retry 3 --retry-delay 10 -o /dev/null https://backend.addressr.io/api-docs | |
| echo "" | |
| # P035 / risk-scorer R3: /debug/shadow-config must be reachable | |
| # without gateway auth (ADR 024 allowlist) and must return the | |
| # documented JSON shape. Catches the silent-no-op P035 failure | |
| # mode automatically on every deploy: if the running addressr-server | |
| # process can't see ADDRESSR_SHADOW_HOST, hostSet will be false | |
| # and we'll know within minutes of deploy instead of hours via | |
| # operator probing. The endpoint must also respect the closed | |
| # lastError.class enum — we assert it against null OR enum members. | |
| echo "P035 R3: probing /debug/shadow-config..." | |
| shadow_body=$(curl -sf --retry 3 --retry-delay 10 \ | |
| https://backend.addressr.io/debug/shadow-config) | |
| echo "$shadow_body" | jq -e '.' > /dev/null || { | |
| echo "/debug/shadow-config did not return valid JSON"; exit 1; | |
| } | |
| # ADR 029 Phase 1 was rolled back 2026-05-14 (v2 OpenSearch | |
| # decommissioned, ADDRESSR_SHADOW_* env vars removed). Per the ADR 031 | |
| # default-off posture, hostSet=false is now the expected steady state | |
| # (shadow capability shipped, no target configured, mirrorRequest | |
| # no-ops). Assert false so an ACCIDENTAL shadow re-enable is caught. | |
| # When ADR 029 Phase 1 is re-attempted, flip this back to "true". | |
| host_set=$(echo "$shadow_body" | jq -r '.hostSet') | |
| if [ "$host_set" != "false" ]; then | |
| echo "/debug/shadow-config hostSet=$host_set, expected false (shadow is intentionally off post-ADR-029-rollback; flip to true when shadow is re-enabled)" | |
| echo "body: $shadow_body"; exit 1 | |
| fi | |
| last_error_class=$(echo "$shadow_body" | jq -r '.lastError.class // "null"') | |
| case "$last_error_class" in | |
| null|AbortError|ConnectionError|AuthError|UnknownError) ;; | |
| *) | |
| echo "/debug/shadow-config lastError.class=$last_error_class, expected null or one of the closed enum values" | |
| exit 1 ;; | |
| esac | |
| echo "P035 R3 ok: hostSet=false (shadow off per ADR 029 rollback), lastError.class=$last_error_class" | |
| echo "" | |
| # ADR 024 bypass probe: direct-without-header must be rejected. | |
| # Any status other than 401 fails the release — enforcement is the | |
| # steady state (ADR 024 accepted, P009 closed). | |
| echo "Probing direct bypass (ADR 024 Confirmation criterion 5)..." | |
| status=$(curl -s -o /dev/null -w "%{http_code}" "https://backend.addressr.io/addresses?q=sydney") | |
| if [ "$status" != "401" ]; then | |
| echo "direct probe returned $status, expected 401 (enforcement regression)"; exit 1 | |
| fi | |
| echo "direct probe status 401 (expected)" | |
| # P040: Cloudflare Worker (ADR 018) auth boundary probes. These are | |
| # distinct from the ADR 024 origin probe above — the worker rejects | |
| # at a different layer with a different body shape. Probing both | |
| # makes a future regression that conflates the two layers loud. | |
| echo "Probing worker referer-accept (ADR 018 safeHosts allowlist)..." | |
| status=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| -H "Referer: https://addressr.io/" \ | |
| "https://api.addressr.io/addresses/GANSW718804790") | |
| if [ "$status" != "200" ]; then | |
| echo "worker probe with Referer returned $status, expected 200 (safeHosts allowlist regression — P040 workaround broken)"; exit 1 | |
| fi | |
| echo "worker probe with Referer status 200 (expected)" | |
| echo "Probing worker no-referer rejection (ADR 018 default-deny)..." | |
| worker_body=$(curl -s "https://api.addressr.io/addresses/GANSW718804790") | |
| if ! echo "$worker_body" | grep -q "no-origin not permitted"; then | |
| echo "worker no-Referer probe did not return ADR 018 rejection body; got: $worker_body" | |
| echo "(this distinguishes the worker layer from the ADR 024 origin layer — body must start with 'no-origin not permitted')" | |
| exit 1 | |
| fi | |
| echo "worker no-Referer rejection body matches ADR 018 (expected)" | |
| # ADR 025 / P007 ranking probes + P019 Link header rel probe: exact | |
| # street-level address must rank first when sub-unit variants share | |
| # the address; root / must advertise every rel in the contract. | |
| auth_header="${{ secrets.TF_VAR_proxy_auth_header }}: ${{ secrets.TF_VAR_proxy_auth_value }}" | |
| # P019: rel completeness on root /. The rel list is the HATEOAS | |
| # contract from test/resources/features/addressv2.feature:8-15 (ADR | |
| # 012). Root / requires ADR 024 auth, so we use $auth_header here. | |
| # This probe replaces the pre-ADR-024 "curl -sf /" line that was | |
| # latently broken under proxy-auth enforcement. | |
| echo "Probing root / Link header rel completeness (P019)..." | |
| link_header=$(curl -sf --retry 3 --retry-delay 10 -D - -o /dev/null \ | |
| -H "$auth_header" https://backend.addressr.io/ \ | |
| | grep -i '^link:' | tr -d '\r' || true) | |
| expected_rels=( | |
| "https://addressr.io/rels/address-search" | |
| "https://addressr.io/rels/locality-search" | |
| "https://addressr.io/rels/postcode-search" | |
| "https://addressr.io/rels/state-search" | |
| "https://addressr.io/rels/api-docs" | |
| "https://addressr.io/rels/health" | |
| ) | |
| for rel in "${expected_rels[@]}"; do | |
| if ! echo "$link_header" | grep -qF "$rel"; then | |
| echo "root Link header missing rel: $rel" | |
| echo "actual link header: $link_header" | |
| exit 1 | |
| fi | |
| done | |
| echo "root / Link header contains all ${#expected_rels[@]} expected rels." | |
| echo "Probing ranking for 278 ROSS RIVER RD AITKENVALE QLD 4814 (issue #375)..." | |
| top_sla=$(curl -sf --retry 3 --retry-delay 10 -H "$auth_header" \ | |
| "https://backend.addressr.io/addresses?q=278%20ROSS%20RIVER%20RD%20AITKENVALE%20QLD%204814" \ | |
| | jq -r '.[0].sla') | |
| if [[ "$top_sla" != "278 ROSS RIVER RD,"* ]]; then | |
| echo "ranking regression: expected top hit to start with '278 ROSS RIVER RD,', got '$top_sla'" | |
| exit 1 | |
| fi | |
| echo "top hit '$top_sla' (street-level, expected)" | |
| echo "Probing ranking for 19 MURRAY RD CHRISTMAS ISLAND OT 6798..." | |
| top_sla=$(curl -sf --retry 3 --retry-delay 10 -H "$auth_header" \ | |
| "https://backend.addressr.io/addresses?q=19%20MURRAY%20RD%20CHRISTMAS%20ISLAND%20OT%206798" \ | |
| | jq -r '.[0].sla') | |
| if [[ "$top_sla" != "19 MURRAY RD, CHRISTMAS ISLAND OT 6798" ]]; then | |
| echo "ranking regression: expected top hit '19 MURRAY RD, CHRISTMAS ISLAND OT 6798', got '$top_sla'" | |
| exit 1 | |
| fi | |
| echo "top hit '$top_sla' (street-level, expected)" | |
| echo "Smoke tests passed." | |
| # TODO: RapidAPI OpenAPI spec sync is deferred — see ADR 023. | |
| # The REST provisioning API is defunct and the GitHub Action | |
| # has no tagged versions. /api-docs is live; for now, use the | |
| # "Import from URL" option in RapidAPI Studio pointing at | |
| # https://backend.addressr.io/api-docs when new endpoints are added. |