Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4071556
Add MathTex to unified stack, add markdown lint script in package.json
webstackdev Dec 17, 2025
54f64c5
Fix MathTex regression where inline equations were broken over multip…
webstackdev Dec 17, 2025
cede0ff
Add Mermaid to Unified stack
webstackdev Dec 17, 2025
d370207
Add debug logging to privacy policy integration, run astro build on G…
webstackdev Dec 17, 2025
8fde319
Sync package lock file
webstackdev Dec 17, 2025
fc44e26
Refactor deploy-preview-comment script to JS Action
webstackdev Dec 17, 2025
2ddafef
Refactor preview-failure-comment script to JS Action
webstackdev Dec 17, 2025
05053ef
Refactor deploy-production-failure script to JS Action
webstackdev Dec 17, 2025
378d930
Refactor ping-turso-keep-alive-query script to JS Action
webstackdev Dec 17, 2025
a345e76
Finish up work refactoring script in Action workflows to JS Actions
webstackdev Dec 17, 2025
f3ae03d
Fix CodeQL issues in refactored JS Actions
webstackdev Dec 18, 2025
d32a3f4
Optimize naming in action workflows for GitHub UI
webstackdev Dec 18, 2025
73d6f2a
Move to Python for scriptions in action workflows
webstackdev Dec 19, 2025
92e9cd2
Fix CodeQL cache poisoning error
webstackdev Dec 19, 2025
11a1530
Add docs on why PRs from forked repos are not allowed
webstackdev Dec 19, 2025
aec52a3
Add workflow guard for forked PR code in deploy preview
webstackdev Dec 19, 2025
010aaa5
Change fail severity for check dependency CI workflow to critical fro…
webstackdev Dec 20, 2025
4a3884e
Temporarily short-circuit E2E test suite to only run 01-smoke/homepag…
webstackdev Dec 20, 2025
2cd6f1d
Skip initializing Google Maps client during E2E tests
webstackdev Dec 20, 2025
ab6f101
Move build from Vercel to GitHub Action
webstackdev Dec 21, 2025
6f63f3e
Fix potential code injection alerts from CodeQL in recent work on Wor…
webstackdev Dec 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 68 additions & 24 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,41 +1,85 @@
# Local dev server configuration
##
# Local dev server configuration (development env only)
##

# Environment variable
DEV_SERVER_PORT="4321"

# For Docker Compose use with container mocks for E2E setup
##
# For Docker Compose use with container mocks for E2E setup (development env only)
##

# Environment variable
COMPOSE_PROJECT_NAME="wb-e2e"

# Mock ConvertKit API (WireMock container that backs E2E tests local
# and on GitHub Actions, production on Vercel)
CONVERTKIT_HTTP_PORT="9010"
CONVERTKIT_API_KEY="mock-convertkit-key"
CONVERTKIT_FORM_ID="100000"
##
# Astro DB - local file-backed dev database (token unused for file connections)
##

# Environment variable
ASTRO_DB_REMOTE_URL="<db_uri_here>"
# Environment secret
ASTRO_DB_APP_TOKEN="<app_token_here>"

##
# Newsletter subscription manager
# Provided by Vercel to Function so no need to bundle with PUBLIC_ prefix
##

# Environment variable
CONVERTKIT_HTTP_PORT="9010" # development env only for WireMock container
# Environment secret
CONVERTKIT_API_KEY="<api_key_here>"

##
# Vercel automatically sends the CRON_SECRET as an Authorization header
# when it invokes your cron job. Your endpoint can then verify this secret
# to ensure the request originated from Vercel.
CRON_SECRET="local-cron-secret"
##

# Like it says on the label
PUBLIC_GOOGLE_MAPS_API_KEY=""
# Environment secret
CRON_SECRET="<cron_secret_here>"

# Mock Resend API (WireMock container that backs transactional email tests
# local and on GitHub Actions, production on Vercel)
RESEND_HTTP_PORT="9011"
RESEND_API_KEY="mock-resend-key"
##
# Bundled into client code so prefixed with PUBLIC_
##

# Environment variable
PUBLIC_GOOGLE_MAPS_API_KEY="<api_key_here>"

##
# SMTP remailer
# Provided by Vercel to Function so no need to bundle with PUBLIC_ prefix
##

# Environment variable
RESEND_HTTP_PORT="9011" # development env only for WireMock container
# Environment secret
RESEND_API_KEY="<api_key_here>"

##
# Observability / external services
SENTRY_AUTH_TOKEN="dev-placeholder-sentry-token"
SENTRY_DSN="https://examplePublicKey@o0.ingest.sentry.io/0"
##

# Environment variable
PUBLIC_SENTRY_DSN="https://examplePublicKey@o0.ingest.sentry.io/0"
# Environment secret
SENTRY_AUTH_TOKEN="<token_here>"

##
# Vercel deployment vars
VERCEL_TOKEN="p1vT5d4M1H2q0NjEtR9bJVxu"
VERCEL_PROJECT_ID="prj_d24xWkR5sY8pMn2qBcLe8F0Z"
VERCEL_ORG_ID="team_C7kQw5vXn0PfH3sJt2Gb9LrY"
##

# Environment secret
VERCEL_TOKEN="<token_here>"
# Environment variable
VERCEL_ORG_ID="<org_id_here>"
# Environment variable
VERCEL_PROJECT_ID="<project_id_here>"

##
# Used for social shares on Mastodon
WEBMENTION_IO_TOKEN="dev-webmention-token"
##

