Project updates from Template #1

Merged
javier merged 123 commits from project/update-from-template into main 2026-09-04 13:40:36 +00:00
6 changed files with 43 additions and 19 deletions
Showing only changes of commit 6c75d5e743 - Show all commits
+1 -1
View File
@@ -10,7 +10,7 @@
**/.build **/.build
**/.swiftpm **/.swiftpm
# Test sources # Local database data directory.
**/Tests/DB **/Tests/DB
# Xcode project (not used by the Linux build) # Xcode project (not used by the Linux build)
+1 -1
View File
@@ -1,5 +1,5 @@
# Template root Makefile — bootstrap convenience only. # Template root Makefile — bootstrap convenience only.
# This file is removed by `Tools/bootstrap` once the template is generated. # This file is removed by `Scripts/bootstrap` once the template is generated.
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
+9 -5
View File
@@ -53,9 +53,9 @@ make bootstrap # or: ./Scripts/bootstrap
| Prompt | Example | Rewrites | | Prompt | Example | Rewrites |
| --- | --- | --- | | --- | --- | --- |
| **Site display name** (PascalCase) | `Loud` | Server name (`LoudWebsite`), `Loud.xcodeproj`, README title. | | **Site display name** (PascalCase) | `Acme` | Server name (`AcmeWebsite`), `Acme.xcodeproj`, README title. |
| **Project slug** (lowercase) | `loud-ams` | Container owner, database name/user, Compose project name. | | **Project slug** (lowercase) | `acme-web` | Container owner, database name/user, Compose project name. |
| **Canonical site URL** (scheme + host) | `https://loud.amsterdam` | The `Sitemap:` reference in `robots.txt` and the `<loc>` entry in `sitemap.xml`. | | **Canonical site URL** (scheme + host) | `https://acme.example.com` | The `Sitemap:` reference in `robots.txt`, the `<loc>` entry in `sitemap.xml`, and `String.Site.origin`. |
- The URL defaults to the reserved `https://site.example.com`; leaving it is - The URL defaults to the reserved `https://site.example.com`; leaving it is
allowed (the script warns) and can be corrected later in the two crawler files. allowed (the script warns) and can be corrected later in the two crawler files.
@@ -84,10 +84,14 @@ make bootstrap # or: ./Scripts/bootstrap
set `ANALYTICS_WEBSITE_ID`. `ANALYTICS_DOMAINS` limits which hosts report; set `ANALYTICS_WEBSITE_ID`. `ANALYTICS_DOMAINS` limits which hosts report;
`ANALYTICS_RECORDER` opts into the session recorder. See `ANALYTICS_RECORDER` opts into the session recorder. See
[Analytics](Services/Website/README.md#analytics). [Analytics](Services/Website/README.md#analytics).
5. **Secrets** — set a real `DATABASE_PASSWORD` in a git-ignored 5. **Site origin** — bootstrap writes the canonical URL into `String.Site.origin`
(override with `SITE_ORIGIN`), leaving it empty for the placeholder. An empty
or non-HTTPS origin disables the HTTPS redirect, which
`HTTPS_TRUST_FORWARDED_PROTO` must enable besides.
6. **Secrets** — set a real `DATABASE_PASSWORD` in a git-ignored
`Services/Website/.env`; the committed `.env.local` is an example and defaults `Services/Website/.env`; the committed `.env.local` is an example and defaults
the password to the slug. the password to the slug.
6. **Domain models** — replace the `Persistence` package's sample `ExampleRecord` 7. **Domain models** — replace the `Persistence` package's sample `ExampleRecord`
/ `ExampleRepository`. The default driver is in-memory SQLite, migrated on / `ExampleRepository`. The default driver is in-memory SQLite, migrated on
every boot; a real site sets `DATABASE_DRIVER=postgres` and migrates out of every boot; a real site sets `DATABASE_DRIVER=postgres` and migrates out of
band (`swift run Website --database-migrate`). See band (`swift run Website --database-migrate`). See
+26 -7
View File
@@ -31,7 +31,7 @@ PLACEHOLDER_URL_PATTERN='https://site\.example\.com'
# --- Prompts ------------------------------------------------------------------ # --- Prompts ------------------------------------------------------------------
printf 'Site display name (PascalCase, e.g. Berlin) [Site]: ' printf 'Site display name (PascalCase, e.g. Acme) [Site]: '
read -r NAME read -r NAME
NAME="${NAME:-Site}" NAME="${NAME:-Site}"
@@ -54,7 +54,7 @@ case "$NAME" in
esac esac
DEFAULT_SLUG="$(printf '%s' "$NAME" | tr '[:upper:]' '[:lower:]')" DEFAULT_SLUG="$(printf '%s' "$NAME" | tr '[:upper:]' '[:lower:]')"
printf 'Project slug (lowercase, used for owner/db/compose, e.g. loud-berlin) [%s]: ' "$DEFAULT_SLUG" printf 'Project slug (lowercase, used for owner/db/compose, e.g. acme-web) [%s]: ' "$DEFAULT_SLUG"
read -r SLUG read -r SLUG
SLUG="${SLUG:-$DEFAULT_SLUG}" SLUG="${SLUG:-$DEFAULT_SLUG}"
@@ -75,7 +75,7 @@ case "$SLUG" in
;; ;;
esac esac
printf 'Canonical site URL (scheme + host, e.g. https://berlin.example.com) [%s]: ' "$PLACEHOLDER_URL" printf 'Canonical site URL (scheme + host, e.g. https://acme.example.com) [%s]: ' "$PLACEHOLDER_URL"
read -r URL read -r URL
URL="${URL:-$PLACEHOLDER_URL}" URL="${URL:-$PLACEHOLDER_URL}"
URL="${URL%/}" # the rewrites append their own path, so drop a trailing slash URL="${URL%/}" # the rewrites append their own path, so drop a trailing slash
@@ -99,7 +99,7 @@ esac
echo echo
echo " display name : $NAME (server name \"${NAME}Website\", ${NAME}.xcodeproj)" echo " display name : $NAME (server name \"${NAME}Website\", ${NAME}.xcodeproj)"
echo " slug : $SLUG (container owner, database name/user, compose project)" echo " slug : $SLUG (container owner, database name/user, compose project)"
echo " canonical URL: $URL (robots.txt sitemap reference, sitemap.xml entry)" echo " canonical URL: $URL (robots.txt, sitemap.xml, String.Site.origin)"
echo echo
printf 'Apply these values? [y/N]: ' printf 'Apply these values? [y/N]: '
read -r CONFIRM read -r CONFIRM
@@ -136,6 +136,13 @@ rewrite "$W/Sources/Library/Public/Extensions/String+Constants.swift" \
-e "s/SiteWebsite/${NAME}Website/g" \ -e "s/SiteWebsite/${NAME}Website/g" \
-e "s/= \"site\"/= \"${SLUG}\"/g" -e "s/= \"site\"/= \"${SLUG}\"/g"
# Canonical origin. Left empty for the placeholder, which keeps the HTTPS
# redirect off rather than aimed at a reserved example domain.
if [ "$URL" != "$PLACEHOLDER_URL" ]; then
rewrite "$W/Sources/Library/Public/Extensions/String+Constants.swift" \
-e "s|^ public static let origin = \"\"\$| public static let origin = \"${URL}\"|"
fi
# Local env example. # Local env example.
rewrite "$W/.env.local" \ rewrite "$W/.env.local" \
-e "s/SiteWebsite/${NAME}Website/g" \ -e "s/SiteWebsite/${NAME}Website/g" \
@@ -162,6 +169,17 @@ rewrite "$W/docker-compose.override.yml" \
rewrite "$W/Makefile" \ rewrite "$W/Makefile" \
-e "s/),site)/),${SLUG})/g" -e "s/),site)/),${SLUG})/g"
# Web app manifest: the identity shown by an install prompt.
rewrite "$W/Resources/Static/site.webmanifest" \
-e "s/^ \"short_name\": \"\"/ \"short_name\": \"${NAME}\"/" \
-e "s/^ \"name\": \"\"/ \"name\": \"${NAME}\"/"
# Persistence integration test: its POSTGRES_TEST_* fallbacks address the local
# container `make db-mount` creates, which is initialised from the slug.
rewrite "Packages/Persistence/Tests/Cases/Public/Methods/ServiceTests.swift" \
-e "s/?? \"site\"/?? \"${SLUG}\"/g" \
-e "s/: \"site\",\$/: \"${SLUG}\",/"
# Crawler files: the absolute origin they must carry. `|` is the sed delimiter, # Crawler files: the absolute origin they must carry. `|` is the sed delimiter,
# since both the placeholder and the replacement contain slashes. # since both the placeholder and the replacement contain slashes.
rewrite "$W/Resources/Static/robots.txt" \ rewrite "$W/Resources/Static/robots.txt" \
@@ -175,7 +193,9 @@ rewrite "$W/README.md" \
-e "s/^# Site Website\$/# ${NAME} Website/" \ -e "s/^# Site Website\$/# ${NAME} Website/" \
-e "s/\*\*Site\*\*/**${NAME}**/" \ -e "s/\*\*Site\*\*/**${NAME}**/" \
-e "s/SiteWebsite/${NAME}Website/g" \ -e "s/SiteWebsite/${NAME}Website/g" \
-e "s/\`site\`/\`${SLUG}\`/g" -e "s/\`site\`/\`${SLUG}\`/g" \
-e "s/\`Site\.xcodeproj\`/\`${NAME}.xcodeproj\`/g" \
-e "s|^\`robots.txt\` and \`sitemap.xml\` need an absolute origin.*\$|\`robots.txt\` and \`sitemap.xml\` carry the absolute origin bootstrap wrote into them: ${URL}. Update both if the canonical origin ever changes.|"
# Xcode project: PBXProject name + directory. # Xcode project: PBXProject name + directory.
if [ -d "$XCODEPROJ" ]; then if [ -d "$XCODEPROJ" ]; then
@@ -218,7 +238,6 @@ rm -f Scripts/bootstrap
rmdir Scripts 2>/dev/null || true rmdir Scripts 2>/dev/null || true
rm -f Makefile # root convenience Makefile (bootstrap only) rm -f Makefile # root convenience Makefile (bootstrap only)
rm -f README.md # template usage docs rm -f README.md # template usage docs
rm -rf .gitea # Gitea template metadata
# --- Done --------------------------------------------------------------------- # --- Done ---------------------------------------------------------------------
@@ -231,7 +250,7 @@ Next steps:
- $W/Sources/Library/Catalogs/Localizable.xcstrings (copy) - $W/Sources/Library/Catalogs/Localizable.xcstrings (copy)
- $W/Sources/Library (landing / 404 pages) - $W/Sources/Library (landing / 404 pages)
- $W/Resources/Static (css, js, favicon, icons) - $W/Resources/Static (css, js, favicon, icons)
- $W/Resources/Static/site.webmanifest (name / short_name) - $W/Resources/Static/site.webmanifest (theme colours)
2. Set a real database password in a git-ignored $W/.env 2. Set a real database password in a git-ignored $W/.env
(the committed .env.local defaults the password to the slug — do NOT ship that). (the committed .env.local defaults the password to the slug — do NOT ship that).
3. Analytics ships OFF, and stays off until you opt in. To enable it: check 3. Analytics ships OFF, and stays off until you opt in. To enable it: check
+2 -2
View File
@@ -134,9 +134,9 @@ every `GET`/`HEAD` whose path ends in a slash is answered with a `301` to the fo
### Site ### Site
| Config key | Environment variable | Default | Description | | Config key | Environment variable | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `site.origin` | `SITE_ORIGIN` | _(empty)_ | The public origin the site is served at (scheme and host, no trailing slash). The HTTPS redirect points at it, and absolute links derive from it. | | `site.origin` | `SITE_ORIGIN` | _(set by bootstrap)_ | The public origin the site is served at (scheme and host, no trailing slash). The HTTPS redirect points at it, and absolute links derive from it. |
Empty by default, which leaves the [HTTPS redirect](#https-redirect) off: a deployment that trusts the forwarded-protocol header without setting this would otherwise redirect at a host the template guessed, and a `301` is cached for a long time. Bootstrap does **not** fill it in — it rewrites the crawler files only. Bootstrap writes the canonical URL it prompts for here, leaving it empty for the placeholder. An empty or non-HTTPS origin disables the [HTTPS redirect](#https-redirect), which `https.trustForwardedProto` must enable besides — a `301` is cached for a long time, so it is never issued at a host nobody named.
### Logging ### Logging
| Config key | Environment variable | Default | Description | | Config key | Environment variable | Default | Description |
@@ -56,10 +56,11 @@ extension String {
} }
/// A namespace for the site string constants. /// A namespace for the site string constants.
public enum Site { public enum Site {
/// The default public origin the site is served at (scheme and host, no trailing slash): empty, which leaves the HTTPS redirect off. /// The default public origin the site is served at (scheme and host, no trailing slash).
/// ///
/// A deployment that trusts the forwarded-protocol header without also setting `site.origin` would otherwise redirect at a host the /// Bootstrap writes the canonical URL it prompts for here, leaving it empty for the placeholder. An empty or non-HTTPS origin disables
/// template guessed for it, and a `301` is cached for a long time. /// the HTTPS redirect, which `https.trustForwardedProto` must enable besides a `301` is cached for a long time, so it is never issued
/// at a host nobody named.
public static let origin = "" public static let origin = ""
} }
} }