diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d5db379 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +# Keep the build context small and avoid leaking local state into the image. +.git +.github +node_modules +.next +npm-debug.log* +.env +.env.* +!.env.example +.husky +.vscode +.idea +*.md +docs +Dockerfile +.dockerignore diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..fe5a601 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,98 @@ +name: Build & publish image + +# Build the site's Docker image OFF the Oracle box (GitHub's runners do the +# heavy 2-4GB build) and push it to GHCR. Dokploy then just *pulls* the +# prebuilt image and runs it — so the apps sharing the Oracle box don't fight +# over RAM/CPU during builds. See docs/deploy.md. + +on: + push: + branches: [main] + # Only rebuild when something that affects the image changes. + paths: + - "app/**" + - "components/**" + - "lib/**" + - "sanity/**" + - "public/**" + - "sanity.config.ts" + - "sanity.cli.ts" + - "next.config.ts" + - "postcss.config.mjs" + - "tailwind.config.ts" + - "tsconfig.json" + - "Dockerfile" + - ".dockerignore" + - "package.json" + - "package-lock.json" + - ".github/workflows/deploy.yml" + workflow_dispatch: {} # allow manual "Run workflow" + +# NOTE: the deploy image is ghcr.io/monashcoding/monashcoding:latest, which is +# deliberately NOT the repo name (monashcoding-site). It's set explicitly in the +# metadata step below; the first push creates that GHCR package and links it here. + +jobs: + build: + # Native ARM64 runner — Oracle Ampere is arm64, and this is free on + # public repos. Avoids ~5x slower QEMU cross-compilation. + runs-on: ubuntu-24.04-arm + permissions: + contents: read + packages: write # push to GHCR + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata (tags) + id: meta + uses: docker/metadata-action@v5 + with: + # Deploy target is ghcr.io/monashcoding/monashcoding:latest, which + # differs from the repo name (monashcoding-site), so set it explicitly. + images: ghcr.io/monashcoding/monashcoding + tags: | + type=raw,value=latest + type=sha,format=long + + - name: Build & push (linux/arm64) + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + # NEXT_PUBLIC_* are inlined into the client bundle at build time and + # the Sanity Studio config reads them at module load. They're all + # browser-public values (a Sanity project id/dataset are not secret), + # so configure them as repo *Variables* (Settings → Secrets and + # variables → Actions → Variables). Fallbacks keep the build working + # before you set them. + build-args: | + NEXT_PUBLIC_SANITY_DATASET=${{ vars.NEXT_PUBLIC_SANITY_DATASET || 'production' }} + NEXT_PUBLIC_SANITY_API_VERSION=${{ vars.NEXT_PUBLIC_SANITY_API_VERSION || '2024-01-18' }} + NEXT_PUBLIC_SANITY_PROJECT_ID=${{ vars.NEXT_PUBLIC_SANITY_PROJECT_ID }} + + # Tell Dokploy to pull the new image and redeploy. Create the app in + # Dokploy with provider "Docker" pointing at + # ghcr.io/monashcoding/monashcoding:latest, then copy its deploy webhook + # URL into the repo secret DOKPLOY_DEPLOY_WEBHOOK. Skipped automatically + # until that secret exists. + - name: Trigger Dokploy redeploy + env: + # Secrets can't be used directly in `if:`, so hoist into env first. + DOKPLOY_DEPLOY_WEBHOOK: ${{ secrets.DOKPLOY_DEPLOY_WEBHOOK }} + if: ${{ env.DOKPLOY_DEPLOY_WEBHOOK != '' }} + run: curl -fsSL -X POST "$DOKPLOY_DEPLOY_WEBHOOK" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6375392 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,75 @@ +# syntax=docker/dockerfile:1 + +# --------------------------------------------------------------------------- +# monashcoding.com — self-hosted image (Oracle Cloud + Dokploy). +# +# This is a SINGLE-package Next.js 16 app (not a monorepo like monmap), so the +# Dockerfile is the simple shape: no outputFileTracingRoot / transpilePackages, +# no workspace filter. `output: "standalone"` emits a self-contained server. +# +# `next build` is SSG here — every page sets `revalidate = false` and +# events/[slug] has generateStaticParams() — so the build QUERIES THE SANITY +# API. The runner therefore needs network access plus the public Sanity vars +# below. Those are NEXT_PUBLIC_* (project id + dataset are not secrets), passed +# as --build-args because Next inlines them into the client bundle and the +# Studio config reads them at module load. +# +# Secrets (RESEND_API_KEY, SANITY_WEBHOOK_SECRET) are used only by API routes +# at RUNTIME, never during build — set them in Dokploy, never as build args. +# --------------------------------------------------------------------------- + +FROM node:22-slim AS base +# Corepack reads the `packageManager` field in package.json to pin the exact +# npm version, so CI resolves the lockfile the same way it was generated. +# Auto-download it without an interactive prompt. +ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 +RUN corepack enable +WORKDIR /app + +# ---- deps + build ------------------------------------------------------- +FROM base AS build + +# The `prepare` script runs husky, which needs a .git dir we don't ship. +# HUSKY=0 makes it a no-op during the image build. +ENV HUSKY=0 + +# Install against the committed lockfile first (better layer caching). +COPY package.json package-lock.json ./ +RUN --mount=type=cache,target=/root/.npm npm ci + +# Now the source. +COPY . . + +# NEXT_PUBLIC_* must exist at build time — inlined into the client bundle and +# read by sanity.config.ts. Pass via --build-arg (Dokploy: Build → Build Args). +ARG NEXT_PUBLIC_SANITY_PROJECT_ID +ARG NEXT_PUBLIC_SANITY_DATASET +ARG NEXT_PUBLIC_SANITY_API_VERSION +ENV NEXT_PUBLIC_SANITY_PROJECT_ID=$NEXT_PUBLIC_SANITY_PROJECT_ID \ + NEXT_PUBLIC_SANITY_DATASET=$NEXT_PUBLIC_SANITY_DATASET \ + NEXT_PUBLIC_SANITY_API_VERSION=$NEXT_PUBLIC_SANITY_API_VERSION \ + NEXT_TELEMETRY_DISABLED=1 + +RUN npm run build + +# ---- runtime ------------------------------------------------------------ +FROM node:22-slim AS runner +WORKDIR /app +ENV NODE_ENV=production \ + NEXT_TELEMETRY_DISABLED=1 \ + PORT=3000 \ + HOSTNAME=0.0.0.0 + +# Run as a non-root user. +RUN groupadd --system --gid 1001 nodejs \ + && useradd --system --uid 1001 --gid nodejs nextjs + +# standalone/ ships server.js + a pruned node_modules. Static assets and +# public/ aren't traced into it, so copy them alongside. +COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=build --chown=nextjs:nodejs /app/public ./public + +USER nextjs +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 0000000..26c54d0 --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,139 @@ +# Deploying monashcoding.com (Oracle Cloud + Dokploy) + +The club's main site runs as a Docker container on an Oracle Cloud VM, fronted +by [Dokploy](https://dokploy.com). It moved off Vercel when we consolidated all +the club's apps onto the one box. + +## Architecture: build off-box, run on-box + +The Oracle VM is shared by several apps (monmap, mploy, monashcoding). A Docker +build peaks at 2–4 GB RAM and pegs the CPU; several of those racing on one box +is how you OOM production. So **we never build on the Oracle box**: + +``` +push to main ──▶ GitHub Actions (ubuntu-24.04-arm) + │ builds linux/arm64 image + ▼ + GHCR: ghcr.io/monashcoding/monashcoding:latest + │ Dokploy pulls (webhook-triggered) + ▼ + Oracle VM: `node server.js` (~200–400 MB idle) +``` + +This is a **single-package Next.js 16 app** (App Router) with `output: +"standalone"`. Unlike monmap it has no workspace/monorepo layout, so the +Dockerfile is the simple shape. Content comes from **Sanity** (hosted CMS) and +the contact form sends via **Resend** (hosted) — there is **no database on the +Oracle box** for this app. + +## Build-time vs runtime env + +`next build` is **SSG** here: every page sets `revalidate = false` and +`events/[slug]` has `generateStaticParams()`, so the build **queries the Sanity +API**. That means: + +- **Build-time (baked into the image, set as GitHub Variables → build-args):** + the public Sanity vars. A Sanity project id + dataset are not secrets — they + ship in the client bundle regardless — so they live in repo *Variables*, not + Secrets. + - `NEXT_PUBLIC_SANITY_PROJECT_ID` + - `NEXT_PUBLIC_SANITY_DATASET` (default `production`) + - `NEXT_PUBLIC_SANITY_API_VERSION` (default `2024-01-18`) +- **Runtime only (set in Dokploy, never build args):** the actual secrets, used + only by API routes. + - `RESEND_API_KEY` — contact form email (`/api/send`, `/api/event-reminder`) + - `SANITY_WEBHOOK_SECRET` — verifies the on-demand revalidate webhook + (`/api/revalidate`) + +## One-time GitHub setup + +1. **Repo Variables** (Settings → Secrets and variables → Actions → _Variables_). + All browser-public, so Variables not Secrets: + - `NEXT_PUBLIC_SANITY_PROJECT_ID` = _(the Sanity project id)_ + - `NEXT_PUBLIC_SANITY_DATASET` = `production` + - `NEXT_PUBLIC_SANITY_API_VERSION` = `2024-01-18` + The workflow has fallbacks for dataset + API version; set the project id or + the build falls back to an empty id and fails (`sanity.config.ts` throws). +2. **Repo Secret**: `DOKPLOY_DEPLOY_WEBHOOK` = the deploy webhook URL Dokploy + generates for the app (added after the Dokploy setup below). Until it exists, + the workflow builds/pushes but skips the redeploy trigger. +3. **Make the GHCR package public** (or give Dokploy a read token) so the VM can + pull without auth: after the first push, open the package at + `github.com/orgs/monashcoding/packages` → Package settings → change + visibility to Public. (The package is named `monashcoding`, not + `monashcoding-site`.) + +## One-time Dokploy setup + +Dokploy panel: + +1. **Create Application** → Provider: **Docker**. + - Image: `ghcr.io/monashcoding/monashcoding:latest` + - (If you kept the package private: add GHCR registry credentials — a GitHub + PAT with `read:packages`.) +2. **Environment** (runtime vars): + ``` + RESEND_API_KEY= + SANITY_WEBHOOK_SECRET= + NEXT_PUBLIC_SANITY_PROJECT_ID= + NEXT_PUBLIC_SANITY_DATASET=production + NEXT_PUBLIC_SANITY_API_VERSION=2024-01-18 + ``` + (The `NEXT_PUBLIC_*` are already baked into the image at build; setting them + here too is harmless and keeps server-side reads consistent.) +3. **Port**: container listens on `3000`. +4. **Domains**: serve the apex as canonical, www as a redirect. + - `monashcoding.com` → container port `3000` → enable HTTPS (Let's Encrypt). + - `www.monashcoding.com` → 301 redirect to `monashcoding.com`. +5. **Deploy webhook**: copy the app's deploy webhook URL into the GitHub repo + secret `DOKPLOY_DEPLOY_WEBHOOK` so each pushed image auto-redeploys. + +## DNS + cutover (apex domain — do this carefully) + +This is the club's highest-visibility site. **Verify the container is healthy +before repointing DNS** — don't swap a working site for a 502: + +1. In Dokploy, watch the pull/restart logs until the container is up. +2. From the Oracle box, confirm the app answers on the apex Host header: + ``` + curl -I -H "Host: monashcoding.com" http://localhost + ``` + Expect a `200` (or a Next redirect), not a `502`/`connection refused`. +3. Only then touch DNS. On Cloudflare, the apex needs an **A record** (not a + CNAME): + - `A @ ` — **grey-cloud (DNS-only) first** so Dokploy can + complete the Let's Encrypt HTTP-01 challenge and issue the cert. + - `CNAME www monashcoding.com` + - Once the cert is issued and the site loads over HTTPS, **orange-cloud + (proxy)** both records. + +## Cutting the cord with Vercel + +`vercel.json` (`{ "github": { "enabled": false, "silent": true } }`) stops +Vercel's GitHub integration from running deploy checks on push. For a complete +cut, also: + +- **Disconnect the Git integration** in the Vercel dashboard (Project → + Settings → Git → Disconnect). +- **Remove any required "Vercel" status check** in the GitHub branch-protection + rule for `main`, or PRs will hang waiting on a check that no longer runs. + +## Deploying a change + +Just push to `main`. GitHub Actions builds + pushes the image, then hits the +Dokploy webhook, which pulls and restarts the container. Watch the run under the +repo's Actions tab; watch the pull/restart in Dokploy. + +To deploy manually: Actions → **Build & publish image** → _Run workflow_, then +hit **Deploy** in Dokploy. + +## Notes + +- Only `NEXT_PUBLIC_*` are baked in at build time (client-bundle values) — hence + build-args. The real secrets are runtime-only. +- The build reaches out to the Sanity API (SSG). If Sanity is unreachable or the + project id is wrong, `next build` fails in CI — that's the fast feedback, not + a broken deploy. +- Content edited in Sanity Studio (`/studio`) shows up via the on-demand + revalidate webhook (`/api/revalidate`, guarded by `SANITY_WEBHOOK_SECRET`). + Point the Sanity webhook at `https://monashcoding.com/api/revalidate`. diff --git a/next.config.ts b/next.config.ts index 88c7b2c..276a2a5 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,9 @@ import type { NextConfig } from "next"; import withBundleAnalyzer from "@next/bundle-analyzer"; const nextConfig: NextConfig = { + // Emit a self-contained server bundle (.next/standalone/server.js) so the + // Docker runtime image doesn't need node_modules or the package manager. + output: "standalone", images: { remotePatterns: [ { diff --git a/package-lock.json b/package-lock.json index bc2a324..747b168 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,7 +113,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -688,7 +687,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2474,7 +2472,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.11.tgz", "integrity": "sha512-bWdeR8gWM87l4DB/kYSF9A+dVackzDb/V56Tq7QVrQ7rn86W0rgZFtlL3g3pem6AeGcb9NQNoy3ao4WpW4h5tQ==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -2682,7 +2679,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -3123,7 +3119,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3165,7 +3160,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -3215,7 +3209,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -3302,7 +3295,6 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -5226,7 +5218,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5727,7 +5718,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -5826,7 +5816,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7290,7 +7279,6 @@ "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", @@ -7561,7 +7549,6 @@ "resolved": "https://registry.npmjs.org/@portabletext/editor/-/editor-4.2.4.tgz", "integrity": "sha512-zkeBSVKjVfyGi3qGf/Yx58320mBC8axg4AscIN1bgON8GeJTMASg1GD6tNPe9q0d3A7xIRjU42KT/XDrcx2J7g==", "license": "MIT", - "peer": true, "dependencies": { "@portabletext/block-tools": "^5.0.0", "@portabletext/keyboard-shortcuts": "^2.1.2", @@ -8152,7 +8139,6 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz", "integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -9265,7 +9251,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9484,7 +9469,6 @@ "resolved": "https://registry.npmjs.org/@sanity/client/-/client-7.14.0.tgz", "integrity": "sha512-eXue3rc4MqJh89mvuTC0h0pdoY8lwXjlV8odFB3EF7aSFKF7F5BL0NU2mlTrCZYbPAlV3JTvMPPLGJCORqOKDw==", "license": "MIT", - "peer": true, "dependencies": { "@sanity/eventsource": "^5.0.2", "get-it": "^8.7.0", @@ -9875,8 +9859,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@sanity/json-match": { "version": "1.0.5", @@ -10297,7 +10280,6 @@ "resolved": "https://registry.npmjs.org/@sanity/cli-core/-/cli-core-0.1.0-alpha.6.tgz", "integrity": "sha512-yrZWRxSBZBuk+FxXULZI1gwOVWIPZ9tvdrswWmHsnqAU8051y9sDb9+ntHU8rn6c9jsRS0cJBJH+uhIlXt+1bA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/prompts": "^8.1.0", "@oclif/core": "^4.8.0", @@ -10714,7 +10696,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12542,7 +12523,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12637,7 +12617,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -12999,7 +12978,6 @@ "resolved": "https://registry.npmjs.org/@sanity/types/-/types-5.4.0.tgz", "integrity": "sha512-wy+w1K2WuMj+6xcP3z4dDLdMQiyinfemi0koX0HQulZ+s6uSPi2rDKgixe0+7hcQD8k1m2fGpDfvCfnlXAT0Vg==", "license": "MIT", - "peer": true, "dependencies": { "@sanity/client": "^7.14.0", "@sanity/media-library-types": "^1.2.0" @@ -14014,7 +13992,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -14049,7 +14026,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -14120,7 +14096,6 @@ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz", "integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==", "license": "MIT", - "peer": true, "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", @@ -14214,7 +14189,6 @@ "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", @@ -14888,7 +14862,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -15735,7 +15708,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -15964,7 +15936,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -17131,7 +17102,6 @@ "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -18049,6 +18019,31 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -18349,7 +18344,6 @@ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -18424,7 +18418,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -18610,7 +18603,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -19430,8 +19422,7 @@ "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.11.tgz", "integrity": "sha512-LaI+KaX2NFkfn1ZGHoKCmcfv7yrZsC3b8NtWsTVQeHkq4F27vI5igUuO53sxqDEa2gNQMHFPmpojDw/1zmUK7w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fraction.js": { "version": "5.3.4", @@ -20767,7 +20758,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.23.2" } @@ -21841,7 +21831,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -22478,7 +22467,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -22979,7 +22967,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -23078,7 +23065,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -23245,7 +23231,6 @@ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", "devOptional": true, "license": "MPL-2.0", - "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -23277,7 +23262,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23298,7 +23282,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23319,7 +23302,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23340,7 +23322,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23361,7 +23342,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23382,7 +23362,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23403,7 +23382,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23424,7 +23402,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23445,7 +23422,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23466,7 +23442,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -23487,7 +23462,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -26706,7 +26680,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -26741,7 +26714,6 @@ "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.38.2.tgz", "integrity": "sha512-7DwuT7Tkst41ZjSj287g7C9c5/D3Xx5rMgBosg0dadbUPoZD2HNzkadKPol1d2PJAoI9f+Jeh1/v9YfLzpFGVw==", "license": "Zlib", - "peer": true, "peerDependencies": { "three": ">= 0.157.0 < 0.183.0" } @@ -27111,7 +27083,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -27142,7 +27113,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -28179,7 +28149,6 @@ "resolved": "https://registry.npmjs.org/sanity/-/sanity-5.4.0.tgz", "integrity": "sha512-N/oNn//xFiepDUjga3binKNzn/6HTP+RlH5sA7pmpgRUBtX6ZUo+NqG6Qj3vN/kRU9omN5oYTwRnGCLJb79lAw==", "license": "MIT", - "peer": true, "dependencies": { "@date-fns/tz": "^1.4.1", "@dnd-kit/core": "^6.3.1", @@ -28711,7 +28680,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -28745,8 +28713,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/sanity/node_modules/read-pkg": { "version": "5.2.0", @@ -28850,7 +28817,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -29383,15 +29349,13 @@ "version": "0.120.0", "resolved": "https://registry.npmjs.org/slate/-/slate-0.120.0.tgz", "integrity": "sha512-CXK/DADGgMZb4z9RTtXylzIDOxvmNJEF9bXV2bAGkLWhQ3rm7GORY9q0H/W41YJvAGZsLbH7nnrhMYr550hWDQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/slate-dom": { "version": "0.119.0", "resolved": "https://registry.npmjs.org/slate-dom/-/slate-dom-0.119.0.tgz", "integrity": "sha512-foc8a2NkE+1SldDIYaoqjhVKupt8RSuvHI868rfYOcypD4we5TT7qunjRKJ852EIRh/Ql8sSTepXgXKOUJnt1w==", "license": "MIT", - "peer": true, "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", @@ -29960,7 +29924,6 @@ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.8.tgz", "integrity": "sha512-Kq/W41AKQloOqKM39zfaMdJ4BcYDw/N5CIq4/GTI0YjU6pKcZ1KKhk6b4du0a+6RA9pIfOP/eu94Ge7cu+PDCA==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.4.0", "@emotion/unitless": "0.10.0", @@ -30136,8 +30099,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -30344,8 +30306,7 @@ "version": "0.167.1", "resolved": "https://registry.npmjs.org/three/-/three-0.167.1.tgz", "integrity": "sha512-gYTLJA/UQip6J/tJvl91YYqlZF47+D/kxiWrbTon35ZHlXEN0VOo+Qke2walF1/x92v55H6enomymg4Dak52kw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/three-mesh-bvh": { "version": "0.8.3", @@ -30456,7 +30417,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -30696,7 +30656,6 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -30927,7 +30886,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -31483,7 +31441,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -32098,7 +32055,6 @@ "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.25.1.tgz", "integrity": "sha512-oyvsNH5pF2qkHmiHEMdWqc3OjDtoZOH2MTAI35r01f/ZQWOD+VLOiYqo65UgQET0XMA5s9eRm8fnsIo+82biEw==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/xstate" @@ -32270,7 +32226,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index f1e152b..37a9d26 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "author": "", "license": "ISC", "type": "module", + "packageManager": "npm@11.11.0", "bugs": { "url": "https://github.com/monashcoding/monashcoding-site/issues" }, diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..c73478f --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "github": { + "enabled": false, + "silent": true + } +}