feat(ui): Add static asset caching#5113
Conversation
Previously, bootstrap and font-awesome were manually vendored under
`ui/app/lib/`. They are now fetched via `npm`. Vite bundles these two
and the elm-datepicker at build time into a single file.
URL changes:
- /script.js → /assets/index-[hash].js (content-hashed)
- /lib/bootstrap-*/css/bootstrap.min.css{,.map} → merged into /assets/index-[hash].css
- /lib/elm-datepicker/css/elm-datepicker.css → merged into /assets/index-[hash].css
- /lib/font-awesome-*/css/font-awesome{,.min}.css → merged into /assets/index-[hash].css
- /lib/font-awesome-*/fonts/{woff,woff2,ttf,eot,svg} → /assets/fontawesome-webfont-[hash].{ext}
FontAwesome.otf was removed entirely, this is not a web font.
Files under /assets/ are served with "Cache-Control: public,
max-age=31536000, immutable" (content-hashed filenames make them safe to
cache forever).
In a previous PR (prometheus#5028), `Alertmanager` stopped sending "Last-Modified:
Thu, 01 Jan 1970 00:00:01 GMT". I introduced this behaviour by accident,
but consider this correct behaviour. The findings are documented via a
test.
This change removes various build artifacts, most importantly
`ui/app/script.js`. The following command no longer works in isolation:
```sh
go get github.com/prometheus/alertmanager/ui
```
Users now must build `ui/app/dist` from source.
The MIME types are now hard-coded, removing the dependency on the OS
MIME database. In practice, this would only affect `FontAwesome.otf`,
which we no longer serve anyway.
Signed-off-by: Solomon Jacobs <solomonjacobs@protonmail.com>
Previously, the Makefile used `:=` for the TEMPOPENAPI variable, causing `mktemp` to run at parse time on every `make` invocation. The temp dir was only cleaned up inside the `src/Data` recipe. In the GitHub workflow, this left a root-owned `700` directory on the host, which blocks `hashFiles` in post-job cleanups. Because of this, it was previously not possible to invoke any `ui/app/Makefile` command during the `test` stage. Signed-off-by: Solomon Jacobs <solomonjacobs@protonmail.com>
📝 WalkthroughWalkthroughThis pull request migrates the Alertmanager frontend from a simple script-based build to a modern Vite-based pipeline with OpenAPI code generation, removing CSS-loading logic from the Elm application and restructuring static asset serving. Changes
Sequence DiagramssequenceDiagram
participant Dev as Developer
participant Make as make build
participant Docker as OpenAPI<br/>Generator
participant Node as npm build<br/>(Vite)
participant Elm as Elm Compiler
participant Dist as dist/
Dev->>Make: Invoke make build
Make->>Docker: Run container<br/>api/v2/openapi.yaml
Docker->>Docker: Generate src/Data
Docker->>Make: Copy src/Data & src/DateTime.elm
Make->>Node: Invoke npm run build<br/>(src/main.js entry)
Node->>Elm: Transform Elm modules
Elm->>Node: Compiled JS + CSS
Node->>Dist: Write dist/ + .build_stamp
sequenceDiagram
participant Browser
participant Server as ui/web.go
participant Dist as app/dist<br/>(embedded FS)
participant LocalStorage as Browser<br/>localStorage
participant Elm as Elm.Main
participant Port as Elm Ports
Browser->>Server: GET /
Server->>Dist: Lookup index.html
Dist->>Server: Return index.html
Server->>Browser: Serve index.html
Browser->>Browser: Load <script type="module"><br/>src/main.js
Browser->>LocalStorage: Read flags<br/>(firstDayOfWeek, etc.)
LocalStorage->>Browser: flags object
Browser->>Elm: Initialize with flags,<br/>node id="root"
Elm->>Browser: Render UI
User->>Browser: Change setting
Browser->>Port: persistDefaultCreator
Port->>LocalStorage: Store preference
LocalStorage->>LocalStorage: Persist
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
ui/app/src/main.js (1)
6-14: Consider guardingJSON.parsecalls against corrupted localStorage values.
JSON.parse(localStorage.getItem('firstDayOfWeek'))andJSON.parse(localStorage.getItem('groupExpandAll'))will throw if the stored value is malformed (e.g., manually edited or corrupted). While the Elm decoder handlesnullgracefully viaResult.withDefault, a parse error would crash initialization.🛡️ Optional: Add defensive parsing
+function safeJsonParse(key, fallback) { + try { + const item = localStorage.getItem(key); + return item !== null ? JSON.parse(item) : fallback; + } catch { + return fallback; + } +} + const app = Elm.Main.init({ node: document.getElementById('root'), flags: { production: true, - firstDayOfWeek: JSON.parse(localStorage.getItem('firstDayOfWeek')), + firstDayOfWeek: safeJsonParse('firstDayOfWeek', null), defaultCreator: localStorage.getItem('defaultCreator'), - groupExpandAll: JSON.parse(localStorage.getItem('groupExpandAll')) + groupExpandAll: safeJsonParse('groupExpandAll', null) } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/app/src/main.js` around lines 6 - 14, Guard the JSON.parse calls used in the Elm startup flags to avoid throwing on malformed localStorage values: wrap parsing of localStorage.getItem('firstDayOfWeek') and localStorage.getItem('groupExpandAll') in a safe parse (e.g., try/catch or a small helper like safeJSONParse) and fall back to null or a sensible default when parsing fails, then pass those safe values into Elm.Main.init(flags) alongside defaultCreator; update the code around Elm.Main.init and the flags construction to use the safe parser for firstDayOfWeek and groupExpandAll.ui/app/Makefile (2)
21-30: Thetesttarget should also be declared.PHONY.Similar to
all, thetesttarget should be declared.PHONYto ensure it always runs regardless of whether a file namedtestexists. This is flagged by checkmake.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/app/Makefile` around lines 21 - 30, Add the `test` target to the `.PHONY` declaration so Make always executes the `test` recipe regardless of a file named "test"; update the existing `.PHONY` line (alongside the `all` target) to include `test` (referencing the Makefile's `test` target) so checkmake warnings are resolved.
9-9: Declareallas.PHONYto prevent file collision.If a file named
allexists in the directory, Make will consider the target up-to-date and skip execution. Thealltarget should be declared.PHONY.Suggested fix
Add this declaration near line 56 (with the existing
.PHONYdeclarations):+.PHONY: all +all: src/Data build test + -.PHONY: build +.PHONY: build test build: .build_stamp🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/app/Makefile` at line 9, The `all` Make target is not declared phony, so if a file named "all" exists Make may skip running targets; update the Makefile to include `all` in the existing `.PHONY` declaration (add `all` to the comma/space-separated list with the other phony targets) so the `all: src/Data build test` target always executes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@ui/app/Makefile`:
- Around line 21-30: Add the `test` target to the `.PHONY` declaration so Make
always executes the `test` recipe regardless of a file named "test"; update the
existing `.PHONY` line (alongside the `all` target) to include `test`
(referencing the Makefile's `test` target) so checkmake warnings are resolved.
- Line 9: The `all` Make target is not declared phony, so if a file named "all"
exists Make may skip running targets; update the Makefile to include `all` in
the existing `.PHONY` declaration (add `all` to the comma/space-separated list
with the other phony targets) so the `all: src/Data build test` target always
executes.
In `@ui/app/src/main.js`:
- Around line 6-14: Guard the JSON.parse calls used in the Elm startup flags to
avoid throwing on malformed localStorage values: wrap parsing of
localStorage.getItem('firstDayOfWeek') and
localStorage.getItem('groupExpandAll') in a safe parse (e.g., try/catch or a
small helper like safeJSONParse) and fall back to null or a sensible default
when parsing fails, then pass those safe values into Elm.Main.init(flags)
alongside defaultCreator; update the code around Elm.Main.init and the flags
construction to use the safe parser for firstDayOfWeek and groupExpandAll.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 25288fc3-3dc4-4e02-bbf8-278dd0d76d21
⛔ Files ignored due to path filters (9)
ui/app/lib/bootstrap-4.6.2-dist/css/bootstrap.min.css.mapis excluded by!**/*.mapui/app/lib/font-awesome-4.7.0/fonts/FontAwesome.otfis excluded by!**/*.otfui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.eotis excluded by!**/*.eotui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.svgis excluded by!**/*.svgui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.ttfis excluded by!**/*.ttfui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.woffis excluded by!**/*.woffui/app/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2is excluded by!**/*.woff2ui/app/package-lock.jsonis excluded by!**/package-lock.jsonui/app/public/favicon.icois excluded by!**/*.ico
📒 Files selected for processing (19)
MakefileREADME.mdui/app/.gitignoreui/app/Makefileui/app/index.htmlui/app/lib/bootstrap-4.6.2-dist/css/bootstrap.min.cssui/app/lib/font-awesome-4.7.0/css/font-awesome.cssui/app/lib/font-awesome-4.7.0/css/font-awesome.min.cssui/app/package.jsonui/app/script.jsui/app/src/Main.elmui/app/src/Types.elmui/app/src/Updates.elmui/app/src/Views.elmui/app/src/assets/elm-datepicker.cssui/app/src/main.jsui/app/vite.config.mjsui/web.goui/web_test.go
💤 Files with no reviewable changes (5)
- ui/app/src/Updates.elm
- ui/app/src/Main.elm
- ui/app/lib/font-awesome-4.7.0/css/font-awesome.min.css
- ui/app/src/Types.elm
- ui/app/lib/font-awesome-4.7.0/css/font-awesome.css
sysadmind
left a comment
There was a problem hiding this comment.
I don't really know elm, but from a once over, it seems fine. I didn't try to run the build process, but I don't see anything that I find concerning.
| endif | ||
|
|
||
| all: script.js test | ||
| all: src/Data build test |
There was a problem hiding this comment.
Shouldn't this and the other modified make targets be PHONY?
There was a problem hiding this comment.
Yes, almost all targets in the file should be PHONY. I have a follow-up commit, where I fix this for all the targets. However, it is not strictly necessary for this feature, and the target wasn't PHONY there before.
|
|
||
|
|
||
| set -e; \ | ||
| $(DOCKER) run \ |
There was a problem hiding this comment.
Should this container have --rm? Should it stick around? If it's going to stay around, I would expect it to have a more unique --name
There was a problem hiding this comment.
The trap will remove the docker container again, and we can't immediately remove the container, since we need to copy the files.
|
I also did a rough review and manually tested some functionality. So far it looks good to me. |
Spaceman1701
left a comment
There was a problem hiding this comment.
I didn't patch this into my checkout and run it, but I trust that you've run it @SoloJacobs.
* [CHANGE] `go get github.com/prometheus/alertmanager/ui` will now fail as compiled UI assets are no longer checked into the repository. Downstream builds that rely on these assets being present in the source tree must now build the UI from source. prometheus#5113 * [CHANGE] The '--enable-feature=auto-gomaxprocs' option is deprecated and will be removed in v0.33. This flag currently has no effect and can be safely removed from any startup scripts. prometheus#5090 * [CHANGE] Update internal function signatures across multiple packages. This affects any project that integrates `Alertmanager` code. * [ENHANCEMENT] Add static asset caching. prometheus#5113 * [ENHANCEMENT] Reduce memory allocations through pre-sizing collections and batch allocation. prometheus#5020 * [ENHANCEMENT] Replace help with documentation in navigation bar. prometheus#4943 * [ENHANCEMENT] docs(ha): Update high availability documentation. prometheus#5136 * [ENHANCEMENT] docs: Add `auth_secret_file` for smtp in document. prometheus#5036 * [ENHANCEMENT] docs: Add description for global `telegram_bot_token`. prometheus#5114 * [ENHANCEMENT] docs: Add note about notifier timeouts. prometheus#5077 * [ENHANCEMENT] docs: Fix `force_implicit_tls` config field name. prometheus#5030 * [ENHANCEMENT] docs: Link community supported integrations. prometheus#4978 * [ENHANCEMENT] docs: Remove duplicate header. prometheus#5034 * [ENHANCEMENT] docs: Update mutual tls reference in high availability documentation. prometheus#5120 * [ENHANCEMENT] tracing: Use noop spans when tracing disabled. prometheus#5118 * [FEATURE] Add silence annotations. prometheus#4965 * [FEATURE] Add silence logging option. prometheus#4163 * [FEATURE] Add support for multiple matcher set silences. prometheus#4957 * [FEATURE] Add the reason for notifying in dedup stage. prometheus#4971 * [FEATURE] mattermost: Flatten attachments into top-level config. prometheus#5009 * [FEATURE] mattermost: Support global webhook url. prometheus#4998 * [FEATURE] slack: Add default color from template. prometheus#5014 * [FEATURE] slack: Allow receiver to edit existing messages. prometheus#5007 * [FEATURE] template: Add dict, map and append functions. prometheus#5093 * [FEATURE] webhook: Add full payload templating support for notifier. prometheus#5011 * [BUGFIX] config: Check for empty cluster tls client config. prometheus#5126 * [BUGFIX] config: Don't crash upon reading empty config for notifier. prometheus#4979 * [BUGFIX] config: Fix ipv6 address handling in hostport.string(). prometheus#5040 * [BUGFIX] mattermost: Omit empty text field in notifications. prometheus#4985 * [BUGFIX] telegram: Send fallback message when notification exceeds character limit. prometheus#5074 * [BUGFIX] ui: Fix escaping for matcher values with quotes. prometheus#4862 * [BUGFIX] ui: Handle special chars in silence regex-matchers. prometheus#4942 * [BUGFIX] ui: Support utf-8 label names in matchers. prometheus#5089 Signed-off-by: Solomon Jacobs <solomonjacobs@protonmail.com>
* [CHANGE] `go get github.com/prometheus/alertmanager/ui` will now fail as compiled UI assets are no longer checked into the repository. Downstream builds that rely on these assets being present in the source tree must now build the UI from source. #5113 * [CHANGE] The '--enable-feature=auto-gomaxprocs' option is deprecated and will be removed in v0.33. This flag currently has no effect and can be safely removed from any startup scripts. #5090 * [CHANGE] Update internal function signatures across multiple packages. This affects any project that integrates `Alertmanager` code. * [ENHANCEMENT] Add static asset caching. #5113 * [ENHANCEMENT] Reduce memory allocations through pre-sizing collections and batch allocation. #5020 * [ENHANCEMENT] Replace help with documentation in navigation bar. #4943 * [ENHANCEMENT] docs(ha): Update high availability documentation. #5136 * [ENHANCEMENT] docs: Add `auth_secret_file` for smtp in document. #5036 * [ENHANCEMENT] docs: Add description for global `telegram_bot_token`. #5114 * [ENHANCEMENT] docs: Add note about notifier timeouts. #5077 * [ENHANCEMENT] docs: Fix `force_implicit_tls` config field name. #5030 * [ENHANCEMENT] docs: Link community supported integrations. #4978 * [ENHANCEMENT] docs: Remove duplicate header. #5034 * [ENHANCEMENT] docs: Update mutual tls reference in high availability documentation. #5120 * [ENHANCEMENT] tracing: Use noop spans when tracing disabled. #5118 * [FEATURE] Add silence annotations. #4965 * [FEATURE] Add silence logging option. #4163 * [FEATURE] Add support for multiple matcher set silences. #4957 * [FEATURE] Add the reason for notifying in dedup stage. #4971 * [FEATURE] mattermost: Flatten attachments into top-level config. #5009 * [FEATURE] mattermost: Support global webhook url. #4998 * [FEATURE] slack: Add default color from template. #5014 * [FEATURE] slack: Allow receiver to edit existing messages. #5007 * [FEATURE] template: Add dict, map and append functions. #5093 * [FEATURE] webhook: Add full payload templating support for notifier. #5011 * [BUGFIX] config: Check for empty cluster tls client config. #5126 * [BUGFIX] config: Don't crash upon reading empty config for notifier. #4979 * [BUGFIX] config: Fix ipv6 address handling in hostport.string(). #5040 * [BUGFIX] mattermost: Omit empty text field in notifications. #4985 * [BUGFIX] telegram: Send fallback message when notification exceeds character limit. #5074 * [BUGFIX] ui: Fix escaping for matcher values with quotes. #4862 * [BUGFIX] ui: Handle special chars in silence regex-matchers. #4942 * [BUGFIX] ui: Support utf-8 label names in matchers. #5089 Signed-off-by: Solomon Jacobs <solomonjacobs@protonmail.com>
|
Seems this change missed ui-assets tarball release, like in Prometheus. Sometimes is hard to bring up npm only for build assets... Please provide tarball with release |
|
Hi @k0ste , thanks for the feedback. Could you provide a reference to the upstream project where this is needed? The build process between alertmanager and Prometheus differs. Kind regards |
For example EPEL prometheus package We also have custom build with reduced SD integrations, also but don't builtin assets to binary. Source SPEC %global debug_package %{nil}
%define _build_id_links none
Summary: An open-source systems monitoring and alerting toolkit
Name: prometheus
Version: 3.11.0
%define _archversion 3.10.0-1
Release: 1%{?dist}
License: Apache-2.0
BuildRequires: golang >= 1.25 git systemd-rpm-macros
%define _uri github.com/%{name}
Url: https://%{_uri}/%{name}
Source0: https://codeload.%{_uri}/%{name}/tar.gz/refs/tags/v%{version}#/%{name}-%{version}.tar.gz
Source1: %{url}/releases/download/v%{version}/%{name}-web-ui-%{version}.tar.gz
Source2: https://gitlab.archlinux.org/archlinux/packaging/packages/%{name}/-/raw/%{_archversion}/%{name}.sysusers
Source3: https://gitlab.archlinux.org/archlinux/packaging/packages/%{name}/-/raw/%{_archversion}/%{name}.service
Source4: https://gitlab.archlinux.org/archlinux/packaging/packages/%{name}/-/raw/%{_archversion}/%{name}.conf
%description
Prometheus, a Cloud Native Computing Foundation project, is a systems and
service monitoring system. It collects metrics from configured targets at given
intervals, evaluates rule expressions, displays the results, and can trigger
alerts when specified conditions are observed
%prep
%setup -c -q
%setup -a 1 -qcTD
rm -rf "%{name}-%{version}/web/ui/static"
mv "static" "%{name}-%{version}/web/ui"
export GOPATH="%{_builddir}/gopath"
export GOBIN="${GOPATH}/bin"
mkdir -p "${GOPATH}/src/%{_uri}"
ln -snf "%{_builddir}/%{name}-%{version}/%{name}-%{version}" \
"${GOPATH}/src/%{_uri}/%{name}"
%build
export GOCACHE="${HOME}/pkgcache/go-cache"
export GOMODCACHE="${HOME}/pkgcache/go"
export GOTMPDIR="%{_builddir}"
export GOPATH="%{_builddir}/gopath"
export GOBIN="${GOPATH}/bin"
cd "${GOPATH}/src/%{_uri}/%{name}"
eval "$(go env | grep -e "GOHOSTOS" -e "GOHOSTARCH")"
pushd "web/ui"
go generate -x
popd
go generate -x -tags="plugins" "./plugins"
GOFLAGS="-linkmode external \
-X %{_uri}/common/version.Version=%{version} \
-X %{_uri}/common/version.Revision=%{release} \
-X %{_uri}/common/version.Branch=tarball \
-X %{_uri}/common/version.BuildUser=$(whoami)@mockbuild \
-X %{_uri}/common/version.BuildDate=$(date +%%Y%%m%%d)"
# Build prometheus & promtool
# tags: https://github.com/prometheus/prometheus/blob/main/.promu.yml#L14
# WARNING: 'builtinassets' break the build
# 'remove_all_sd' - we don't need any SD, except file/HTTP (builtin by default)
for e in "%{name}" "promtool"
do
GOOS="${GOHOSTOS}" GOARCH="${GOHOSTARCH}" \
go build -x \
-tags="netgo,remove_all_sd" \
-buildmode="pie" \
-trimpath \
-mod="readonly" \
-modcacherw \
-ldflags "${GOFLAGS}" "./cmd/${e}"
done
%check
cd "%{name}-%{version}"
eval "$(go env | grep -e "GOHOSTOS" -e "GOHOSTARCH")"
GOOS="${GOHOSTOS}" GOARCH="${GOHOSTARCH}" go test -x ./...
%install
install -Dm0644 "%{SOURCE2}" "%{buildroot}%{_sysusersdir}/%{name}.conf"
install -Dm0644 "%{SOURCE3}" -t "%{buildroot}%{_unitdir}"
install -Dm0644 "%{SOURCE4}" "%{buildroot}%{_sysconfdir}/conf.d/%{name}"
pushd "%{name}-%{version}"
install -Dm0755 -d "%{buildroot}%{_sharedstatedir}/%{name}"
install -Dm0755 "%{name}" -t "%{buildroot}%{_bindir}"
install -Dm0755 "promtool" -t "%{buildroot}%{_bindir}"
install -Dm0644 "documentation/examples/%{name}.yml" -t \
"%{buildroot}%{_sysconfdir}/%{name}"
# Web
install -Dm0755 -d "%{buildroot}%{_datadir}/%{name}/web/ui"
cp --recursive "web/ui/static" "%{buildroot}%{_datadir}/%{name}/web/ui"
# Examples
install -Dm0644 "documentation/examples/%{name}"*".yml" -t \
"%{buildroot}%{_datadir}/%{name}/examples"
%pre
%sysusers_create_package %{name} "%{SOURCE2}"
%post
%systemd_post %{name}.service
%preun
%systemd_preun %{name}.service
%postun
%systemd_postun %{name}.service
%files
%license %{name}-%{version}/LICENSE
%attr(0755,root,root) %{_bindir}/{%{name},promtool}
%dir %attr(0755,%{name},%{name}) %{_sharedstatedir}/%{name}
%defattr(0644,root,root,0755)
%{_datadir}/%{name}
%{_unitdir}/%{name}.service
%{_sysusersdir}/%{name}.conf
%config(noreplace) %{_sysconfdir}/conf.d/%{name}
%config(noreplace) %{_sysconfdir}/%{name}/%{name}.yml |
This PR uses
viteto build the Elm ui. This has a number of consequences, which are detailed in the commit messages.Most importantly, it introduces caching for static assets and removes the
script.jsbuild artifact from our repo.This is an overview over the files provided by Alertmanager
v0.31.1via theui, which might be handy for debugging:/favicon.icoimage/vnd.microsoft.icon/text/html; charset=utf-8/script.jstext/javascript; charset=utf-8/lib/bootstrap-4.6.2-dist/css/bootstrap.min.csstext/css; charset=utf-8/lib/bootstrap-4.6.2-dist/css/bootstrap.min.css.maptext/plain; charset=utf-8/lib/elm-datepicker/css/elm-datepicker.csstext/css; charset=utf-8/lib/font-awesome-4.7.0/css/font-awesome.csstext/css; charset=utf-8/lib/font-awesome-4.7.0/css/font-awesome.min.csstext/css; charset=utf-8/lib/font-awesome-4.7.0/fonts/FontAwesome.otfapplication/vnd.oasis.opendocument.formula-template/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.eotapplication/vnd.ms-fontobject/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.svgimage/svg+xml/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.ttffont/ttf/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.wofffont/woff/lib/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2font/woff2Pull Request Checklist
(I checked the binary sizes, and
vitewill show the web artifacts.)ui. This can be done via a work space:Which user-facing changes does this PR introduce?
Summary by CodeRabbit
New Features
Tests
Chores
make buildinstead ofgo install.