oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1// The editor bundle (spec §3.1, §8: the one surface that needs
2// scripts) is a committed, prebuilt artifact — never built by this app
3// or by CI — produced by fixtures/build-bundles.mjs and committed under
4// internal/slides/static/ as editor.<hash>.js. Docs' assets.go
5// pattern, adopted: the bundle's name is resolved once at package init
6// and the process refuses to start without it.
7//
8// It lives in static/ beside the vendored stylesheets rather than in a
9// tree of its own because render.go's assets already embeds and
10// fingerprints everything under static/. The bundle carries a content
11// hash in its own name (so a build that changed the bytes changes the
12// URL) and assets.Path adds a second, which is harmless: the asset
13// handler strips one hash to find the file and serves it immutable
14// when the hash it stripped matches the content, which it always does.
15package slides
16
17import (
18 "fmt"
19 "io/fs"
20 "strings"
21)
22
23// bundleKinds is every bundle that must exist. Listed rather than
24// discovered from the directory: a build that produced no bundle at all
25// would otherwise start cleanly and serve an editor page whose script
26// tag 404s, which is the same silent-failure shape the exactly-one rule
27// below exists to prevent. One today; Docs has two.
28var bundleKinds = []string{"editor"}
29
30// bundleNames maps each committed bundle's kind to its filename under
31// static/ — "editor" to "editor.f13f5e0ce6fa7467.js" — resolved once,
32// at package init, from the embedded tree.
33//
34// Exactly one file must ever be present per kind.
35// fixtures/build-bundles.mjs deletes every previous "<kind>.<hash>.js"
36// before writing the new one, so a tree with two (a script edited to
37// skip that step, a hand-added file, a bad merge) is a mistake — this
38// panics at init rather than silently picking one, because a wrong
39// bundle silently served is indistinguishable from a correct one until
40// it disagrees with the server at runtime.
41var bundleNames = mustResolveBundleNames()
42
43// isBundleFile reports whether name is the shape build-bundles.mjs
44// writes: the kind, a dot, sixteen hex digits, ".js".
45func isBundleFile(kind, name string) bool {
46 rest, ok := strings.CutPrefix(name, kind+".")
47 if !ok {
48 return false
49 }
50 h, ok := strings.CutSuffix(rest, ".js")
51 if !ok || len(h) != 16 {
52 return false
53 }
54 for _, c := range h {
55 if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
56 return false
57 }
58 }
59 return true
60}
61
62func mustResolveBundleNames() map[string]string {
63 entries, err := fs.ReadDir(appFS, "static")
64 if err != nil {
65 panic(fmt.Sprintf("slides: read embedded static/: %v", err))
66 }
67 out := make(map[string]string, len(bundleKinds))
68 for _, kind := range bundleKinds {
69 var found []string
70 for _, e := range entries {
71 if !e.IsDir() && isBundleFile(kind, e.Name()) {
72 found = append(found, e.Name())
73 }
74 }
75 if len(found) != 1 {
76 panic(fmt.Sprintf("slides: want exactly one %q bundle in internal/slides/static/, found %v — run `node fixtures/build-bundles.mjs` and commit its output", kind, found))
77 }
78 out[kind] = found[0]
79 }
80 return out
81}
82
83// bundlePath is the URL a committed bundle is served at, through the
84// same fingerprinting handler every stylesheet goes through. The
85// template's <script> tag uses this rather than a literal, so it can
86// never drift from what the handler actually serves.
87func bundlePath(kind string) string {
88 name, ok := bundleNames[kind]
89 if !ok {
90 // Unreachable while every caller passes a member of
91 // bundleKinds, which init has already proved resolvable.
92 panic(fmt.Sprintf("slides: no committed bundle named %q", kind))
93 }
94 return assets.Path("static/" + name)
95}
96
97// editorBundlePath is the editor's bundle — templates/editor.html's
98// <script type="module"> tag.
99func editorBundlePath() string { return bundlePath("editor") }
100