mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 08:03:13 +00:00
Merge branch 'main' into feat/ab/sh-382-extra-doc-view
This commit is contained in:
commit
e1b03dc151
12 changed files with 61 additions and 137 deletions
33
.github/workflows/deploy-web.yaml
vendored
33
.github/workflows/deploy-web.yaml
vendored
|
|
@ -15,6 +15,10 @@ name: Deploy Web
|
|||
#
|
||||
# Bucket and distribution come from SSM after assuming the Environment's
|
||||
# DEPLOY_ROLE_ARN. Nothing here creates an HCP run. Quality gates live in CI.
|
||||
#
|
||||
# The SPA checkout is the resolved content ref. Deploy scripts are copied
|
||||
# from github.workflow_sha so workflow_dispatch of an older SHA still runs
|
||||
# the current upload/verify path.
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -131,6 +135,27 @@ jobs:
|
|||
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
|
||||
echo "Building ${sha}"
|
||||
|
||||
- name: Checkout workflow deploy scripts
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.workflow_sha }}
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
scripts
|
||||
sparse-checkout-cone-mode: true
|
||||
path: .workflow-scripts
|
||||
|
||||
- name: Install workflow deploy scripts
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f .workflow-scripts/scripts/upload-sourcemaps.sh
|
||||
test -f .workflow-scripts/scripts/verify-cloudfront-release.sh
|
||||
test -f .workflow-scripts/scripts/summarize-cloudfront-live-state.sh
|
||||
mkdir -p scripts
|
||||
cp .workflow-scripts/scripts/upload-sourcemaps.sh scripts/
|
||||
cp .workflow-scripts/scripts/verify-cloudfront-release.sh scripts/
|
||||
cp .workflow-scripts/scripts/summarize-cloudfront-live-state.sh scripts/
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: "24"
|
||||
|
|
@ -145,6 +170,14 @@ jobs:
|
|||
VITE_SENTRY_RELEASE: ${{ steps.commit.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# vars.VITE_SENTRY_DSN is unset today. An empty env value would
|
||||
# override .env.production and disable Sentry (Vite does not let
|
||||
# .env overwrite an existing variable).
|
||||
if [ -n "${VITE_SENTRY_DSN:-}" ]; then
|
||||
export VITE_SENTRY_DSN
|
||||
else
|
||||
unset VITE_SENTRY_DSN
|
||||
fi
|
||||
case "${TARGET_ENVIRONMENT}" in
|
||||
dev)
|
||||
export VITE_API_URL="https://api.dev.seahaven.com/api"
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ isolation). A task is not done until this is green.
|
|||
are migration evidence reviewed by a human before an approved apply
|
||||
(`terraform/README.md`). G13 fails a diff that contains both `terraform/`
|
||||
and deployable application files (`src/`, `public/`, `pages/`, `config/`,
|
||||
`index.html`, Vite/tsconfig, `.env*`, or `scripts/deploy-web.sh`). Workflow,
|
||||
`index.html`, Vite/tsconfig, or `.env*`). Workflow,
|
||||
docs, and gate-script changes may travel with either side. Runtime isolation
|
||||
stays: `deploy-web.yaml` ignores `terraform/**`, and app-only tags skip HCP
|
||||
when workspace trigger patterns miss.
|
||||
|
|
|
|||
|
|
@ -87,6 +87,6 @@ surface; keep comments inline and high-signal.
|
|||
Infra and application **PRs** stay separate. GitHub Actions owns SPA content
|
||||
(`deploy-web.yaml`). HCP Terraform owns the bucket and CloudFront. A change set
|
||||
that includes both `terraform/` and deployable application files (`src/`,
|
||||
`public/`, `pages/`, `config/`, `index.html`, Vite/tsconfig, `.env*`, or
|
||||
`scripts/deploy-web.sh`) fails G13. Workflow, docs, and gate-script changes may
|
||||
travel with either side.
|
||||
`public/`, `pages/`, `config/`, `index.html`, Vite/tsconfig, or `.env*`)
|
||||
fails G13. Workflow, docs, and gate-script changes may travel with either
|
||||
side.
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import sys
|
||||
|
||||
APP_SCRIPT_NAMES = {
|
||||
"scripts/deploy-web.sh",
|
||||
}
|
||||
|
||||
APP_ROOTS = (
|
||||
"src/",
|
||||
"public/",
|
||||
|
|
@ -33,7 +29,7 @@ def is_terraform_path(path: str) -> bool:
|
|||
|
||||
def is_app_path(path: str) -> bool:
|
||||
normalized = path.replace("\\", "/")
|
||||
if normalized in APP_SCRIPT_NAMES or normalized in APP_FILES:
|
||||
if normalized in APP_FILES:
|
||||
return True
|
||||
if normalized in {"src", "public", "pages", "config"}:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Content publish step for the environment deploy workflows
|
||||
# (`.github/workflows/deploy.yml`, `.github/workflows/deploy-staging.yml`).
|
||||
#
|
||||
# Runs as the GitHub OIDC deploy role. Builds the SPA, uploads it to the
|
||||
# environment's S3 bucket with the right cache headers, and invalidates
|
||||
# CloudFront. It never touches infrastructure.
|
||||
#
|
||||
# Runs from the repo root. The target is resolved from, in order:
|
||||
# 1. SITE_BUCKET + CLOUDFRONT_DISTRIBUTION_ID (pinned by the workflow; used by
|
||||
# dev, whose CloudFormation outputs disappear during Terraform adoption)
|
||||
# 2. the BucketName/DistributionId outputs of STACK_NAME (staging)
|
||||
set -euo pipefail
|
||||
|
||||
STACK_NAME="${STACK_NAME:-shoc-frontend-dev}"
|
||||
REGION="${AWS_REGION:-us-east-1}"
|
||||
WAIT_FOR_INVALIDATION="${WAIT_FOR_INVALIDATION:-false}"
|
||||
|
||||
echo "Building SPA (VITE_API_URL comes from the process environment or .env.production)..."
|
||||
export VITE_APP_COMMIT_SHA="${VITE_APP_COMMIT_SHA:-${GITHUB_SHA:-}}"
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
BUCKET="${SITE_BUCKET:-}"
|
||||
DIST_ID="${CLOUDFRONT_DISTRIBUTION_ID:-}"
|
||||
|
||||
if [[ -n "${BUCKET}" && -n "${DIST_ID}" ]]; then
|
||||
echo "Using pinned target: bucket ${BUCKET}, distribution ${DIST_ID}."
|
||||
elif [[ -n "${BUCKET}" || -n "${DIST_ID}" ]]; then
|
||||
echo "::error::Set both SITE_BUCKET and CLOUDFRONT_DISTRIBUTION_ID, or neither." >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Reading stack outputs from ${STACK_NAME}..."
|
||||
stack_output() {
|
||||
aws cloudformation describe-stacks \
|
||||
--stack-name "${STACK_NAME}" \
|
||||
--region "${REGION}" \
|
||||
--query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" \
|
||||
--output text
|
||||
}
|
||||
|
||||
BUCKET="$(stack_output BucketName)"
|
||||
DIST_ID="$(stack_output DistributionId)"
|
||||
|
||||
if [[ -z "${BUCKET}" || "${BUCKET}" == "None" || -z "${DIST_ID}" || "${DIST_ID}" == "None" ]]; then
|
||||
echo "::error::Could not resolve BucketName/DistributionId from stack ${STACK_NAME}." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Uploading hashed assets (immutable) to s3://${BUCKET}..."
|
||||
# Everything except index.html: long-lived + immutable, prune stale objects.
|
||||
aws s3 sync dist/ "s3://${BUCKET}/" \
|
||||
--delete \
|
||||
--exclude "index.html" \
|
||||
--exclude "*.map" \
|
||||
--cache-control "public,max-age=31536000,immutable"
|
||||
|
||||
echo "Uploading index.html (never cached)..."
|
||||
aws s3 cp dist/index.html "s3://${BUCKET}/index.html" \
|
||||
--cache-control "no-cache,no-store,must-revalidate" \
|
||||
--content-type "text/html"
|
||||
|
||||
echo "Invalidating CloudFront ${DIST_ID}..."
|
||||
INVALIDATION_ID="$(aws cloudfront create-invalidation \
|
||||
--distribution-id "${DIST_ID}" \
|
||||
--paths "/*" \
|
||||
--query 'Invalidation.Id' \
|
||||
--output text)"
|
||||
|
||||
if [[ "${WAIT_FOR_INVALIDATION}" == "true" ]]; then
|
||||
echo "Waiting for CloudFront invalidation ${INVALIDATION_ID}..."
|
||||
aws cloudfront wait invalidation-completed \
|
||||
--distribution-id "${DIST_ID}" \
|
||||
--id "${INVALIDATION_ID}"
|
||||
fi
|
||||
|
||||
echo "Web deploy complete."
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read .release/current JSON from stdin and write GitHub Actions outputs."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> int:
|
||||
raw = sys.stdin.read().strip()
|
||||
data = json.loads(raw) if raw else {}
|
||||
current = data.get("current") or ""
|
||||
previous = data.get("previous") or ""
|
||||
output_path = os.environ["GITHUB_OUTPUT"]
|
||||
with open(output_path, "a", encoding="utf-8") as handle:
|
||||
handle.write(f"live_current={current}\n")
|
||||
handle.write(f"live_previous={previous}\n")
|
||||
print(
|
||||
"Pointer live current="
|
||||
+ (current or "<empty>")
|
||||
+ " previous="
|
||||
+ (previous or "<empty>")
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -340,13 +340,13 @@ class ImportPlanCheckerTests(unittest.TestCase):
|
|||
self.assertFalse((REPOSITORY / "terraform" / "versions.tf").exists())
|
||||
self.assertFalse((REPOSITORY / "terraform" / "main.tf").exists())
|
||||
|
||||
def test_dev_adoption_complete_is_pinned_in_locals(self) -> None:
|
||||
def test_adoption_complete_is_pinned_in_locals(self) -> None:
|
||||
dev = (REPOSITORY / "terraform/live/dev/main.tf").read_text(encoding="utf-8")
|
||||
staging = (REPOSITORY / "terraform/live/staging/main.tf").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
self.assertRegex(dev, r"adoption_complete\s+= true")
|
||||
self.assertRegex(staging, r"adoption_complete\s+= false")
|
||||
self.assertRegex(staging, r"adoption_complete\s+= true")
|
||||
self.assertNotIn('variable "adoption_complete"', dev)
|
||||
self.assertNotIn('variable "environment"', dev)
|
||||
self.assertNotIn('variable "release_version_label"', dev)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ class IsolationTests(unittest.TestCase):
|
|||
"src/app/routes.tsx",
|
||||
"public/favicon.ico",
|
||||
"index.html",
|
||||
"scripts/deploy-web.sh",
|
||||
]
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ fi
|
|||
COMMIT_SHA="$(printf '%s' "${COMMIT_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
RELEASE="shoc-frontend@${COMMIT_SHA}"
|
||||
|
||||
if ! npm exec --no -- sentry-cli --version >/dev/null 2>&1; then
|
||||
echo "sentry-cli is not in this SPA tree; skipping source-map upload"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
npm exec --no -- sentry-cli sourcemaps upload \
|
||||
--org "${SENTRY_ORG}" \
|
||||
--project "${SENTRY_PROJECT}" \
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ to the bucket root and invalidates `/*`.
|
|||
|
||||
Creating, formatting, initializing with `-backend=false`, and validating these
|
||||
files does not authorize an AWS, HCP Terraform, GitHub, or deployment
|
||||
mutation. Live cutover waits for an explicit greenlight.
|
||||
mutation.
|
||||
|
||||
Do not collapse these roots into one `terraform/` tree in this PR. Flattening
|
||||
retargets two live HCP working directories and is its own change.
|
||||
Do not collapse these roots into one `terraform/` tree. Flattening retargets
|
||||
two live HCP working directories and is its own change.
|
||||
|
||||
## Fixed targets
|
||||
|
||||
|
|
@ -61,15 +61,14 @@ and gate-script changes may travel with either side. G13 is
|
|||
`npm run test:terraform` and `npm run verify` wrap the same gates. They never
|
||||
create an HCP run or touch AWS.
|
||||
|
||||
## Cutover (greenlight only)
|
||||
## Workspaces
|
||||
|
||||
1. Keep HCP working directories `terraform/live/dev` and
|
||||
`terraform/live/staging`. Dev VCS branch `main`. Staging tag regex
|
||||
`^v[0-9]+\.[0-9]+\.[0-9]+-staging$`.
|
||||
2. Auto-apply off. Apply the origin-path move for **dev** before any
|
||||
`--delete` root sync.
|
||||
3. Create GitHub Environment `dev` (staging already exists). Set
|
||||
`DEPLOY_ROLE_ARN` on each.
|
||||
4. Enable `deploy-web.yaml`. Then delete leftover `deploy.yml` /
|
||||
`deploy-staging.yml` and repo `TF_API_TOKEN`, `TERRAFORM_CONTENT_CD_ENABLED`,
|
||||
and `AWS_DEPLOY_ROLE_ARN`.
|
||||
Dev (`shoc-frontend-new-dev`) watches `main` with working directory
|
||||
`terraform/live/dev` and auto-apply on. Staging (`shoc-frontend-new-staging`)
|
||||
watches tag regex `^v[0-9]+\.[0-9]+\.[0-9]+-staging$` with working directory
|
||||
`terraform/live/staging` and auto-apply on. Merges to `main` do not apply
|
||||
staging.
|
||||
|
||||
GitHub Environments `dev` and `staging` set `DEPLOY_ROLE_ARN` and allow `main`
|
||||
plus tag `v*`. Promote staging with `gh release create vX.Y.Z-staging --target
|
||||
main`.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Live Terraform roots
|
||||
|
||||
`live/dev/` is the adopted HCP workspace `shoc-frontend-new-dev`.
|
||||
`live/staging/` is `shoc-frontend-new-staging` (`adoption_complete = false`).
|
||||
`live/staging/` is `shoc-frontend-new-staging` (`adoption_complete = true`).
|
||||
|
||||
Do not collapse these into one `terraform/` root in the same PR as application
|
||||
CD. Flattening retargets two live HCP working directories and belongs in its
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
locals {
|
||||
# Staging is not fully adopted. Pinned in code, never a workspace variable.
|
||||
adoption_complete = false
|
||||
# Controlled ownership transfer. Pinned in code, never a workspace variable.
|
||||
adoption_complete = true
|
||||
|
||||
environment = "staging"
|
||||
workspace_name = "shoc-frontend-new-staging"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue