#!/usr/bin/env bash
# Seeds a fresh data dir with content for visual review of every view.
#
#   scripts/seed-design.sh            # creates ./data-test-design and seeds it
#   DATA_DIR=data-test-design PORT=3999 ./hearthforge
#
# Admin login: admin / designdesign. Second user: alice / designdesign.
set -euo pipefail

ROOT=$(cd "$(dirname "$0")/.." && pwd)
DATA_DIR=${DATA_DIR:-$ROOT/data-test-design}
PORT=${PORT:-3999}
BASE=http://localhost:$PORT
BIN=$ROOT/hearthforge
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

export GIT_AUTHOR_NAME="Alice Example" GIT_AUTHOR_EMAIL=alice@example.com
export GIT_COMMITTER_NAME="Alice Example" GIT_COMMITTER_EMAIL=alice@example.com
export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null

rm -rf "$DATA_DIR"
mkdir -p "$DATA_DIR/repos"
(cd "$ROOT" && go build -o hearthforge ./cmd/hearthforge)
DATA_DIR=$DATA_DIR ADMIN_PASSWORD=designdesign "$BIN" init

# ---------- git repos on disk ----------
bare() { git init -q --bare -b main "$DATA_DIR/repos/$1.git"; }
work() { rm -rf "$TMP/w"; git init -q -b main "$TMP/w"; cd "$TMP/w"; }
push() { git push -q "$DATA_DIR/repos/$1.git" --all && git push -q "$DATA_DIR/repos/$1.git" --tags; }
commit() { git add -A && GIT_AUTHOR_DATE="$2" GIT_COMMITTER_DATE="$2" git commit -q -m "$1"; }

# website: the main showcase repo.
bare website
work
mkdir -p src/components src/lib/deeply/nested/directory/structure docs assets
cat > README.md <<'EOF'
# Website

A small static site generator used as the demo project for **Hearthforge**.

## Features

- Renders Markdown to HTML with `goldmark`
- Live reload during development
- Zero configuration for the common case

## Quick start

```sh
git clone http://localhost:3999/website.git
cd website
make serve
```

| Command | Description |
|---------|-------------|
| `make build` | Build the site into `dist/` |
| `make serve` | Serve with live reload on port 8080 |
| `make clean` | Remove build output |

> [!NOTE]
> This is demo content. The commands do nothing.

### Task list

- [x] Markdown rendering
- [x] Syntax highlighting
- [ ] Search
- [ ] RSS feed

A very long line without any spaces to test overflow handling: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

![Logo](assets/logo.png)

1. First step
2. Second step
   - nested item
   - another nested item
3. Third step

Inline `code`, *emphasis*, **strong**, ~~strikethrough~~, and a [link](https://example.com).

---

Footer text.
EOF
cat > Makefile <<'EOF'
.PHONY: build serve clean

build:
	go run ./cmd/site build

serve:
	go run ./cmd/site serve --port 8080

clean:
	rm -rf dist
EOF
cat > src/main.go <<'EOF'
// Package main is the entry point of the demo site generator.
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
)

// Page is one rendered page.
type Page struct {
	Title   string
	Slug    string
	Content string
}

func main() {
	out := flag.String("out", "dist", "output directory")
	flag.Parse()

	pages, err := loadPages("content")
	if err != nil {
		log.Fatal(err)
	}
	for _, p := range pages {
		dst := filepath.Join(*out, p.Slug+".html")
		if err := os.WriteFile(dst, []byte(render(p)), 0o644); err != nil {
			log.Fatal(err)
		}
		fmt.Println("wrote", dst)
	}
}

func loadPages(dir string) ([]Page, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}
	var pages []Page
	for _, e := range entries {
		if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
			continue
		}
		b, err := os.ReadFile(filepath.Join(dir, e.Name()))
		if err != nil {
			return nil, err
		}
		pages = append(pages, Page{
			Title:   strings.TrimSuffix(e.Name(), ".md"),
			Slug:    strings.TrimSuffix(e.Name(), ".md"),
			Content: string(b),
		})
	}
	return pages, nil
}

