Fix console auth CloudFormation validation #413
Workflow file for this run
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: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - main | |
| permissions: | |
| contents: read | |
| jobs: | |
| azure-container-apps-pack: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 | |
| with: | |
| terraform_version: 1.15.8 | |
| terraform_wrapper: false | |
| - uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1 | |
| with: | |
| version: v0.72.0 | |
| cache: true | |
| - name: Validate the Azure Container Apps deployment pack | |
| working-directory: deploy/azure-container-apps | |
| run: | | |
| set -euo pipefail | |
| terraform fmt -check -recursive | |
| terraform init -backend=false -input=false -lockfile=readonly | |
| terraform validate | |
| terraform test -no-color | tee "$RUNNER_TEMP/azure-terraform-test.txt" | |
| shellcheck \ | |
| deploy.sh \ | |
| verify.sh \ | |
| verify-acs-prerequisites.sh \ | |
| rollback.sh \ | |
| cleanup.sh | |
| test "$(< .containerapp-extension-version)" = "1.3.0b4" | |
| test "$(< .communication-control-plane-api-version)" = \ | |
| "2026-03-18" | |
| node --check event-grid.mjs | |
| node --check event-grid-ensure.mjs | |
| node --check event-grid-verify.mjs | |
| node --check event-grid-delete.mjs | |
| node --check ../../scripts/azure-terminal-delivery.mjs | |
| trivy config \ | |
| --exit-code 1 \ | |
| --severity HIGH,CRITICAL \ | |
| --skip-version-check \ | |
| . | |
| terraform providers schema -json \ | |
| > "$RUNNER_TEMP/azure-provider-schema.json" | |
| jq --exit-status ' | |
| .provider_schemas[ | |
| "registry.terraform.io/hashicorp/azurerm" | |
| ].resource_schemas as $resources | |
| | $resources.azurerm_key_vault_secret.block.attributes.value_wo.write_only | |
| and $resources.azurerm_postgresql_flexible_server.block.attributes.administrator_password_wo.write_only | |
| ' "$RUNNER_TEMP/azure-provider-schema.json" >/dev/null | |
| if grep --recursive --fixed-strings --quiet \ | |
| 'resource "azurerm_eventgrid_event_subscription"' .; then | |
| echo "Event Grid secret delivery must remain outside Terraform state." | |
| exit 1 | |
| fi | |
| if grep --fixed-strings --quiet \ | |
| 'event-grid-test-test-test-test-test' \ | |
| "$RUNNER_TEMP/azure-terraform-test.txt"; then | |
| echo "Terraform test output exposed the Event Grid test secret." | |
| exit 1 | |
| fi | |
| cloud-run-pack: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 | |
| with: | |
| terraform_version: 1.15.8 | |
| terraform_wrapper: false | |
| - uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1 | |
| with: | |
| version: v0.72.0 | |
| cache: true | |
| - name: Validate the Cloud Run deployment pack | |
| working-directory: deploy/cloud-run | |
| env: | |
| GOOGLE_OAUTH_ACCESS_TOKEN: invalid-ci-token | |
| TF_VAR_api_key: re_DO_NOT_PERSIST_API_KEY_ABCDEFGHIJKL | |
| TF_VAR_database_password: DO_NOT_PERSIST_DATABASE_PASSWORD_1234 | |
| TF_VAR_image: >- | |
| ghcr.io/haya-inc/hayasend@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef | |
| TF_VAR_project_id: hayasend-test-project | |
| run: | | |
| set -euo pipefail | |
| terraform fmt -check -recursive | |
| terraform init -backend=false -input=false -lockfile=readonly | |
| terraform validate | |
| terraform test -no-color | |
| shellcheck deploy.sh cleanup.sh verify-zero-residue.sh | |
| trivy config \ | |
| --exit-code 1 \ | |
| --severity HIGH,CRITICAL \ | |
| --skip-version-check \ | |
| . | |
| TF_VAR_enable_pubsub_wakeup=true terraform plan \ | |
| -input=false \ | |
| -lock=false \ | |
| -no-color \ | |
| -refresh=false \ | |
| -out="$RUNNER_TEMP/cloud-run.tfplan" \ | |
| > "$RUNNER_TEMP/cloud-run-plan.txt" | |
| grep --fixed-strings --quiet \ | |
| 'connector_enforcement = "REQUIRED"' \ | |
| "$RUNNER_TEMP/cloud-run-plan.txt" | |
| grep --fixed-strings --quiet \ | |
| 'secret_data_wo = (write-only attribute)' \ | |
| "$RUNNER_TEMP/cloud-run-plan.txt" | |
| grep --fixed-strings --quiet \ | |
| 'resource "google_pubsub_topic" "wakeup"' \ | |
| "$RUNNER_TEMP/cloud-run-plan.txt" | |
| grep --fixed-strings --quiet \ | |
| 'role = "roles/pubsub.publisher"' \ | |
| "$RUNNER_TEMP/cloud-run-plan.txt" | |
| grep --fixed-strings --quiet \ | |
| 'role = "roles/pubsub.subscriber"' \ | |
| "$RUNNER_TEMP/cloud-run-plan.txt" | |
| if grep --fixed-strings --quiet \ | |
| "$TF_VAR_api_key" "$RUNNER_TEMP/cloud-run-plan.txt"; then | |
| echo "Terraform plan exposed the HayaSend API key." | |
| exit 1 | |
| fi | |
| if grep --fixed-strings --quiet \ | |
| "$TF_VAR_database_password" "$RUNNER_TEMP/cloud-run-plan.txt"; then | |
| echo "Terraform plan exposed the database password." | |
| exit 1 | |
| fi | |
| terraform show -json "$RUNNER_TEMP/cloud-run.tfplan" \ | |
| > "$RUNNER_TEMP/cloud-run-plan.json" | |
| if grep --fixed-strings --quiet \ | |
| "$TF_VAR_api_key" "$RUNNER_TEMP/cloud-run-plan.json"; then | |
| echo "The saved Terraform plan exposed the HayaSend API key." | |
| exit 1 | |
| fi | |
| if grep --fixed-strings --quiet \ | |
| "$TF_VAR_database_password" \ | |
| "$RUNNER_TEMP/cloud-run-plan.json"; then | |
| echo "The saved Terraform plan exposed the database password." | |
| exit 1 | |
| fi | |
| render-pack: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Verify the pinned Render CLI and deployment scripts | |
| env: | |
| RENDER_CLI_SHA256: 6cdcd11897b7bd7e673317e6f4aaf041b654d818444f3b1efec7240a835f79ec | |
| RENDER_CLI_VERSION: 2.22.0 | |
| run: | | |
| set -euo pipefail | |
| cli_directory="$RUNNER_TEMP/render-cli" | |
| mkdir -p "$cli_directory" | |
| curl --fail --location --silent --show-error \ | |
| --output "$cli_directory/render.zip" \ | |
| "https://github.com/render-oss/cli/releases/download/v${RENDER_CLI_VERSION}/cli_${RENDER_CLI_VERSION}_linux_amd64.zip" | |
| printf '%s %s\n' \ | |
| "$RENDER_CLI_SHA256" \ | |
| "$cli_directory/render.zip" \ | |
| | sha256sum --check | |
| unzip -q "$cli_directory/render.zip" -d "$cli_directory" | |
| render_cli="$cli_directory/cli_v${RENDER_CLI_VERSION}" | |
| test "$("$render_cli" --version | sed -n '1p')" = \ | |
| "render v${RENDER_CLI_VERSION}" | |
| "$render_cli" blueprints validate --help > /dev/null | |
| shellcheck \ | |
| deploy/render/deploy.sh \ | |
| deploy/render/verify.sh \ | |
| deploy/render/proof.sh \ | |
| deploy/render/verify-project.sh \ | |
| deploy/render/cleanup.sh \ | |
| deploy/render/cleanup-project.sh | |
| ruby -ryaml -e \ | |
| 'YAML.safe_load(File.read(ARGV.fetch(0)), permitted_classes: [], permitted_symbols: [], aliases: false)' \ | |
| deploy/render/render.yaml | |
| railway-pack: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24.18.1 | |
| cache: npm | |
| - name: Install npm | |
| run: npm install --global --ignore-scripts npm@12.0.2 | |
| - name: Verify the pinned Railway CLI and deployment pack | |
| env: | |
| HAYASEND_API_KEY: re_RAILWAY_CI_VALIDATION_DO_NOT_USE | |
| RAILWAY_CLI_SHA256: c6169e27e87d95d73fb7a30ec2c6b6c767c042f8a3ba9aa98c92deccda8db10c | |
| RAILWAY_CLI_VERSION: 5.30.1 | |
| run: | | |
| set -euo pipefail | |
| cli_directory="$RUNNER_TEMP/railway-cli" | |
| mkdir -p "$cli_directory" | |
| curl --fail --location --silent --show-error \ | |
| --output "$cli_directory/railway.tar.gz" \ | |
| "https://github.com/railwayapp/cli/releases/download/v${RAILWAY_CLI_VERSION}/railway-v${RAILWAY_CLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | |
| printf '%s %s\n' \ | |
| "$RAILWAY_CLI_SHA256" \ | |
| "$cli_directory/railway.tar.gz" \ | |
| | sha256sum --check | |
| tar -xzf "$cli_directory/railway.tar.gz" -C "$cli_directory" | |
| test "$("$cli_directory/railway" --version)" = \ | |
| "railway ${RAILWAY_CLI_VERSION}" | |
| "$cli_directory/railway" config plan --help > /dev/null | |
| test "$(< deploy/railway/.railway-cli-version)" = \ | |
| "$RAILWAY_CLI_VERSION" | |
| test "$(< deploy/railway/.railway-cli-linux-x86_64-sha256)" = \ | |
| "$RAILWAY_CLI_SHA256" | |
| npm ci | |
| npm run validate:railway | |
| shellcheck \ | |
| deploy/railway/deploy.sh \ | |
| deploy/railway/verify.sh \ | |
| deploy/railway/cleanup.sh | |
| flyio-pack: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Verify the pinned flyctl and deployment pack | |
| env: | |
| FLYCTL_SHA256: 000edf4711de7b1a854a11f8148912dc63b30fba99ae52d3bbce98627015ec26 | |
| FLYCTL_VERSION: 0.4.75 | |
| run: | | |
| set -euo pipefail | |
| cli_directory="$RUNNER_TEMP/flyctl" | |
| mkdir -p "$cli_directory" | |
| curl --fail --location --silent --show-error \ | |
| --output "$cli_directory/flyctl.tar.gz" \ | |
| "https://github.com/superfly/flyctl/releases/download/v${FLYCTL_VERSION}/flyctl_${FLYCTL_VERSION}_Linux_x86_64.tar.gz" | |
| printf '%s %s\n' \ | |
| "$FLYCTL_SHA256" \ | |
| "$cli_directory/flyctl.tar.gz" \ | |
| | sha256sum --check | |
| tar -xzf "$cli_directory/flyctl.tar.gz" -C "$cli_directory" | |
| test "$("$cli_directory/flyctl" version | sed -n '1s/ .*//p')" = \ | |
| "flyctl" | |
| test "$( | |
| "$cli_directory/flyctl" version | | |
| sed -n '1s/^flyctl v\([^ ]*\).*/\1/p' | |
| )" = "$FLYCTL_VERSION" | |
| "$cli_directory/flyctl" config validate --help > /dev/null | |
| "$cli_directory/flyctl" mpg create --help > /dev/null | |
| "$cli_directory/flyctl" storage create --help > /dev/null | |
| test "$(< deploy/flyio/.flyctl-version)" = "$FLYCTL_VERSION" | |
| test "$(< deploy/flyio/.flyctl-linux-x86_64-sha256)" = \ | |
| "$FLYCTL_SHA256" | |
| image="$( | |
| sed -n 's/^ image = "\(.*\)"/\1/p' \ | |
| deploy/flyio/fly.toml | |
| )" | |
| machine_digest="$( | |
| docker buildx imagetools inspect --raw "$image" | | |
| jq --raw-output ' | |
| [ | |
| .manifests[] | | |
| select( | |
| .platform.os == "linux" and | |
| .platform.architecture == "amd64" | |
| ) | |
| ] | | |
| if length == 1 then | |
| .[0].digest | |
| else | |
| error("ambiguous Linux/amd64 manifest") | |
| end | |
| ' | |
| )" | |
| test "$(< deploy/flyio/.image-linux-amd64-sha256)" = \ | |
| "$machine_digest" | |
| node scripts/validate-flyio-config.mjs | |
| shellcheck -x \ | |
| deploy/flyio/lib.sh \ | |
| deploy/flyio/provision.sh \ | |
| deploy/flyio/deploy.sh \ | |
| deploy/flyio/verify.sh \ | |
| deploy/flyio/proof.sh \ | |
| deploy/flyio/verify-bucket-empty.sh \ | |
| deploy/flyio/cleanup-proof-machine.sh \ | |
| deploy/flyio/rollback.sh \ | |
| deploy/flyio/cleanup.sh | |
| vercel-pack: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24.18.1 | |
| cache: npm | |
| - name: Install npm | |
| run: npm install --global --ignore-scripts npm@12.0.2 | |
| - run: npm ci | |
| - name: Verify the pinned Vercel SDKs, CLI, and deployment pack | |
| run: | | |
| set -euo pipefail | |
| npm run validate:vercel | |
| shellcheck -x \ | |
| deploy/vercel/lib.sh \ | |
| deploy/vercel/deploy.sh \ | |
| deploy/vercel/verify.sh \ | |
| deploy/vercel/rollback.sh \ | |
| deploy/vercel/cleanup.sh | |
| for script in \ | |
| deploy/vercel/blob-count.mjs \ | |
| deploy/vercel/blob-store-assert-empty.mjs \ | |
| deploy/vercel/blob-store-create.mjs \ | |
| deploy/vercel/blob-store-delete.mjs \ | |
| deploy/vercel/blob-store-lifecycle.mjs \ | |
| deploy/vercel/blob-store-verify.mjs \ | |
| deploy/vercel/extract-production-blob-token.mjs \ | |
| deploy/vercel/neon-branch-create.mjs \ | |
| deploy/vercel/neon-branch-delete.mjs \ | |
| deploy/vercel/neon-branch-verify.mjs \ | |
| deploy/vercel/neon-branch.mjs \ | |
| deploy/vercel/project-create.mjs \ | |
| deploy/vercel/project-delete.mjs \ | |
| deploy/vercel/project-lifecycle.mjs \ | |
| deploy/vercel/project-verify.mjs \ | |
| scripts/vercel-blob-hosted-proof.mjs; do | |
| node --check "$script" | |
| done | |
| version="$(< deploy/vercel/.vercel-cli-version)" | |
| expected_integrity="$(< deploy/vercel/.vercel-cli-integrity)" | |
| metadata="$(npm view "vercel@$version" version dist.integrity --json)" | |
| test "$( | |
| jq --raw-output \ | |
| '(if type == "array" then .[0] else . end).version' \ | |
| <<<"$metadata" | |
| )" = "$version" | |
| test "$( | |
| jq --raw-output \ | |
| '(if type == "array" then .[0] else . end).["dist.integrity"]' \ | |
| <<<"$metadata" | |
| )" = \ | |
| "$expected_integrity" | |
| postgres-substrate: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| services: | |
| postgres: | |
| image: postgres:18-alpine@sha256:9a8afca54e7861fd90fab5fdf4c42477a6b1cb7d293595148e674e0a3181de15 | |
| env: | |
| POSTGRES_DB: hayasend_test | |
| POSTGRES_PASSWORD: hayasend-test | |
| POSTGRES_USER: hayasend | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready --username=hayasend --dbname=hayasend_test" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24.18.1 | |
| cache: npm | |
| - name: Install npm | |
| run: npm install --global --ignore-scripts npm@12.0.2 | |
| - run: npm ci | |
| - name: Exercise PostgreSQL 18 store, jobs, and portable runtime | |
| env: | |
| HAYASEND_POSTGRES_TEST_URL: >- | |
| postgres://hayasend:hayasend-test@127.0.0.1:5432/hayasend_test | |
| run: npm run test:postgres | |
| - name: Smoke-test production console proof in the built image | |
| env: | |
| HAYASEND_POSTGRES_TEST_URL: >- | |
| postgres://hayasend:hayasend-test@127.0.0.1:5432/hayasend_test | |
| run: | | |
| set -euo pipefail | |
| docker build \ | |
| --build-arg VERSION=ci \ | |
| --build-arg REVISION=${{ github.sha }} \ | |
| --tag hayasend:portable-ci \ | |
| . | |
| if docker run --rm \ | |
| --network host \ | |
| --env HAYASEND_MODE=portable \ | |
| --env HAYASEND_DATABASE_URL="$HAYASEND_POSTGRES_TEST_URL" \ | |
| --env HAYASEND_API_KEY=re_portable_container_ci \ | |
| --env HAYASEND_TRANSPORT=console \ | |
| hayasend:portable-ci \ | |
| node dist/portable/migrate.js \ | |
| > "$RUNNER_TEMP/portable-console-without-guard.log" 2>&1; then | |
| echo "Production console mode started without its proof guard." | |
| exit 1 | |
| fi | |
| grep --fixed-strings --quiet \ | |
| '"error_type":"validation_error"' \ | |
| "$RUNNER_TEMP/portable-console-without-guard.log" | |
| docker run --rm \ | |
| --network host \ | |
| --env HAYASEND_MODE=portable \ | |
| --env HAYASEND_DATABASE_URL="$HAYASEND_POSTGRES_TEST_URL" \ | |
| --env HAYASEND_API_KEY=re_portable_container_ci \ | |
| --env HAYASEND_TRANSPORT=console \ | |
| --env HAYASEND_CONSOLE_PROOF_CONFIRM=isolated-non-sending \ | |
| hayasend:portable-ci \ | |
| node dist/portable/migrate.js | |
| docker run --detach \ | |
| --name hayasend-portable-ci \ | |
| --network host \ | |
| --read-only \ | |
| --cap-drop ALL \ | |
| --security-opt no-new-privileges \ | |
| --env HAYASEND_MODE=portable \ | |
| --env HAYASEND_DATABASE_URL="$HAYASEND_POSTGRES_TEST_URL" \ | |
| --env HAYASEND_API_KEY=re_portable_container_ci \ | |
| --env HAYASEND_TRANSPORT=console \ | |
| --env HAYASEND_CONSOLE_PROOF_CONFIRM=isolated-non-sending \ | |
| --env HAYASEND_PORT=8790 \ | |
| hayasend:portable-ci | |
| for attempt in $(seq 1 20); do | |
| if curl --fail --silent http://127.0.0.1:8790/readyz > /dev/null; then | |
| break | |
| fi | |
| if [ "$attempt" = 20 ]; then | |
| docker logs hayasend-portable-ci | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| test "$( | |
| docker inspect --format '{{.Config.User}}' hayasend-portable-ci | |
| )" = "node" | |
| - name: Stop portable container | |
| if: always() | |
| run: docker rm --force hayasend-portable-ci || true | |
| test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24.18.1 | |
| cache: npm | |
| - uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5 | |
| with: | |
| deno-version: v2.9.4 | |
| cache: true | |
| - uses: aws-actions/setup-sam@89ddb14d60e682855e3fea4be85b3c56485de310 # v3 | |
| with: | |
| version: 1.165.0 | |
| - name: Install npm | |
| run: npm install --global --ignore-scripts npm@12.0.2 | |
| - run: | | |
| npm ci | |
| echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" | |
| - run: npm run check | |
| - run: npm run check:supabase-hook | |
| - run: npm run check:workers | |
| - run: npm run check:conformance | |
| - run: npm run validate:flyio | |
| - run: npm run validate:railway | |
| - run: npm run validate:render | |
| - run: npm run validate:vercel | |
| - run: npm test | |
| - run: npm run build | |
| - name: Validate CloudFormation | |
| run: | | |
| python -m pip install --disable-pip-version-check cfn-lint==1.53.3 | |
| cfn-lint template.yaml | |
| - name: Validate OpenAPI | |
| run: npm run lint:openapi | |
| - name: Build and verify static site | |
| run: | | |
| npm run site:build | |
| npm run site:verify | |
| - name: Build and load Lambda bundles | |
| run: | | |
| sam build | |
| node scripts/verify-lambda-bundles.mjs | |
| - name: Build local container | |
| run: | | |
| docker build --check . | |
| docker build \ | |
| --build-arg VERSION=ci \ | |
| --build-arg REVISION=${{ github.sha }} \ | |
| --tag hayasend:ci \ | |
| . | |
| - name: Smoke-test hardened local container | |
| run: | | |
| smoke_api_key="re_""hayasend_dev" | |
| docker run --detach \ | |
| --name hayasend-ci \ | |
| --read-only \ | |
| --cap-drop ALL \ | |
| --security-opt no-new-privileges \ | |
| --publish 127.0.0.1:8787:8787 \ | |
| hayasend:ci | |
| for attempt in $(seq 1 20); do | |
| if curl --fail --silent http://127.0.0.1:8787/healthz > /dev/null; then | |
| break | |
| fi | |
| if [ "$attempt" = 20 ]; then | |
| docker logs hayasend-ci | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| test "$(docker inspect --format '{{.Config.User}}' hayasend-ci)" = "node" | |
| curl --fail --silent http://127.0.0.1:8787/emails \ | |
| --header "Authorization: Bearer ${smoke_api_key}" \ | |
| --header 'Content-Type: application/json' \ | |
| --header 'Idempotency-Key: container-smoke' \ | |
| --data '{"from":"Smoke <sender@example.com>","to":"recipient@example.net","subject":"Container smoke test","text":"Read-only local mode works."}' \ | |
| | grep --quiet '"id":"email_' | |
| - name: Stop local container | |
| if: always() | |
| run: docker rm --force hayasend-ci || true | |
| python-sdk: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 24.18.1 | |
| cache: npm | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 | |
| with: | |
| python-version: "3.14.6" | |
| cache: pip | |
| cache-dependency-path: tests/sdk/python-requirements.txt | |
| - name: Install npm | |
| run: npm install --global --ignore-scripts npm@12.0.2 | |
| - run: npm ci | |
| - name: Install the official Resend Python SDK | |
| run: > | |
| python -m pip install | |
| --disable-pip-version-check | |
| --only-binary=:all: | |
| --require-hashes | |
| --requirement tests/sdk/python-requirements.txt | |
| - name: Exercise Python SDK compatibility | |
| env: | |
| HAYASEND_API_KEY: re_hayasend_dev | |
| HAYASEND_BASE_URL: http://127.0.0.1:8787 | |
| run: | | |
| set -euo pipefail | |
| if HAYASEND_BASE_URL=http://api.resend.com:80 \ | |
| python tests/sdk/python_resend_compat.py \ | |
| 2> "$RUNNER_TEMP/python-sdk-endpoint-guard.log"; then | |
| echo "The SDK compatibility test accepted a production endpoint." | |
| exit 1 | |
| fi | |
| grep --quiet "loopback" "$RUNNER_TEMP/python-sdk-endpoint-guard.log" | |
| npm run dev > "$RUNNER_TEMP/hayasend-python-sdk.log" 2>&1 & | |
| hayasend_server_pid=$! | |
| trap 'kill "$hayasend_server_pid" 2>/dev/null || true' EXIT | |
| for attempt in $(seq 1 20); do | |
| if curl --fail --silent "$HAYASEND_BASE_URL/healthz" > /dev/null; then | |
| break | |
| fi | |
| if [ "$attempt" = 20 ]; then | |
| cat "$RUNNER_TEMP/hayasend-python-sdk.log" | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| python tests/sdk/python_resend_compat.py | |
| openapi-contract: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version: 24.18.1 | |
| cache: npm | |
| - name: Install npm | |
| run: npm install --global --ignore-scripts npm@12.0.2 | |
| - run: npm ci | |
| - name: Exercise the published OpenAPI contract | |
| env: | |
| HAYASEND_CONTRACT_BASE_URL: http://127.0.0.1:8787 | |
| SCHEMATHESIS_VERSION: "4.24.3" | |
| SCHEMATHESIS_IMAGE: >- | |
| ghcr.io/schemathesis/schemathesis@sha256:dd1ebf7519958c34c276a65c20f9f2f808dbefb06c86163eb284ff5674c6a9f3 | |
| run: | | |
| set -euo pipefail | |
| if HAYASEND_CONTRACT_BASE_URL=https://api.resend.com \ | |
| node scripts/openapi-contract-safety.mjs \ | |
| 2> "$RUNNER_TEMP/openapi-contract-endpoint-guard.log"; then | |
| echo "The OpenAPI contract test accepted a production endpoint." | |
| exit 1 | |
| fi | |
| grep --quiet "loopback" \ | |
| "$RUNNER_TEMP/openapi-contract-endpoint-guard.log" | |
| contract_base_url="$( | |
| node scripts/openapi-contract-safety.mjs | |
| )" | |
| contract_api_key="re_""hayasend_dev" | |
| test "$( | |
| docker run --rm \ | |
| --read-only \ | |
| --cap-drop ALL \ | |
| --security-opt no-new-privileges \ | |
| "$SCHEMATHESIS_IMAGE" \ | |
| --version | |
| )" = "schemathesis, version $SCHEMATHESIS_VERSION" | |
| npm run dev > "$RUNNER_TEMP/hayasend-openapi-contract.log" 2>&1 & | |
| hayasend_server_pid=$! | |
| trap 'kill "$hayasend_server_pid" 2>/dev/null || true' EXIT | |
| for attempt in $(seq 1 20); do | |
| if curl --fail --silent \ | |
| "$contract_base_url/healthz" > /dev/null; then | |
| break | |
| fi | |
| if [ "$attempt" = 20 ]; then | |
| cat "$RUNNER_TEMP/hayasend-openapi-contract.log" | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| batch_response="$( | |
| curl --fail-with-body --silent --show-error \ | |
| --request POST \ | |
| --header "Authorization: Bearer ${contract_api_key}" \ | |
| --header "Content-Type: application/json" \ | |
| --header "Idempotency-Key: openapi-contract/batch" \ | |
| --data '[ | |
| { | |
| "from": "Contract <contract@example.test>", | |
| "to": ["recipient@example.test"], | |
| "subject": "Contract batch", | |
| "text": "Synthetic OpenAPI batch contract." | |
| } | |
| ]' \ | |
| "$contract_base_url/emails/batch" | |
| )" | |
| jq --exit-status ' | |
| (.data | type == "array" and length == 1) and | |
| (.data[0].id | test("^email_[a-f0-9]{32}$")) | |
| ' <<< "$batch_response" | |
| docker run --rm \ | |
| --network host \ | |
| --read-only \ | |
| --cap-drop ALL \ | |
| --security-opt no-new-privileges \ | |
| --env SCHEMATHESIS_COVERAGE=false \ | |
| --env SCHEMATHESIS_HOOKS=/work/schemathesis_hooks.py \ | |
| --tmpfs /tmp:rw,noexec,nosuid,size=64m \ | |
| --workdir /tmp \ | |
| --mount \ | |
| "type=bind,source=$GITHUB_WORKSPACE/openapi.yaml,target=/work/openapi.yaml,readonly" \ | |
| --mount \ | |
| "type=bind,source=$GITHUB_WORKSPACE/tests/contract/schemathesis_hooks.py,target=/work/schemathesis_hooks.py,readonly" \ | |
| "$SCHEMATHESIS_IMAGE" \ | |
| run /work/openapi.yaml \ | |
| --url "$contract_base_url" \ | |
| --header "Authorization: Bearer ${contract_api_key}" \ | |
| --phases fuzzing \ | |
| --mode all \ | |
| --checks \ | |
| not_a_server_error,status_code_conformance,content_type_conformance,response_schema_conformance \ | |
| --max-examples 10 \ | |
| --generation-deterministic \ | |
| --workers 1 \ | |
| --max-failures 20 \ | |
| --request-timeout 2 \ | |
| --suppress-health-check filter_too_much \ | |
| --no-color | |
| npm-package: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version: 24.18.1 | |
| package-manager-cache: false | |
| - name: Install npm | |
| run: npm install --global --ignore-scripts npm@12.0.2 | |
| - uses: aws-actions/setup-sam@89ddb14d60e682855e3fea4be85b3c56485de310 # v3 | |
| with: | |
| version: 1.165.0 | |
| - run: npm ci | |
| - name: Build and exercise the distributable CLI | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| printf 'must not ship\n' > dist/stale-cross-branch-artifact.txt | |
| npm pack --json --pack-destination "$RUNNER_TEMP" \ | |
| > "$RUNNER_TEMP/npm-pack.json" | |
| jq ' | |
| if type == "array" then | |
| .[0] | |
| else | |
| .["@haya-inc/hayasend"] | |
| end | |
| ' "$RUNNER_TEMP/npm-pack.json" \ | |
| > "$RUNNER_TEMP/npm-pack-metadata.json" | |
| version="$(node --print "require('./package.json').version")" | |
| expected_archive="haya-inc-hayasend-${version}.tgz" | |
| archive="$(jq --raw-output '.filename' \ | |
| "$RUNNER_TEMP/npm-pack-metadata.json")" | |
| test "$archive" = "$expected_archive" | |
| test -f "$RUNNER_TEMP/$archive" | |
| test "$( | |
| node scripts/npm-package-integrity.mjs < "$RUNNER_TEMP/$archive" | |
| )" = "$( | |
| jq --raw-output '.integrity' \ | |
| "$RUNNER_TEMP/npm-pack-metadata.json" | |
| )" | |
| jq '{entryCount, size, unpackedSize, filename}' \ | |
| "$RUNNER_TEMP/npm-pack-metadata.json" | |
| jq --exit-status ' | |
| .entryCount <= 639 and | |
| .size < 900000 and | |
| .unpackedSize < 4600000 and | |
| ([.files[].path] | index("dist/cli.js") != null) and | |
| ([.files[].path] | | |
| index("dist/cli-resend-migration.js") != null) and | |
| ([.files[].path] | | |
| index("dist/cli-aws-bootstrap.js") != null) and | |
| ([.files[].path] | | |
| index("dist/operator-console.js") != null) and | |
| ([.files[].path] | | |
| index("src/operator-console.ts") != null) and | |
| ([.files[].path] | | |
| index("dist/operator-console-view.js") != null) and | |
| ([.files[].path] | | |
| index("src/operator-console-view.tsx") != null) and | |
| ([.files[].path] | | |
| index("dist/console-auth.js") != null) and | |
| ([.files[].path] | | |
| index("src/console-auth.ts") != null) and | |
| ([.files[].path] | | |
| index("deploy/aws-cloudformation-bootstrap.yaml") != null) and | |
| ([.files[].path] | index("NOTICE") != null) and | |
| ([.files[].path] | | |
| index("src/cli-resend-migration.ts") != null) and | |
| ([.files[].path] | | |
| index("migrations/0003_cloudflare_suppressions.sql") != null) and | |
| ([.files[].path] | | |
| index("migrations/postgres/0002_application_store.sql") != null) and | |
| ([.files[].path] | | |
| index("migrations/postgres/0003_jobs.sql") != null) and | |
| ([.files[].path] | | |
| index("migrations/postgres/0004_provider_message_correlation.sql") != null) and | |
| ([.files[].path] | | |
| index("dist/portable/migrate.js") != null) and | |
| ([.files[].path] | | |
| index("dist/portable/worker.js") != null) and | |
| ([.files[].path] | | |
| index("dist/portable/hosted-proof.js") != null) and | |
| ([.files[].path] | | |
| index("dist/portable/backup-restore-proof.js") != null) and | |
| ([.files[].path] | | |
| index("dist/portable/backup-restore-seed.js") != null) and | |
| ([.files[].path] | | |
| index("dist/portable/backup-restore-verify.js") != null) and | |
| ([.files[].path] | | |
| index("scripts/cloudflare-cost-model.mjs") != null) and | |
| ([.files[].path] | index("src/aws/api.ts") != null) and | |
| ([.files[].path] | index("src/workers/index.ts") != null) and | |
| ([.files[].path] | | |
| index("schemas/runtime-capabilities.v1.schema.json") != null) and | |
| ([.files[].path] | | |
| index("schemas/deployment-capabilities.v1.schema.json") != null) and | |
| ([.files[].path] | | |
| index("conformance/deployments/aws-ses.v1.json") != null) and | |
| ([.files[].path] | | |
| index("conformance/deployments/azure-container-apps-acs.v1.json") != null) and | |
| ([.files[].path] | | |
| index("conformance/runtimes/portable-postgres.v1.json") != null) and | |
| ([.files[].path] | | |
| index("conformance/providers/azure-communication-services.v1.json") != null) and | |
| ([.files[].path] | | |
| index("conformance/providers/sendgrid.v1.json") != null) and | |
| ([.files[].path] | | |
| index("dist/provider-capability-registry.js") != null) and | |
| ([.files[].path] | | |
| index("dist/runtime-capability-registry.js") != null) and | |
| ([.files[].path] | | |
| index("dist/deployment-capability-registry.js") != null) and | |
| ([.files[].path] | | |
| index("dist/capability-profiles.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/portable-attachment-storage.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/azure/acs-email-transport.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/azure/acs-email-events.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/sendgrid/sendgrid-email-transport.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/sendgrid/sendgrid-email-events.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/google-pubsub-wakeup.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/wakeup-job-queue.js") != null) and | |
| ([.files[].path] | | |
| index("dist/ports/job-wakeup.js") != null) and | |
| ([.files[].path] | | |
| index("dist/adapters/vercel-blob-attachment-storage.js") != null) and | |
| ([.files[].path] | | |
| index("dist/vercel/functions.js") != null) and | |
| ([.files[].path] | index("app.ts") != null) and | |
| ([.files[].path] | index("api/queue.ts") != null) and | |
| ([.files[].path] | index("api/reconcile.ts") != null) and | |
| ([.files[].path] | index("vercel.json") != null) and | |
| ([.files[].path] | | |
| index("deploy/vercel/deploy.sh") != null) and | |
| ([.files[].path] | | |
| index("deploy/vercel/.vercel-cli-version") != null) and | |
| ([.files[].path] | | |
| index("conformance/runtimes/vercel-serverless.v1.json") != null) and | |
| ([.files[].path] | | |
| index("deploy/cloud-run/main.tf") != null) and | |
| ([.files[].path] | | |
| index("deploy/cloud-run/.terraform.lock.hcl") != null) and | |
| ([.files[].path] | | |
| index("deploy/cloud-run/.terraform-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/cloud-run/backend.tf.example") != null) and | |
| ([.files[].path] | | |
| index("deploy/cloud-run/rollback.sh") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/main.tf") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/.terraform.lock.hcl") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/.terraform-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/.azure-cli-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/.containerapp-extension-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/.communication-control-plane-api-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/verify-acs-prerequisites.sh") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/event-grid.mjs") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/event-grid-ensure.mjs") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/event-grid-verify.mjs") != null) and | |
| ([.files[].path] | | |
| index("deploy/azure-container-apps/event-grid-delete.mjs") != null) and | |
| ([.files[].path] | | |
| index("deploy/render/render.yaml") != null) and | |
| ([.files[].path] | | |
| index("deploy/render/.render-cli-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/render/.render-schema-sha256") != null) and | |
| ([.files[].path] | | |
| index("deploy/render/rollback.sh") != null) and | |
| ([.files[].path] | | |
| index("deploy/railway/.railway/railway.ts") != null) and | |
| ([.files[].path] | | |
| index("deploy/railway/.railway-cli-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/railway/.railway-cli-linux-x86_64-sha256") != null) and | |
| ([.files[].path] | | |
| index("deploy/railway/rollback.sh") != null) and | |
| ([.files[].path] | | |
| index("deploy/flyio/fly.toml") != null) and | |
| ([.files[].path] | | |
| index("deploy/flyio/.flyctl-version") != null) and | |
| ([.files[].path] | | |
| index("deploy/flyio/.flyctl-linux-x86_64-sha256") != null) and | |
| ([.files[].path] | | |
| index("deploy/flyio/.image-linux-amd64-sha256") != null) and | |
| ([.files[].path] | | |
| index("conformance/readiness.v1.json") != null) and | |
| ([.files[].path] | | |
| index("scripts/azure-terminal-delivery.mjs") != null) and | |
| ([.files[].path] | | |
| index("scripts/extract-portable-hosted-proof.mjs") != null) and | |
| ([.files[].path] | | |
| index("scripts/vercel-blob-hosted-proof.mjs") != null) and | |
| ([.files[].path] | | |
| index("schemas/readiness-matrix.v1.schema.json") != null) and | |
| ([.files[].path] | index("template.yaml") != null) and | |
| ([.files[].path] | | |
| index("dist/stale-cross-branch-artifact.txt") == null) | |
| ' "$RUNNER_TEMP/npm-pack-metadata.json" > /dev/null | |
| if tar --list --gzip --file "$RUNNER_TEMP/$archive" | | |
| grep --extended-regexp --invert-match --quiet \ | |
| '^package/(LICENSE|NOTICE|README\.md|package\.json|api/.*|app\.ts|conformance/.*|deploy/.*|dist/.*|migrations/.*|schemas/.*|scripts/(azure-terminal-delivery|clean-dist|cloudflare-cost-model|extract-portable-hosted-proof|npm-sam-compat|validate-vercel-config|vercel-blob-hosted-proof)\.mjs|src/.*|template\.yaml|vercel\.json)$'; then | |
| echo "The npm package contains an unexpected path." | |
| tar --list --gzip --file "$RUNNER_TEMP/$archive" | |
| exit 1 | |
| fi | |
| consumer="$RUNNER_TEMP/hayasend-consumer" | |
| project="$RUNNER_TEMP/hayasend-project" | |
| npm install \ | |
| --ignore-scripts \ | |
| --no-audit \ | |
| --no-fund \ | |
| --prefix "$consumer" \ | |
| "$RUNNER_TEMP/$archive" | |
| "$consumer/node_modules/.bin/hayasend" help \ | |
| > "$RUNNER_TEMP/hayasend-help.txt" | |
| grep --quiet "HayaSend CLI" "$RUNNER_TEMP/hayasend-help.txt" | |
| npm exec \ | |
| --yes \ | |
| --ignore-scripts \ | |
| --package="$RUNNER_TEMP/$archive" \ | |
| -- hayasend help \ | |
| > "$RUNNER_TEMP/hayasend-npx-help.txt" | |
| grep --quiet "HayaSend CLI" "$RUNNER_TEMP/hayasend-npx-help.txt" | |
| package_root="$consumer/node_modules/@haya-inc/hayasend" | |
| test -f "$package_root/src/workers/index.ts" | |
| test -f \ | |
| "$package_root/migrations/0003_cloudflare_suppressions.sql" | |
| test -f \ | |
| "$package_root/migrations/postgres/0002_application_store.sql" | |
| test -f \ | |
| "$package_root/migrations/postgres/0003_jobs.sql" | |
| test -f \ | |
| "$package_root/migrations/postgres/0004_provider_message_correlation.sql" | |
| test -f "$package_root/dist/portable/migrate.js" | |
| test -f "$package_root/dist/portable/worker.js" | |
| test -f \ | |
| "$package_root/dist/portable/backup-restore-proof.js" | |
| test -f \ | |
| "$package_root/dist/portable/backup-restore-seed.js" | |
| test -f \ | |
| "$package_root/dist/portable/backup-restore-verify.js" | |
| test -f "$package_root/scripts/cloudflare-cost-model.mjs" | |
| test -f \ | |
| "$package_root/schemas/runtime-capabilities.v1.schema.json" | |
| test -f \ | |
| "$package_root/schemas/deployment-capabilities.v1.schema.json" | |
| test -f \ | |
| "$package_root/conformance/deployments/aws-ses.v1.json" | |
| test -f \ | |
| "$package_root/conformance/deployments/azure-container-apps-acs.v1.json" | |
| test -f \ | |
| "$package_root/conformance/runtimes/portable-postgres.v1.json" | |
| test -f \ | |
| "$package_root/conformance/providers/azure-communication-services.v1.json" | |
| test -f \ | |
| "$package_root/dist/provider-capability-registry.js" | |
| test -f \ | |
| "$package_root/dist/runtime-capability-registry.js" | |
| test -f \ | |
| "$package_root/dist/deployment-capability-registry.js" | |
| test -f "$package_root/dist/capability-profiles.js" | |
| test -f \ | |
| "$package_root/dist/adapters/portable-attachment-storage.js" | |
| test -f \ | |
| "$package_root/dist/adapters/azure/acs-email-transport.js" | |
| test -f \ | |
| "$package_root/dist/adapters/azure/acs-email-events.js" | |
| test -f "$package_root/deploy/cloud-run/main.tf" | |
| test -f "$package_root/deploy/cloud-run/.terraform.lock.hcl" | |
| test -f "$package_root/deploy/cloud-run/.terraform-version" | |
| test -f "$package_root/deploy/cloud-run/backend.tf.example" | |
| test -x "$package_root/deploy/cloud-run/deploy.sh" | |
| test -x "$package_root/deploy/cloud-run/rollback.sh" | |
| test -x "$package_root/deploy/cloud-run/cleanup.sh" | |
| test -f "$package_root/deploy/azure-container-apps/main.tf" | |
| test -f \ | |
| "$package_root/deploy/azure-container-apps/.terraform.lock.hcl" | |
| test -f \ | |
| "$package_root/deploy/azure-container-apps/.terraform-version" | |
| test -f \ | |
| "$package_root/deploy/azure-container-apps/.azure-cli-version" | |
| test -f \ | |
| "$package_root/deploy/azure-container-apps/.containerapp-extension-version" | |
| test -f \ | |
| "$package_root/deploy/azure-container-apps/.communication-control-plane-api-version" | |
| test -x "$package_root/deploy/azure-container-apps/deploy.sh" | |
| test -x "$package_root/deploy/azure-container-apps/verify.sh" | |
| test -x \ | |
| "$package_root/deploy/azure-container-apps/verify-acs-prerequisites.sh" | |
| test -x "$package_root/deploy/azure-container-apps/rollback.sh" | |
| test -x "$package_root/deploy/azure-container-apps/cleanup.sh" | |
| test -x "$package_root/deploy/azure-container-apps/event-grid.mjs" | |
| test -x \ | |
| "$package_root/deploy/azure-container-apps/event-grid-ensure.mjs" | |
| test -x \ | |
| "$package_root/deploy/azure-container-apps/event-grid-verify.mjs" | |
| test -x \ | |
| "$package_root/deploy/azure-container-apps/event-grid-delete.mjs" | |
| test -f "$package_root/deploy/render/render.yaml" | |
| test -f "$package_root/deploy/render/.render-cli-version" | |
| test -f "$package_root/deploy/render/.render-schema-sha256" | |
| test -x "$package_root/deploy/render/deploy.sh" | |
| test -x "$package_root/deploy/render/verify.sh" | |
| test -x "$package_root/deploy/render/proof.sh" | |
| test -x "$package_root/deploy/render/verify-project.sh" | |
| test -x "$package_root/deploy/render/rollback.sh" | |
| test -x "$package_root/deploy/render/cleanup.sh" | |
| test -x "$package_root/deploy/render/cleanup-project.sh" | |
| test -f \ | |
| "$package_root/deploy/railway/.railway/railway.ts" | |
| test -f "$package_root/deploy/railway/.railway-cli-version" | |
| test -f \ | |
| "$package_root/deploy/railway/.railway-cli-linux-x86_64-sha256" | |
| test -x "$package_root/deploy/railway/deploy.sh" | |
| test -x "$package_root/deploy/railway/verify.sh" | |
| test -x "$package_root/deploy/railway/rollback.sh" | |
| test -x "$package_root/deploy/railway/cleanup.sh" | |
| test -f "$package_root/deploy/flyio/fly.toml" | |
| test -f "$package_root/deploy/flyio/.flyctl-version" | |
| test -f \ | |
| "$package_root/deploy/flyio/.flyctl-linux-x86_64-sha256" | |
| test -f \ | |
| "$package_root/deploy/flyio/.image-linux-amd64-sha256" | |
| test -x "$package_root/deploy/flyio/provision.sh" | |
| test -x "$package_root/deploy/flyio/deploy.sh" | |
| test -x "$package_root/deploy/flyio/verify.sh" | |
| test -x "$package_root/deploy/flyio/proof.sh" | |
| test -x \ | |
| "$package_root/deploy/flyio/verify-bucket-empty.sh" | |
| test -x \ | |
| "$package_root/deploy/flyio/cleanup-proof-machine.sh" | |
| test -x "$package_root/deploy/flyio/rollback.sh" | |
| test -x "$package_root/deploy/flyio/cleanup.sh" | |
| test -f \ | |
| "$package_root/scripts/azure-terminal-delivery.mjs" | |
| test -f \ | |
| "$package_root/scripts/extract-portable-hosted-proof.mjs" | |
| test -f \ | |
| "$package_root/scripts/vercel-blob-hosted-proof.mjs" | |
| test -x "$consumer/node_modules/.bin/railway-iac-ts" | |
| ( | |
| cd "$consumer" | |
| HAYASEND_API_KEY=re_RAILWAY_PACKAGE_VALIDATION_DO_NOT_USE \ | |
| SENDGRID_API_KEY=SG.RAILWAY_PACKAGE_VALIDATION_DO_NOT_USE_000000 \ | |
| SENDGRID_EVENT_WEBHOOK_PUBLIC_KEY=RAILWAY_PACKAGE_VALIDATION_PUBLIC_KEY_DO_NOT_USE_0000000000000000 \ | |
| node --input-type=module --eval ' | |
| import { evaluateRailwayFile } from "railway/iac"; | |
| const result = await evaluateRailwayFile(process.argv[1]); | |
| if (result.graph.project.name !== "hayasend-railway") { | |
| process.exit(1); | |
| } | |
| ' "$package_root/deploy/railway/.railway/railway.ts" | |
| ) | |
| test -f "$package_root/conformance/readiness.v1.json" | |
| test -f \ | |
| "$package_root/schemas/readiness-matrix.v1.schema.json" | |
| test -x "$consumer/node_modules/.bin/esbuild" | |
| npm_root="$(npm root --global)" | |
| npm_compat="$RUNNER_TEMP/npm-sam-compat" | |
| mkdir "$npm_compat" | |
| cp "$package_root/scripts/npm-sam-compat.mjs" "$npm_compat/npm" | |
| chmod 700 "$npm_compat/npm" | |
| sam validate \ | |
| --lint \ | |
| --template-file "$package_root/template.yaml" | |
| HAYASEND_REAL_NPM_CLI="$npm_root/npm/bin/npm-cli.js" \ | |
| PATH="$npm_compat:$consumer/node_modules/.bin:$PATH" sam build \ | |
| --parallel \ | |
| --template-file "$package_root/template.yaml" \ | |
| --base-dir "$package_root" \ | |
| --build-dir "$RUNNER_TEMP/hayasend-sam-build" | |
| test -f "$RUNNER_TEMP/hayasend-sam-build/template.yaml" | |
| mkdir "$project" | |
| ( | |
| cd "$project" | |
| "$consumer/node_modules/.bin/hayasend" init > init.json | |
| ) | |
| jq --exit-status ' | |
| .ok == true and | |
| .created == [ | |
| "compose.hayasend.yaml", | |
| ".env.hayasend.example" | |
| ] | |
| ' "$project/init.json" > /dev/null | |
| grep --fixed-strings --quiet \ | |
| "image: ghcr.io/haya-inc/hayasend:${version}" \ | |
| "$project/compose.hayasend.yaml" |