Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
52 changes: 34 additions & 18 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,49 @@ permissions:
id-token: write

concurrency:
group: pages
cancel-in-progress: true
group: pages-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
cache: npm

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.1'
bundler-cache: true

- name: Install Node dependencies
run: npm ci

- name: Check executable bits
run: npm run check:mode

- name: Build site
run: npm run build
- name: Validate documentation hosting tools
run: npm run check:docs-hosting

- name: Verify generated CSS
run: npm run check:css
- name: Build site
run: npm run build:pages

- name: Validate generated HTML
run: npm run check:html

- name: Validate canonical URLs
run: npm run check:canonical-urls

- name: Validate rendered content and metadata
run: npm run check:site-semantics && npm run test:content

- name: Validate classic URL redirects
run: npm run test:redirect-worker && npm run check:redirect-worker

- name: Validate email routing
run: npm run test:email-worker && npm run check:email-worker

- name: Collect active HTML pages
id: active_pages
run: |
Expand All @@ -65,6 +71,8 @@ jobs:
with:
args: >-
--verbose --no-progress --offline --exclude-mail
--root-dir '${{ github.workspace }}/_site'
--exclude '^file://.*/_site/doc(-latest)?(/|$)'
${{ steps.active_pages.outputs.pages }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -75,6 +83,8 @@ jobs:
with:
args: >-
--verbose --no-progress --exclude-mail
--root-dir '${{ github.workspace }}/_site'
--exclude '^file://.*/_site/doc(-latest)?(/|$)'
${{ steps.active_pages.outputs.pages }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -83,12 +93,18 @@ jobs:
uses: actions/configure-pages@v5

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
path: ./_site
retention-days: 30

deploy:
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
# Keep production deployment manual until the Astro cutover is complete.
# Pushes and pull requests still build and validate the full Pages artifact.
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'
concurrency:
group: pages-production
cancel-in-progress: false
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
Expand All @@ -97,4 +113,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
151 changes: 151 additions & 0 deletions .github/workflows/workers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
name: Deploy edge workers

on:
workflow_dispatch:
inputs:
operation:
description: Deployment operation
required: true
default: deploy
type: choice
options:
- deploy
- remove-canary
environment:
description: Cloudflare environment
required: true
default: staging
type: choice
options:
- staging
- canary
- production
worker:
description: Worker to deploy
required: true
default: documentation
type: choice
options:
- both
- documentation
- redirects
- email

permissions:
contents: read

concurrency:
group: edge-workers-${{ inputs.environment }}
cancel-in-progress: false

jobs:
deploy:
name: Deploy ${{ inputs.worker }} to ${{ inputs.environment }}
runs-on: ubuntu-latest
environment: cloudflare-${{ inputs.environment }}
steps:
- name: Validate production ref
if: inputs.environment == 'production'
env:
DEPLOY_REF: ${{ github.ref }}
DEPLOY_WORKER: ${{ inputs.worker }}
run: |
if [[ "$DEPLOY_REF" == refs/heads/main ]]; then exit 0; fi
if [[ "$DEPLOY_WORKER" == documentation && "$DEPLOY_REF" =~ ^refs/heads/release/[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?-website$ ]]; then exit 0; fi
echo "Production requires main, or an approved release website branch for documentation." >&2
exit 2

- name: Validate operation
if: inputs.operation == 'remove-canary' && inputs.environment != 'canary'
run: |
echo "remove-canary requires the canary environment" >&2
exit 2

- name: Validate canary worker
if: inputs.environment == 'canary' && inputs.worker != 'documentation'
run: |
echo "canary operations require the documentation worker" >&2
exit 2

- name: Validate email environment
if: inputs.worker == 'email' && inputs.environment != 'production'
run: |
echo "the email worker is deployed only through the production environment" >&2
exit 2

- name: Checkout
uses: actions/checkout@v7

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Validate documentation Worker
if: inputs.worker == 'both' || inputs.worker == 'documentation'
run: npm run test:docs-worker && npm run check:docs-worker

- name: Validate redirect Worker
if: inputs.worker == 'both' || inputs.worker == 'redirects'
run: npm run test:redirect-worker && npm run check:redirect-worker

- name: Validate email Worker
if: inputs.worker == 'email'
run: npm run test:email-worker && npm run check:email-worker

- name: Deploy documentation Worker to staging
if: inputs.operation == 'deploy' && inputs.environment == 'staging' && (inputs.worker == 'both' || inputs.worker == 'documentation')
run: npx wrangler deploy --env="" --config workers/docs/wrangler.jsonc

- name: Verify staging documentation
if: inputs.operation == 'deploy' && inputs.environment == 'staging' && (inputs.worker == 'both' || inputs.worker == 'documentation')
run: |
version=$(node -p "JSON.parse(require('fs').readFileSync('workers/docs/wrangler.jsonc', 'utf8')).vars.LATEST_DOC_VERSION")
for attempt in 1 2 3 4 5 6; do
if node scripts/docs/smoke-worker.mjs https://docs-staging.gecode.dev "$version"; then exit 0; fi
if [ "$attempt" -eq 6 ]; then exit 1; fi
echo "Waiting for the staging deployment to propagate (attempt $attempt)."
sleep 10
done

- name: Deploy documentation Worker to production
if: inputs.operation == 'deploy' && inputs.environment == 'production' && (inputs.worker == 'both' || inputs.worker == 'documentation')
run: npx wrangler deploy --env production --config workers/docs/wrangler.jsonc

- name: Deploy documentation Worker to canary
if: inputs.operation == 'deploy' && inputs.environment == 'canary' && (inputs.worker == 'both' || inputs.worker == 'documentation')
run: npx wrangler deploy --env canary --config workers/docs/wrangler.jsonc

- name: Verify documentation canary
if: inputs.operation == 'deploy' && inputs.environment == 'canary' && inputs.worker == 'documentation'
run: |
version=$(node -p "JSON.parse(require('fs').readFileSync('workers/docs/wrangler.jsonc', 'utf8')).env.canary.vars.LATEST_DOC_VERSION")
for attempt in 1 2 3 4 5 6; do
if node scripts/docs/smoke-worker.mjs https://www.gecode.dev "$version" --immutable-only; then exit 0; fi
if [ "$attempt" -eq 6 ]; then exit 1; fi
echo "Waiting for the canary deployment to propagate (attempt $attempt)."
sleep 10
done

- name: Remove documentation canary
if: inputs.operation == 'remove-canary'
run: npx wrangler delete --env canary --config workers/docs/wrangler.jsonc --force

- name: Deploy redirect Worker to staging
if: inputs.operation == 'deploy' && inputs.environment == 'staging' && (inputs.worker == 'both' || inputs.worker == 'redirects')
run: npx wrangler deploy --env="" --config workers/redirects/wrangler.jsonc

- name: Deploy redirect Worker to production
if: inputs.operation == 'deploy' && inputs.environment == 'production' && (inputs.worker == 'both' || inputs.worker == 'redirects')
run: npx wrangler deploy --env production --config workers/redirects/wrangler.jsonc

- name: Deploy email Worker to production
if: inputs.operation == 'deploy' && inputs.environment == 'production' && inputs.worker == 'email'
run: npx wrangler deploy --config workers/email/wrangler.jsonc
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
node_modules/
_site/
.astro/
.astro-public/
dist/
.jekyll-cache/
.jekyll-metadata
.sass-cache/
.lighthouseci/
.wrangler/
.documentation-manifests/
full-cf-token*.txt
.DS_Store
vendor/bundle/
.bundle/
43 changes: 35 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Gecode Website (Jekyll + GitHub Pages)
# Gecode Website (Astro + GitHub Pages)

This repository hosts the Gecode website and is built with Jekyll and Tailwind CSS.
This repository hosts the Gecode website and is built with Astro and Tailwind
CSS 4. React is available for future interactive islands, but the current site
does not require client-side JavaScript.

## Requirements

- Ruby `3.1.6` (or compatible `>= 3.1`)
- Bundler (for the selected Ruby version)
- Node.js `20.x`
- Node.js `22.12` or newer (Node.js 24 is used in CI)
- npm

## Quick Start
Expand All @@ -17,7 +17,7 @@ This repository hosts the Gecode website and is built with Jekyll and Tailwind C
npm ci
```

2. Start local development (Tailwind watch + Jekyll serve):
2. Start local development:

```bash
npm run dev
Expand All @@ -39,12 +39,39 @@ npm run check:quality

This command:

- rebuilds CSS and site output
- verifies generated CSS is in sync with source
- builds the active Astro site
- checks executable-bit hygiene
- validates generated active-site HTML pages
- checks content transformations, metadata, and canonical URLs
- validates the documentation and redirect Workers

For the GitHub Pages artifact, including the mailing-list archive but excluding
documentation now served from R2, run:

```bash
npm run build:pages
```

The legacy rollback/comparison build still copies both frozen archives:

```bash
npm run build:deploy
```

That legacy artifact exceeds GitHub Pages' 1 GB published-site limit and must
not be used by the production workflow.

## Notes on Scope

- `doc/` and `users-archive/` are treated as frozen historical archives.
- Active-page checks intentionally exclude those frozen archives.
- The Jekyll files remain temporarily as source for the compatibility content
loader. They can be converted to native Astro content collections in a later,
reviewable migration step.

## Migration Plans

- [Astro migration analysis](docs/astro-migration.md)
- [Classical site parity review](docs/classic-parity-review.md)
- [Deployment runbook](docs/deployment-runbook.md)
- [Static documentation hosting plan](docs/static-documentation-hosting.md)
4 changes: 2 additions & 2 deletions _news/2009-03-26-models-by-h--229-kan-kjellerstrand.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Models by Håkan Kjellerstrand
title: Models by Håkan Kjellerstrand
---

<a href="http://www.hakank.org/constraint_programming_blog/2009/03/my_first_gecode_models_1.html">
H&#229;kan's blog</a> now contains a collection of
<a href="http://www.hakank.org/gecode">Gecode models</a>.
<a href="http://www.hakank.org/gecode">Gecode models</a>.
20 changes: 20 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
import sitemap from "@astrojs/sitemap";
import tailwindcss from "@tailwindcss/vite";
import archiveDev from "./scripts/archive-dev.mjs";

export default defineConfig({
site: "https://www.gecode.dev",
publicDir: ".astro-public",
output: "static",
outDir: "./_site",
trailingSlash: "always",
build: {
format: "directory",
},
integrations: [react(), sitemap()],
vite: {
plugins: [tailwindcss(), archiveDev()],
},
});
Loading
Loading