func render(p Page) string {
	return "<!doctype html><title>" + p.Title + "</title><main>" + p.Content + "</main>"
}
EOF
cat > src/components/header.js <<'EOF'
export function Header({ title, links = [] }) {
  const nav = links.map((l) => `<a href="${l.href}">${l.label}</a>`).join("");
  return `<header class="site-header"><h1>${title}</h1><nav>${nav}</nav></header>`;
}

// A very long line to test horizontal scrolling in the blob view: const veryLongVariableName = someFunction(anotherFunction(yetAnotherFunction(withManyArguments, andMoreArguments, andEvenMoreArguments, untilTheLineIsReallyLong)));
EOF
cat > src/components/styles.css <<'EOF'
:root {
  --bg: #fff;
  --fg: #111;
}
.site-header {
  display: flex;
  justify-content: space-between;
  padding: 1rem;
}
EOF
echo 'package deep' > src/lib/deeply/nested/directory/structure/deep.go
cat > docs/CONTRIBUTING.md <<'EOF'
# Contributing

Send patches through the Patches tab. Sign your commits.
EOF
touch "docs/a file name with spaces and a rather long title that keeps going.md"
printf 'this-is-a-very-long-file-name-without-spaces-that-should-not-break-the-tree-layout-when-it-is-rendered.txt\n' > "this-is-a-very-long-file-name-without-spaces-that-should-not-break-the-tree-layout-when-it-is-rendered.txt"
cp "$ROOT/web/static/assets/favicon.svg" assets/logo.svg
cp "$ROOT/tests/data-test-13/avatars/1.png" assets/logo.png 2>/dev/null || cp "$ROOT/web/static/assets/favicon.svg" assets/logo.png
head -c 4096 /dev/urandom > assets/blob.bin
printf 'root = true\n\n[*]\nindent_style = tab\n' > .editorconfig
commit "Initial commit" "2025-11-02T10:00:00+01:00"

for i in $(seq 1 28); do
  echo "line $i" >> docs/CHANGELOG.md
  commit "Update changelog, entry $i

This is the body of commit $i. It explains the reasoning in more detail
and wraps over several lines so the commit view has something to show.

Signed-off-by: Alice Example <alice@example.com>" "2025-11-$(printf %02d $(( (i % 27) + 2 )))T1$((i % 10)):00:00+01:00"
done
cat > src/lib/render.go <<'EOF'
package lib

// Render turns Markdown into HTML.
func Render(md string) string {
	return "<p>" + md + "</p>"
}
EOF
commit "Add render helper with a subject line that is deliberately very long so that truncation and wrapping in the commit list can be checked" "2026-01-05T09:30:00+01:00"
git tag -a v0.1.0 -m "First tagged release"
sed -i 's/<p>/<article>/; s/<\/p>/<\/article>/' src/lib/render.go
commit "render: use article element" "2026-02-10T14:00:00+01:00"
git tag v0.2.0
git tag -a v1.0.0-rc.1 -m "Release candidate"
git checkout -q -b feature/search
mkdir -p src/search
cat > src/search/index.go <<'EOF'
package search

// Index is a naive full text index.
type Index map[string][]string

// Add indexes one document.
func (i Index) Add(slug, text string) {
	i[slug] = append(i[slug], text)
}
EOF
commit "search: add naive index" "2026-03-01T11:00:00+01:00"
git checkout -q main
git checkout -q -b fix/typo-in-readme
sed -i 's/Zero configuration/Zero-configuration/' README.md
commit "readme: hyphenate zero-configuration" "2026-03-03T08:00:00+01:00"
git checkout -q main
git checkout -q -b a-branch-with-a-very-long-name-that-should-be-truncated-in-the-selector
echo x > x.txt
commit "long branch" "2026-03-04T08:00:00+01:00"
git checkout -q main
push website

# Patch files from the branches.
git format-patch -q -1 feature/search --stdout > "$TMP/search.patch"
git format-patch -q -1 fix/typo-in-readme --stdout > "$TMP/typo.patch"
# A conflicting patch: edit a line that main changed after the base.
git checkout -q -b conflict HEAD~1
sed -i 's/Package main is/Package main was/' src/main.go
commit "main: change package doc" "2026-03-05T08:00:00+01:00"
git format-patch -q -1 --stdout > "$TMP/conflict.patch"
git checkout -q main
sed -i 's/Package main is/Package main IS/' src/main.go
commit "main: shout package doc" "2026-03-06T08:00:00+01:00"
push website

