| 1 | .PHONY: ci suite suite-roles gofmt no-rastrillo size history root staticcheck govulncheck gitleaks browser heights-sweep site site-links-published site-publish |
| 2 | |
| 3 | # CGO_ENABLED=0 across the tree is an acceptance criterion, not a |
| 4 | # preference: Fiesta is CSS, scripts and the Go that embeds and checks |
| 5 | # them, and nothing here may quietly pull in a cgo dependency because |
| 6 | # a C toolchain happened to be present. |
| 7 | export CGO_ENABLED = 0 |
| 8 | |
| 9 | # The gate runs on one known Go, not whichever release the machine has. |
| 10 | # govulncheck reports the standard library's vulnerabilities for the |
| 11 | # toolchain doing the scan, so a runner one patch release behind would |
| 12 | # fail the gate for code nobody changed, and a laptop and a runner on |
| 13 | # different patches would disagree about what passing means. go fetches |
| 14 | # this release through the module proxy on first use. |
| 15 | # |
| 16 | # A plain =, not ?=: Go's own container images set GOTOOLCHAIN=local, and |
| 17 | # ?= would quietly keep whatever Go they ship. Override on the command |
| 18 | # line if you must: make ci GOTOOLCHAIN=local. Raise it with each Go |
| 19 | # security release; govulncheck naming a stdlib package "Found in" this |
| 20 | # version is the signal. |
| 21 | # |
| 22 | # Held on 1.26 deliberately, as rastrillo is. Go 1.27 writes export |
| 23 | # data that x/tools before v0.50.0 cannot read, and v0.50.0 needs go |
| 24 | # 1.26 in go.mod: moving to 1.27 raises the Go floor for every app that |
| 25 | # imports Fiesta, so it is its own change. |
| 26 | export GOTOOLCHAIN = go1.26.10 |
| 27 | |
| 28 | BIN := $(CURDIR)/.build |
| 29 | |
| 30 | # Keep compiler/linker scratch on the checkout filesystem when shared /tmp is full. |
| 31 | export GOTMPDIR ?= $(BIN)/tmp |
| 32 | |
| 33 | # ci is the one gate: what a runner executes and what you run before |
| 34 | # pushing are the same definition. .amadan/ci.d/ reports these one by |
| 35 | # one; it never keeps its own copy of a command. |
| 36 | # |
| 37 | # The cheap checks go first and alone, so a stray gofmt still costs |
| 38 | # seconds, and the size gate next, so a bundle over the line is the |
| 39 | # first thing a red run says. history then makes a runner's shallow |
| 40 | # clone whole, once, before the suite's shards share it. Everything |
| 41 | # else is the suite. |
| 42 | ci: gofmt no-rastrillo size history suite |
| 43 | |
| 44 | # The suite's roles. Under amadan, .amadan/ci.d/50-suite@N runs N |
| 45 | # shards at once and each runs one role, so the run takes as long as |
| 46 | # the slowest role rather than the sum of them all; by hand, or as a |
| 47 | # step with no @N, the roles run one after another. N must equal the |
| 48 | # number of roles here: hack/suite.sh refuses any other count rather |
| 49 | # than run some roles twice and others nowhere, and ci_test.go checks |
| 50 | # the two agree before a runner ever sees them. |
| 51 | # |
| 52 | # The slowest role is how long the suite takes, so the slow packages |
| 53 | # are roles of their own, sliced until every role runs in under a |
| 54 | # minute on a runner. The browser packages are a role each, running |
| 55 | # only the tests the browser build adds (a package's plain tests run in |
| 56 | # root, or in its test.<pkg> roles), and ROOT_SPLIT's packages leave |
| 57 | # root's go test ./... for test.<pkg> roles. ci_test.go checks that |
| 58 | # every test runs in exactly one role. |
| 59 | # A package written pkg@n is n roles, each a slice of its tests, dealt |
| 60 | # by hack/gotest.sh by the seconds in hack/test-weights.txt. In a |
| 61 | # role's name a / in the path is a . so internal/gallery@2 becomes |
| 62 | # browser.internal.gallery~1of2 and ~2of2. A test too long for a slice |
| 63 | # of its own is dealt into parts by the test itself (the height drives, |
| 64 | # the two axe sweeps, the HTML tab's renders, the floors drive). The |
| 65 | # tasks that add a browser package append here and rename 50-suite@N |
| 66 | # to match. |
| 67 | BROWSER_PKGS := internal/harness check@14 internal/gallery@18 internal/gallery/history internal/www@4 |
| 68 | ROOT_SPLIT := internal/gallery@3 cmd/fiesta@2 internal/www@2 |
| 69 | go-roles = $(foreach p,$(2),$(if $(findstring @,$(p)),$(call go-slices,$(1),$(subst /,.,$(word 1,$(subst @, ,$(p)))),$(word 2,$(subst @, ,$(p)))),$(1).$(subst /,.,$(p)))) |
| 70 | go-slices = $(foreach k,$(shell seq 1 $(3)),$(1).$(2)~$(k)of$(3)) |
| 71 | BROWSER_ROLES := $(call go-roles,browser,$(BROWSER_PKGS)) |
| 72 | TEST_ROLES := $(call go-roles,test,$(ROOT_SPLIT)) |
| 73 | SUITE := root $(TEST_ROLES) staticcheck govulncheck gitleaks $(BROWSER_ROLES) |
| 74 | |
| 75 | suite: |
| 76 | @MAKE='$(MAKE)' ./hack/suite.sh $(SUITE) |
| 77 | |
| 78 | # What ci_test.go counts against the step's @N. |
| 79 | suite-roles: |
| 80 | @echo $(SUITE) |
| 81 | |
| 82 | # The repo's own Go files, not everything under the checkout: GOTMPDIR is |
| 83 | # .build/tmp, and a go command killed mid-build (a Ctrl-C, or a runner |
| 84 | # cancelling a job a newer push superseded) leaves its generated |
| 85 | # _testmain.go and cgo files there. gofmt -l . walked into them and failed |
| 86 | # the next run on files nobody wrote. Dot-directories are pruned, the same |
| 87 | # rule ./... applies. It is a walk of the working tree rather than a git |
| 88 | # listing, so an unstaged deletion is simply absent instead of a missing |
| 89 | # path. NUL-delimited so a path with a space stays one argument, and a |
| 90 | # gofmt error (a file that does not parse) fails the target instead of |
| 91 | # vanishing into stderr. |
| 92 | gofmt: |
| 93 | @out=$$(find . -name '.?*' -type d -prune -o -name '*.go' -type f -print0 | xargs -0 gofmt -l) || exit 1; \ |
| 94 | if [ -n "$$out" ]; then echo "gofmt needed on:"; echo "$$out"; exit 1; fi |
| 95 | |
| 96 | # Fiesta never imports Rastrillo, and the packages a Go program imports |
| 97 | # from Fiesta never link a browser. Both are tests in norastrillo_test.go; |
| 98 | # this runs them alone, first, so a broken promise is the first thing a |
| 99 | # red run says. |
| 100 | no-rastrillo: |
| 101 | go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$$' -count=1 . |
| 102 | |
| 103 | # size is the one-link promise: each one-file theme the release build |
| 104 | # makes from the tree gzips under 25 KiB. One test, run alone and |
| 105 | # verbosely so the three figures are in the log of every run; the |
| 106 | # test's own package runs again inside root with the rest. |
| 107 | size: |
| 108 | go test ./internal/build -run '^TestTheOneLinkIsUnder25KiBGzipped$$' -count=1 -v |
| 109 | |
| 110 | # FIESTA_TEST_REQUIRE_NODE turns every Node skip into a failure here: on |
| 111 | # a laptop without Node a skip is honest, but a gate that lost node |
| 112 | # would otherwise go green having checked none of the JavaScript. |
| 113 | # Override with FIESTA_TEST_REQUIRE_NODE= to run without it. |
| 114 | FIESTA_TEST_REQUIRE_NODE ?= 1 |
| 115 | # FIESTA_TEST_REQUIRE_GIT does the same for git: the tag guard of |
| 116 | # fiesta release -candidate is tested in a scratch repository, and a |
| 117 | # runner without git would otherwise pass with the guard untested. |
| 118 | FIESTA_TEST_REQUIRE_GIT ?= 1 |
| 119 | # FIESTA_TEST_REQUIRE_TAGS does the same for the release tags: |
| 120 | # releases_test.go decides what to check from them, and a clone with |
| 121 | # none would pass it having compared nothing. make history fetches them |
| 122 | # into a runner's shallow clone. |
| 123 | FIESTA_TEST_REQUIRE_TAGS ?= 1 |
| 124 | TEST_ENV := FIESTA_TEST_REQUIRE_NODE=$(FIESTA_TEST_REQUIRE_NODE) FIESTA_TEST_REQUIRE_GIT=$(FIESTA_TEST_REQUIRE_GIT) FIESTA_TEST_REQUIRE_TAGS=$(FIESTA_TEST_REQUIRE_TAGS) |
| 125 | # root builds, vets and tests every package but ROOT_SPLIT's, whose |
| 126 | # tests run in the test.<pkg> roles; `make root $(TEST_ROLES)` is the |
| 127 | # old whole. The browser drives are behind the browser tag and run as |
| 128 | # roles of their own. |
| 129 | root: |
| 130 | go build ./... |
| 131 | go vet ./... |
| 132 | $(TEST_ENV) ./hack/gotest.sh root '$(ROOT_SPLIT)' |
| 133 | |
| 134 | # test.<pkg>[~<k>of<n>]: one package of plain tests as a role of its own, |
| 135 | # for a package too slow to sit inside root. |
| 136 | test.%: | $(BIN)/tmp |
| 137 | $(TEST_ENV) ./hack/gotest.sh test '$*' |
| 138 | |
| 139 | # staticcheck catches what vet does not: deprecated APIs, values that are |
| 140 | # never read, and the &*x that looks like a copy and is not. |
| 141 | # staticcheck.conf at the root says which checks. |
| 142 | # |
| 143 | # go run with a version, not a tool directive: Fiesta is a library, and |
| 144 | # a tool's requirements in this go.mod would join every consuming app's |
| 145 | # module graph. Bump it alongside the go directive - a staticcheck older |
| 146 | # than the Go it reads does not know that release's deprecations, and |
| 147 | # says nothing - and alongside a new minor GOTOOLCHAIN, whose export |
| 148 | # data an older one cannot decode at all. |
| 149 | # |
| 150 | # -tags browser because no file is excluded by it: it is a strict superset |
| 151 | # of the plain build, and it is the only way the browser drives get read. |
| 152 | STATICCHECK := honnef.co/go/tools/cmd/staticcheck@v0.8.2 |
| 153 | # staticcheck keeps its cache in the user cache directory, ~/.cache, |
| 154 | # which a sandboxed CI runner mounts read-only: "failed to initialize |
| 155 | # build cache ... read-only file system". Where the runner hands the job |
| 156 | # an absolute GOCACHE, staticcheck's goes inside it: writable wherever |
| 157 | # Go's is, and evicted with it. Anywhere else it stays where staticcheck |
| 158 | # puts it. Not beside it: a writable GOCACHE says nothing about its |
| 159 | # parent. |
| 160 | ifneq ($(filter /%,$(firstword $(GOCACHE))),) |
| 161 | export STATICCHECK_CACHE ?= $(GOCACHE)/staticcheck |
| 162 | endif |
| 163 | |
| 164 | staticcheck: |
| 165 | go run $(STATICCHECK) -tags browser ./... |
| 166 | |
| 167 | # govulncheck reports only vulnerabilities this code can reach, so a |
| 168 | # finding is a real call path, not a dependency merely present in go.sum. |
| 169 | # It reads the live vulnerability database, so it can go red with no |
| 170 | # change here: that is the point of running it on every push. A module |
| 171 | # finding is fixed by raising the requirement; a standard-library one by |
| 172 | # raising GOTOOLCHAIN above. |
| 173 | GOVULNCHECK := golang.org/x/vuln/cmd/govulncheck@v1.8.0 |
| 174 | govulncheck: |
| 175 | go run $(GOVULNCHECK) ./... |
| 176 | |
| 177 | # history makes a shallow clone whole: a runner clones one commit deep |
| 178 | # and with no tags, so gitleaks below would scan one commit while |
| 179 | # promising the history, and releases_test.go would find no tag to |
| 180 | # hold releases/ to. The whole history rather than a range of the |
| 181 | # pushed commits: a runner cannot name where a push began, and |
| 182 | # Fiesta's history is small (fetching it into a fresh one-commit clone |
| 183 | # took a third of a second). Its own step before the suite, because |
| 184 | # the suite's shards share one .git and two fetches at once would race |
| 185 | # for its locks. On a runner (AMADAN_CI) it also fetches the release |
| 186 | # tags into a clone that is already whole, since a tag pushed after a |
| 187 | # commit was fetched is not always followed; elsewhere a whole clone |
| 188 | # is left alone, so make ci by hand never fetches. |
| 189 | history: |
| 190 | @if [ "$$(git rev-parse --is-shallow-repository)" = true ]; then \ |
| 191 | echo "history: a shallow clone; fetching the whole history and the release tags"; \ |
| 192 | git fetch -q --unshallow origin 'refs/tags/v*:refs/tags/v*'; \ |
| 193 | elif [ -n "$${AMADAN_CI:-}" ]; then \ |
| 194 | git fetch -q origin 'refs/tags/v*:refs/tags/v*'; \ |
| 195 | fi |
| 196 | @if [ "$$(git rev-parse --is-shallow-repository)" != false ]; then echo "history: the clone is still shallow"; exit 1; fi |
| 197 | @echo "history: $$(git rev-list --count HEAD) commits; release tags: $$(git tag -l 'v*' | paste -sd' ' -)" |
| 198 | |
| 199 | # The whole git history, not only the tree: a key committed and deleted |
| 200 | # a year ago is still in every clone, and this repository is public. |
| 201 | # A shallow clone is refused rather than scanned, since its history is |
| 202 | # one commit; make history comes first in ci for that. |
| 203 | # --redact keeps anything it does find out of the CI log. |
| 204 | # |
| 205 | # The history of HEAD, not of every ref: gitleaks' default is |
| 206 | # `git log --all`, and a runner's clone keeps a ref for every branch it |
| 207 | # has built, so one branch's leak turned every other branch red, |
| 208 | # main included. A leak on a branch is that branch's own run's to find. |
| 209 | # The rest of the options are gitleaks' own defaults, which --log-opts |
| 210 | # replaces wholesale. |
| 211 | GITLEAKS := github.com/zricethezav/gitleaks/v8@v8.30.1 |
| 212 | gitleaks: |
| 213 | @if [ "$$(git rev-parse --is-shallow-repository)" != false ]; then echo "gitleaks: this clone is shallow, so its history is one commit; run make history first"; exit 1; fi |
| 214 | go run $(GITLEAKS) git --no-banner --redact --log-opts='--full-history --diff-filter=tuxdb HEAD' . |
| 215 | |
| 216 | # The browser drives, one package or slice per browser.<pkg>[~<k>of<n>] |
| 217 | # target (a / in the path is a . in the name). `make browser` runs them |
| 218 | # one after another; the suite runs each as its own shard. |
| 219 | # FIESTA_BROWSER_OPTIONAL stays unset on purpose: a skip is not a pass, |
| 220 | # so a machine that loses its browser fails loudly instead of reporting |
| 221 | # green. Chromium profiles also need room when the shared /tmp tmpfs |
| 222 | # fills, which hack/gotest.sh handles. |
| 223 | browser: $(BROWSER_ROLES) |
| 224 | |
| 225 | # The browser packages that need full Chromium rather than a headless |
| 226 | # shell, where both are installed (hack/gotest.sh). check's drives |
| 227 | # include the busy button, which needs the back/forward cache the |
| 228 | # shell never restores; the gallery's history package holds the legs |
| 229 | # that need that restore or a real new tab, and nothing else, so the |
| 230 | # cost of full Chromium falls on them alone. |
| 231 | export FULL_CHROMIUM := check internal/gallery/history |
| 232 | |
| 233 | browser.%: | $(BIN)/tmp |
| 234 | ./hack/gotest.sh browser '$*' |
| 235 | |
| 236 | # heights-sweep measures the preview heights table on this machine's |
| 237 | # Chromium and fonts and prints it as Go source for heights.go: both |
| 238 | # height drives over all twelve locales (FIESTA_SWEEP=full), at once, |
| 239 | # each into its own log, printed when both are done. It reports; the |
| 240 | # drives still fail a short box. Not in ci: it takes a quarter of an |
| 241 | # hour. To calibrate on a runner, push a throwaway branch whose one |
| 242 | # commit makes .amadan/ci.d this single step, 10-heights-sweep, being |
| 243 | # `exec make heights-sweep`; the table is in that run's log |
| 244 | # (amadan ci status rastrillo/fiesta -branch <it> -json). Then delete |
| 245 | # the branch. |
| 246 | HEIGHTS_SWEEP := FitTheirContent FitAtThePhonesNarrowest |
| 247 | heights-sweep: | $(BIN)/tmp |
| 248 | @d=$$(mktemp -d) && status=0 && pids= && \ |
| 249 | for drive in $(HEIGHTS_SWEEP); do \ |
| 250 | FIESTA_SWEEP=full ./hack/gotest.sh browser internal.gallery -timeout 30m -v -run "^TestPreviewFrameHeights$${drive}1of" > "$$d/$$drive.log" 2>&1 & \ |
| 251 | pids="$$pids $$!"; \ |
| 252 | done; \ |
| 253 | for pid in $$pids; do wait "$$pid" || status=1; done; \ |
| 254 | for drive in $(HEIGHTS_SWEEP); do echo "==> $$drive"; cat "$$d/$$drive.log"; done; \ |
| 255 | rm -rf "$$d"; exit $$status |
| 256 | |
| 257 | # site assembles fiesta.rastrillo.org into .build/site/public from the |
| 258 | # committed releases and the site source: each release under its exact |
| 259 | # version, _redirects sending /M/* to the newest M.y.z and the design |
| 260 | # system's former addresses to their pages, _headers caching /M/ and |
| 261 | # each release for five minutes, and at the root the pages |
| 262 | # internal/www assembles from site/, the landing page, the design |
| 263 | # system under /design-system/ and site/static/. The tree is |
| 264 | # .build/site/public; the command's own .fiesta stamp sits above it in |
| 265 | # .build/site, so nothing hidden is in the shipped tree. Nothing in ci |
| 266 | # depends on this target, since the releases are committed and checked |
| 267 | # there. It is for looking at: the site that ships is site-publish's, |
| 268 | # the one build that lets browsers keep a release for a year. |
| 269 | site: | $(BIN)/tmp |
| 270 | go run ./cmd/fiesta site -releases releases -site site -out .build/site |
| 271 | |
| 272 | # site-links-published is the ship check for links: the commit |
| 273 | # SITE_REF names (HEAD unless told), exported with git archive, so |
| 274 | # nothing uncommitted or untracked in the checkout counts, and its |
| 275 | # site's every link resolved as the edge will serve that tree, with |
| 276 | # /1/ reaching the newest release committed under releases/ and |
| 277 | # nothing else (FIESTA_LINKS_PUBLISHED=1). The suite's run of the same |
| 278 | # test lets a /1/ link reach the release the source would build while |
| 279 | # that release is uncut, so it passes a link that 404s the day the |
| 280 | # site ships ahead of its release; this does not. Not in ci, for that |
| 281 | # reason: it fails from a change to js/, css/ or i18n/ until the |
| 282 | # release carrying it is committed, as it should. Run it, and see it |
| 283 | # pass, before shipping the site. |
| 284 | # |
| 285 | # Each step that can fail ends the recipe with its own failure, and the |
| 286 | # scratch tree goes in an EXIT trap. A status kept in a variable through |
| 287 | # an && chain came out 0 when the export failed, so a bad SITE_REF |
| 288 | # passed with no link checked. The archive is written to a file rather |
| 289 | # than piped, so a failing git archive is not hidden behind tar's |
| 290 | # status. The trap keeps the status it was given, and turns a pass into |
| 291 | # a failure when the tree cannot be removed: a trap whose own status is |
| 292 | # dropped reported success with a checked-out site left in TMPDIR. |
| 293 | SITE_REF ?= HEAD |
| 294 | site-links-published: | $(BIN)/tmp |
| 295 | @d=$$(mktemp -d) || exit 1; \ |
| 296 | trap 's=$$?; rm -rf "$$d" || { echo "site-links-published: could not remove $$d"; \ |
| 297 | [ $$s -ne 0 ] || s=1; }; exit $$s' EXIT; \ |
| 298 | trap 'exit 130' INT TERM; \ |
| 299 | git archive --format=tar -o "$$d/site.tar" '$(SITE_REF)' || { \ |
| 300 | echo "site-links-published: git archive could not export $(SITE_REF)"; exit 1; }; \ |
| 301 | mkdir "$$d/tree" && tar -x -C "$$d/tree" -f "$$d/site.tar" || { \ |
| 302 | echo "site-links-published: could not extract $(SITE_REF)'s tree"; exit 1; }; \ |
| 303 | if ! grep -q FIESTA_LINKS_PUBLISHED "$$d/tree/internal/www/links_test.go"; then \ |
| 304 | echo "site-links-published: $(SITE_REF)'s link check has no published-only mode, so it would pass through the fallback; check a commit that has it"; exit 1; \ |
| 305 | fi; \ |
| 306 | cd "$$d/tree" && FIESTA_LINKS_PUBLISHED=1 go test ./internal/www -run '^TestEveryLinkInTheSiteResolves$$' -count=1 -v |
| 307 | |
| 308 | # site-publish builds the site that ships, from a release tag: |
| 309 | # make site-publish SITE_REF=vX.Y.Z, then carlos ship |
| 310 | # .build/publish/public. It is the one build that tells browsers to |
| 311 | # keep a release for a year as immutable. Every other build keeps each |
| 312 | # release five minutes, because until its tag exists a release may be |
| 313 | # rebuilt as a candidate under the same name, and a browser told it |
| 314 | # never changes would hold the old bytes for a year. |
| 315 | # |
| 316 | # It refuses, before building anything, unless SITE_REF is an |
| 317 | # annotated tag vX.Y.Z here, origin holds the same tag, the tag's tree |
| 318 | # has releases/X.Y.Z/ and /X/ would serve it, and the tag's fiesta site |
| 319 | # can be told what is published; then it runs site-links-published on |
| 320 | # the same tag, so the links are checked against what ships. It builds |
| 321 | # from git archive of the tag, as site-links-published does, with the |
| 322 | # tag's own fiesta site, and names every release directory in the |
| 323 | # tag's tree as published. fiesta site checks each against its tag |
| 324 | # vX.Y.Z here, file for file (site.TagDifferences, which |
| 325 | # TestPublishedReleasesMatchTheirTags runs on every push), and writes |
| 326 | # nothing if one differs. A tag from before -published existed is |
| 327 | # refused, rather than shipped with no _headers. The two targets sit |
| 328 | # together this way: site-links-published stays the check to run on |
| 329 | # any ref before tagging, and site-publish runs it again on the tag as |
| 330 | # its own first build step, so nothing ships unchecked. |
| 331 | # |
| 332 | # Each step that can fail ends the recipe with its own failure, and |
| 333 | # the export goes in an EXIT trap, as in site-links-published. The |
| 334 | # built site goes to .build/publish, outside the export, so it |
| 335 | # outlives the trap for carlos ship to read; a run that fails after |
| 336 | # building removes .build/publish/public, so a refused site is never |
| 337 | # there to ship. |
| 338 | PUBLISH_OUT := $(BIN)/publish |
| 339 | site-publish: | $(BIN)/tmp |
| 340 | @ref='$(SITE_REF)'; \ |
| 341 | printf '%s\n' "$$ref" | grep -Eqx 'v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)' || { \ |
| 342 | echo "site-publish: SITE_REF=$$ref is not a release tag vX.Y.Z; name the tag the site ships from"; exit 1; }; \ |
| 343 | version=$${ref#v}; major=$${version%%.*}; \ |
| 344 | [ "$$(git cat-file -t "refs/tags/$$ref" 2>/dev/null)" = tag ] || { \ |
| 345 | echo "site-publish: $$ref is not an annotated tag here; a release is tagged with git tag -a and pushed"; exit 1; }; \ |
| 346 | here=$$(git rev-parse "refs/tags/$$ref") || exit 1; \ |
| 347 | there=$$(GIT_TERMINAL_PROMPT=0 git ls-remote --tags origin "refs/tags/$$ref") || { \ |
| 348 | echo "site-publish: could not ask origin for $$ref"; exit 1; }; \ |
| 349 | [ "$${there%%[[:space:]]*}" = "$$here" ] || { \ |
| 350 | echo "site-publish: origin does not hold this $$ref ($$here); push the tag, or fetch origin's"; exit 1; }; \ |
| 351 | d=$$(mktemp -d) || exit 1; \ |
| 352 | trap 's=$$?; rm -rf "$$d" || { echo "site-publish: could not remove $$d"; \ |
| 353 | [ $$s -ne 0 ] || s=1; }; exit $$s' EXIT; \ |
| 354 | trap 'exit 130' INT TERM; \ |
| 355 | git archive --format=tar -o "$$d/site.tar" "refs/tags/$$ref" || { \ |
| 356 | echo "site-publish: git archive could not export $$ref"; exit 1; }; \ |
| 357 | mkdir "$$d/tree" && tar -x -C "$$d/tree" -f "$$d/site.tar" || { \ |
| 358 | echo "site-publish: could not extract $$ref's tree"; exit 1; }; \ |
| 359 | [ -d "$$d/tree/releases/$$version" ] || { \ |
| 360 | echo "site-publish: $$ref holds no releases/$$version, so the site would not serve the release it is shipped for"; exit 1; }; \ |
| 361 | newest=$$(ls "$$d/tree/releases" | grep -E "^$$major\.[0-9]+\.[0-9]+$$" | sort -t. -k2,2n -k3,3n | tail -n 1); \ |
| 362 | [ "$$newest" = "$$version" ] || { \ |
| 363 | echo "site-publish: /$$major/ would serve $$newest, not $$version; ship the tag of the newest $$major.x"; exit 1; }; \ |
| 364 | grep -q '"published"' "$$d/tree/cmd/fiesta/main.go" || { \ |
| 365 | echo "site-publish: $$ref's fiesta site has no -published, so it cannot tell browsers a release never changes; ship a tag that has it"; exit 1; }; \ |
| 366 | published=$$(ls "$$d/tree/releases" | paste -sd, -); \ |
| 367 | repo=$$(git rev-parse --show-toplevel) || exit 1; \ |
| 368 | $(MAKE) --no-print-directory site-links-published SITE_REF="refs/tags/$$ref" || { \ |
| 369 | echo "site-publish: the links of $$ref's site do not all resolve against its published releases"; exit 1; }; \ |
| 370 | echo "site-publish: building $$ref with $$published published"; \ |
| 371 | (cd "$$d/tree" && go run -buildvcs=false ./cmd/fiesta site -releases releases -site site \ |
| 372 | -out '$(PUBLISH_OUT)' -published "$$published" -git "$$repo") || { \ |
| 373 | echo "site-publish: fiesta site refused $$ref"; exit 1; }; \ |
| 374 | grep -qxF "/$$major/* /$$version/:splat 200" '$(PUBLISH_OUT)/public/_redirects' || { \ |
| 375 | rm -rf '$(PUBLISH_OUT)/public'; \ |
| 376 | echo "site-publish: the built _redirects does not send /$$major/ to $$version; removed $(PUBLISH_OUT)/public"; exit 1; }; \ |
| 377 | echo "site-publish: $(PUBLISH_OUT)/public is $$ref's site, $$published kept for a year; ship that tree" |
| 378 | |
| 379 | root no-rastrillo size staticcheck govulncheck gitleaks: | $(BIN)/tmp |
| 380 | |
| 381 | $(BIN)/tmp: |
| 382 | mkdir -p "$@" |
| 383 | |