seed-design.sh
⎇
Raw
1#!/usr/bin/env bash
2# Seeds a fresh data dir with content for visual review of every view.
3#
4# scripts/seed-design.sh # creates ./data-test-design and seeds it
5# DATA_DIR=data-test-design PORT=3999 ./hearthforge
6#
7# Admin login: admin / designdesign. Second user: alice / designdesign.
8set -euo pipefail
9
10ROOT=$(cd "$(dirname "$0")/.." && pwd)
11DATA_DIR=${DATA_DIR:-$ROOT/data-test-design}
12PORT=${PORT:-3999}
13BASE=http://localhost:$PORT
14BIN=$ROOT/hearthforge
15TMP=$(mktemp -d)
16trap 'rm -rf "$TMP"' EXIT
17
18export GIT_AUTHOR_NAME="Alice Example" GIT_AUTHOR_EMAIL=alice@example.com
19export GIT_COMMITTER_NAME="Alice Example" GIT_COMMITTER_EMAIL=alice@example.com
20export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
21
22rm -rf "$DATA_DIR"
23mkdir -p "$DATA_DIR/repos"
24(cd "$ROOT" && go build -o hearthforge ./cmd/hearthforge)
25DATA_DIR=$DATA_DIR ADMIN_PASSWORD=designdesign "$BIN" init
26
27# ---------- git repos on disk ----------
28bare() { git init -q --bare -b main "$DATA_DIR/repos/$1.git"; }
29work() { rm -rf "$TMP/w"; git init -q -b main "$TMP/w"; cd "$TMP/w"; }
30push() { git push -q "$DATA_DIR/repos/$1.git" --all && git push -q "$DATA_DIR/repos/$1.git" --tags; }
31commit() { git add -A && GIT_AUTHOR_DATE="$2" GIT_COMMITTER_DATE="$2" git commit -q -m "$1"; }
32
33# website: the main showcase repo.
34bare website
35work
36mkdir -p src/components src/lib/deeply/nested/directory/structure docs assets
37cat > README.md <<'EOF'
38# Website
39
40A small static site generator used as the demo project for **Hearthforge**.
41
42## Features
43
44- Renders Markdown to HTML with `goldmark`
45- Live reload during development
46- Zero configuration for the common case
47
48## Quick start
49
50```sh
51git clone http://localhost:3999/website.git
52cd website
53make serve
54```
55
56| Command | Description |
57|---------|-------------|
58| `make build` | Build the site into `dist/` |
59| `make serve` | Serve with live reload on port 8080 |
60| `make clean` | Remove build output |
61
62> [!NOTE]
63> This is demo content. The commands do nothing.
64
65### Task list
66
67- [x] Markdown rendering
68- [x] Syntax highlighting
69- [ ] Search
70- [ ] RSS feed
71
72A very long line without any spaces to test overflow handling: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
73
74![Logo](assets/logo.png)
75
761. First step
772. Second step
78 - nested item
79 - another nested item
803. Third step
81
82Inline `code`, *emphasis*, **strong**, ~~strikethrough~~, and a [link](https://example.com).
83
84---
85
86Footer text.
87EOF
88cat > Makefile <<'EOF'
89.PHONY: build serve clean
90
91build:
92 go run ./cmd/site build
93
94serve:
95 go run ./cmd/site serve --port 8080
96
97clean:
98 rm -rf dist
99EOF
100cat > src/main.go <<'EOF'
101// Package main is the entry point of the demo site generator.
102package main
103
104import (
105 "flag"
106 "fmt"
107 "log"
108 "os"
109 "path/filepath"
110 "strings"
111)
112
113// Page is one rendered page.
114type Page struct {
115 Title string
116 Slug string
117 Content string
118}
119
120func main() {
121 out := flag.String("out", "dist", "output directory")
122 flag.Parse()
123
124 pages, err := loadPages("content")
125 if err != nil {
126 log.Fatal(err)
127 }
128 for _, p := range pages {
129 dst := filepath.Join(*out, p.Slug+".html")
130 if err := os.WriteFile(dst, []byte(render(p)), 0o644); err != nil {
131 log.Fatal(err)
132 }
133 fmt.Println("wrote", dst)
134 }
135}
136
137func loadPages(dir string) ([]Page, error) {
138 entries, err := os.ReadDir(dir)
139 if err != nil {
140 return nil, err
141 }
142 var pages []Page
143 for _, e := range entries {
144 if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
145 continue
146 }
147 b, err := os.ReadFile(filepath.Join(dir, e.Name()))
148 if err != nil {
149 return nil, err
150 }
151 pages = append(pages, Page{
152 Title: strings.TrimSuffix(e.Name(), ".md"),
153 Slug: strings.TrimSuffix(e.Name(), ".md"),
154 Content: string(b),
155 })
156 }
157 return pages, nil
158}
159
160func render(p Page) string {
161 return "<!doctype html><title>" + p.Title + "</title><main>" + p.Content + "</main>"
162}
163EOF
164cat > src/components/header.js <<'EOF'
165export function Header({ title, links = [] }) {
166 const nav = links.map((l) => `<a href="${l.href}">${l.label}</a>`).join("");
167 return `<header class="site-header"><h1>${title}</h1><nav>${nav}</nav></header>`;
168}
169
170// A very long line to test horizontal scrolling in the blob view: const veryLongVariableName = someFunction(anotherFunction(yetAnotherFunction(withManyArguments, andMoreArguments, andEvenMoreArguments, untilTheLineIsReallyLong)));
171EOF
172cat > src/components/styles.css <<'EOF'
173:root {
174 --bg: #fff;
175 --fg: #111;
176}
177.site-header {
178 display: flex;
179 justify-content: space-between;
180 padding: 1rem;
181}
182EOF
183echo 'package deep' > src/lib/deeply/nested/directory/structure/deep.go
184cat > docs/CONTRIBUTING.md <<'EOF'
185# Contributing
186
187Send patches through the Patches tab. Sign your commits.
188EOF
189touch "docs/a file name with spaces and a rather long title that keeps going.md"
190printf '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"
191cp "$ROOT/web/static/assets/favicon.svg" assets/logo.svg
192cp "$ROOT/tests/data-test-13/avatars/1.png" assets/logo.png 2>/dev/null || cp "$ROOT/web/static/assets/favicon.svg" assets/logo.png
193head -c 4096 /dev/urandom > assets/blob.bin
194printf 'root = true\n\n[*]\nindent_style = tab\n' > .editorconfig
195commit "Initial commit" "2025-11-02T10:00:00+01:00"
196
197for i in $(seq 1 28); do
198 echo "line $i" >> docs/CHANGELOG.md
199 commit "Update changelog, entry $i
200
201This is the body of commit $i. It explains the reasoning in more detail
202and wraps over several lines so the commit view has something to show.
203
204Signed-off-by: Alice Example <alice@example.com>" "2025-11-$(printf %02d $(( (i % 27) + 2 )))T1$((i % 10)):00:00+01:00"
205done
206cat > src/lib/render.go <<'EOF'
207package lib
208
209// Render turns Markdown into HTML.
210func Render(md string) string {
211 return "<p>" + md + "</p>"
212}
213EOF
214commit "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"
215git tag -a v0.1.0 -m "First tagged release"
216sed -i 's/<p>/<article>/; s/<\/p>/<\/article>/' src/lib/render.go
217commit "render: use article element" "2026-02-10T14:00:00+01:00"
218git tag v0.2.0
219git tag -a v1.0.0-rc.1 -m "Release candidate"
220git checkout -q -b feature/search
221mkdir -p src/search
222cat > src/search/index.go <<'EOF'
223package search
224
225// Index is a naive full text index.
226type Index map[string][]string
227
228// Add indexes one document.
229func (i Index) Add(slug, text string) {
230 i[slug] = append(i[slug], text)
231}
232EOF
233commit "search: add naive index" "2026-03-01T11:00:00+01:00"
234git checkout -q main
235git checkout -q -b fix/typo-in-readme
236sed -i 's/Zero configuration/Zero-configuration/' README.md
237commit "readme: hyphenate zero-configuration" "2026-03-03T08:00:00+01:00"
238git checkout -q main
239git checkout -q -b a-branch-with-a-very-long-name-that-should-be-truncated-in-the-selector
240echo x > x.txt
241commit "long branch" "2026-03-04T08:00:00+01:00"
242git checkout -q main
243push website
244
245# Patch files from the branches.
246git format-patch -q -1 feature/search --stdout > "$TMP/search.patch"
247git format-patch -q -1 fix/typo-in-readme --stdout > "$TMP/typo.patch"
248# A conflicting patch: edit a line that main changed after the base.
249git checkout -q -b conflict HEAD~1
250sed -i 's/Package main is/Package main was/' src/main.go
251commit "main: change package doc" "2026-03-05T08:00:00+01:00"
252git format-patch -q -1 --stdout > "$TMP/conflict.patch"
253git checkout -q main
254sed -i 's/Package main is/Package main IS/' src/main.go
255commit "main: shout package doc" "2026-03-06T08:00:00+01:00"
256push website
257
258# Other repos.
259bare empty-repo
260
261bare the-quick-brown-fox-jumps-over-the-lazy-dog-repository-with-a-very-long-name
262work
263echo '# Long name' > README.md
264commit "init" "2026-01-01T00:00:00+00:00"
265push the-quick-brown-fox-jumps-over-the-lazy-dog-repository-with-a-very-long-name
266
267bare no-readme
268work
269echo 'print("hi")' > main.py
270commit "add main.py" "2026-01-02T00:00:00+00:00"
271push no-readme
272
273bare private-notes
274work
275printf '# Private\n\nSecret notes.\n' > README.md
276commit "init" "2026-01-03T00:00:00+00:00"
277push private-notes
278
279for i in $(seq 1 12); do
280 bare "sample-$(printf %02d $i)"
281 work
282 printf '# sample-%02d\n' "$i" > README.md
283 commit "init" "2025-0$(( (i % 9) + 1 ))-1${i:0:1}T00:00:00+00:00"
284 push "sample-$(printf %02d $i)"
285done
286cd "$ROOT"
287
288# ---------- server + forms ----------
289DATA_DIR=$DATA_DIR PORT=$PORT "$BIN" >"$TMP/server.log" 2>&1 &
290SERVER=$!
291trap 'kill $SERVER 2>/dev/null; rm -rf "$TMP"' EXIT
292for _ in $(seq 1 50); do curl -sf "$BASE/health" >/dev/null && break; sleep 0.2; done
293
294J=$TMP/admin.jar
295A=$TMP/alice.jar
296post() { # jar path data...
297 local jar=$1 path=$2; shift 2
298 curl -sS -X POST -o /dev/null -w '%{http_code} %{redirect_url}\n' -b "$jar" -c "$jar" "$BASE$path" "$@" | sed "s|^|POST $path -> |"
299}
300
301post "$A" /register --data-urlencode username=alice --data-urlencode password=designdesign --data-urlencode password2=designdesign
302post "$J" /login --data-urlencode username=admin --data-urlencode password=designdesign
303post "$A" /login --data-urlencode username=alice --data-urlencode password=designdesign
304
305# Repo settings.
306post "$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 \
307 --data-urlencode $'issue_template=## Steps to reproduce\n\n1.\n2.\n\n## Expected\n\n## Actual\n' \
308 --data-urlencode $'patch_template=## Summary\n\n## Testing\n'
309post "$J" /private-notes/settings --data-urlencode "description=Only visible to the admin." --data-urlencode is_private=1 --data-urlencode default_branch=main
310post "$J" /no-readme/settings --data-urlencode "description=A repo without a README." --data-urlencode default_branch=main
311post "$J" /sample-03/settings --data-urlencode is_pinned=1 --data-urlencode default_branch=main
312
313# Labels.
314for 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
315 post "$J" /website/settings/labels --data-urlencode "name=${l% *}" --data-urlencode "color=${l##* }"
316done
317
318# CI secret (settings page only shows names).
319post "$J" /website/settings/ci-secrets --data-urlencode name=DEPLOY_TOKEN --data-urlencode value=secret --data-urlencode "description=Token used by the deploy step"
320
321# Issues.
322post "$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
323post "$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
324post "$A" /website/issues --data-urlencode "title=Typo in CONTRIBUTING.md" --data-urlencode "body=" --data-urlencode label_ids=3 --data-urlencode label_ids=4
325post "$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
326post "$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."
327for i in $(seq 6 24); do
328 post "$J" /website/issues --data-urlencode "title=Filler issue number $i for pagination" --data-urlencode "body=Filler." >/dev/null
329done
330post "$J" /website/issues/3/close
331post "$J" /website/issues/5/complete
332post "$J" /website/issues/7/close
333post "$J" /website/issues/8/complete
334
335# Comments and reactions on issue 1.
336post "$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.'
337post "$A" /website/issues/1/comments --data-urlencode "body=Happy to send a patch if you point me at the right file."
338post "$J" /website/issues/1/comments --data-urlencode "body=src/search/index.go, function Query."
339
340post "$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."
341post "$J" /website/issues/1/react --data-urlencode "emoji=👍"
342post "$A" /website/issues/1/react --data-urlencode "emoji=👍"
343post "$A" /website/issues/1/react --data-urlencode "emoji=🎉"
344post "$J" /website/issues/1/react --data-urlencode "emoji=👀" --data-urlencode comment_id=2
345post "$A" /website/issues/1/react --data-urlencode "emoji=❤️" --data-urlencode comment_id=2
346post "$A" /website/issues/1/react --data-urlencode "emoji=🚀" --data-urlencode comment_id=2
347post "$J" /website/issues/2/comments --data-urlencode "body=Short."
348
349# Patches.
350post "$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
351post "$A" /website/patches -F title="readme: hyphenate zero-configuration" -F description="Tiny wording fix." -F patch_file=@"$TMP/typo.patch" -F label_ids=3
352post "$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"
353post "$A" /website/patches -F title="Closed without merging" -F description="" -F patch_file=@"$TMP/typo.patch"
354post "$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"
355for i in $(seq 6 22); do
356 post "$A" /website/patches -F title="Filler patch $i" -F description="" -F patch_file=@"$TMP/typo.patch" >/dev/null
357done
358post "$J" /website/patches/1/comments --data-urlencode "body=Looks good. Merging."
359post "$A" /website/patches/1/react --data-urlencode "emoji=🚀"
360VERSION=$(curl -s -b "$J" "$BASE/website/patches/2" | grep -o 'name="version" value="[^"]*"' | head -1 | sed 's/.*value="//; s/"//')
361post "$J" /website/patches/2/merge --data-urlencode "version=$VERSION"
362post "$J" /website/patches/4/close
363
364# Releases.
365printf 'binary asset content\n' > "$TMP/website-linux-amd64"
366printf 'checksums\n' > "$TMP/SHA256SUMS.txt"
367head -c 300000 /dev/urandom > "$TMP/website-1.0.0-rc.1-darwin-arm64.tar.gz"
368post "$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"
369post "$J" /website/releases -F name="v0.2.0" -F tag_name=v0.2.0 -F notes="Small release. No assets."
370post "$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"
371post "$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."
372
373# SSH keys for the admin.
374ssh-keygen -q -t ed25519 -N '' -C 'laptop' -f "$TMP/k1"
375ssh-keygen -q -t ed25519 -N '' -C 'a-key-with-a-very-long-comment-for-wrapping-tests@workstation.example.internal' -f "$TMP/k2"
376post "$J" /settings/ssh-keys --data-urlencode name="Laptop" --data-urlencode "public_key=$(cat "$TMP/k1.pub")"
377post "$J" /settings/ssh-keys --data-urlencode name="Workstation with a considerably longer key name" --data-urlencode "public_key=$(cat "$TMP/k2.pub")"
378
379# Avatar for alice.
380curl -sS -o /dev/null -b "$A" -c "$A" "$BASE/settings/avatar" -F avatar=@"$ROOT/tests/data-test-13/avatars/1.png" || true
381
382kill $SERVER; wait $SERVER 2>/dev/null || true
383
384# ---------- rows the forms cannot create ----------
385DB=$DATA_DIR/hearthforge.db
386sqlite3 "$DB" <<'SQL'
387INSERT INTO users (username, password_hash, created_at, is_pending, register_application) VALUES
388 ('pending-bob', NULL, '2026-03-01T10:00:00Z', 1, 'I work with Alice on the website and would like to send patches.'),
389 ('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.'),
390 ('pending-dave', NULL, '2026-03-03T12:00:00Z', 1, '');
391
392INSERT INTO ci_run_counters (repo_id, last_run_id) SELECT id, 6 FROM repositories WHERE name='website';
393INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id)
394SELECT 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';
395INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id)
396SELECT 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';
397INSERT 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)
398SELECT 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';
399INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, created_at, repo_run_id)
400SELECT id, 1, 'push', 'd4e5f60718293a4b5c6d7e8f9012345678901234', 'main', 'running', '2026-03-06T11:00:05Z', '2026-03-06T11:00:00Z', 4 FROM repositories WHERE name='website';
401INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, created_at, repo_run_id)
402SELECT id, 1, 'push', 'e5f60718293a4b5c6d7e8f90123456789012345a', 'main', 'queued', '2026-03-06T11:30:00Z', 5 FROM repositories WHERE name='website';
403INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id)
404SELECT 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';
405
406INSERT INTO ci_steps (run_id, name, status, exit_code, started_at, finished_at, log) VALUES
407 (1, 'tools', 'success', 0, '2026-03-06T08:00:05Z', '2026-03-06T08:00:40Z', 'go: downloading mvdan.cc/gofumpt v0.8.0
408go: downloading github.com/golangci/golangci-lint/v2 v2.1.0
409installed'),
410 (1, 'test', 'success', 0, '2026-03-06T08:00:40Z', '2026-03-06T08:03:00Z', 'ok website/src 0.412s
411ok website/src/lib 0.008s
412ok website/src/search 0.031s'),
413 (1, 'build', 'success', 0, '2026-03-06T08:03:00Z', '2026-03-06T08:03:41Z', 'wrote dist/index.html
414wrote dist/about.html
415A very long log line that should scroll or wrap: ' || hex(randomblob(200))),
416 (2, 'tools', 'success', 0, '2026-03-06T09:00:05Z', '2026-03-06T09:00:30Z', 'installed'),
417 (2, 'test', 'failure', 1, '2026-03-06T09:00:30Z', '2026-03-06T09:01:12Z', '--- FAIL: TestIndex (0.00s)
418 index_test.go:12: expected 2 results, got 0
419FAIL
420FAIL website/src/search 0.004s'),
421 (2, 'build', 'skipped', NULL, NULL, NULL, ''),
422 (3, 'tools', 'success', 0, '2026-03-06T10:00:05Z', '2026-03-06T10:00:20Z', 'installed'),
423 (3, 'test', 'cancelled', NULL, '2026-03-06T10:00:20Z', '2026-03-06T10:00:30Z', 'ok website/src 0.4s'),
424 (3, 'build', 'skipped', NULL, NULL, NULL, ''),
425 (4, 'tools', 'success', 0, '2026-03-06T11:00:05Z', '2026-03-06T11:00:20Z', 'installed'),
426 (4, 'test', 'running', NULL, '2026-03-06T11:00:20Z', NULL, '=== RUN TestRender
427'),
428 (4, 'build', 'pending', NULL, NULL, NULL, ''),
429 (5, 'tools', 'pending', NULL, NULL, NULL, ''),
430 (5, 'test', 'pending', NULL, NULL, NULL, ''),
431 (6, 'vulncheck', 'warning', 1, '2026-03-05T08:00:05Z', '2026-03-05T08:02:00Z', 'Vulnerability #1: GO-2026-0001
432 stdlib: something
433');
434INSERT INTO ci_artifacts (run_id, filename, size) VALUES (1, 'website', 8421376), (1, 'dist.tar.gz', 120333);
435SQL
436mkdir -p "$DATA_DIR/ci/artifacts/1"
437printf 'artifact\n' > "$DATA_DIR/ci/artifacts/1/website"
438printf 'artifact\n' > "$DATA_DIR/ci/artifacts/1/dist.tar.gz"
439
440echo
441echo "Seeded $DATA_DIR. Start with:"
442echo " DATA_DIR=$DATA_DIR PORT=$PORT ./hearthforge"
443echo "Login: admin / designdesign, alice / designdesign"
444