# Other repos.
bare empty-repo

bare the-quick-brown-fox-jumps-over-the-lazy-dog-repository-with-a-very-long-name
work
echo '# Long name' > README.md
commit "init" "2026-01-01T00:00:00+00:00"
push the-quick-brown-fox-jumps-over-the-lazy-dog-repository-with-a-very-long-name

bare no-readme
work
echo 'print("hi")' > main.py
commit "add main.py" "2026-01-02T00:00:00+00:00"
push no-readme

bare private-notes
work
printf '# Private\n\nSecret notes.\n' > README.md
commit "init" "2026-01-03T00:00:00+00:00"
push private-notes

for i in $(seq 1 12); do
  bare "sample-$(printf %02d $i)"
  work
  printf '# sample-%02d\n' "$i" > README.md
  commit "init" "2025-0$(( (i % 9) + 1 ))-1${i:0:1}T00:00:00+00:00"
  push "sample-$(printf %02d $i)"
done
cd "$ROOT"

# ---------- server + forms ----------
DATA_DIR=$DATA_DIR PORT=$PORT "$BIN" >"$TMP/server.log" 2>&1 &
SERVER=$!
trap 'kill $SERVER 2>/dev/null; rm -rf "$TMP"' EXIT
for _ in $(seq 1 50); do curl -sf "$BASE/health" >/dev/null && break; sleep 0.2; done

J=$TMP/admin.jar
A=$TMP/alice.jar
post() { # jar path data...
  local jar=$1 path=$2; shift 2
  curl -sS -X POST -o /dev/null -w '%{http_code} %{redirect_url}\n' -b "$jar" -c "$jar" "$BASE$path" "$@" | sed "s|^|POST $path -> |"
}

post "$A" /register --data-urlencode username=alice --data-urlencode password=designdesign --data-urlencode password2=designdesign
post "$J" /login --data-urlencode username=admin --data-urlencode password=designdesign
post "$A" /login --data-urlencode username=alice --data-urlencode password=designdesign

# Repo settings.
post "$J" /website/settings --data-urlencode "description=Static site generator used as the Hearthforge demo project. This description is long enough to wrap onto a second line on narrow screens." --data-urlencode is_pinned=1 --data-urlencode allow_user_labels=1 --data-urlencode default_branch=main \
  --data-urlencode $'issue_template=## Steps to reproduce\n\n1.\n2.\n\n## Expected\n\n## Actual\n' \
  --data-urlencode $'patch_template=## Summary\n\n## Testing\n'
post "$J" /private-notes/settings --data-urlencode "description=Only visible to the admin." --data-urlencode is_private=1 --data-urlencode default_branch=main
post "$J" /no-readme/settings --data-urlencode "description=A repo without a README." --data-urlencode default_branch=main
post "$J" /sample-03/settings --data-urlencode is_pinned=1 --data-urlencode default_branch=main

# Labels.
for l in "bug #d73a4a" "enhancement #a2eeef" "documentation #0075ca" "good first issue #7057ff" "wontfix #ffffff" "help wanted #008672" "a-very-long-label-name-for-wrapping #e4e669" "dark #000000"; do
  post "$J" /website/settings/labels --data-urlencode "name=${l% *}" --data-urlencode "color=${l##* }"
done

# CI secret (settings page only shows names).
post "$J" /website/settings/ci-secrets --data-urlencode name=DEPLOY_TOKEN --data-urlencode value=secret --data-urlencode "description=Token used by the deploy step"

