Skip to content
Draft
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Only the inputs copied by Dockerfile belong in the build context.
**
!package.json
!package-lock.json
!.npmrc
!vite.config.ts
!tsconfig.json
!src/
!src/**
!static/
!static/**
!Dockerfile
!.dockerignore

# Keep secrets and generated/local files out even inside source directories.
**/.env
**/.env.*
**/*.pem
**/*.key
**/secrets.json
**/node_modules
**/.svelte-kit
**/build
**/*.sqlite.db*
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Vite loads ignored .env files. Built Node does not: inject these at startup
# or use node --env-file=.env build. Never pass private values as Docker build args.
# Server-only. Used only when SvelteKit server code calls the CMS API.
CMS_INTERNAL_ORIGIN=http://127.0.0.1:5099

Expand All @@ -7,6 +9,9 @@ CMS_INTERNAL_ORIGIN=http://127.0.0.1:5099

# Member login is disabled until CMS_MEMBER_BRIDGE_SECRET is set privately and matches
# backend SGFDevs__MemberBridge__Secret. Never commit a secret here.
# CMS_MEMBER_BRIDGE_SECRET=
# Set backend SGFDevs__MemberBridge__FrontendOrigin=http://127.0.0.1:3000
# for reset-email links. This is not a browser CORS allowlist.
# CMS_MEMBER_COOKIE_NAME defaults to .AspNetCore.Identity.Application.

# Server-only. Set these only when testing fictional local media through the same-origin proxy.
Expand All @@ -15,5 +20,15 @@ CMS_INTERNAL_ORIGIN=http://127.0.0.1:5099
# MEDIA_UPSTREAM_PATH_PREFIX=/media/
# MEDIA_SOURCE_PUBLIC_ORIGIN=https://media.sgf.dev

# Adapter-node request cap: 8 MiB avatar plus multipart overhead.
BODY_SIZE_LIMIT=9M

# Public build input. Adapter-node 6 uses paths.origin, not runtime ORIGIN.
# Export ORIGIN in the build shell; setting it only in .env/startup has no effect.
# ORIGIN=http://127.0.0.1:3000
# HOST=127.0.0.1
# PORT=3000
# Leave proxy trust headers unset. Build for the external origin behind a proxy.

# Local schema refresh only. npm run api:schema:fetch reads the public OpenAPI doc from this origin.
SGF_CMS_SCHEMA_ORIGIN=http://127.0.0.1:5099
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Frontend CI

on:
push:
pull_request:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
- run: npm run check
- run: npm run api:check
- run: npm run api:member:check
- run: npm run build
env:
ORIGIN: http://127.0.0.1:3000
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ORIGIN is public and fixed in the SvelteKit 3 build. Never pass CMS secrets as build args.
FROM node:24-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json .npmrc ./
RUN npm ci
COPY vite.config.ts tsconfig.json ./
COPY src ./src
COPY static ./static
ARG ORIGIN
RUN test -n "$ORIGIN" && npm run build

FROM node:24-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production HOST=0.0.0.0 PORT=3000 BODY_SIZE_LIMIT=9M
COPY package.json package-lock.json .npmrc ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build --chown=node:node /app/build ./build
USER node
EXPOSE 3000
CMD ["npm", "run", "serve"]
126 changes: 89 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,105 @@
# Springfield Devs website

This repository is for the public [sgf.dev](https://sgf.dev) frontend.
The Umbraco CMS and backend live in [cms.sgf.dev](https://github.com/sgfdevs/cms.sgf.dev).

This branch starts the SvelteKit frontend rewrite with a minimal Svelte 5 app and Tailwind CSS 4 wired through Vite. It does not include the final page migration, site theme, asset migration, generated API client, or live CMS integration yet.

## Requirements

- Node.js 22.17 or newer. Node 24 is supported.
- npm 11 or newer is recommended because the lockfile was generated with npm 11.

## Development

Install dependencies:

```sh
npm ci
```

Run the local dev server:
The public [sgf.dev](https://sgf.dev) frontend uses SvelteKit 3, Svelte 5 and
Tailwind CSS 4. The [Umbraco backend](https://github.com/sgfdevs/cms.sgf.dev)
runs separately. Page SSR, generated API clients, the same-origin media proxy
and member forms already call the CMS from frontend server code. Browsers do
not call the CMS member bridge directly.

## Requirements and configuration

Use Node.js 22.17 or newer. The container and CI use Node 24, matching the current
local runtime. npm 11 is recommended for the existing lockfile. Run `npm ci` once.
The tracked `vite.config.ts` contains the SvelteKit configuration and
`adapter-node`, including the existing runes policy and Tailwind plugin.
SvelteKit 3 does not need a separate `svelte.config.js` here. `tsconfig.json`,
`src/env.ts` and `.npmrc` are also tracked build inputs.

Copy `.env.example` to an ignored `.env` for Vite development. Supply real keys
privately, never in Git, client code or Docker build arguments:

- `CMS_INTERNAL_ORIGIN` is the server-only CMS origin, without a path.
- `CMS_DELIVERY_API_KEY` must match backend `Umbraco__CMS__DeliveryApi__ApiKey`.
Content-page loads require it even with native Delivery `PublicAccess=true`.
The native key can authorize preview content. Keep it private.
- `CMS_MEMBER_BRIDGE_SECRET` must match backend `SGFDevs__MemberBridge__Secret`.
Member calls fail closed without it. Match `CMS_MEMBER_COOKIE_NAME` only if
the CMS cookie name has been customized.
- Media proxy origins and path fences are private server settings. Point local
media upstreams only at fictional local media, not production storage.
- Backend `SGFDevs__MemberBridge__FrontendOrigin` must be the frontend origin,
for example `http://127.0.0.1:3000`, so reset emails link to Kit.

Native login, session, logout and emailed password reset passed a bounded local
native-CMS journey. That builtin-member-only runtime lacked legacy SGF properties.
It cannot exercise SGF registration, profile editing or avatar uploads correctly.
Those paths still need the reviewed SGF schema. Imports remain paused; this
packaging does not install, import or activate CMS schema or content.

## Split local development

In the CMS shell, use an existing ordinary Development installation and privately
supply the matching bridge secret and native Delivery key. Set the reset link
origin and start the CMS without its historical launch profile:

```sh
npm run dev
cd ../cms.sgf.dev
export ASPNETCORE_ENVIRONMENT=Development DOTNET_ENVIRONMENT=Development
export SGFDevs__MemberBridge__FrontendOrigin=http://127.0.0.1:3000
dotnet run --project SgfDevs --no-launch-profile --urls http://127.0.0.1:5099
```

Check Svelte and TypeScript:
This assumes that shell already has the private database, media and other CMS
settings for the owned installation. It is not a fresh-install recipe. See the
backend README for native SMTP/Mailpit settings. Do not mix those ordinary runtime
settings with sealed install-only bootstrap profiles.

```sh
npm run check
```

Build the production Node server:
In a separate frontend shell, with the ignored `.env` configured:

```sh
npm run build
npm run dev -- --host 127.0.0.1 --port 3000 --strictPort
```

Preview the built app with Vite:
Vite loads `.env` during development. `npm run preview` is a Vite preview, not the
adapter-node production server. `SGF_CMS_SCHEMA_ORIGIN` is only for explicit local
schema refreshes. CI freshness checks use committed snapshots, not a live CMS.

```sh
npm run preview
```

Run the adapter-node production server after a build:
## Built Node server

```sh
npm run serve
npm run check
ORIGIN=http://127.0.0.1:3000 npm run build
HOST=127.0.0.1 PORT=3000 BODY_SIZE_LIMIT=9M npm run serve
```

## Backend dependency

The frontend will later consume APIs from `cms.sgf.dev`. That API contract and generated OpenAPI client are not part of this layer.
Before `serve`, inject private CMS/media variables into the process environment
through your private environment loader or secret store. The built server does
not load `.env` automatically. With Node 24, an ignored private dotenv file can
also be loaded explicitly with `node --env-file=.env build` instead of `npm run serve`.
No CMS secret is needed to build the source.

This pinned adapter-node 6 uses SvelteKit `paths.origin`, not runtime `ORIGIN`.
Set the public `ORIGIN` at build time; `vite.config.ts` fixes the supplied origin
in the output. The Dockerfile refuses to build without it. A build without it
falls back to request-host inference and is not suitable for this setup. Rebuild for a different frontend origin. Setting `ORIGIN` only at
startup has no effect. For a reverse proxy, build with the external HTTPS origin,
not the internal Node listener address. Keep `PROTOCOL_HEADER`, `HOST_HEADER`,
`PORT_HEADER` and `ADDRESS_HEADER` unset. Do not trust arbitrary forwarded headers.
Restrict access to the listener behind the proxy and retain Kit's origin/CSRF
checks. `PUBLIC_SITE_ORIGIN` controls canonical metadata, not request trust.

Set `BODY_SIZE_LIMIT=9M` for the built server to allow the existing 8 MiB avatar
limit plus multipart overhead. Application validation still limits accepted files.

## Container and CI

`Dockerfile` builds Node output with the existing lockfile, then installs only
production dependencies in a non-root Node 24 runtime. Supply the public origin
with `--build-arg ORIGIN=https://www.sgf.dev`. Inject private settings at container
startup, not during image creation. Inside a container, `CMS_INTERNAL_ORIGIN`
must name a CMS address reachable from that container, not its own loopback.
The image defaults to port 3000 and `BODY_SIZE_LIMIT=9M`; it does not include a CMS.

`.dockerignore` permits only build inputs and excludes local secrets and output.
The CI workflow runs `npm ci`, check, committed public/member API freshness checks
and build. It does not deploy, use action secrets or build/push an image.
4 changes: 3 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export default defineConfig({
runes: ({ filename }) =>
filename.split(/[/\\]/).includes('node_modules') ? undefined : true
},
adapter: adapter()
adapter: adapter(),
// adapter-node 6 reads paths.origin, not the older runtime ORIGIN variable.
paths: process.env.ORIGIN ? { origin: process.env.ORIGIN } : undefined
})
]
});
Loading