Release

2026-11-01

Skelet 0.3.0

Minor release - may break, under 0.x, released 2026-11-01 - 79 entries. Every release

  1. v0.3.1Patch release
    • Added2
    • Fixed7
    • Security4
  2. v0.3.0Minor release - may break, under 0.x
    • Added6
    • Changed18
    • Removed8
    • Fixed47
  3. v0.2.3Patch release
    • Added8
    • Changed11
    • Fixed25
    • Security6
  4. v0.2.2Patch release
    • Security3
  5. v0.2.1Patch release
    • Added1
    • Changed1
    • Fixed2
  6. v0.2.0First in this changelog
    • Added9

Notes

v0.3.0

v0.3.0

Minor release - may break, under 0.x

Released
Entries
79
  • Added6
  • Changed18
  • Removed8
  • Fixed47

Storefronts become templates you copy and own, built on shared packages that hold everything that isn’t design. The Next.js demo is now the nextjs-vetrina template, astro-shop joins it, and both run on one framework-free core, so a fix to cart, checkout or routing reaches every shop through a merge. Every dev command is now the skelet CLI, which you install once and start projects with through skelet new. Upgrading takes a few renames: install the CLI the README’s way, since ./skelet and bin/ are gone; vetrina-demo is nextjs-vetrina in your env/ files, .skelet-apps, docker-compose.override.yml and any --apps list; and on small or prod each storefront runs under a compose profile of its own name, so add it to COMPOSE_PROFILES in .env, or re-run skelet setup, which does it for you.

Added6

  • A second template, astro-shop, built for speed. An Astro 7 storefront under templates/ on the same front API as nextjs-vetrina, for small catalogs. It has a home page, product pages, a cart and a one-page checkout. Pages are rendered once and served from Astro’s route cache until the backend purges them through /api/revalidate, the same contract nextjs-vetrina implements. Every page works without JavaScript, and the scripts it does ship come to about 5 KB gzipped. Product JSON-LD, canonical URLs, robots.txt and sitemap.xml are built in. It has a light and a dark theme that follow the system until the shopper picks one, and self-hosted Poppins and Montserrat fonts - ?font=montserrat on any URL switches the font for testing. It runs on the same storefront core as nextjs-vetrina, through @skelet/storefront-astro. Run it with the astro-shop profile on port 3001, and test it with skelet test storefront astro-shop.

  • The product picker and the Products list filter by category, collection, price, stock and attributes. A category includes its subcategories, a price is read in each product’s own currency, and stock follows the storefront’s rule - a product with no stock record counts as in stock. The admin API’s productsPaged takes the same filters as categoryIds, collectionIds, priceMin / priceMax and inStock.

  • skelet doctor checks more of what breaks a clone. git new enough for setup’s sparse-checkout, Go on the host against go.mod (lint and parts of codegen run it there), Node against .nvmrc for the frontends you run on the host, plugins.json against the plugin directories by the rules codegen enforces, whether the pre-commit hook is installed, and a WSL clone sitting on the Windows drive. Outside a project, the installed skelet doctor checks the machine alone, before skelet new.

  • Install the CLI, then start a project with skelet new. curl -fsSL https://github.com/skeletg/skelet/releases/latest/download/install.sh | sh puts skelet on your PATH after checking it against the release’s checksums, and skelet new my-shop fetches a release into my-shop and runs its setup. The project is a git checkout with Skelet as its upstream remote and your own main branch, so a later release is a merge. Inside a project the installed skelet hands every command to that project’s own CLI, so it never runs one with a different release’s code. Each release now carries the CLI for macOS and Linux, built by the tag push with a provenance attestation.

  • skelet setup draws its questions in the terminal. Arrow keys pick the target, the template and the search engine, checkbox lists take the apps, dev tools and payment gateways, and each answer folds into one line under its section. Before anything is written a review lists every answer - enter goes ahead, e asks again from the answers given, q leaves with nothing written. The slow steps run behind a spinner, their output in .skelet/setup.log, shown when one fails. Without a terminal, or with --yes, --profile or NO_COLOR, setup asks in plain lines as before.

  • skelet test --changed runs only what a change reaches. Go keeps its test cache, keyed on the code and on the migration files, so only the packages a change touches rerun; each frontend runs its typecheck and only the vitest files that import a changed file, and an app nothing touched is skipped. “Changed” is everything since the branch left develop plus what isn’t committed yet, or --changed=REF. Every test run also starts faster: leftover test database clones are dropped in one go, and the database flush they cause runs in the background after the Go tests instead of stalling the start of the next run for a minute or more.