# Issues.
post "$A" /website/issues --data-urlencode "title=Search returns no results for multi-word queries" --data-urlencode $'body=## Steps to reproduce\n\n1. Open the search box\n2. Type `hello world`\n\n## Expected\n\nResults containing both words.\n\n## Actual\n\nNothing. Console shows:\n\n```\nTypeError: cannot read properties of undefined\n    at search (index.js:42)\n```\n\n- [x] reproduced on main\n- [ ] reproduced on v0.2.0\n\n| Browser | Result |\n|---|---|\n| Firefox | broken |\n| Chromium | broken |\n\n> Quoting a previous comment here.\n\nA very long unbreakable token: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' --data-urlencode label_ids=1 --data-urlencode label_ids=6
post "$J" /website/issues --data-urlencode "title=Add RSS feed" --data-urlencode "body=Would be nice to have an Atom or RSS feed for the blog section." --data-urlencode label_ids=2
post "$A" /website/issues --data-urlencode "title=Typo in CONTRIBUTING.md" --data-urlencode "body=" --data-urlencode label_ids=3 --data-urlencode label_ids=4
post "$J" /website/issues --data-urlencode "title=This issue has an extremely long title that goes on and on and on to test how the header wraps when there is a lot of text in it and it just keeps going" --data-urlencode "body=Body." --data-urlencode label_ids=7 --data-urlencode label_ids=8 --data-urlencode label_ids=5
post "$A" /website/issues --data-urlencode "title=Live reload does not pick up new files" --data-urlencode "body=New files under content/ are ignored until restart."
for i in $(seq 6 24); do
  post "$J" /website/issues --data-urlencode "title=Filler issue number $i for pagination" --data-urlencode "body=Filler." >/dev/null
done
post "$J" /website/issues/3/close
post "$J" /website/issues/5/complete
post "$J" /website/issues/7/close
post "$J" /website/issues/8/complete

# Comments and reactions on issue 1.
post "$J" /website/issues/1/comments --data-urlencode $'body=Thanks for the report. I can reproduce this.\n\nThe tokenizer splits on whitespace but the query builder joins with `AND` incorrectly.'
post "$A" /website/issues/1/comments --data-urlencode "body=Happy to send a patch if you point me at the right file."
post "$J" /website/issues/1/comments --data-urlencode "body=src/search/index.go, function Query."

post "$A" /website/issues/1/comments/2/edit --data-urlencode "edit_body=Happy to send a patch if you point me at the right file. Edited: sent as patch 1."
post "$J" /website/issues/1/react --data-urlencode "emoji=👍"
post "$A" /website/issues/1/react --data-urlencode "emoji=👍"
post "$A" /website/issues/1/react --data-urlencode "emoji=🎉"
post "$J" /website/issues/1/react --data-urlencode "emoji=👀" --data-urlencode comment_id=2
post "$A" /website/issues/1/react --data-urlencode "emoji=❤️" --data-urlencode comment_id=2
post "$A" /website/issues/1/react --data-urlencode "emoji=🚀" --data-urlencode comment_id=2
post "$J" /website/issues/2/comments --data-urlencode "body=Short."

# Patches.
post "$A" /website/patches -F title="search: add naive index" -F $'description=## Summary\n\nAdds a first, naive full text index.\n\n## Testing\n\n`go test ./...`' -F patch_file=@"$TMP/search.patch" -F label_ids=2
post "$A" /website/patches -F title="readme: hyphenate zero-configuration" -F description="Tiny wording fix." -F patch_file=@"$TMP/typo.patch" -F label_ids=3
post "$A" /website/patches -F title="main: change package doc (conflicts with main)" -F description="This one should fail to apply." -F patch_file=@"$TMP/conflict.patch"
post "$A" /website/patches -F title="Closed without merging" -F description="" -F patch_file=@"$TMP/typo.patch"
post "$A" /website/patches -F title="A patch with a very long title that keeps going well past the width of the page in order to exercise wrapping in the header row" -F description="" -F patch_file=@"$TMP/search.patch"
for i in $(seq 6 22); do
  post "$A" /website/patches -F title="Filler patch $i" -F description="" -F patch_file=@"$TMP/typo.patch" >/dev/null
done
post "$J" /website/patches/1/comments --data-urlencode "body=Looks good. Merging."
post "$A" /website/patches/1/react --data-urlencode "emoji=🚀"
VERSION=$(curl -s -b "$J" "$BASE/website/patches/2" | grep -o 'name="version" value="[^"]*"' | head -1 | sed 's/.*value="//; s/"//')
post "$J" /website/patches/2/merge --data-urlencode "version=$VERSION"
post "$J" /website/patches/4/close

