Fix invite link showing http and add draft board link during live draft
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 1m24s
🚀 Deploy / ʦ TypeScript (pull_request) Successful in 1m15s
🚀 Deploy / 🔍 Lint (pull_request) Successful in 48s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 1m24s
🚀 Deploy / ʦ TypeScript (pull_request) Successful in 1m15s
🚀 Deploy / 🔍 Lint (pull_request) Successful in 48s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
- Use x-forwarded-proto header (allowlisted to http/https only) when building the invite link origin so it shows https behind a TLS proxy - Show "View Draft Board" link in the DraftInfoCard header when the draft is actively running, matching the link already shown on active/completed seasons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4fa927e66b
commit
41481a9812
4 changed files with 166 additions and 11 deletions
|
|
@ -18,6 +18,8 @@ export interface DraftInfoCardProps {
|
||||||
isDraftOrderSet: boolean;
|
isDraftOrderSet: boolean;
|
||||||
/** true when season status is pre_draft or draft */
|
/** true when season status is pre_draft or draft */
|
||||||
isDraftOrPreDraft: boolean;
|
isDraftOrPreDraft: boolean;
|
||||||
|
/** true when season status is specifically draft (live) */
|
||||||
|
isDraft?: boolean;
|
||||||
draftDateTime?: string | Date | null;
|
draftDateTime?: string | Date | null;
|
||||||
draftBoardHref: string;
|
draftBoardHref: string;
|
||||||
draftRoomHref: string;
|
draftRoomHref: string;
|
||||||
|
|
@ -56,8 +58,10 @@ function DraftInfoCardVariant({
|
||||||
draftIncrementTime,
|
draftIncrementTime,
|
||||||
sportsCount,
|
sportsCount,
|
||||||
isDraftOrderSet,
|
isDraftOrderSet,
|
||||||
|
isDraft,
|
||||||
draftDateTime,
|
draftDateTime,
|
||||||
draftTimezone,
|
draftTimezone,
|
||||||
|
draftBoardHref,
|
||||||
draftRoomHref,
|
draftRoomHref,
|
||||||
queueBuilderHref,
|
queueBuilderHref,
|
||||||
userDraftPosition,
|
userDraftPosition,
|
||||||
|
|
@ -115,15 +119,26 @@ function DraftInfoCardVariant({
|
||||||
<GradientIcon icon={LayoutGrid} className="h-5 w-5 shrink-0" />
|
<GradientIcon icon={LayoutGrid} className="h-5 w-5 shrink-0" />
|
||||||
<h2 className="text-xl font-bold leading-none tracking-tight">Draft Info</h2>
|
<h2 className="text-xl font-bold leading-none tracking-tight">Draft Info</h2>
|
||||||
</div>
|
</div>
|
||||||
{isDraftOrderSet ? (
|
<div className="flex items-center gap-3">
|
||||||
<Button asChild>
|
{isDraft && (
|
||||||
<Link to={draftRoomHref}>Enter Draft Room</Link>
|
<Link
|
||||||
</Button>
|
to={draftBoardHref}
|
||||||
) : queueBuilderHref ? (
|
className="flex items-center gap-0.5 text-xs font-semibold uppercase tracking-wide text-electric hover:underline"
|
||||||
<Button asChild variant="outline">
|
>
|
||||||
<Link to={queueBuilderHref}>Set Pre-Draft Queue</Link>
|
View Draft Board
|
||||||
</Button>
|
<ChevronRight className="h-3.5 w-3.5" />
|
||||||
) : null}
|
</Link>
|
||||||
|
)}
|
||||||
|
{isDraftOrderSet ? (
|
||||||
|
<Button asChild>
|
||||||
|
<Link to={draftRoomHref}>Enter Draft Room</Link>
|
||||||
|
</Button>
|
||||||
|
) : queueBuilderHref ? (
|
||||||
|
<Button asChild variant="outline">
|
||||||
|
<Link to={queueBuilderHref}>Set Pre-Draft Queue</Link>
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,8 +203,14 @@ export async function loader(args: Route.LoaderArgs) {
|
||||||
// Check if draft order is set
|
// Check if draft order is set
|
||||||
const isDraftOrderSet = draftSlots.length > 0;
|
const isDraftOrderSet = draftSlots.length > 0;
|
||||||
|
|
||||||
// Extract origin for client use (avoids SSR/client mismatch on invite URLs)
|
// Extract origin for client use (avoids SSR/client mismatch on invite URLs).
|
||||||
const origin = new URL(args.request.url).origin;
|
// Respect x-forwarded-proto so the invite URL shows https when behind a TLS-terminating proxy.
|
||||||
|
const requestUrl = new URL(args.request.url);
|
||||||
|
const rawProto = args.request.headers.get("x-forwarded-proto");
|
||||||
|
const proto = rawProto?.split(",")[0].trim() ?? "";
|
||||||
|
const origin = (proto === "https" || proto === "http")
|
||||||
|
? `${proto}://${requestUrl.host}`
|
||||||
|
: requestUrl.origin;
|
||||||
|
|
||||||
// Fetch recent audit log entries for the "Recent Activity" summary widget
|
// Fetch recent audit log entries for the "Recent Activity" summary widget
|
||||||
const recentActivity = season
|
const recentActivity = season
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
|
||||||
sportsCount={sportsCount}
|
sportsCount={sportsCount}
|
||||||
isDraftOrderSet={isDraftOrderSet}
|
isDraftOrderSet={isDraftOrderSet}
|
||||||
isDraftOrPreDraft={isDraftOrPreDraft}
|
isDraftOrPreDraft={isDraftOrPreDraft}
|
||||||
|
isDraft={season.status === "draft"}
|
||||||
draftDateTime={season.draftDateTime}
|
draftDateTime={season.draftDateTime}
|
||||||
draftTimezone={currentUserTimezone ?? season.overnightPauseTimezone}
|
draftTimezone={currentUserTimezone ?? season.overnightPauseTimezone}
|
||||||
draftBoardHref={`/leagues/${league.id}/draft-board/${season.id}`}
|
draftBoardHref={`/leagues/${league.id}/draft-board/${season.id}`}
|
||||||
|
|
|
||||||
133
docs/ci-build-optimization.md
Normal file
133
docs/ci-build-optimization.md
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
# CI Build Optimization Plan
|
||||||
|
|
||||||
|
The Forgejo Actions build pipeline currently takes ~15 minutes on main branch pushes:
|
||||||
|
- ~5 min: `setup-buildx-action`
|
||||||
|
- ~5 min: build and push to container registry
|
||||||
|
- ~5 min: deploy job (docker pull + migrate + compose up)
|
||||||
|
|
||||||
|
This plan cuts that to ~3-5 minutes for typical code changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Causes
|
||||||
|
|
||||||
|
### 1. Dockerfile layer invalidation (biggest win)
|
||||||
|
|
||||||
|
The `development-dependencies-env` stage copies *all* source files before running `npm ci`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM node:20-alpine AS development-dependencies-env
|
||||||
|
COPY . /app # <-- invalidates npm cache on EVERY commit
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm ci
|
||||||
|
```
|
||||||
|
|
||||||
|
Every single push blows the npm install cache layer, even if `package.json` hasn't changed. This means:
|
||||||
|
- The build job re-runs `npm ci` inside Docker on every commit (~1-2 min)
|
||||||
|
- The server's `docker compose pull` re-downloads the large node_modules layer (~200-300MB) on every deploy
|
||||||
|
|
||||||
|
The `production-dependencies-env` stage already does this correctly — the dev stage needs the same fix.
|
||||||
|
|
||||||
|
### 2. Buildx setup taking 5 minutes
|
||||||
|
|
||||||
|
`setup-buildx-action` normally takes ~20-30 seconds. 5 minutes means QEMU multi-platform emulators are being installed (for arm64 support). Since we only target `linux/amd64`, explicitly declaring the platform skips QEMU installation entirely.
|
||||||
|
|
||||||
|
### 3. No npm cache in CI jobs
|
||||||
|
|
||||||
|
The `test`, `typecheck`, and `lint` jobs each run `npm ci` independently with no caching. This adds ~30-60s per job on cold runners.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### Fix 1: Dockerfile — copy only manifests before `npm ci`
|
||||||
|
|
||||||
|
**File:** `Dockerfile`
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM node:20-alpine AS development-dependencies-env
|
||||||
|
COPY package.json package-lock.json .npmrc /app/ # only manifests
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
FROM node:20-alpine AS production-dependencies-env
|
||||||
|
COPY ./package.json package-lock.json .npmrc /app/
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
FROM node:20-alpine AS build-env
|
||||||
|
COPY . /app/
|
||||||
|
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20-alpine
|
||||||
|
COPY ./package.json package-lock.json /app/
|
||||||
|
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||||
|
COPY --from=build-env /app/build /app/build
|
||||||
|
COPY --from=build-env /app/dist /app/dist
|
||||||
|
COPY ./drizzle /app/drizzle
|
||||||
|
COPY ./scripts /app/scripts
|
||||||
|
COPY ./instrument.server.mjs /app/instrument.server.mjs
|
||||||
|
WORKDIR /app
|
||||||
|
CMD ["npm", "run", "start"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix 2: Declare platform in build-push-action
|
||||||
|
|
||||||
|
**File:** `.forgejo/workflows/deploy.yml` — the `build` job's "Build and Push" step
|
||||||
|
|
||||||
|
Add `platforms: linux/amd64`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: 🗄️ Build and Push to Container Registry
|
||||||
|
uses: https://github.com/docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ vars.CONTAINER_REGISTRY }}/brackt:latest
|
||||||
|
cache-from: type=registry,ref=${{ vars.CONTAINER_REGISTRY }}/brackt:buildcache
|
||||||
|
cache-to: type=registry,ref=${{ vars.CONTAINER_REGISTRY }}/brackt:buildcache,mode=max
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix 3: Add npm cache to CI jobs
|
||||||
|
|
||||||
|
**File:** `.forgejo/workflows/deploy.yml` — `test`, `typecheck`, `lint` jobs
|
||||||
|
|
||||||
|
Add after checkout in each job:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: 📦 Cache node_modules
|
||||||
|
uses: https://github.com/actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-npm-
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: If Forgejo's runner doesn't support the cache action, this silently no-ops (harmless). An alternative is merging the three parallel jobs into one that installs once and runs all checks sequentially — unconditionally saves two `npm ci` calls.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected Results
|
||||||
|
|
||||||
|
`docker pull` on the server is incremental — it only downloads layers that changed since the last deploy. After the Dockerfile fix, most deploys only pull the small build-output layer instead of the full node_modules.
|
||||||
|
|
||||||
|
| Step | Before | After (code change) | After (dep change) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Setup buildx | ~5 min | ~30 sec | ~30 sec |
|
||||||
|
| Build + push | ~5 min | ~1-2 min | ~3-4 min |
|
||||||
|
| Deploy (docker pull + up) | ~5 min | ~1-2 min | ~3-4 min |
|
||||||
|
| **Total** | **~15 min** | **~3-5 min** | **~8-10 min** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. Push a code-only commit (no package.json change) — confirm Docker build uses registry cache for npm install layers and the build step completes in under 2 minutes
|
||||||
|
2. Push a commit changing package.json — confirm npm cache is correctly invalidated and reruns
|
||||||
|
3. Check the `setup-buildx-action` log — should show ~30s, no QEMU download
|
||||||
|
4. Check deploy job — `docker compose pull` should show most layers as `Already exists`
|
||||||
|
5. Confirm deployed app is functional
|
||||||
Loading…
Add table
Reference in a new issue