# Astro DB - local file-backed dev database (token unused for file connections)
ASTRO_DB_REMOTE_URL="file:./.astro/content.db"
ASTRO_DB_APP_TOKEN=""
# ASTRO_DATABASE_FILE must be exported in the shell (see README)
# Environment secrets
WEBMENTION_IO_TOKEN="<token_here>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from __future__ import annotations

from pathlib import Path
from types import ModuleType
from typing import Any

import pytest


def load_action_module() -> ModuleType:
action_root = Path(__file__).resolve().parents[1]
module_path = action_root / "src" / "main.py"

import importlib.util
import sys

spec = importlib.util.spec_from_file_location("check_prereqs", module_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module


class MockResponse:
def __init__(self, *, ok: bool, status_code: int, json_data: Any | None = None):
self.ok = ok
self.status_code = status_code
self._json_data = json_data

def json(self) -> Any:
return self._json_data


def test_sets_should_deploy_false_when_missing_required_runs(monkeypatch: pytest.MonkeyPatch) -> None:
module = load_action_module()

inputs = {
"github-token": "ghs_test",
"sha": "abc",
"required-workflows-json": '[{"id":"lint.yml","label":"Lint"}]',
"build-workflow-file": "build-preview.yml",
"artifact-name": "vercel-build-preview",
"skip-hotfix": "false",
"skip-forks": "false",
}

monkeypatch.setenv("GITHUB_REPOSITORY", "webstackdev/astro.webstackbuilders.com")
monkeypatch.setenv("GITHUB_API_URL", "https://api.github.com")

outputs: dict[str, str] = {}
notices: list[str] = []
failures: list[str] = []

monkeypatch.setattr(module.core, "get_input", lambda name, required=False: inputs.get(name, ""))
monkeypatch.setattr(module.core, "set_output", lambda k, v: outputs.__setitem__(k, v))
monkeypatch.setattr(module.core, "notice", lambda m: notices.append(m))
monkeypatch.setattr(module.core, "set_failed", lambda m: failures.append(m))

def fake_get(url: str, **kwargs: Any) -> MockResponse:
if "/actions/workflows/lint.yml/runs" in url:
return MockResponse(ok=True, status_code=200, json_data={"workflow_runs": []})
return MockResponse(ok=False, status_code=404)

monkeypatch.setattr(module.requests, "get", fake_get)

module.run()

assert failures == []
assert outputs["should_deploy"] == "false"
assert any("prerequisites not met" in n for n in notices)


def test_outputs_artifact_download_url_when_all_prereqs_succeed(monkeypatch: pytest.MonkeyPatch) -> None:
module = load_action_module()

inputs = {
"github-token": "ghs_test",
"sha": "abc",
"required-workflows-json": '[{"id":"build-preview.yml","label":"Build Preview"}]',
"build-workflow-file": "build-preview.yml",
"artifact-name": "vercel-build-preview",
"skip-hotfix": "false",
"skip-forks": "false",
}

monkeypatch.setenv("GITHUB_REPOSITORY", "webstackdev/astro.webstackbuilders.com")
monkeypatch.setenv("GITHUB_API_URL", "https://api.github.com")

outputs: dict[str, str] = {}
failures: list[str] = []

monkeypatch.setattr(module.core, "get_input", lambda name, required=False: inputs.get(name, ""))
monkeypatch.setattr(module.core, "set_output", lambda k, v: outputs.__setitem__(k, v))
monkeypatch.setattr(module.core, "set_failed", lambda m: failures.append(m))

def fake_get(url: str, **kwargs: Any) -> MockResponse:
if "/actions/workflows/build-preview.yml/runs" in url:
return MockResponse(ok=True, status_code=200, json_data={"workflow_runs": [{"id": 123, "conclusion": "success"}]})
if "/actions/runs/123/artifacts" in url:
return MockResponse(
ok=True,
status_code=200,
json_data={"artifacts": [{"name": "vercel-build-preview", "archive_download_url": "https://api.github.com/a.zip"}]},
)
return MockResponse(ok=False, status_code=404)

monkeypatch.setattr(module.requests, "get", fake_get)

module.run()

assert failures == []
assert outputs["should_deploy"] == "true"
assert outputs["artifact_download_url"] == "https://api.github.com/a.zip"
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Check Prerequisites and Locate Build Artifact
description: Verifies required workflow runs succeeded for a SHA and returns the build artifact download URL.

inputs:
github-token:
description: GitHub token used to query workflow runs and artifacts.
required: true
sha:
description: Commit SHA to verify.
required: true
trigger-event:
description: workflow_run.event (or current event) for additional gating.
required: false
default: ""
head-branch:
description: Head branch (for hotfix gating).
required: false
default: ""
is-fork:
description: "'true' if PR head repo is a fork."
required: false
default: "false"
skip-hotfix:
description: "'true' to skip when head branch starts with hotfix/."
required: false
default: "false"
skip-forks:
description: "'true' to skip when is-fork is true."
required: false
default: "false"
require-trigger-event:
description: If set, only deploy when trigger-event matches.
required: false
default: ""
build-workflow-file:
description: Workflow file name that produced the artifact (e.g. build-preview.yml).
required: true
artifact-name:
description: Artifact name to download.
required: true
required-workflows-json:
description: JSON array of {id,label} workflow descriptors that must have a successful run for the SHA.
required: true

outputs:
should_deploy:
description: "'true' if all prerequisites succeeded and artifact exists."
value: ${{ steps.run.outputs.should_deploy }}
artifact_download_url:
description: Artifact archive download URL.
value: ${{ steps.run.outputs.artifact_download_url }}

runs:
using: composite
steps:
- id: run
name: Verify prerequisites
working-directory: ${{ github.action_path }}
run: python3 src/main.py
shell: bash
Loading
Loading