# Releases.
printf 'binary asset content\n' > "$TMP/website-linux-amd64"
printf 'checksums\n' > "$TMP/SHA256SUMS.txt"
head -c 300000 /dev/urandom > "$TMP/website-1.0.0-rc.1-darwin-arm64.tar.gz"
post "$J" /website/releases -F name="v0.1.0 – First release" -F tag_name=v0.1.0 -F $'notes=## Highlights\n\n- Markdown rendering\n- Live reload\n\n## Breaking changes\n\nNone.\n\n## Full changelog\n\n'"$(for i in $(seq 1 12); do printf -- '- Change number %d with some explanatory text\n' "$i"; done)" -F include_source_code=on -F files=@"$TMP/website-linux-amd64" -F files=@"$TMP/SHA256SUMS.txt"
post "$J" /website/releases -F name="v0.2.0" -F tag_name=v0.2.0 -F notes="Small release. No assets."
post "$J" /website/releases -F name="1.0.0 release candidate 1 with a long name that should wrap somewhere sensible" -F tag_name=v1.0.0-rc.1 -F notes="" -F include_source_code=on -F files=@"$TMP/website-1.0.0-rc.1-darwin-arm64.tar.gz"
post "$J" /website/releases -F name="Nightly build" -F create_tag=on -F tag_name=nightly-2026-03-06 -F revision=main -F notes="Automated nightly build."

# SSH keys for the admin.
ssh-keygen -q -t ed25519 -N '' -C 'laptop' -f "$TMP/k1"
ssh-keygen -q -t ed25519 -N '' -C 'a-key-with-a-very-long-comment-for-wrapping-tests@workstation.example.internal' -f "$TMP/k2"
post "$J" /settings/ssh-keys --data-urlencode name="Laptop" --data-urlencode "public_key=$(cat "$TMP/k1.pub")"
post "$J" /settings/ssh-keys --data-urlencode name="Workstation with a considerably longer key name" --data-urlencode "public_key=$(cat "$TMP/k2.pub")"

# Avatar for alice.
curl -sS -o /dev/null -b "$A" -c "$A" "$BASE/settings/avatar" -F avatar=@"$ROOT/tests/data-test-13/avatars/1.png" || true

kill $SERVER; wait $SERVER 2>/dev/null || true

# ---------- rows the forms cannot create ----------
DB=$DATA_DIR/hearthforge.db
sqlite3 "$DB" <<'SQL'
INSERT INTO users (username, password_hash, created_at, is_pending, register_application) VALUES
  ('pending-bob', NULL, '2026-03-01T10:00:00Z', 1, 'I work with Alice on the website and would like to send patches.'),
  ('pending-carol', NULL, '2026-03-02T11:00:00Z', 1, 'A much longer application text. It goes on for a while to test how the queue item wraps. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'),
  ('pending-dave', NULL, '2026-03-03T12:00:00Z', 1, '');

