seed-design.sh
| 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. |
| 8 | set -euo pipefail |
| 9 | |
| 10 | ROOT=$(cd "$(dirname "$0")/.." && pwd) |
| 11 | DATA_DIR=${DATA_DIR:-$ROOT/data-test-design} |
| 12 | PORT=${PORT:-3999} |
| 13 | BASE=http://localhost:$PORT |
| 14 | BIN=$ROOT/hearthforge |
| 15 | TMP=$(mktemp -d) |
| 16 | trap 'rm -rf "$TMP"' EXIT |
| 17 | |
| 18 | export GIT_AUTHOR_NAME="Alice Example" GIT_AUTHOR_EMAIL=alice@example.com |
| 19 | export GIT_COMMITTER_NAME="Alice Example" GIT_COMMITTER_EMAIL=alice@example.com |
| 20 | export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null |
| 21 | |
| 22 | rm -rf "$DATA_DIR" |
| 23 | mkdir -p "$DATA_DIR/repos" |
| 24 | (cd "$ROOT" && go build -o hearthforge ./cmd/hearthforge) |
| 25 | DATA_DIR=$DATA_DIR ADMIN_PASSWORD=designdesign "$BIN" init |
| 26 | |
| 27 | # ---------- git repos on disk ---------- |
| 28 | bare() { git init -q --bare -b main "$DATA_DIR/repos/$1.git"; } |
| 29 | work() { rm -rf "$TMP/w"; git init -q -b main "$TMP/w"; cd "$TMP/w"; } |
| 30 | push() { git push -q "$DATA_DIR/repos/$1.git" --all && git push -q "$DATA_DIR/repos/$1.git" --tags; } |
| 31 | commit() { git add -A && GIT_AUTHOR_DATE="$2" GIT_COMMITTER_DATE="$2" git commit -q -m "$1"; } |
| 32 | |
| 33 | # website: the main showcase repo. |
| 34 | bare website |
| 35 | work |
| 36 | mkdir -p src/components src/lib/deeply/nested/directory/structure docs assets |
| 37 | cat > README.md <<'EOF' |
| 38 | # Website |
| 39 | |
| 40 | A 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 |
| 51 | git clone http://localhost:3999/website.git |
| 52 | cd website |
| 53 | make 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 | |
| 72 | A very long line without any spaces to test overflow handling: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
| 73 | |
| 74 |  |
| 75 | |
| 76 | 1. First step |
| 77 | 2. Second step |
| 78 | - nested item |
| 79 | - another nested item |
| 80 | 3. Third step |
| 81 | |
| 82 | Inline `code`, *emphasis*, **strong**, ~~strikethrough~~, and a [link](https://example.com). |
| 83 | |
| 84 | --- |
| 85 | |
| 86 | Footer text. |
| 87 | EOF |
| 88 | cat > Makefile <<'EOF' |
| 89 | .PHONY: build serve clean |
| 90 | |
| 91 | build: |
| 92 | go run ./cmd/site build |
| 93 | |
| 94 | serve: |
| 95 | go run ./cmd/site serve --port 8080 |
| 96 | |
| 97 | clean: |
| 98 | rm -rf dist |
| 99 | EOF |
| 100 | cat > src/main.go <<'EOF' |
| 101 | // Package main is the entry point of the demo site generator. |
| 102 | package main |
| 103 | |
| 104 | import ( |
| 105 | "flag" |
| 106 | "fmt" |
| 107 | "log" |
| 108 | "os" |
| 109 | "path/filepath" |
| 110 | "strings" |
| 111 | ) |
| 112 | |
| 113 | // Page is one rendered page. |
| 114 | type Page struct { |
| 115 | Title string |
| 116 | Slug string |
| 117 | Content string |
| 118 | } |
| 119 | |
| 120 | func 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 | |
| 137 | func 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 | |
| 160 | func render(p Page) string { |
| 161 | return "<!doctype html><title>" + p.Title + "</title><main>" + p.Content + "</main>" |
| 162 | } |
| 163 | EOF |
| 164 | cat > src/components/header.js <<'EOF' |
| 165 | export 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))); |
| 171 | EOF |
| 172 | cat > 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 | } |
| 182 | EOF |
| 183 | echo 'package deep' > src/lib/deeply/nested/directory/structure/deep.go |
| 184 | cat > docs/CONTRIBUTING.md <<'EOF' |
| 185 | # Contributing |
| 186 | |
| 187 | Send patches through the Patches tab. Sign your commits. |
| 188 | EOF |
| 189 | touch "docs/a file name with spaces and a rather long title that keeps going.md" |
| 190 | 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" |
| 191 | cp "$ROOT/web/static/assets/favicon.svg" assets/logo.svg |
| 192 | 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 |
| 193 | head -c 4096 /dev/urandom > assets/blob.bin |
| 194 | printf 'root = true\n\n[*]\nindent_style = tab\n' > .editorconfig |
| 195 | commit "Initial commit" "2025-11-02T10:00:00+01:00" |
| 196 | |
| 197 | for i in $(seq 1 28); do |
| 198 | echo "line $i" >> docs/CHANGELOG.md |
| 199 | commit "Update changelog, entry $i |
| 200 | |
| 201 | This is the body of commit $i. It explains the reasoning in more detail |
| 202 | and wraps over several lines so the commit view has something to show. |
| 203 | |
| 204 | Signed-off-by: Alice Example <alice@example.com>" "2025-11-$(printf %02d $(( (i % 27) + 2 )))T1$((i % 10)):00:00+01:00" |
| 205 | done |
| 206 | cat > src/lib/render.go <<'EOF' |
| 207 | package lib |
| 208 | |
| 209 | // Render turns Markdown into HTML. |
| 210 | func Render(md string) string { |
| 211 | return "<p>" + md + "</p>" |
| 212 | } |
| 213 | EOF |
| 214 | 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" |
| 215 | git tag -a v0.1.0 -m "First tagged release" |
| 216 | sed -i 's/<p>/<article>/; s/<\/p>/<\/article>/' src/lib/render.go |
| 217 | commit "render: use article element" "2026-02-10T14:00:00+01:00" |
| 218 | git tag v0.2.0 |
| 219 | git tag -a v1.0.0-rc.1 -m "Release candidate" |
| 220 | git checkout -q -b feature/search |
| 221 | mkdir -p src/search |
| 222 | cat > src/search/index.go <<'EOF' |
| 223 | package search |
| 224 | |
| 225 | // Index is a naive full text index. |
| 226 | type Index map[string][]string |
| 227 | |
| 228 | // Add indexes one document. |
| 229 | func (i Index) Add(slug, text string) { |
| 230 | i[slug] = append(i[slug], text) |
| 231 | } |
| 232 | EOF |
| 233 | commit "search: add naive index" "2026-03-01T11:00:00+01:00" |
| 234 | git checkout -q main |
| 235 | git checkout -q -b fix/typo-in-readme |
| 236 | sed -i 's/Zero configuration/Zero-configuration/' README.md |
| 237 | commit "readme: hyphenate zero-configuration" "2026-03-03T08:00:00+01:00" |
| 238 | git checkout -q main |
| 239 | git checkout -q -b a-branch-with-a-very-long-name-that-should-be-truncated-in-the-selector |
| 240 | echo x > x.txt |
| 241 | commit "long branch" "2026-03-04T08:00:00+01:00" |
| 242 | git checkout -q main |
| 243 | push website |
| 244 | |
| 245 | # Patch files from the branches. |
| 246 | git format-patch -q -1 feature/search --stdout > "$TMP/search.patch" |
| 247 | git 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. |
| 249 | git checkout -q -b conflict HEAD~1 |
| 250 | sed -i 's/Package main is/Package main was/' src/main.go |
| 251 | commit "main: change package doc" "2026-03-05T08:00:00+01:00" |
| 252 | git format-patch -q -1 --stdout > "$TMP/conflict.patch" |
| 253 | git checkout -q main |
| 254 | sed -i 's/Package main is/Package main IS/' src/main.go |
| 255 | commit "main: shout package doc" "2026-03-06T08:00:00+01:00" |
| 256 | push website |
| 257 | |
| 258 | # Other repos. |
| 259 | bare empty-repo |
| 260 | |
| 261 | bare the-quick-brown-fox-jumps-over-the-lazy-dog-repository-with-a-very-long-name |
| 262 | work |
| 263 | echo '# Long name' > README.md |
| 264 | commit "init" "2026-01-01T00:00:00+00:00" |
| 265 | push the-quick-brown-fox-jumps-over-the-lazy-dog-repository-with-a-very-long-name |
| 266 | |
| 267 | bare no-readme |
| 268 | work |
| 269 | echo 'print("hi")' > main.py |
| 270 | commit "add main.py" "2026-01-02T00:00:00+00:00" |
| 271 | push no-readme |
| 272 | |
| 273 | bare private-notes |
| 274 | work |
| 275 | printf '# Private\n\nSecret notes.\n' > README.md |
| 276 | commit "init" "2026-01-03T00:00:00+00:00" |
| 277 | push private-notes |
| 278 | |
| 279 | for 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)" |
| 285 | done |
| 286 | cd "$ROOT" |
| 287 | |
| 288 | # ---------- server + forms ---------- |
| 289 | DATA_DIR=$DATA_DIR PORT=$PORT "$BIN" >"$TMP/server.log" 2>&1 & |
| 290 | SERVER=$! |
| 291 | trap 'kill $SERVER 2>/dev/null; rm -rf "$TMP"' EXIT |
| 292 | for _ in $(seq 1 50); do curl -sf "$BASE/health" >/dev/null && break; sleep 0.2; done |
| 293 | |
| 294 | J=$TMP/admin.jar |
| 295 | A=$TMP/alice.jar |
| 296 | post() { # 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 | |
| 301 | post "$A" /register --data-urlencode username=alice --data-urlencode password=designdesign --data-urlencode password2=designdesign |
| 302 | post "$J" /login --data-urlencode username=admin --data-urlencode password=designdesign |
| 303 | post "$A" /login --data-urlencode username=alice --data-urlencode password=designdesign |
| 304 | |
| 305 | # Repo settings. |
| 306 | 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 \ |
| 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' |
| 309 | post "$J" /private-notes/settings --data-urlencode "description=Only visible to the admin." --data-urlencode is_private=1 --data-urlencode default_branch=main |
| 310 | post "$J" /no-readme/settings --data-urlencode "description=A repo without a README." --data-urlencode default_branch=main |
| 311 | post "$J" /sample-03/settings --data-urlencode is_pinned=1 --data-urlencode default_branch=main |
| 312 | |
| 313 | # Labels. |
| 314 | 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 |
| 315 | post "$J" /website/settings/labels --data-urlencode "name=${l% *}" --data-urlencode "color=${l##* }" |
| 316 | done |
| 317 | |
| 318 | # CI secret (settings page only shows names). |
| 319 | post "$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. |
| 322 | 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 |
| 323 | 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 |
| 324 | post "$A" /website/issues --data-urlencode "title=Typo in CONTRIBUTING.md" --data-urlencode "body=" --data-urlencode label_ids=3 --data-urlencode label_ids=4 |
| 325 | 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 |
| 326 | 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." |
| 327 | for 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 |
| 329 | done |
| 330 | post "$J" /website/issues/3/close |
| 331 | post "$J" /website/issues/5/complete |
| 332 | post "$J" /website/issues/7/close |
| 333 | post "$J" /website/issues/8/complete |
| 334 | |
| 335 | # Comments and reactions on issue 1. |
| 336 | 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.' |
| 337 | post "$A" /website/issues/1/comments --data-urlencode "body=Happy to send a patch if you point me at the right file." |
| 338 | post "$J" /website/issues/1/comments --data-urlencode "body=src/search/index.go, function Query." |
| 339 | |
| 340 | 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." |
| 341 | post "$J" /website/issues/1/react --data-urlencode "emoji=👍" |
| 342 | post "$A" /website/issues/1/react --data-urlencode "emoji=👍" |
| 343 | post "$A" /website/issues/1/react --data-urlencode "emoji=🎉" |
| 344 | post "$J" /website/issues/1/react --data-urlencode "emoji=👀" --data-urlencode comment_id=2 |
| 345 | post "$A" /website/issues/1/react --data-urlencode "emoji=❤️" --data-urlencode comment_id=2 |
| 346 | post "$A" /website/issues/1/react --data-urlencode "emoji=🚀" --data-urlencode comment_id=2 |
| 347 | post "$J" /website/issues/2/comments --data-urlencode "body=Short." |
| 348 | |
| 349 | # Patches. |
| 350 | 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 |
| 351 | 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 |
| 352 | 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" |
| 353 | post "$A" /website/patches -F title="Closed without merging" -F description="" -F patch_file=@"$TMP/typo.patch" |
| 354 | 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" |
| 355 | for 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 |
| 357 | done |
| 358 | post "$J" /website/patches/1/comments --data-urlencode "body=Looks good. Merging." |
| 359 | post "$A" /website/patches/1/react --data-urlencode "emoji=🚀" |
| 360 | VERSION=$(curl -s -b "$J" "$BASE/website/patches/2" | grep -o 'name="version" value="[^"]*"' | head -1 | sed 's/.*value="//; s/"//') |
| 361 | post "$J" /website/patches/2/merge --data-urlencode "version=$VERSION" |
| 362 | post "$J" /website/patches/4/close |
| 363 | |
| 364 | # Releases. |
| 365 | printf 'binary asset content\n' > "$TMP/website-linux-amd64" |
| 366 | printf 'checksums\n' > "$TMP/SHA256SUMS.txt" |
| 367 | head -c 300000 /dev/urandom > "$TMP/website-1.0.0-rc.1-darwin-arm64.tar.gz" |
| 368 | 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" |
| 369 | post "$J" /website/releases -F name="v0.2.0" -F tag_name=v0.2.0 -F notes="Small release. No assets." |
| 370 | 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" |
| 371 | 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." |
| 372 | |
| 373 | # SSH keys for the admin. |
| 374 | ssh-keygen -q -t ed25519 -N '' -C 'laptop' -f "$TMP/k1" |
| 375 | ssh-keygen -q -t ed25519 -N '' -C 'a-key-with-a-very-long-comment-for-wrapping-tests@workstation.example.internal' -f "$TMP/k2" |
| 376 | post "$J" /settings/ssh-keys --data-urlencode name="Laptop" --data-urlencode "public_key=$(cat "$TMP/k1.pub")" |
| 377 | post "$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. |
| 380 | curl -sS -o /dev/null -b "$A" -c "$A" "$BASE/settings/avatar" -F avatar=@"$ROOT/tests/data-test-13/avatars/1.png" || true |
| 381 | |
| 382 | kill $SERVER; wait $SERVER 2>/dev/null || true |
| 383 | |
| 384 | # ---------- rows the forms cannot create ---------- |
| 385 | DB=$DATA_DIR/hearthforge.db |
| 386 | sqlite3 "$DB" <<'SQL' |
| 387 | INSERT 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 | |
| 392 | INSERT INTO ci_run_counters (repo_id, last_run_id) SELECT id, 6 FROM repositories WHERE name='website'; |
| 393 | INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id) |
| 394 | 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'; |
| 395 | INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id) |
| 396 | 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'; |
| 397 | 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) |
| 398 | 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'; |
| 399 | INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, created_at, repo_run_id) |
| 400 | SELECT id, 1, 'push', 'd4e5f60718293a4b5c6d7e8f9012345678901234', 'main', 'running', '2026-03-06T11:00:05Z', '2026-03-06T11:00:00Z', 4 FROM repositories WHERE name='website'; |
| 401 | INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, created_at, repo_run_id) |
| 402 | SELECT id, 1, 'push', 'e5f60718293a4b5c6d7e8f90123456789012345a', 'main', 'queued', '2026-03-06T11:30:00Z', 5 FROM repositories WHERE name='website'; |
| 403 | INSERT INTO ci_runs (repo_id, triggered_by, trigger_source, commit_sha, commit_branch, status, started_at, finished_at, created_at, repo_run_id) |
| 404 | 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'; |
| 405 | |
| 406 | INSERT 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 |
| 408 | go: downloading github.com/golangci/golangci-lint/v2 v2.1.0 |
| 409 | installed'), |
| 410 | (1, 'test', 'success', 0, '2026-03-06T08:00:40Z', '2026-03-06T08:03:00Z', 'ok website/src 0.412s |
| 411 | ok website/src/lib 0.008s |
| 412 | ok website/src/search 0.031s'), |
| 413 | (1, 'build', 'success', 0, '2026-03-06T08:03:00Z', '2026-03-06T08:03:41Z', 'wrote dist/index.html |
| 414 | wrote dist/about.html |
| 415 | A 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 |
| 419 | FAIL |
| 420 | FAIL 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 | '); |
| 434 | INSERT INTO ci_artifacts (run_id, filename, size) VALUES (1, 'website', 8421376), (1, 'dist.tar.gz', 120333); |
| 435 | SQL |
| 436 | mkdir -p "$DATA_DIR/ci/artifacts/1" |
| 437 | printf 'artifact\n' > "$DATA_DIR/ci/artifacts/1/website" |
| 438 | printf 'artifact\n' > "$DATA_DIR/ci/artifacts/1/dist.tar.gz" |
| 439 | |
| 440 | echo |
| 441 | echo "Seeded $DATA_DIR. Start with:" |
| 442 | echo " DATA_DIR=$DATA_DIR PORT=$PORT ./hearthforge" |
| 443 | echo "Login: admin / designdesign, alice / designdesign" |
| 444 |