Added
- Publish native arm64 release archives for Linux and macOS, with matching npm
packages and shell-installer support. Apple Silicon and Linux arm64 installs
now receive binaries built and exercised on their native runner architecture. zigapagos release --summary: after a build, print on stdout an inventory of the files it
emitted, grouped by category — pages, page aliases and alternatives, page assets, site
assets, build assets, SPA shells, SPA routing manifests and the SPA 404 fallback. Every entry
is recorded where the file is written, andtests/summary/summary.shcompares the printed set
against the emitted tree, so the report cannot describe a tree the build did not produce. A
build with rendering errors prints a one-line refusal instead of an inventory — on stdout too,
so--summary >fileanswers on the same stream whatever the build's outcome.- A shell installer,
curl -fsSL https://valthon.github.io/zigapagos/install.sh | sh, now the
headline install method on the README and the download page. It installs a complete
zigapagos — the binary, the@z/runtimetree it renders islands and SPAs through, and Bun and
ZigBase when the host has neither — under~/.local/share/zigapagos, with a generated launcher
in~/.local/bin. Nosudo, no edits to shell startup files, and nothing written until each
download has been verified against the release's published SHA-256 sums. It is idempotent: a
second run installs alongside the first and repoints the launcher.--version,--prefix,
--bin-dir,--no-bunand--no-zigbasecover the rest. Windows hosts are refused with the
same wording the npm package uses, rather than being given an emulated build that looks native. - A
runtime.tar.xzrelease asset: the@z/runtimetree with its dependencies vendored. This is
what makes an install outside npm able to render an island at all — the per-target archives
carry the binary alone, and the sidecar, bundlers and slicers are scripts inside that tree. It
is staged by the same code that stages npm's copy (npm/stage-runtime.mjs), so the two
channels ship the same files by construction. zigapagos releasebuilds the per-site islands runtime slice. The second
pass over the built island bundles (/islands/_runtime.js) used to run only
as a build-graph step, so a toolchain-free build silently shipped the full
shared runtime to every island page.zigapagos releaseemits host config and the strict-CSP artifacts. The
per-namespace server config (.spamarker +zigbase.static_routes.zig,
nginx.nginx.conf,.htaccess) and the site-widecsp.{nginx.conf,apache.conf,zigbase.txt}
are written over the finished output tree. Every npm-path build until now
shipped a tree with neither, which loses SPA deep-link fallback and serves a
CSP that blocks the site's own inline import map.zigapagos release --source-maps, replacingOptions.source_maps. Still
opt-in and off by default.docs/runtime-dependencies.md— what the standalone binary needs at run
time, stated once instead of inferred. A table covering every command and the
external programs it requires; whenzigapagos releaseactually needs Bun
(the condition is the configuration, not whether the site has islands — with
ZIGAPAGOS_RUNTIME_DIRset, a site with none still spawns the sidecar); how
the pinned ZigBase is resolved, cached and fetched, including thecurland
tarthe fetch shells out to; and what each distribution supplies. Notably:
a release archive carries the binary alone, so islands and SPAs built from one
need an@z/runtimetree pointed at byZIGAPAGOS_RUNTIME_DIR—@z/runtime
isprivate: trueand cannot be installed from npm on its own.tests/meta/runtime-deps-doc.shchecks that page against the sources every
claim came from: the command table againstsrc/main.zig'sCommandenum,
the ZigBase pin and cache path againstsrc/cli/zigbase.zig, the environment
variable againstsrc/cli/release.zig,@z/runtime's privacy against
runtime/package.json, the binary-only release archive against
build/release.zig, and every flag the page names against the file that
parses it.
Changed
- Build the x86_64 macOS archive natively on
macos-15-intelinstead of
cross-compiling it on an arm64macos-latestrunner. - Release and development builds now share the checked-in Wuffs translation
shims instead of release builds invokingzig translate-c. This removes a
hand-maintained divergence and avoids the Zig 0.16 translation crash that
blocked native arm64 artifacts. docs/runtime-dependencies.md's distribution table gains aninstall.shcolumn, and its
gate (tests/meta/runtime-deps-doc.sh) gains a rule that fails the build if that column
ever describes a script or an asset that no longer exists.zigapagos initnow scaffolds only the frontmatter a page needs..author
and.draftare gone from every template and.dateremains only on the blog
posts and devlog years whose listing layouts render one —.titleand
.layoutare the only required fields, and the scaffold no longer models the
optional ones as obligatory. The sample homepage and the quick start now say
which fields have defaults.zigapagos migrate --helpnow states outright that the command converts
nothing: it reads the Astro project, writes aMIGRATION.mdworklist, and
the port itself is manual —--scaffoldbeing the one exception, and only
for islands. The README bullet and the site's overview page said or implied
otherwise.zigapagos devis zero-config. Run it in a site directory with no
arguments and it works:--sitedefaults topublic, the same directory a barezigapagos release
writes to;- the rebuild command defaults to this binary's own
release, resolved by
absolute path rather than by name onPATH, so an npm install and a
downloaded release tarball both work (it waszig build, which named a
toolchain a standalone user never installed); - the island/SPA source directories to watch are derived from the entries
releasediscovers, so a component edit rebuilds without--watch-dir; - a missing
zigbaseis fetched from the pinned release into the cache
(SHA256-verified) instead of failing with instructions.--no-download
restores the previous behaviour for offline machines and for CI that pins
its own binary.zigapagos e2eis unchanged: it still fetches only on
--download-zigbase, because an unannounced network fetch in CI is a
surprise rather than a convenience.
zigapagos initnow points a new site atzigapagos devrather than at the
bare command.
Removed
-
The bundled live server is gone, along with its
--proxyreverse-proxy
mode, theserveandserversubcommands, and the bare-command entry point
that started it (issue #56).zigapagosis a standalone executable, and a
standalone executable has no default action: run bare it now prints its help
and exits 0, which is whatnpx zigapagosdoes too. An argument that names no
command prints the same menu and exits non-zero. -
The consumer zig-build API is gone.
zigapagos.website(),zigapagos.e2e()
andzigapagos.dev(), the option types (Options,Island,Spa,
BuildAsset,E2eOptions,DevOptions) and the wholebuild/half that
served them no longer exist. A site is built by RUNNING thezigapagos
binary;zig buildbuilds zigapagos itself and nothing else. Nothing in a
zigapagos project needs a Zig toolchain, abuild.zig, abuild.zig.zonor a
.pathdependency on this repository any more.The replacements, all of which already existed:
was now zigapagos.website(b, .{ .islands = …, .spas = … })zigapagos release --island=SRC --spa='SRC|BASE'zigapagos.e2e(b, opts, .{})+zig build e2e -- CMDzigapagos e2e --site=DIR -- CMDzigapagos.dev(b, opts, .{})+zig build devzigapagos devOptions.source_maps = truezigapagos release --source-mapsOptions.not_found--spa-not-found=NAMEOptions.build_assets--build-asset=NAME PATH [--install=P | --install-always=P] -
zigapagos release --spa-chunks=and--spa-slice=are removed. They existed
to handreleasebundles the build graph had already produced; it now builds
them itself. -
zigapagos init --from-astro --zigapagos-pathis removed with the
build.zig.zonit filled in. The importer scaffolds abuild.shinstead.
Fixed
zigapagos releasenow honoursZIGAPAGOS_HOT_ISLANDS, passing--hotto
the island bundle driver when it is set. Nothing on thereleasepath read
the variablezigapagos devsets, so a dev rebuild produced non-hot island
bundles and an island hot-swap silently reset everyuseStateinstead of
preserving it.- A CLI report no longer overwrites the command's own stderr when both streams are redirected
to one file (cmd >f 2>&1).explain,doctor,validateandmigrate --doctorbuilt
their buffered stdout writers withIo.File.writer, which writes positionally from an offset
of its own, so the end-of-command flush landed on top of bytes stderr had already committed —
silently corrupting the merged output. They now usewriterStreaming. - Island bundles are minified. The build graph passed
--minifyto the
shared runtime, both runtime slicers and every SPA bundle, and to islands
alone did not. The four islands on this project's own marketing site shrink
from 4113 to 2094 bytes. - The island-sidecar spawn diagnostics no longer send you to a
build.zig
that does not exist. All three ENOENT messages ended by pointing at the
consumer build API's.islandstable, which is gone; they now name the flag
that actually configures each input (--bun,--island-sidecar,
--island-src-dir) andZIGAPAGOS_RUNTIME_DIR. The interpreter message also
claimed bun alone cannot enable islands on a toolchain-free install "because
the sidecar script and@z/runtimecome from that Zig build integration" —
false for the npm path, which ships both, and true only of a release archive. - The README no longer claims a release archive "gets you the same thing" as
the npm channel. It does not: the archive is the binary alone, so it has
neither Bun nor the@z/runtimetree that islands and SPAs need. The
quick-start's "a plain content site needs neither it nor Bun" was ambiguous
in the same direction — settingZIGAPAGOS_RUNTIME_DIRis exactly what makes
a content-only build require Bun, andnpx zigapagosalways sets it. - A failed island-sidecar spawn now names the input that is actually missing. A
bunthat is
not onPATHpreviously producedfailed to spawn island sidecar (bun …/render.ts): FileNotFound, which reads as "render.ts is missing" about a path that resolves; the
interpreter, the sidecar script and the island source dir are now reported separately, each
with the fix for that specific case. - The changelog's own description of what
zigapagos versionprints. It documented
git describe's rawv0.1.1-<n>-g<sha>spelling, whichbuild/config.zignever
emits — it reformats that into av-prefixed semver version
(v0.2.0-dev.7+e1d7033) — and the paragraph is published as the site's changelog
page. All three shapes the binary can actually print are now listed, and
tests/changelog/version-shape.shfails the build if the list and the emitter
disagree. zigapagos devno longer risks a crash or invalid free when overlapping watched
directories refer to the same path with different spellings. Allocation failures and
malformed input also fail cleanly instead of leaking or indexing out of bounds.
Known limitations
- With
auto_heading_idson, a same-page reference through the$link.ref('slug')Scripty
directive still fails withunknown ref— SuperMD's owninvalid_refcheck runs inside
Ast.init, before ids can be injected. Plain Markdown links ([t](#slug),
[t](/other#slug)) are validated later and work fine;$link.unsafeRef('slug')is the
workaround for the Scripty-directive case. - A content-authored
<z-island>only accepts static props (:propsZiggy
literals and literalprop-NAME="value"attributes).prop-NAME="$page.*"
Scripty expressions do not resolve in content — Scripty is evaluated by
SuperHTML at layout render time, and an=htmlfence's body is emitted
verbatim, never run through SuperHTML's template evaluator. A page-bound
prop still needs a layout. - No Windows support until the Zig 0.17 port. Inherited upstream code
(src/cli/watcher/WindowsWatcher.zig,src/wuffs.zig) does not compile on
stable Zig 0.16.0 —os.windowshas neitherOVERLAPPEDnorPAGE_READONLY
there — and the fix rides upstream's 0.17-dev branch. - No FreeBSD binary, and a source build does not substitute for one: the
watcher-selection logic compiles the inotify-basedLinuxWatcheron FreeBSD,
whoseinotify_init1/inotify_add_watch/inotify_rm_watchthat target does
not have, and there is no checked-in Wuffs shim for it undersrc/hacks/.
Both blockers are recorded against the shipped matrix inbuild/release.zig,
which is the list to re-add the target to once they are fixed. - Strict CSP requires deploying the emitted header. The build writes the
hash-strict policy, but serving it (and re-serving it after a rebuild, since the
hashes are byte-exact) is the host's job.style-srcstill needs
unsafe-inlinefor the framework's inlinestyleattributes. - A per-locale
host_url_overridecannot be exercised locally.zigapagos dev
points ZigBase at one built tree and serves it verbatim on one origin, so a
multi-host locale set builds correctly but can only be checked as deployed. The
emitted output is unaffected; this is a preview limitation, not a build one. - Prebuilt binaries cover four Unix targets:
x86_64-linux-musl,
aarch64-linux-musl,x86_64-macosandaarch64-macos, each built on its own
native runner, withruntime.tar.xzandSHA256SUMSbeside them. arm64 archives
start at this release — earlier tags are x64-only, andv0.1.0predates prebuilt
binaries entirely. Any other host still needs a source build. - Pre-1.0: APIs may change between minor versions.
Internal
- CI now builds its reusable
zigapagosbinary for the architecture's baseline CPU instead
of the build runner's native CPU. A runner with AVX-512 produced an artifact whose
compiler_rt.memcpyexecuted AVX-512 unconditionally on unrelated downstream runners;
hosts without that feature raised SIGILL, and a signal on a render worker could leave the
main thread waiting forever. The artifact gate now rejects host-width YMM/ZMM memcpy code. - Contributors blocked by a Codeberg outage can run
scripts/rescue-codeberg.shto
verify and warm the exact pinnedtranslate-cpackages from GitHub before retrying
their normal build. site/scripts/md-to-smd.tsno longer rewrites link targets inside inline code spans.
It already left fenced blocks alone, but a link-shaped string between backticks in a
paragraph was rewritten like a real link — silently, in both directions: a published
target became a$link.page(...)directive the author never wrote, and an unpublished one
became a bare GitHub URL with no marker at all. The second row was live on this site's own
changelog page, where a sentence about thecontent directives published a link to
https://github.com/valthon/zigapagos/blob/main/…. Spans are now split per CommonMark (a
run of N backticks closes on the next run of exactly N) and only the prose between them is
rewritten;slugifyHeadingunwraps spans the same way, so a multi-backtick span in a
heading no longer leaves its padding spaces behind as an extra hyphen in the anchor (#66).- A fence language is looked up in
fenceLangRemapwithObject.hasOwnrather than a raw
index, so a fence taggedconstructor,toString,valueOf,hasOwnPropertyor
__proto__is a miss instead of resolving throughObject.prototypeand splicing a
stringified function into the mirror's language slot. The module is documented as
copy-me code taking a caller-supplied table, and the caller supplying it is exactly the
person who cannot see the lookup (#67). - npm publishing authenticates with OIDC trusted publishing instead of a stored
registry credential. Each package namesvalthon/zigapagos+release.ymlas its
trusted publisher on npmjs.com, and thepublish-npmjob exchanges the OIDC identity
GitHub mints for the run for a short-lived registry token — so the right to publish
belongs to that one workflow rather than to a credential anything able to read it
could use. The job's guard that failed whenNPM_PUBLISH_ENABLEDwas set but no
stored credential was configured went with it; against a repository that deliberately
has none, that guard would have failed the next tag. TheNPM_PUBLISH_ENABLEDarming
switch is unchanged. - The publish job installs and asserts
npm >= 11.5before uploading anything. OIDC
trusted publishing is an npm 11.5 feature andnode-version: 24does not imply it —
node 24.2.0 bundles npm 11.3.0, and 24.5.0 was the first with 11.5.1 — so an older
npm would have failed as a bare authentication error partway through a
dependency-ordered publish. tests/meta/npm-oidc.shpins the publishing job's shape:publish-npmmust keep
id-token: write— without it GitHub mints no identity and OIDC has nothing to
exchange — and both gating conditions. The job runs only on av*tag, so without a
gate these are properties nothing tests until a release.- The published-release smoke workflow now also installs
zigapagos@<version>from npm
on each platform and runs it. The archives it already checked cannot show a platform
package that was never published, or anoptionalDependenciesresolution that yields
an install with no binary in it. - Release CI now warms the exact pinned
translate-cpackages from Zig's official
GitHub archive before resolving the dependency graph. This avoids persistent
Codeberg protocol failures on GitHub-hosted runners; content hashes and a
resolved-graph gate ensure the mirror cannot silently supply different or stale
dependency bytes. Only dependency fetching is retried, while compilation runs once.