INSERT INTO ci_run_counters (repo_id, last_run_id) SELECT id, 6 FROM repositories WHERE name='website';
INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id)
SELECT id, 1, 'push', 'a1b2c3d4e5f60718293a4b5c6d7e8f9012345678', 'main', 'success', '2026-03-06T08:00:05Z', '2026-03-06T08:03:41Z', '2026-03-06T08:00:00Z', 1 FROM repositories WHERE name='website';
INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id)
SELECT id, 1, 'push', 'b2c3d4e5f60718293a4b5c6d7e8f901234567890', 'feature/search', 'failure', '2026-03-06T09:00:05Z', '2026-03-06T09:01:12Z', '2026-03-06T09:00:00Z', 2 FROM repositories WHERE name='website';
INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_tag, status, started_at, finished_at, created_at, repo_run_id, variable_overrides)
SELECT id, 1, 'manual', 'c3d4e5f60718293a4b5c6d7e8f90123456789012', 'v0.2.0', 'cancelled', '2026-03-06T10:00:05Z', '2026-03-06T10:00:30Z', '2026-03-06T10:00:00Z', 3, '{"TARGET":"staging","VERBOSE":"1"}' FROM repositories WHERE name='website';
INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, created_at, repo_run_id)
SELECT id, 1, 'push', 'd4e5f60718293a4b5c6d7e8f9012345678901234', 'main', 'running', '2026-03-06T11:00:05Z', '2026-03-06T11:00:00Z', 4 FROM repositories WHERE name='website';
INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, created_at, repo_run_id)
SELECT id, 1, 'push', 'e5f60718293a4b5c6d7e8f90123456789012345a', 'main', 'queued', '2026-03-06T11:30:00Z', 5 FROM repositories WHERE name='website';
INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id)
SELECT id, 1, 'push', 'f60718293a4b5c6d7e8f90123456789012345abc', 'main', 'warning', '2026-03-05T08:00:05Z', '2026-03-05T08:02:00Z', '2026-03-05T08:00:00Z', 6 FROM repositories WHERE name='website';

INSERT INTO ci_steps (run_id, name, status, exit_code, started_at, finished_at, log) VALUES
  (1, 'tools', 'success', 0, '2026-03-06T08:00:05Z', '2026-03-06T08:00:40Z', 'go: downloading mvdan.cc/gofumpt v0.8.0
go: downloading github.com/golangci/golangci-lint/v2 v2.1.0
installed'),
  (1, 'test', 'success', 0, '2026-03-06T08:00:40Z', '2026-03-06T08:03:00Z', 'ok  	website/src	0.412s
ok  	website/src/lib	0.008s
ok  	website/src/search	0.031s'),
  (1, 'build', 'success', 0, '2026-03-06T08:03:00Z', '2026-03-06T08:03:41Z', 'wrote dist/index.html
wrote dist/about.html
A very long log line that should scroll or wrap: ' || hex(randomblob(200))),
  (2, 'tools', 'success', 0, '2026-03-06T09:00:05Z', '2026-03-06T09:00:30Z', 'installed'),
  (2, 'test', 'failure', 1, '2026-03-06T09:00:30Z', '2026-03-06T09:01:12Z', '--- FAIL: TestIndex (0.00s)
    index_test.go:12: expected 2 results, got 0
FAIL
FAIL	website/src/search	0.004s'),
  (2, 'build', 'skipped', NULL, NULL, NULL, ''),
  (3, 'tools', 'success', 0, '2026-03-06T10:00:05Z', '2026-03-06T10:00:20Z', 'installed'),
  (3, 'test', 'cancelled', NULL, '2026-03-06T10:00:20Z', '2026-03-06T10:00:30Z', 'ok  	website/src	0.4s'),
  (3, 'build', 'skipped', NULL, NULL, NULL, ''),
  (4, 'tools', 'success', 0, '2026-03-06T11:00:05Z', '2026-03-06T11:00:20Z', 'installed'),
  (4, 'test', 'running', NULL, '2026-03-06T11:00:20Z', NULL, '=== RUN   TestRender
'),
  (4, 'build', 'pending', NULL, NULL, NULL, ''),
  (5, 'tools', 'pending', NULL, NULL, NULL, ''),
  (5, 'test', 'pending', NULL, NULL, NULL, ''),
  (6, 'vulncheck', 'warning', 1, '2026-03-05T08:00:05Z', '2026-03-05T08:02:00Z', 'Vulnerability #1: GO-2026-0001
  stdlib: something
');
INSERT INTO ci_artifacts (run_id, filename, size) VALUES (1, 'website', 8421376), (1, 'dist.tar.gz', 120333);
SQL
mkdir -p "$DATA_DIR/ci/artifacts/1"
printf 'artifact\n' > "$DATA_DIR/ci/artifacts/1/website"
printf 'artifact\n' > "$DATA_DIR/ci/artifacts/1/dist.tar.gz"

echo
echo "Seeded $DATA_DIR. Start with:"
echo "  DATA_DIR=$DATA_DIR PORT=$PORT ./hearthforge"
echo "Login: admin / designdesign, alice / designdesign"
