{member.role}
{member.name}
{member.bio}
diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 06073dc..a68e7cc 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,93 +1,41 @@ -name: Deploy to Cloudflare Pages - -# Preview deployments are created for pushes to any non-master branch. -# Pushing to master creates the production deployment (served on custom domain). -# Non-master branches get a unique preview URL for client review. +name: Validate and deploy static Worker preview on: push: + branches: [astro-platform-pilot] workflow_dispatch: - -env: - # These will be replaced during site creation - CLOUDFLARE_PROJECT_NAME: 3031023-xyz - CLOUDFLARE_CUSTOM_DOMAIN: 3031023.xyz + inputs: + site_key: + description: Site key to deploy to its non-production Worker + required: true + type: choice + options: [www, blog] jobs: - deploy: - name: Deploy to Cloudflare Pages + preview: + name: Preview ${{ inputs.site_key || 'www' }} runs-on: ubuntu-latest + permissions: + contents: read + env: + SITE_KEY: ${{ inputs.site_key || 'www' }} + ASTRO_TELEMETRY_DISABLED: '1' steps: - - name: Checkout code + - name: Checkout source uses: https://github.com/actions/checkout@v4 - - - name: Deploy to Cloudflare Pages (Direct Upload) - run: npx wrangler pages deploy . --project-name=${{ env.CLOUDFLARE_PROJECT_NAME }} --branch=${GITHUB_REF#refs/heads/} + - name: Use Node 22 + uses: https://github.com/actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - name: Install locked dependencies + run: npm ci + - name: Validate and build selected site + run: npm run build:site + - name: Verify selected output + run: npm run verify:build + - name: Deploy non-production Worker + run: npx wrangler deploy --config "wrangler/${SITE_KEY}.jsonc" env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CF_WORKER_PREVIEW_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - - - name: Attach custom domain (main only) - if: github.ref == 'refs/heads/main' - run: | - echo "Adding domain ${CLOUDFLARE_CUSTOM_DOMAIN} to project ${CLOUDFLARE_PROJECT_NAME}..." - BODY=$(printf '{"name": "%s"}' "${CLOUDFLARE_CUSTOM_DOMAIN}") - HTTP_STATUS=$(curl -s -o /tmp/cf_response.txt -w "%{http_code}" -X POST \ - "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${CLOUDFLARE_PROJECT_NAME}/domains" \ - -H "Authorization: Bearer *** " \ - -H "Content-Type: application/json" \ - -d "${BODY}") - echo "HTTP Status: ${HTTP_STATUS}" - echo "Response: $(cat /tmp/cf_response.txt)" - if [ "${HTTP_STATUS}" -ge 200 ] && [ "${HTTP_STATUS}" -lt 300 ]; then - echo "Domain attached successfully" - else - echo "::warning::Domain attach returned ${HTTP_STATUS} - check domain and permissions" - fi - env: - CR_AUTH: ${{ secrets.CF_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - - - name: Create DNS record for custom domain - if: github.ref == 'refs/heads/main' - run: | - # Extract root zone from the custom domain (e.g. product-example.sloanops.com -> sloanops.com) - ROOT_ZONE="${CLOUDFLARE_CUSTOM_DOMAIN#*.}" - echo "Root zone: ${ROOT_ZONE}" - # Look up the Cloudflare zone ID - ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${ROOT_ZONE}" \ - -H "Authorization: Bearer *** " \ - -H "Content-Type: application/json") - ZONE_ID=$(echo "$ZONE_RESPONSE" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data.get('result',[{}])[0].get('id',''))" 2>/dev/null || echo "") - if [ -z "$ZONE_ID" ]; then - echo "::warning::Could not find zone ID for ${ROOT_ZONE} - check Cloudflare token has Zone:Zone:Read permission" - exit 0 - fi - echo "Zone ID: ${ZONE_ID}" - # Create CNAME record (skip if it already exists - idempotent) - RECORDS_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?type=CNAME&name=${CLOUDFLARE_CUSTOM_DOMAIN}" \ - -H "Authorization: Bearer *** " \ - -H "Content-Type: application/json") - EXISTING=$(echo "$RECORDS_RESPONSE" | python3 -c "import sys,json; data=json.load(sys.stdin); print(len(data.get('result',[])))" 2>/dev/null || echo "0") - if [ "$EXISTING" -gt 0 ]; then - echo "DNS record already exists, skipping creation" - else - BODY=$(printf '{"type":"CNAME","name":"%s","content":"%s.pages.dev","proxied":true}' "${CLOUDFLARE_CUSTOM_DOMAIN}" "${CLOUDFLARE_PROJECT_NAME}") - HTTP_STATUS=$(curl -s -o /tmp/cf_dns_response.txt -w "%{http_code}" -X POST \ - "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \ - -H "Authorization: Bearer *** " \ - -H "Content-Type: application/json" \ - -d "${BODY}") - echo "DNS record HTTP Status: ${HTTP_STATUS}" - echo "Response: $(cat /tmp/cf_dns_response.txt)" - if [ "${HTTP_STATUS}" -ge 200 ] && [ "${HTTP_STATUS}" -lt 300 ]; then - echo "DNS record created successfully" - else - echo "::warning::DNS record creation returned ${HTTP_STATUS} - ensure token has Zone:DNS:Edit permission" - fi - fi - env: - CR_AUTH: ${{ secrets.CF_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - CLOUDFLARE_CUSTOM_DOMAIN: ${{ env.CLOUDFLARE_CUSTOM_DOMAIN }} - CLOUDFLARE_PROJECT_NAME: ${{ env.CLOUDFLARE_PROJECT_NAME }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dce21c --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.astro/ +.wrangler/ +.env +.env.* +!.env.example diff --git a/README.md b/README.md index 1f94035..1787920 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,43 @@ -# Happy Tails Pet Care +# Happy Tails Astro pilot -A professional website created for Happy Tails Pet Care using SloanOps template system. +This repository is a static Astro pilot for `3031023.xyz`. The existing +Cloudflare Pages site remains the rollback target while this branch is tested. -## Overview +## Repository layout -This site was generated using the SloanOps client onboarding system and is designed for deployment to Cloudflare Pages via Gitea Actions CI/CD. +- `sites/www/` is the primary-site content location; it serves `3031023.xyz`. +- `sites/blog/` is the separate `blog.3031023.xyz` sample site. +- `sites.json` is the operator-controlled manifest. It is not public intake. +- `src/` contains shared Astro presentation code. +- `schemas/` and `fixtures/` provide the Phase 1 JSON contract proof. -## Customization +The blog is static JSON in this phase. Markdown content, CMS workflows, public +sign-up changes, n8n changes, and onboarding-worker changes are deliberately +out of scope. -### Content Updates -- Edit HTML files in the root directory to update page content -- Modify images in the `images/` directory -- Update text content directly in HTML files +## Local checks -### Styling -- All colors, fonts, and spacing are controlled via CSS variables in `css/style.css` -- To rebrand: modify the `:root` variables at the top of `css/style.css` -- Font families can be changed in the HTML `
` section +Use Node 22. -### Deployment -Pushes to the `master` branch trigger automatic deployment to Cloudflare Pages. -Preview deployments are created for all other branches. - -## Structure - -``` -3031023-xyz/ -├── index.html # Home page -├── about.html # About page (if included) -├── services.html # Services page (if included) -├── products.html # Products page (if included) -├── contact.html # Contact page -├── css/ -│ └── style.css # All styles with CSS variables -├── js/ -│ └── main.js # Interactive components (mobile menu, etc.) -├── images/ # Image assets -└── .gitea/ - └── workflows/ - └── deploy.yml # Cloudflare Pages CI/CD configuration +```bash +npm ci +SITE_KEY=www npm run build:site +SITE_KEY=www npm run verify:build +SITE_KEY=blog npm run build:site +SITE_KEY=blog npm run verify:build ``` -## Deployment Setup +Each selected site builds to `dist/Our pack
The Happy Tails team brings patience, playfulness, and real experience to every visit.
How we work
{site.story.copy}
Our team communicates clearly, keeps routines consistent, and makes safety a habit—not an afterthought.
Meet the team
{member.role}
{member.bio}
Get in touch
Reach out to ask a question or start planning the right care routine.
Happy Tails Pet Care
{site.contact.address}
{site.contact.hours}
The journal
{post.tag}
{post.excerpt}
This static sample deliberately uses JSON. Markdown publishing starts only in the later, separately approved content-update phase.
The Happy Tails difference
We keep our pack intentionally small, so every dog gets the calm attention and consistent care they deserve.
{feature.description}
Loved by local families
)}★★★★★“{review.quote}”
Review cards are a presentation pattern. Before launch, display only reviews approved for reuse and preserve their source, date, rating, and attribution under the business’s Google review policy.
Services
Flexible, personal care—with no one-size-fits-all routines.
{service.price}
{service.description}
A simple start
Tell us a little about their routine, personality, and what would make their day better.
Book a meet and greet →