App standards
Versioning
How an app pins frappe-nix and its profile, how a release reaches every app, and what each version number promises.
On this page
frappe-nix stays on main and releases with tags vX.Y.Z. A branch per major, release-1, is fast-forwarded to each v1.* tag and never moves otherwise. Apps follow that branch, so updating their lock only ever proposes a release, never an arbitrary main commit.
How an app pins frappe-nix
An app that has opted in pins one frappe-nix release in four places, and sync keeps them in step:
flake.nixnamesgithub:Avunu/frappe-nix/release-1, unless the app sets[tool.frappe-nix.dev-shell] frappe-nix-url(a tag, a commit, a fork or a mirror).flake.lockrecords the commit. For the default URL that is always av1.*tag commit, becauserelease-1only fast-forwards to tags.The caller workflows use
uses: …@<that commit> # vX.Y.Z.frappe-init --syncrewrites them fromflake.lock, and--checkfails when they disagree.The jobs that run without Nix install
frappe-nix-toolsfrom the same commit.
An app with an org profile also locks the standards-profile input, so its managed files come from exactly one profile revision too. The result is one frappe-nix version and one profile revision per app commit, everywhere.
How a release reaches every app
A pull request merges to frappe-nix's
main. frappe-nix'srelease.ymlruns release-please, which opens or updates the release PR. The version bump changes the# v<version>comment that the caller workflows oftests/fixtures/standards-appcarry, so the same run re-renders the fixture offline with the PR's frappe-nix-tools and pushes it to the PR branch;mainnever holds a fixture thatfrappe-nix sync --checkrejects. Because a push made withGITHUB_TOKENstarts no workflow, the same run then dispatchescheck.yml(itslintandflakejobs, without the VM tests),pr-policy.ymlanddocs.yml(the requiredbuild / Check) on that PR.Merging the release PR is the decision to ship. release-please tags
vX.Y.Zand writes the GitHub release;release.ymlthen movesrelease-<major>to the tag with a RESTPATCHthat GitHub refuses unless it is a fast-forward (it creates the branch on a major's first release), reads it back and fails unless the branch equals the tag.Each app is moved deliberately, with a token that may change workflow files:
frappe-nix repo rollout --to vX.Y.Z --repo example-org/my_app frappe-nix repo rollout --to vX.Y.Z --all --fleet fleet.jsonFor each app it updates the
frappe-nixinput, relocks, runsfrappe-init --sync(which rewrites the caller SHAs and every changed managed file) and openschore(deps): frappe-nix vX.Y.Zwith auto-merge on. Until that happens, the app's nightlydriftjob keeps an issue open.The bump PR merges once its
ci / *checks pass. If a stricter check in the new release fails the app, the PR waits for a fix pushed to its branch.
Dependabot never moves frappe-nix or standards-profile: the bump has to rewrite workflow files, which GITHUB_TOKEN cannot push. An app without the ci or dependabot modules simply runs nix flake update frappe-nix && frappe-init --sync when it wants a new release.
What each version number promises
frappe-nix follows semver for everything an app consumes: reusable workflows, the [tool.frappe-nix] table, profiles, modules, tools and their flags.
Major: a removed or renamed reusable-workflow input, secret, output or job name; changed required-check contexts; a new
schemafor the table or for profiles; a removed or renamed module or parameter, or a changed meaning of an existing value; a change--synccannot apply on its own; a removed tool or flag; a module turned on inminimal; a change to a releasedrecommended@<minor>snapshot other than one that accepts strictly more code; or a new schema default forreleases.branching,releases.version-scheme,releases.tag-prefixorintegration-branch. A new major gets a newrelease-<N>branch, and apps move to it by editing the flake URL in a deliberate PR.Minor: new managed content or checks that
--syncapplies, even when they fail code that passed before (the bump PR then waits); a new module, off in both built-in profiles and in every existing snapshot; a newrecommended@<minor>snapshot, which plainrecommendedthen means; new parameters, inputs, tools or flags, each with a default that keeps the old behaviour.Patch: fixes, and pin bumps such as nixpkgs, devenv, the action SHAs inside the reusable workflows and the pre-commit hook revisions.
Within v1:
a reusable workflow at any
v1.yaccepts a caller rendered by anyv1.x, because inputs are only ever added, with defaults;schema = 1keys of the table and of profiles, and module names, are never removed;a deprecated key keeps working for one minor, and
--checkwarns about it with exit 0;minimalrenders the same file set in everyv1.x, and a releasedrecommended@<minor>resolves to the same values in every laterv1.x;an app that has not opted in is never touched.
An app that pins a snapshot (profile = "recommended@1.0"), or an org profile that extends one, therefore sees only new checks for modules it already has on, never new modules or new defaults.
Org profiles
An org profile repository should release the same way: tags vX.Y.Z and a branch v<major> fast-forwarded to each tag, so apps write profile = "github:<owner>/<repo>/v1" and lock a tag commit. Its own semver is about what apps see: major for a change that makes frappe-nix profile validate or an app's --check fail without an app edit, minor for modules turned on or parameters that --sync applies, patch for org-value fixes such as a URL. requires-frappe-nix names the frappe-nix versions a profile is written for; sync refuses a profile whose range excludes the running frappe-nix (exit 3), so an app that moves frappe-nix past that range moves the profile in the same PR. A profile moves into apps with frappe-nix repo rollout --profile-to <ref>|latest.
frappe-nix's own releases
release-please (
release-please-config.json,.release-please-manifest.json) uses thesimplerelease type, soversion.txtholds the version. It movespy/frappe_nix_tools/pyproject.toml(project.version) and the__version__block offrappe_nix_tools/__init__.pywith it, sofrappe-nix --versionalways names the release. Tags carry thev.The first release is
v1.0.0, set by aRelease-As: 1.0.0footer on the commit that introduced the release flow. A later major is made the same way, deliberately:pr-policyrefuses!andBREAKING CHANGEin frappe-nix's titles and commits (frappe-nix policy --pr --major-free), so no ordinary merge can bump the major by accident.Commit rules:
committed.toml(conventional commits, frappe's types, a subject of at most 100 characters).pr-policy.ymlchecks the PR's base (main), its title and each commit. A Dependabot PR's title and Dependabot's own commits are exempt from the subject length alone, since a SHA-pinned bump's generated subject runs far past 100 characters (spec §5.9). It is its own workflow so that a retitle re-runs it without rebuilding theflakejob.Dependabot writes
fix(deps)for the bumps apps run (nix inputs, the action SHAs, pre-commit hook revisions), so each of those ships as a patch release, andchore(deps)for frappe-nix's own dev environment (/dev), which releases nothing.check.ymlbuilds the NixOS VM tests on pull requests and pushes. Aworkflow_dispatchruns them only with the inputvm-tests: true; the release flow's dispatch leaves it off.Repository policy:
repo-policy/self/holds frappe-nix's own settings and rulesets:mainrequires a pull request and the checkslint,flakeandpr-policy;release-*may be created and moved only by GitHub Actions, with no deletion or force-push;v*tags may not be deleted or moved. Apply them with a token that has admin on the repository:frappe-nix repo apply --self --dry-run # the JSON diff, no writes frappe-nix repo apply --self