Changed18

  • The CMS page editor is down to five sections: Layout, Content, SEO, Page settings and Channels. Publishing, scheduling, review and the cache rebuild live in one menu in the header, which also says where the page is live. Version history and comments open in their own dialogs, and the history says what each version changed and what restoring one would undo. The SEO previews and settings fold into one line that sums up the on-page checks, and each block keeps its tracking and visibility settings under Advanced. Blocks open and close without jumping, and the one you click stays on screen while another closes. In global scope, one notice explains why the layout and content are read-only and offers a scope that can edit them. The live preview shrinks to leave the editor room, and on a screen too narrow for both it folds to a rail that opens over the editor - it used to squeeze the editor out of sight. It can be hidden on wide screens too.

  • The docs, messages and help say skelet, the installed CLI, instead of ./skelet. In a project it runs that project’s own code; with only Go, go -C cmd/skelet run . <verb> does the same.

  • skelet doctor warns when the skelet on your PATH is a link to a project’s ./skelet, which projects no longer have. The installer replaces such a link with --force.

  • skelet check structure looks in every tracked file for a bin/ script that no longer exists, not only in Markdown and YAML, so a Go or TypeScript message pointing at a retired script fails the gate instead of reaching someone.

  • Every dev command is part of the skelet CLI. In a project, the installed skelet builds that project’s own cmd/skelet on first use - with host Go when there is one, otherwise inside the golang image, so Docker alone is still enough - and runs it, so a command always matches the code you’re on. Verbs, flags and exit codes are as they were.

  • Storefronts are scaffolded with skelet add channel <name>, the pair to rm channel. new channel keeps working and says it’s add channel now.

  • skelet doctor is part of the Go CLI. The same checks, columns and tally, and it still fails only on a FAIL.

  • The pre-commit hook finds the skelet CLI on its own. skelet hooks writes a hook that runs skelet check through the CLI on your PATH, ~/.local/bin/skelet, the one it was installed with, or go run from the checkout - git GUIs often run hooks without your shell’s PATH. The hook an earlier skelet hooks wrote is updated the next time you run any skelet command, and a hook of your own is no longer overwritten unless you pass --force. It also installs into worktrees.

  • skelet setup is part of the Go CLI. Same questions, flags and files, but it no longer needs OpenSSL - which stock macOS doesn’t have - or anything else on the host, and the env files it creates are readable by you alone.

  • Storefronts are made from templates. The demo storefront is now the nextjs-vetrina template under templates/, and apps/channels/ holds a project’s own shops. skelet add channel <name> copies a template there (--template <name>, or --from <shop> for one of your own), points its paths at its new home and adds it to the lockfile. The templates and the packages they build on stay upstream’s, so merging a release brings their fixes without touching your shop. skelet rm channel leaves the templates alone.

  • skelet setup leaves the templates out unless you ask for one. Setup is for working on your own store: your storefronts under apps/channels/ default to yes, a template to no, and --yes takes that default (--all still means everything). A template left out of the working tree is still forked by skelet add channel, straight from git.

  • Setup offers your first storefront. With no shop of your own yet, skelet setup asks which template to copy and what to call it, and makes it the way skelet add channel would - selected, and running where your other frontends run. --template <name> and --shop <name> do it without asking, and skelet new hands them on: skelet new my-store --template astro-shop.

  • The storefronts are one pnpm workspace. pnpm-workspace.yaml and pnpm-lock.yaml at the root cover apps/channels/*, templates/* and the new packages/*, and the workspace’s catalog keeps the versions they share in step, so a package and the app using it resolve the same React. The dashboard stays a project of its own.

  • A storefront’s logic is @skelet/storefront-core, with no framework in it. Everything the demo kept in app/lib that isn’t design - the GraphQL client and its operations, auth, cart, checkout, channel and locale, the actions - now lives in packages/storefront-core, which asks the request it runs in for cookies, headers, locale and channel instead of calling Next. @skelet/storefront-next answers those the Next way and wraps the actions as server actions, @skelet/storefront-astro answers them the Astro way, and @skelet/storefront-react holds the client hooks any React framework can use. A Next storefront’s routing is the adapter’s as well - channel and locale, the session refresh, the visitor id - so its proxy.ts is a call to storefrontProxy and a matcher. A shop forked from a template keeps up with fixes to that code by merging a release, without touching its own pages. Codegen is the core’s, run by skelet gen, and skelet check fails on a framework import in the core and reads the shared lists from it.

  • A storefront’s dev container mounts the whole repo at /var/www/skelet, read-only but for its own directory and packages/, with env/ hidden behind a tmpfs, and its prod image builds from the repo root with a .dockerignore of its own beside the Dockerfile. skelet doctor notes an override still mounting a storefront at /var/www/storefront, and a .skelet-apps naming a storefront that isn’t there.

  • vetrina-demo is now nextjs-vetrina everywhere it’s named, since a template carries its framework in its name: the compose service and profile, the skelet-nextjs-vetrina image and its NEXTJS_VETRINA_IMAGE variable, and env/{prod,small}-nextjs-vetrina.env. Rename your env/ files and update .skelet-apps, docker-compose.override.yml and any --apps list that names the old one.

  • Storefront prod images install only what their template depends on, run as root-owned files with no npm, yarn or corepack, and carry their own HEALTHCHECK; the prod and small compose files run them with init. CI checks the whole storefront workspace in one job and smoke-runs each storefront image.

  • formatPrice takes a locale, and writes a currency with no symbol configured the locale’s way rather than as its code. astro-shop formats every price through it, in the channel’s language.

Removed8

  • The scripts behind skelet check, its gates and skelet changelog are gone - skelet check, check pins, check structure, check contracts and changelog are part of the CLI now.

  • The scripts behind skelet gen and skelet lint are gone - both are part of the CLI now, and skelet check codegen is skelet gen --check. The golangci-lint version they pin lives in the CLI’s source, where skelet check pins and Renovate find it.

  • The scripts in bin/ behind down, stop, restart, ps, logs, serve, sh, cli, psql, db, search, release, provider and fixperms are gone - those commands are part of the skelet CLI now. Run them as skelet <command>: the old psql script is skelet psql, opensearch is skelet search, and push-images is skelet release.

  • The scripts behind skelet add channel and skelet rm channel are gone - every verb is part of the CLI now.

  • ./skelet and bin/ are gone. Install the CLI the README’s way - inside a project it runs that project’s own code, and one from an earlier release still gets its ./skelet. With only Go, go -C cmd/skelet run . <verb> does the same. A pre-commit hook from an older skelet hooks, which ran a script that’s gone, is rewritten by any skelet command, and skelet doctor fails on one that’s left.

  • The scripts behind skelet test, skelet test e2e and skelet test storefront are gone - all three are part of the CLI now, along with the disposable-database helpers they shared.

  • The scripts behind skelet go, skelet pnpm and skelet gen sqlc, gqlgen and buf are gone - they’re part of the CLI now.

  • The script behind skelet up is gone - it’s part of the CLI now, port check and env syncs included. bin/ holds only what skelet add channel and skelet rm channel still run.

Fixed47

  • A CMS page’s search-indexing switch sticks. The editor had two of them that both saved on every Save, so changing the slug in the same save could put indexing back the way it was. The switch now lives in the SEO panel only.

  • Creating a record no longer leaves a stale draft behind. On a slower machine, the language, currency, country and other create pages could store their own emptied form as a draft just after the create, so the next visit to the page offered to restore a blank form. The form now keeps what was created until the page moves on.

  • A production dashboard no longer prints raw message syntax. Any text with a number or a plural in it - the table pager’s row count and “of N” pages among them - showed up as {totalRows, plural, one {# row} other {# rows}} instead of the formatted words, in English and in installed language packs alike. Development builds were never affected.

  • A project’s own v* tag no longer tries to publish a Skelet release. The release workflow runs only in Skelet’s repository, so a project made with skelet new can tag its versions freely. The docs also say how a project keeps Renovate to its own shops, so its bumps don’t collide with the next release’s.

  • A small or prod setup serves the storefront you picked. The demo storefront always started and the edge always proxied to it, whatever setup was told. It now runs under its own compose profile like astro-shop, and setup writes env files only for the storefronts you picked, turns their profiles on in COMPOSE_PROFILES, and points STOREFRONT_REVALIDATE_URL and the edge’s new STOREFRONT_UPSTREAM at the one it serves. A value you set by hand stays.

  • The dashboard previews your first shop. skelet add channel on a dev clone sets VITE_STOREFRONT_ORIGIN to the new shop’s port when .env names none, instead of leaving previews on the template’s :3000.

  • Messages that named retired scripts say which skelet command to run. The ops CLI’s plugin, job and data-patch help, the plugin scaffold’s README and migration templates, the generated plugin list, the remote-provider errors and the dashboard’s dev admin switcher pointed at scripts that are now skelet cli, skelet psql, skelet db migrate, skelet logs and skelet provider.

  • skelet gen --check catches a generated file that’s new, and lists only what it changed. It compared the diff of tracked files, so a newly generated file that wasn’t committed passed, and files that were already modified before the run showed up as stale.

  • skelet sh, cli and provider find a running app container by its service name. They looked for skelet-app in the container list, so with COMPOSE_PROJECT_NAME set they never saw it and started a second app container every time.

  • skelet search needs neither jq nor curl, prints its table everywhere, and rejects a --limit that isn’t a number instead of sending OpenSearch broken JSON.

  • A failed skelet db export leaves nothing behind, and skelet db import without a terminal says to re-run with -y instead of failing on /dev/tty.

  • skelet serve reads the root .env the way compose does rather than running it as a shell script.

  • skelet add channel refuses an override with no services: section. It added the storefront’s volumes there, dropped the service itself, and reported it written.

  • skelet add channel handles an override that lists volumes: above services:. The new volumes landed under services:, compose rejected the file, and nothing could be added.

  • skelet add channel leaves the source’s node_modules and .next behind instead of copying them into the fork and deleting them afterwards - a host install alone can run to a gigabyte.

  • skelet add channel refuses a name the override already uses, and a --from that isn’t a storefront name, before anything is copied.

  • skelet rm channel says when a tracked storefront holds untracked files. It called the directory recoverable with git checkout, which brings none of them back.

  • skelet test e2e says it needs ClickHouse before it touches anything. Under skelet up --lean it dropped and recreated the e2e Postgres databases, then stopped on a compose error about a service that wasn’t running.

  • skelet help <command> <sub> describes the subcommand. skelet help test e2e and skelet help check pins printed the parent command’s help.

  • skelet gen writes sqlc’s files as you. sqlc ran as its image’s root, so on a Linux Docker engine every regenerated file under internal/db/sqlc belonged to root.

  • skelet gen buf hands its arguments to buf as they are. They were spliced into a shell command, so one with a space or a quote in it was split or misread.

  • skelet up’s port check stops when its input ends. Ctrl-D at its prompt asked the same question again, forever.

  • Two services moved off busy ports in one go can’t land on the same port. The second was offered, or allowed, the port the first had just moved to, and compose then failed to bind it.

  • env/signals.env gets the value core runs with when env/go.env names a key twice. It copied the first line, while compose reads the last.

  • Re-running skelet up leaves an unchanged stack alone. Every --build gave the images a new ID through buildx’s default provenance attestation, so the app, Signals and Docker-run frontends were recreated on every run. The dev images are built without it now; set BUILDX_NO_DEFAULT_ATTESTATIONS yourself to choose otherwise.

  • Doctor’s *.localhost check can fail. Its fallback passed on any output from curl, including the 000 curl prints when the name doesn’t resolve, so the check never warned. Now only a failed lookup warns, and any answer from the server - a 404 included - passes.

  • A prod setup starts its datastores. Setup created the root .env empty, without the template’s COMPOSE_PROFILES=bundled, so compose left the bundled Postgres, pgbouncer, Valkey and ClickHouse out of the deployment. A new .env for small or prod now starts from .env.example.

  • Small and prod pass the storefront revalidation secret. Compose hands the app and the storefront STOREFRONT_REVALIDATE_SECRET from the root .env, which setup never filled, so both got an empty one over the value generated into the backend’s env file. Setup now copies it across like the other shared secrets.

  • The dev storefront signs revalidations with the secret core checks. It had a fixed value while setup generated core’s; it now reads core’s from the root .env, which setup fills.

  • Setup no longer rewrites plugins.json in the old format. It only understood bare 0/1 entries, so a run dropped module plugins from the file and could write the name of a parked _plugin directory, which the build then refused. It now merges its gateway picks in the form skelet cli plugin writes, removes parked names an older run left, and leaves the rest alone.

  • skelet setup --profile FILE leaves FILE alone. It ran the file as a shell script to read it and wrote the answers back into it; it now only reads it, and saves the answers to .skelet-profile.

  • A skelet setup --yes re-run keeps the frontends where they run. With a profile from before the Docker-or-host question, it emptied .skelet-apps, moving every containerized app to the host.

  • skelet add channel suggests a setup command that keeps your other apps. It built the --apps list from .skelet-apps, which only names the apps running in Docker.

  • The Next.js storefront’s production image serves its pages again. Every page answered 404: in production Next runs the proxy a second time, on the path its own rewrite pointed at, and the rule refusing a direct /<channel>/ URL refused that pass too. The rewrite now carries a mark only the running server knows, and its second pass goes straight through - without refreshing the session or reporting a crawl again.

  • A storefront’s dev container can no longer change the checkout. It mounts the repo read-only, writing only its own directory and packages/, with the root .env and the rest of apps/ hidden as well as env/. It installs frozen, skelet pnpm <name> add|remove|update runs over the workspace’s manifests in a throwaway container, and the new skelet pnpm --lockfile refreshes the lockfile by itself. skelet doctor notes a shop in the override still mounting the checkout writable - recreate it from the template’s block. Run skelet up to pick it up.

  • Open redirects. A returnTo with a tab, a newline or a backslash in it is refused, a posted locale no longer decides where an action lands, and a locale is only ever a locale code in front of a path.

  • Masquerade links install only masquerade sessions. The backend marks the tokens it mints for an operator, and the storefront’s /masquerade landing refuses any other - a customer’s own token in a crafted link would have signed whoever clicked it into the sender’s account. It redirects to a relative /.

  • The backend proxy keeps to its routes. /t/%2e%2e/api/... matched a proxied prefix and resolved outside it.

  • A purge drops everything it names. Both templates’ revalidate routes kept only the first 64 tags and paths; the body is bounded by size now. A warm request stays on the storefront’s origin and follows no redirect, since it carries the revalidate secret.

  • The session refresh logs a shopper out only when the backend says the token is no good - not on a rate limit or an outage - names the shopper’s address to the backend, and hands the rotated refresh token to the render too.

  • astro-shop’s route cache kept one channel’s page for another host when hosts map to channels; pages now vary by host there. Its pages are dropped by menu and channel purges as well, kept for the admin’s cache time, and render catalog reads without a shopper’s session.

  • A cart the backend couldn’t read is kept. astro-shop deleted the cart cookie whenever reading the cart failed.

  • Shopper actions no longer purge every cached page. Signing in or out, consent and placing an order re-render the shopper’s own page instead of revalidating the whole site.

  • skelet add channel copies into place last, so a failure leaves nothing half-made, renames the source only where its name stands whole, defaults to nextjs-vetrina, rewrites the ports of a template on its own (astro-shop’s 3001), and fails loudly when the lockfile can’t be refreshed. skelet rm channel refuses a template setup left out of the working tree, which it used to strip from the tracked compose files.

  • skelet setup offers a first shop only to a plain interactive dev setup, asks before where the frontends run so the shop goes with them, and refuses a shop name a compose service already has.

  • skelet release builds only the frontends the clone runs when setup recorded a selection.

  • A monthly subscription started on the 29th-31st no longer skips a month. Each renewal date was the previous one plus a month, so the first short month knocked it off its day for good - started on Jan 31, it next billed on Mar 3, with no February invoice, and stayed on the 3rd. Renewals now count from the subscription’s start (or the end of its trial, or when it was resumed), and a month too short for the day bills on its last day instead: Jan 31, Feb 28, Mar 31. Yearly plans started on Feb 29 bill on Feb 28 in other years. Existing subscriptions keep the date they’re on now.

Around it

Newer and older