diff --git a/.github/scripts/cloudflare-deploy.sh b/.github/scripts/cloudflare-deploy.sh new file mode 100755 index 0000000..fc0a820 --- /dev/null +++ b/.github/scripts/cloudflare-deploy.sh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash +# +# cloudflare-deploy.sh — post-deploy GitHub integration for Cloudflare Workers/Pages +# +# Usage: +# cloudflare-deploy.sh comment Post or update a PR comment with the preview URL +# cloudflare-deploy.sh deployment Create a GitHub Deployment and job summary +# +# Required environment: +# WRANGLER_OUTPUT_FILE_DIRECTORY Directory where wrangler wrote its output artifacts +# GH_TOKEN GitHub token (usually secrets.GITHUB_TOKEN) +# GITHUB_REPOSITORY owner/repo (set automatically by Actions) +# +# For 'comment': +# PR_NUMBER Pull request number +# +# For 'deployment': +# GITHUB_HEAD_REF / GITHUB_REF_NAME Branch ref (set automatically by Actions) +# CLOUDFLARE_ACCOUNT_ID Cloudflare account ID (for dashboard link) + +set -euo pipefail + +die() { + echo "error: $*" >&2 + exit 1 +} + +# Read the first wrangler output entry matching one of the supported types. +# +# Wrangler writes newline-delimited JSON files named +# wrangler-output--.json into WRANGLER_OUTPUT_FILE_DIRECTORY. +# We read all files once and search in priority order: +# pages-deploy-detailed > deploy > version-upload +read_deploy_output() { + local dir="${WRANGLER_OUTPUT_FILE_DIRECTORY:?WRANGLER_OUTPUT_FILE_DIRECTORY must be set}" + + # Gather all matching files. Use nullglob-safe find to avoid errors on + # empty directories. + local files + files=$(find "$dir" -maxdepth 1 -name 'wrangler-output-*.json' 2>/dev/null | sort) + + if [[ -z "${files}" ]]; then + die "no wrangler output files found in ${dir}" + fi + + # Slurp all lines from all output files into a single stream, then filter. + # This avoids re-reading the directory for each entry type. + local -a file_list + mapfile -t file_list <<< "${files}" + + local all_entries + all_entries=$(cat "${file_list[@]}" 2>/dev/null) + + local entry_type + local match + for entry_type in "pages-deploy-detailed" "deploy" "version-upload"; do + match=$(jq -c "select(.type == \"${entry_type}\")" <<< "${all_entries}" 2>/dev/null | head -n1) + if [[ -n "${match}" ]]; then + echo "${match}" + return + fi + done + + die "no deployment output entry found in wrangler artifacts" +} + +# Extract the deployment URL from whichever entry type we found. +extract_url() { + local entry="$1" + local entry_type + entry_type=$(jq -r '.type' <<< "${entry}") + + case "${entry_type}" in + pages-deploy-detailed) + jq -r '.url // empty' <<< "${entry}" + ;; + deploy) + jq -r '.targets[0] // empty' <<< "${entry}" + ;; + version-upload) + jq -r '.preview_url // empty' <<< "${entry}" + ;; + *) + die "unknown entry type: ${entry_type}" + ;; + esac +} + +# Post or update a PR comment with the preview URL. +cmd_comment() { + local pr="${PR_NUMBER:?PR_NUMBER must be set}" + + local entry + local url + local alias_url + local body + + entry=$(read_deploy_output) + url=$(extract_url "${entry}") + [[ -z "${url}" ]] && die "could not extract deployment URL from wrangler output" + + alias_url=$(jq -r '.alias // empty' <<< "${entry}" 2>/dev/null) + + body=" +### Branch preview + +🔗 [${alias_url}](${alias_url}) ([direct commit link](${url})) +" + + # Look for an existing comment to update (avoids spamming on repeated pushes). + local existing_comment + existing_comment=$( + gh api "repos/${GITHUB_REPOSITORY}/issues/${pr}/comments" \ + --jq '.[] | select(.body | contains("")) | .id' \ + 2>/dev/null | head -n1 + ) || true + + if [[ -n "${existing_comment}" ]]; then + gh api "repos/${GITHUB_REPOSITORY}/issues/comments/${existing_comment}" \ + -X PATCH -f body="${body}" --silent + echo "Updated existing comment ${existing_comment}" + else + gh api "repos/${GITHUB_REPOSITORY}/issues/${pr}/comments" \ + -f body="${body}" --silent + echo "Posted new comment on PR #${pr}" + fi +} + +# Create a GitHub Deployment + status and write a job summary. +cmd_deployment() { + local entry + entry=$(read_deploy_output) + + local url + url=$(extract_url "${entry}") + [[ -z "${url}" ]] && die "could not extract deployment URL from wrangler output" + + local entry_type + entry_type=$(jq -r '.type' <<< "${entry}") + + local ref="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:?}}" + local environment="preview" + local log_url="" + + # Pages deployments have richer metadata. + if [[ "${entry_type}" == "pages-deploy-detailed" ]]; then + environment=$(jq -r '.environment // "preview"' <<< "${entry}") + + local project_name + project_name=$(jq -r '.pages_project // empty' <<< "${entry}") + + local cf_deployment_id + cf_deployment_id=$(jq -r '.deployment_id // empty' <<< "${entry}") + + local account_id="${CLOUDFLARE_ACCOUNT_ID:-}" + + if [[ -n "${account_id}" && -n "${project_name}" && -n "${cf_deployment_id}" ]]; then + log_url="https://dash.cloudflare.com/${account_id}/pages/view/${project_name}/${cf_deployment_id}" + fi + fi + + # Create the deployment. + # Passing an empty JSON array for required_contexts disables commit status + # checks on the deployment object. The gh cli -f flag cannot represent an + # empty array, so we pipe raw JSON via --input. + local gh_deployment_id + gh_deployment_id=$( + jq -n \ + --arg ref "${ref}" \ + --arg env "${environment}" \ + --arg desc "Cloudflare Deploy" \ + '{ + ref: $ref, + environment: $env, + auto_merge: false, + description: $desc, + required_contexts: [] + }' \ + | gh api "repos/${GITHUB_REPOSITORY}/deployments" \ + --method POST --input - --jq '.id' + ) + + if [[ -z "${gh_deployment_id}" ]]; then + die "failed to create GitHub deployment" + fi + + # Set deployment status to success. + local status_body + status_body=$( + jq -n \ + --arg env "${environment}" \ + --arg url "${url}" \ + --arg desc "Cloudflare Deploy" \ + --arg log_url "${log_url}" \ + '{ + state: "success", + environment: $env, + environment_url: $url, + description: $desc, + auto_inactive: false + } + | if $log_url != "" then . + {log_url: $log_url} else . end' + ) + + gh api "repos/${GITHUB_REPOSITORY}/deployments/${gh_deployment_id}/statuses" \ + --method POST --input - --silent <<< "${status_body}" + + echo "Created GitHub deployment ${gh_deployment_id} → ${url}" + + # Write job summary if the variable is available. + if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + { + echo "### Cloudflare Deploy" + echo "" + echo "| | |" + echo "|---|---|" + echo "| **URL** | ${url} |" + echo "| **Environment** | ${environment} |" + if [[ -n "${log_url}" ]]; then + echo "| **Dashboard** | [View](${log_url}) |" + fi + } >> "${GITHUB_STEP_SUMMARY}" + fi +} + +main() { + case "${1:-}" in + comment) cmd_comment ;; + deployment) cmd_deployment ;; + *) + echo "Usage: $(basename "$0") {comment|deployment}" >&2 + exit 1 + ;; + esac +} + +main "$@" diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 735ce85..22aa2b2 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -11,6 +11,8 @@ permissions: contents: read pages: write id-token: write + deployments: write + pull-requests: write jobs: build: @@ -41,6 +43,62 @@ jobs: name: dist path: dist/ + deploy-cloudflare: + needs: build + if: github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') + runs-on: ubuntu-latest + environment: + name: cloudflare + url: ${{ steps.cf-url.outputs.value }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .tool-versions + + - name: Enable corepack + run: corepack enable + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Download dist artifact + uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + + - name: Deploy to Cloudflare Pages + id: deploy + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + WRANGLER_OUTPUT_FILE_DIRECTORY: .wrangler-output + CF_BRANCH: ${{ github.head_ref || github.ref_name }} + run: pnpm wrangler pages deploy --branch="$CF_BRANCH" + + - name: Extract deployment URL + id: cf-url + if: always() && steps.deploy.outcome == 'success' + run: | + url=$(cat .wrangler-output/wrangler-output-*.json 2>/dev/null \ + | jq -r 'select(.type == "pages-deploy-detailed") | .url // empty' \ + | head -1) + echo "value=${url}" >> "$GITHUB_OUTPUT" + echo "### Cloudflare Pages" >> "$GITHUB_STEP_SUMMARY" + echo "**URL:** ${url}" >> "$GITHUB_STEP_SUMMARY" + + - name: Post PR comment + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WRANGLER_OUTPUT_FILE_DIRECTORY: .wrangler-output + PR_NUMBER: ${{ github.event.pull_request.number }} + run: .github/scripts/cloudflare-deploy.sh comment + deploy: needs: build if: github.event_name == 'push' && github.ref == 'refs/heads/main' diff --git a/docs/prd-cloudflare-pages-migration.md b/docs/prd-cloudflare-pages-migration.md new file mode 100644 index 0000000..37ad850 --- /dev/null +++ b/docs/prd-cloudflare-pages-migration.md @@ -0,0 +1,91 @@ +# Migrate Hosting to Cloudflare Pages + +## Problem Statement + +The documentation site is currently hosted on GitHub Pages at `chinmina.github.io`. The `chinmina.dev` domain has been registered with Cloudflare, and the canonical home for the docs should be `docs.chinmina.dev`. GitHub Pages does not integrate with the Cloudflare-managed domain, and the two deployments need to coexist during the transition while search engines index the new URL. + +## Solution + +Deploy the site to Cloudflare Pages (project: `chinmina`) via the existing GitHub Actions pipeline. The Astro `site` config is updated to `https://docs.chinmina.dev`, which causes all builds — both Cloudflare Pages and GitHub Pages — to emit canonical links pointing to the new domain. GitHub Pages remains live and up to date throughout, serving as a fallback and preserving the old URL until a redirect strategy is decided separately. + +## Requirements + +### Canonical URL and SEO + +1. The site shall set `https://docs.chinmina.dev` as the canonical base URL in `astro.config.mjs`. +2. The site shall emit a `` tag on every page, resolving to the corresponding URL under `https://docs.chinmina.dev`. +3. When a build is deployed to GitHub Pages, the system shall emit canonical links pointing to `https://docs.chinmina.dev`, not to `chinmina.github.io`. + +### Cloudflare Pages Deployment + +4. When a commit is pushed to `main`, the CI shall build the site and deploy it to Cloudflare Pages as a production deployment under `https://chinmina.pages.dev`. +5. When a pull request is opened or updated, the CI shall build the site and deploy it to a Cloudflare Pages preview URL. +6. While a pull request is open, its Cloudflare Pages preview deployment shall remain accessible at its preview URL. +7. When a Cloudflare Pages deployment completes, the CI shall surface the deployment URL in the workflow summary. +8. If the Cloudflare Pages deployment step fails, then the CI shall fail and not mark the workflow as successful. + +### GitHub Pages Deployment (Continued) + +9. When a commit is pushed to `main`, the CI shall also deploy the same build to GitHub Pages. +10. If the GitHub Pages deployment step fails, then the CI shall fail and not mark the workflow as successful. + +### Build Pipeline + +11. The CI shall install the D2 diagramming tool before running the Astro build. +12. The CI shall produce a single build artifact shared by both the Cloudflare Pages and GitHub Pages deployment jobs. +13. If the build step fails, then the CI shall not attempt either deployment. + +### DNS and Domain + +14. The system shall serve the Cloudflare Pages production deployment at `https://docs.chinmina.dev` via a DNS CNAME record in Cloudflare. +15. The Cloudflare Pages project shall enforce HTTPS for all requests to `docs.chinmina.dev`. + +### Optional + +16. Where a pull request triggers a CI build, the CI shall output the Cloudflare Pages preview URL as a GitHub Actions step summary. + +## Implementation Decisions + +**Workflow restructure**: The current `withastro/action` couples the build to GitHub Pages artifact upload. To share one build between two deploy targets, the build must be extracted into explicit steps: install D2, set up Node.js (via `actions/setup-node` with `enable-corepack: true` — corepack reads the `packageManager` field in `package.json` and provisions the pinned pnpm version automatically), run `pnpm install`, run `pnpm run build`, then upload two artifacts — one as a GitHub Pages artifact (`actions/upload-pages-artifact`) and one as a generic `dist/` artifact (`actions/upload-artifact`). The two deploy jobs run in parallel after the build job completes. + +**GitHub Pages deploy job**: Unchanged in behaviour. Conditional on `github.ref == 'refs/heads/main'`. Consumes the GitHub Pages artifact via `actions/deploy-pages`. + +**Cloudflare Pages deploy job**: Runs on all branches (for preview support). Downloads the `dist/` artifact and deploys via `cloudflare/wrangler-action` with `command: pages deploy dist --project-name=chinmina`. Requires `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` as GitHub Actions secrets. + +**`wrangler.toml`**: A minimal `wrangler.toml` at the repo root declares `name = "chinmina"` and `pages_build_output_dir = "dist"`. This makes the project identity explicit and removes the need to pass flags in the workflow command. + +**Canonical link handling**: Astro emits `` automatically based on the `site` config. Changing `site` to `https://docs.chinmina.dev` is sufficient — no changes to `Head.astro` are required. The `site` change and the Cloudflare Pages go-live must ship in the same merge to avoid a window where GitHub Pages serves canonicals pointing to a domain not yet live. + +**Cloudflare Pages project**: Must be created in the Cloudflare dashboard (or via Wrangler) before the first deployment. Custom domain `docs.chinmina.dev` is configured in the Cloudflare Pages project settings. DNS is a CNAME record: `docs.chinmina.dev` → `chinmina.pages.dev`. + +**GitHub Pages remains live**: GitHub Pages is not disabled as part of this work. It continues to receive deployments from `main` and serves the site at `chinmina.github.io` with canonical links pointing to `docs.chinmina.dev`. Decommissioning GitHub Pages is deferred to a future redirect-strategy workstream. + +**GitHub Actions secrets needed**: +- `CLOUDFLARE_API_TOKEN` — scoped to Cloudflare Pages edit permissions +- `CLOUDFLARE_ACCOUNT_ID` — the Cloudflare account hosting the `chinmina` project + +## Testing Decisions + +This is an infrastructure and configuration change. There are no unit tests. All requirements map to manual acceptance checks performed after deployment: + +| Requirement | Verification | +|---|---| +| 1–3 (canonical) | View page source on both `docs.chinmina.dev` and `chinmina.github.io`; confirm canonical tag resolves to `docs.chinmina.dev` | +| 4 (production deploy) | Merge to `main`; confirm Cloudflare Pages production deployment succeeds and site is reachable at `chinmina.pages.dev` | +| 5–6 (preview deploy) | Open a PR; confirm a preview URL appears in the workflow summary and is reachable | +| 9 (GH Pages continued) | Merge to `main`; confirm GitHub Pages deployment succeeds and `chinmina.github.io` reflects the change | +| 11 (D2) | Confirm a page containing a D2 diagram renders correctly on `docs.chinmina.dev` | +| 14–15 (DNS + HTTPS) | `curl -I https://docs.chinmina.dev`; confirm 200 and valid TLS certificate | + +## Out of Scope + +- Redirecting `chinmina.github.io` to `docs.chinmina.dev` (deferred to a separate workstream) +- Disabling GitHub Pages +- Redirecting the apex domain `chinmina.dev` to `docs.chinmina.dev` +- Any changes to site content or structure + +## Further Notes + +The `withastro/action` action is a convenience wrapper that bundles Node.js setup, pnpm detection, build, and GitHub Pages artifact upload in one step. Replacing it with explicit steps adds a few lines to the workflow but gives full control over the build environment — necessary here because of the D2 pre-install step and the need to share the build output with a second deploy target. + +The Cloudflare Pages project must exist before the first pipeline run. Creating it via the Cloudflare dashboard (connect to GitHub, select repo, set build command to `pnpm run build` and output dir to `dist`) is the recommended path, but with `wrangler.toml` in place, `wrangler pages project create chinmina` also works. diff --git a/package.json b/package.json index 36ba78e..84fd490 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.5", - "vitest": "^3.0.0" + "vitest": "^3.0.0", + "wrangler": "^4.77.0" }, "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4613381..fef5a61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: vitest: specifier: ^3.0.0 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(yaml@2.8.2) + wrangler: + specifier: ^4.77.0 + version: 4.77.0 packages: @@ -193,6 +196,53 @@ packages: '@clack/prompts@1.1.0': resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==} + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.16.0': + resolution: {integrity: sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20260317.1': + resolution: {integrity: sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260317.1': + resolution: {integrity: sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260317.1': + resolution: {integrity: sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260317.1': + resolution: {integrity: sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260317.1': + resolution: {integrity: sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@ctrl/tinycolor@4.2.0': resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} engines: {node: '>=14'} @@ -682,9 +732,16 @@ packages: cpu: [x64] os: [win32] + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -724,6 +781,15 @@ packages: cpu: [x64] os: [win32] + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -907,6 +973,13 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.15': + resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} + '@terrastruct/d2@0.1.33': resolution: {integrity: sha512-eK5hyfGIJFolC7sUsiKvWdY9xGFctTe3d+PSijo09IYDso8psztC+A4SammizXtlwYZpnnW0AtDjfBYauceSeA==} @@ -1096,6 +1169,9 @@ packages: bcp-47@2.1.0: resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1282,6 +1358,9 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -1720,6 +1799,11 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + miniflare@4.20260317.2: + resolution: {integrity: sha512-qNL+yWAFMX6fr0pWU6Lx1vNpPobpnDSF1V8eunIckWvoIQl8y1oBjL2RJFEGY3un+l3f9gwW9dirDPP26usYJQ==} + engines: {node: '>=18.0.0'} + hasBin: true + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -1801,6 +1885,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2037,6 +2124,10 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + svgo@4.0.0: resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} engines: {node: '>=16'} @@ -2117,6 +2208,13 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.24.4: + resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -2452,10 +2550,37 @@ packages: engines: {node: '>=8'} hasBin: true + workerd@1.20260317.1: + resolution: {integrity: sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.77.0: + resolution: {integrity: sha512-E2Gm69+K++BFd3QvoWjC290RPQj1vDOUotA++sNHmtKPb7EP6C8Qv+1D5Ii73tfZtyNgakpqHlh8lBBbVWTKAQ==} + engines: {node: '>=20.3.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260317.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} @@ -2493,6 +2618,12 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -2712,6 +2843,33 @@ snapshots: '@clack/core': 1.1.0 sisteransi: 1.0.5 + '@cloudflare/kv-asset-handler@0.4.2': {} + + '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260317.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260317.1 + + '@cloudflare/workerd-darwin-64@1.20260317.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260317.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260317.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260317.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260317.1': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@ctrl/tinycolor@4.2.0': {} '@emmetio/abbreviation@2.3.3': @@ -3019,8 +3177,15 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 @@ -3073,6 +3238,18 @@ snapshots: '@pagefind/windows-x64@1.4.0': optional: true + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': dependencies: '@types/estree': 1.0.8 @@ -3227,6 +3404,10 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.15': {} + '@terrastruct/d2@0.1.33': {} '@types/chai@5.2.3': @@ -3529,6 +3710,8 @@ snapshots: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 + blake3-wasm@2.1.5: {} + boolbase@1.0.0: {} cac@6.7.14: {} @@ -3682,6 +3865,8 @@ snapshots: entities@6.0.1: {} + error-stack-parser-es@1.0.5: {} + es-module-lexer@1.7.0: {} es-module-lexer@2.0.0: {} @@ -4574,6 +4759,18 @@ snapshots: transitivePeerDependencies: - supports-color + miniflare@4.20260317.2: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.24.4 + workerd: 1.20260317.1 + ws: 8.18.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + mrmime@2.0.1: {} ms@2.1.3: {} @@ -4663,6 +4860,8 @@ snapshots: path-browserify@1.0.1: {} + path-to-regexp@6.3.0: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -5024,6 +5223,8 @@ snapshots: dependencies: inline-style-parser: 0.2.7 + supports-color@10.2.2: {} + svgo@4.0.0: dependencies: commander: 11.1.0 @@ -5082,6 +5283,12 @@ snapshots: undici-types@7.16.0: {} + undici@7.24.4: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -5371,12 +5578,38 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + workerd@1.20260317.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260317.1 + '@cloudflare/workerd-darwin-arm64': 1.20260317.1 + '@cloudflare/workerd-linux-64': 1.20260317.1 + '@cloudflare/workerd-linux-arm64': 1.20260317.1 + '@cloudflare/workerd-windows-64': 1.20260317.1 + + wrangler@4.77.0: + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260317.1) + blake3-wasm: 2.1.5 + esbuild: 0.27.3 + miniflare: 4.20260317.2 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260317.1 + optionalDependencies: + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + ws@8.18.0: {} + xxhash-wasm@1.1.0: {} y18n@5.0.8: {} @@ -5415,6 +5648,19 @@ snapshots: yocto-queue@1.2.2: {} + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.15 + cookie: 1.1.1 + youch-core: 0.3.3 + zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..c40a95f --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,2 @@ +name = "chinmina" +pages_build_output_dir = "dist"