* ci(release): build FreeBSD aarch64 archives by default
app packaging/freebsd/package.sh scripts now honor CARGO_BUILD_TARGET, so
aarch64 is no longer opt-in: freebsd-architectures defaults to
'["x86_64","aarch64"]'. Callers can still narrow it to x86_64 only.
* docs(readme): FreeBSD aarch64 is in the default freebsd-architectures
The aarch64 job uses FreeBSD's quarterly packages, which since 2026-10-28 have no `cmake` meta-package, only `cmake-core`. `pkg install cmake` failed and stopped every app's release build. `cmake-core` is in both the quarterly and latest sets.
* Provision coverage fonts for archive builds and test runners
* Keep coverage fonts optional unless a pin is requested
* Pin nested actions used by coverage jobs
* Export the font path only after a requested checkout
* Measure optional Python tooling coverage in Sonar
* ci: restore craft-common-actions tracking main
* ci: share Python coverage configuration with XML reporting
* sonarcloud: optionally shard Rust coverage across parallel nextest partitions
* sonarcloud: default coverage to four shards
* sonarcloud: build the instrumented archive once and shard tests from it
* sonarcloud: skip sharded scan steps when the plan job produces no shards
* sonarcloud: validate shard count, pin the archive's toolchain, merge only test binaries
- Preserve full SPDX expression as a single shell-quoted array element in PKGBUILD license
- Use cp --remove-destination to replace any destination symlink when copying license files
- Update test_pkgbuild_and_makepkg_conf_safe_serialization test assertion to expect preserved expression
- Restrict SonarCloud project-key grammar to start with an alphanumeric/underscore, reject leading hyphens/flags, and enforce 400 character length limit (F-006)
- Verify Nix extra-args argument parsing without eval (F-005)
- Verify Windows ARM64 PowerShell environment variable passing and path component sanitization (F-007)
- Add unit test verifying FreeBSD workflow architecture pre-validation rejects invalid architectures and injection payloads prior to VM execution (F-009)
- Verify FreeBSD custom-test-command environment data passing (F-010)
- Disallow carriage returns and newlines upfront in resolve-version to prevent GitHub output-file protocol forgery
- Strictly validate generated build date against YYYY-MM-DD format
- Sanitize APP_TITLE against carriage returns and newlines in step summary
- Validate package tokens in install-apt-packages, rejecting options, path traversals, and injection payloads
- Terminate options with -- when invoking apt-get install
- Add unit tests for install-apt-packages validation and new test suite for resolve-version
* ast-grep: a reusable structural-lint workflow driven by the app's sgconfig.yml
A reusable workflow and composite action that install a pinned
ast-grep (checked against a SHA256 kept in the action, since its
releases publish none), run the caller's rule tests when its
sgconfig.yml has testConfigs, then scan with that config. CI and a
local `ast-grep scan` read the same rules. Findings become PR
annotations; a failing scan also lists every finding as file:line,
since GitHub shows only the first few annotations of a step.
No rules ship here. test-ast-grep.yml checks the action against a
fixture: clean code passes, a finding fails, a path outside the
repository is refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* ast-grep: run the action from the workflow's own commit; always run rule tests
ast-grep.yml loads the action with `$/actions/ast-grep`, GitHub's
self-repository syntax, so a caller that pins the workflow to a commit,
or calls a fork's, runs the action from the same commit rather than
whatever is on main.
The rule tests no longer depend on grepping for `testConfigs:`, which
missed valid YAML such as `testConfigs :`. ast-grep test runs every
time; with no testConfigs it has no tests to run.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Remove i18n-gate.yml, the skip-i18n-gate input and the i18n-gate jobs in
release.yml and freebsd.yml. Keep checkout-craft-i18n and the
CRAFT_I18N_DIR / CRAFT_I18N_REQUIRED=1 env, which fail only on a
missing or invalid craft-i18n checkout.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Moves the per-app 'Git dependencies come from storytold' job into
craft-common-actions' release.yml, gated after it via the version job, so
every craftapp gets the check at @main instead of a copy per repo.
* ci: add a shared CI workflow for the craft apps
A reusable workflow that runs each app's quality gate (by default
`cargo xtask ci`) on one or more runners, with the suite's shared setup:
cached apt packages, the repository's toolchain, prebuilt cargo tools,
the craft-fonts and craft-i18n checkouts, and a dependency cache that only
the default branch writes.
The rust-cache key includes the root Cargo.toml: rust-cache hashes only
the workspace members' manifests, so a [profile] change in a virtual root
manifest would otherwise keep restoring the old cache.
Part of #60.
* tests: cover the shared CI workflow
test_ci.py checks that third-party actions are pinned to commits, that
only non-PR runs on the default branch save the cache, that caller input
reaches run blocks only through env, and runs the caller-environment
script under bash to check it writes valid KEY=VALUE lines and rejects
others. The craft-fonts test also holds ci.yml to the suite's pin, and the
test workflow runs on changes to ci.yml.
* docs: document the shared CI workflow
Describes what ci.yml sets up and runs, with a plain caller and one that
adds a Windows leg on main, manual runs and full-ci pull requests.
* actions: don't persist credentials in the craft-fonts and craft-i18n checkouts
Both repositories are public and nothing later in a job runs git in them,
so the token has no use there.
* ci: name the shared workflow gate.yml
The repository's own CI proposed in #21 takes .github/workflows/ci.yml.
Callers use .github/workflows/gate.yml@main; the job still reports as
gate (<os>).
* sonarcloud: measure Rust coverage with cargo-llvm-cov and import the LCOV report
The scan never imported coverage, so SonarCloud counted only the few Python and
PowerShell files as coverable and showed 0.0% for every project.
* sonarcloud: --ignore-run-fail keeps coverage after a red test; drop persisted checkout credentials before running Cargo
The amd64 security source added in #61 used http://security.ubuntu.com,
reintroducing the clear-text apt source that #26 removed for
ports.ubuntu.com. security.ubuntu.com serves the same archive over HTTPS.
GitHub's Ubuntu mirror is sometimes slow: a FilmCraft CI run spent 5 min 42 s downloading
21 MB of build dependencies (storytold/filmcraft#2860), and FigCraft's apt step took 4 to 9
minutes in two of eight runs (craft-common-actions#60). The action points apt's archive
directory at a cached folder, so a run with a warm cache downloads only the package lists.
This reverts commit b86cdd3. Wayland drag-and-drop waits for winit 0.31,
and Flatpak portal drops will be resolved with zbus (already in every
app via rfd) rather than libdbus. Records the decision as ADR 0001;
tracking in #64.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(release): give riscv64 packages the glibc floor of their runner
The riscv64 job builds on ubuntu-24.04, so its GUI binaries need GLIBC_2.39,
but the apps' nfpm.yaml declares the 2.35 floor of the other Linux jobs.
Ubuntu 22.04 can't host the cross build: jammy's ncurses security update was
never built for riscv64, so libtinfo6 can't match across architectures.
Export GLIBC_FLOOR=2.39 on the riscv64 job for each app's package.sh and
nfpm.yaml to declare. Refs #44.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(release): say GLIBC_FLOOR reaches the packages only through the app's nfpm.yaml
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ubuntu published a zlib security update (1:1.3.dfsg-3.1ubuntu2.3) at 18:50
UTC on 2026-10-22. ports.ubuntu.com had the riscv64 build at once, but the
runner's amd64 mirror (azure.archive.ubuntu.com) still served 2.2. zlib1g is
Multi-Arch: same, so apt could not pair the versions and every release's
Linux riscv64 job failed with "held broken packages".
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The identity step hashed the caller's source hash into the cache prefix and
restore-keys only matched that prefix, so any revision that touched Cargo.toml,
Cargo.lock, build.rs, .cargo, rust-toolchain or packaging/ started from an
empty cache. Releases bump a version in the manifests, and PRs that change a
dependency do too. Issue #33 records the consequence on the aarch64 leg:
compiling ~260 dependency crates takes 4h32m under emulation, and 93 PR runs
were cancelled at the 300-minute limit with 0 passing.
Keep the exact key as the primary match and add a second restore key that pins
only what decides artifact compatibility: the guest toolchain and packages, the
serialized configuration and the profile. Cargo's fingerprints still reject
stale artifacts, so restoring a previous revision only saves work.
Bump the cache namespace to v3 because the key layout changed. Existing v2
entries are not readable through the new prefixes, so the first run after this
merge starts cold, as for any namespace bump.
Refs #33, #39
* ci(release): cross-compile FreeBSD aarch64 in the x86_64 VM
The FreeBSD release job built aarch64 in an emulated aarch64 guest, which
ran into the 300-minute limit for most apps, so aarch64 was dropped from
releases (#39). #49 fixed the same problem in freebsd.yml by cross-compiling
in the KVM-accelerated x86_64 guest; this ports that setup to release.yml.
The release VM is now always x86_64. For aarch64 it pins the quarterly
package branch, installs lang/rust and runs the same "Set up the aarch64
cross toolchain" step as freebsd.yml; a test keeps the two copies identical.
The build step sets the version before loading the cross environment, so
xtask still builds for the guest. The built-in packager reads binaries from
target/<triple>/release, and the job fails if an archive does not match the
matrix architecture.
aarch64 stays opt-in through the new freebsd-architectures input (default
'["x86_64"]'): each app's package.sh still reads target/release and names
its archive after uname -m, so it must honor CARGO_BUILD_TARGET first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci(release): reject unsupported FreeBSD architectures before the VM starts
The guest is always x86_64 and only aarch64 is cross-compiled, so any other
freebsd-architectures value would build x86_64 binaries and the fallback
packager would label them with that value. Check matrix.arch on the host
first and fail for anything but x86_64 and aarch64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
An app's craft-i18n.rev switches craft-i18n on. checkout-craft-i18n reads
it (trimmed: Windows checks it out with CRLF), checks out that SHA and sets
CRAFT_I18N_DIR and CRAFT_I18N_REQUIRED=1 for every build job in release.yml,
freebsd.yml and windows-arm64.yml; the FreeBSD guests export them too.
The i18n-gate workflow runs cargo test --workspace i18n_gate with
CRAFT_I18N_GATE=1 and fails if no test ran. Release packaging needs it,
skip-i18n-gate skips it on workflow_dispatch only, and freebsd.yml runs it
on pull requests that touch craft-i18n.rev. Without the pin file every
step is a no-op and the gate succeeds.
Claude-Session: https://claude.ai/code/session_01B5qjdPbdcyPMikAqXxAAdW
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The VM's shell is root, and root is never refused a file: a test that
locks a file and expects "permission denied" fails, and a permission bug
root would hide goes unseen. Default and custom tests now run as
craft-test through su; the build and packaging stay root.
Human-reviewed, AI-assisted, nobody's mailbox harmed.