Prepare v0.3.11 release #421
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 <= 655 and | |
| .size < 940000 and | |
| .unpackedSize < 4800000 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-client.generated.js") != null) and | |
| ([.files[].path] | | |
| index("src/operator-console-client.generated.ts") != null) and | |
| ([.files[].path] | | |
| index("dist/operator-console-client.js") != null) and | |
| ([.files[].path] | | |
| index("src/operator-console-client.tsx") != null) and | |
| ([.files[].path] | | |
| index("dist/operator-console-model.js") != null) and | |
| ([.files[].path] | | |
| index("src/operator-console-model.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" |