From 3df06a999258671845fa87da33fc88953ac0f270 Mon Sep 17 00:00:00 2001 From: Alex Maldonado Date: Fri, 7 Aug 2026 15:23:58 -0400 Subject: [PATCH] feat: implement CI --- .bumpversion.toml | 29 ++ .gitea/workflows/ci.yml | 19 + .gitea/workflows/docs.yml | 67 ++++ .gitea/workflows/nightly.yml | 71 ++++ .gitea/workflows/release.yml | 61 +++ CHANGELOG.md | 11 + Cargo.toml | 2 +- README.md | 17 +- RELEASING.md | 77 ++++ pixi.lock | 729 +++++++++++++++++++++++++++++++++++ pixi.toml | 81 ++-- 11 files changed, 1126 insertions(+), 38 deletions(-) create mode 100644 .bumpversion.toml create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/docs.yml create mode 100644 .gitea/workflows/nightly.yml create mode 100644 .gitea/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 RELEASING.md diff --git a/.bumpversion.toml b/.bumpversion.toml new file mode 100644 index 0000000..698f98b --- /dev/null +++ b/.bumpversion.toml @@ -0,0 +1,29 @@ +[tool.bumpversion] +current_version = "26.8.0" +parse = """(?x) + (?P + (?:[1-9][0-9]?)\\. # YY short year, no leading zero + (?:1[0-2]|[1-9]) # MM month 1-12, no leading zero + ) + \\.(?P\\d+) # patch +""" +serialize = ["{release}.{patch}"] +allow_dirty = false +commit = true +tag = true +message = "Release {new_version}" +tag_name = "{new_version}" +tag_message = "coursebank {new_version}" + +[tool.bumpversion.parts.release] +calver_format = "{YY}.{MM}" + +[[tool.bumpversion.files]] +filename = "Cargo.toml" +search = 'version = "{current_version}"' +replace = 'version = "{new_version}"' + +[[tool.bumpversion.files]] +filename = "pixi.toml" +search = 'version = "{current_version}"' +replace = 'version = "{new_version}"' diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..abb8b88 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + check: + runs-on: pixi-build-rust + steps: + - uses: actions/checkout@v4 + + - name: Install the pinned toolchain + run: pixi install + + - name: Verify (formatting, lint, tests, docs) + run: pixi run check diff --git a/.gitea/workflows/docs.yml b/.gitea/workflows/docs.yml new file mode 100644 index 0000000..0a33ea6 --- /dev/null +++ b/.gitea/workflows/docs.yml @@ -0,0 +1,67 @@ +name: Deploy docs + +on: + push: + branches: [main] + tags: ['*.*.*'] + workflow_dispatch: + +env: + SITE_SLUG: coursebank + CRATE: coursebank + +jobs: + deploy: + runs-on: pixi-build-rust + steps: + - uses: actions/checkout@v4 + + - name: Write the channel switcher + run: | + cat > "${{ gitea.workspace }}/docs-banner.html" <<'HTML' +
+ Docs channel: + release| + nightly + +
+ HTML + + - name: Build the API docs + env: + RUSTDOCFLAGS: "--html-before-content ${{ gitea.workspace }}/docs-banner.html" + run: | + pixi install --locked + pixi run doc-build + + - name: Redirect the channel root to the crate page + run: | + printf '%s\n' "" > target/doc/index.html + + - name: Publish the channel to /srv/www + run: | + case "${{ gitea.ref }}" in + refs/tags/*) channel=release ;; + *) channel=nightly ;; + esac + dest="/srv/www/${SITE_SLUG}/${channel}" + mkdir -p "$dest" + rsync -a --delete "target/doc/" "$dest/" + + - name: Point the site root at the latest release, else nightly + run: | + root="/srv/www/${SITE_SLUG}" + if [ -d "$root/release/${CRATE}" ]; then + target="release/${CRATE}/" + else + target="nightly/${CRATE}/" + fi + printf '%s\n' "" > "$root/index.html" diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml new file mode 100644 index 0000000..01c43e7 --- /dev/null +++ b/.gitea/workflows/nightly.yml @@ -0,0 +1,71 @@ +name: Nightly + +on: + push: + branches: [main] + schedule: + - cron: '0 6 * * *' + workflow_dispatch: + +jobs: + nightly: + runs-on: pixi-build-rust + permissions: + contents: write + env: + API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }} + TOKEN: ${{ secrets.GITEA_TOKEN }} + TAG: nightly + ASSET: coursebank-nightly-linux-x64.tar.gz + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install the pinned toolchain + run: pixi install + + - name: Build the release binary + run: pixi run build + + - name: Package + run: | + set -euo pipefail + tar -czf "$ASSET" \ + -C target/release coursebank \ + -C "${{ gitea.workspace }}" LICENSE.md + sha256sum "$ASSET" > "$ASSET.sha256" + + - name: Publish the rolling nightly prerelease + run: | + set -euo pipefail + auth="Authorization: token ${TOKEN}" + # jq is fetched on demand; no toolchain change needed for it. + jq() { pixi exec --spec jq -- jq "$@"; } + + sha="${{ gitea.sha }}" + version=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/') + body="Nightly build of ${version} at $(date -u +%Y-%m-%d) (commit ${sha:0:8}). Not a stable release." + + # Drop the previous nightly release and its moving tag, if present, so + # the new one points at the current commit. + existing=$(curl -fsSL -H "$auth" "${API}/releases/tags/${TAG}" 2>/dev/null || true) + if [ -n "$existing" ]; then + id=$(printf '%s' "$existing" | jq -r '.id // empty') + [ -n "$id" ] && curl -fsSL -X DELETE -H "$auth" "${API}/releases/${id}" + fi + curl -fsSL -X DELETE -H "$auth" "${API}/tags/${TAG}" 2>/dev/null || true + + # Create the release at the current commit. + rid=$(curl -fsSL -X POST -H "$auth" -H "Content-Type: application/json" \ + "${API}/releases" \ + -d "$(jq -n --arg tag "$TAG" --arg sha "$sha" --arg body "$body" \ + '{tag_name:$tag, target_commitish:$sha, name:"Nightly", body:$body, draft:false, prerelease:true}')" \ + | jq -r '.id') + + # Attach the tarball and its checksum. + for f in "$ASSET" "$ASSET.sha256"; do + curl -fsSL -X POST -H "$auth" \ + -F "attachment=@${f}" \ + "${API}/releases/${rid}/assets?name=${f}" + done diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..38a59d4 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release + +on: + push: + tags: + - '*.*.*' + +jobs: + release: + runs-on: pixi-build-rust + permissions: + contents: write + env: + API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }} + TOKEN: ${{ secrets.GITEA_TOKEN }} + TAG: ${{ gitea.ref_name }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check the tag matches the crate version + run: | + set -euo pipefail + version=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/') + tag="${TAG#v}" + if [ "$tag" != "$version" ]; then + echo "tag ${TAG} does not match Cargo.toml version ${version}" >&2 + exit 1 + fi + + - name: Install the pinned toolchain + run: pixi install + + - name: Build the full bundle (binary and license notices) + run: | + pixi run setup-tools + pixi run dist + + - name: Package and publish the release + run: | + set -euo pipefail + auth="Authorization: token ${TOKEN}" + jq() { pixi exec --spec jq -- jq "$@"; } + + version=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/') + asset="coursebank-${version}-linux-x64.tar.gz" + tar -czf "$asset" -C dist coursebank LICENSE.md THIRD-PARTY-LICENSES.txt + sha256sum "$asset" > "$asset.sha256" + + rid=$(curl -fsSL -X POST -H "$auth" -H "Content-Type: application/json" \ + "${API}/releases" \ + -d "$(jq -n --arg tag "$TAG" \ + '{tag_name:$tag, name:$tag, body:"See CHANGELOG.md.", draft:false, prerelease:false}')" \ + | jq -r '.id') + + for f in "$asset" "$asset.sha256"; do + curl -fsSL -X POST -H "$auth" \ + -F "attachment=@${f}" \ + "${API}/releases/${rid}/assets?name=${f}" + done diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..809e495 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to calendar versioning (`YY.MM.PATCH`); see [README.md](README.md#versioning). + +## [Unreleased] + +### Added + +- First public release. diff --git a/Cargo.toml b/Cargo.toml index 1a969d1..421d1e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coursebank" -version = "0.1.0" +version = "26.8.0" edition = "2024" rust-version = "1.85" description = "Author, assemble, administer, and analyze leveled course assessments from YAML item banks" diff --git a/README.md b/README.md index bf73ee2..67a3cb9 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,14 @@ coursebank builds with Rust 1.85 or newer (edition 2024). The repository uses [pixi](https://pixi.sh) to pin the toolchain and wrap the common tasks: ```sh -pixi run build # release binary at target/release/coursebank +pixi run build # release binary at target/release/coursebank +pixi run install # install the binary onto your PATH pixi run tests -pixi run check # format, lint, test, and doc build +pixi run check # verify: formatting, lint, tests, and doc build ``` +`pixi task list` prints every task with a one-line description. + Plain cargo works too with a recent toolchain: ```sh @@ -50,6 +53,16 @@ coursebank export typst exam-4 --form A Run `coursebank --help` for the full command set. +## Versioning + +Releases use calendar versioning in `YY.MM.PATCH` form. +The first release of August 2026 is `26.8.0`; a second that month is `26.8.1`; the first in September is `26.9.0`. +Neither the year nor the month is zero-padded, because the version also has to parse as SemVer and SemVer forbids leading zeros. +The number carries no compatibility promise, so treat any release as one that can change behavior and read [`CHANGELOG.md`](CHANGELOG.md) before upgrading a live course. + +`pixi run bump` cuts the next version and tags it; pushing that tag publishes a release, and every push to `main` refreshes a rolling `nightly` prerelease. +The workflows live in `.gitea/workflows/`; [RELEASING.md](RELEASING.md) explains the release flow and what to configure on the Gitea side. + ## License coursebank is source-available under the [Prosperity Public License 3.0.0](LICENSE.md). diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..60747f8 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,77 @@ +# Releasing + +This is the maintainer's guide to versioning and the release automation. +End users do not need it; they want the [README](README.md) and the setup guide. + +## Versioning + +Releases are dated, not semantic. The version is `YY.MM.PATCH`: + +- `YY` is the two-digit year. 2026 is `26`. +- `MM` is the month with no leading zero. August is `8`, not `08`. + The version has to parse as SemVer (Cargo insists), and SemVer rejects leading zeros in a numeric field, so `26.08.0` is invalid and `26.8.0` is the form to use. +- `PATCH` starts at `0` in a new month and counts up for any further releases that month. + +So the sequence across a few releases reads `26.8.0`, `26.8.1`, `26.9.0`. +These sort correctly both as dates and as SemVer, because each field is compared as a number. + +The number is a date, not a compatibility contract. +Any release can change behavior; the [CHANGELOG](CHANGELOG.md) is where that is written down. + +This is separate from `rust-version` in `Cargo.toml`, which is the oldest Rust that compiles the crate (currently `1.85`, the edition-2024 floor). +The pixi manifest pins a specific recent toolchain for reproducible builds. The two numbers answer different questions and are allowed to differ. + +## Cutting a stable release + +The version lives in two files, `Cargo.toml` and `pixi.toml`. +bump-my-version keeps them in step and computes the next number from today's date; it is configured in `.bumpversion.toml` and installed in the `release` pixi environment. + +1. Record what changed: move the `Unreleased` notes in `CHANGELOG.md` under a heading for the new version, and commit that. + A clean working tree is required, so this commit comes first. +2. Preview the number the bump would produce: + + ```sh + pixi run bump-show + ``` + +3. Cut it: + + ```sh + pixi run bump + ``` + + This rewrites the version in both manifests, makes a `Release ` commit, and tags it. + The number follows the calendar: the first release in a month is `.0`, and a later one that month is `.1`. + To see every edit before it happens, run `pixi run --environment release bump-my-version bump patch --dry-run --verbose`. + +4. Push the commit and its tag: + + ```sh + git push --follow-tags + ``` + +The pushed tag triggers `.gitea/workflows/release.yml`, which rebuilds from the tagged commit, checks that the tag matches the `Cargo.toml` version (the bump has just made them agree), and publishes a Gitea release with the packaged binary and its checksum attached. +A `v` prefix on the tag is tolerated if you ever tag by hand. + +## The workflows + +All three live in `.gitea/workflows/` and build through pixi, the same way the site deploy does, so the runner needs pixi rather than a hand-installed Rust toolchain. +Each workflow installs pixi if it is not already on the runner. + +- `ci.yml` runs on every push to `main` and every pull request. + It runs `pixi run check`: formatting, clippy, the full test suite, and a docs build. + This is the gate. +- `nightly.yml` runs on every push to `main`, so the nightly build tracks the branch. + It also carries an optional daily `cron`. + It builds the binary and publishes it as a single rolling prerelease tagged `nightly`, replacing the previous one so the tag always points at the current tip. +- `release.yml` runs on a version tag. + It builds, regenerates the third-party license notices, packages the full bundle, and publishes a normal (non-prerelease) release named after the tag. + +## Publishing the API docs + +`.gitea/workflows/docs.yml` builds the rustdoc and publishes it in two channels: `release/`, built from a version tag, and `nightly/`, built from `main`. +The site root redirects to the latest release, falling back to nightly until the first release exists. +Each page carries a small switcher to flip between the two channels. + +It runs on the `pixi-build-rust` runner, so the doc build reuses the baked toolchain and the crate cache rather than compiling cold. +That runner builds inside a container, so give its job containers access to the docs directory by adding it to the runner's `config.yaml` alongside the cache volumes. diff --git a/pixi.lock b/pixi.lock index 861727f..229bc73 100644 --- a/pixi.lock +++ b/pixi.lock @@ -394,6 +394,232 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1600.0.11.8-hb561403_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/typst-0.15.1-h6fdd925_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + release: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.46.1-default_h4852527_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.46.1-default_hfdba357_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.46.1-default_h4852527_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.11.0-h4d9bdce_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-14.4.0-h611768e_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-14.4.0-hc6a0c74_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.4.0-h9711763_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-14.4.0-hc469d41_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h54a6638_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.1.0-ha9f2e26_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.1.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.1.0-he0feb66_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-14.4.0-hf39dbba_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.1.0-h934c35e_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py314h2e6c369_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rust-1.96.1-h53717f1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd70dff1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bracex-3.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bump-my-version-1.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.3-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore2-2.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx2-2.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-14.4.0-hd9a9cd0_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-14.4.0-ha5b54cb_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-settings-2.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.8-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-x86_64-unknown-linux-gnu-1.96.1-h2c6d0dc_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.28-h4ee821c_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcmatch-11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + osx-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bracex-3.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bump-my-version-1.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.3-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore2-2.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx2-2.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-settings-2.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.8-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-x86_64-apple-darwin-1.96.1-h38e4360_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-64-26.0-h62b880e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcmatch-11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h374f1ed_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/c-compiler-1.11.0-h7a00415_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools-1030.6.3-llvm19_1_h67a6458_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_impl_osx-64-1030.6.3-llvm19_1_h6ae9dcd_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-1030.6.3-llvm19_1_h67a6458_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19-19.1.7-default_h9399c5b_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang-19.1.7-default_h1323312_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_impl_osx-64-19.1.7-default_ha1a018a_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/clang_osx-64-19.1.7-h8a78ed7_34.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/compiler-rt-19.1.7-he914875_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64-956.6-llvm19_1_hc3792c1_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-956.6-llvm19_1_hf4e8e46_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp19.1-19.1.7-default_h9399c5b_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-22.1.8-h19cb2f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.8.1-hcc62823_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-hd1f9c09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.18-h57a12c2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libllvm19-19.1.7-h56e7563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.3-hbb4bfdb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-hf3981d6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-h8c25ef7_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.53.4-h77d7759_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-16-2.15.3-h0d7f165_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.15.3-h0712280_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.2-hbb4bfdb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-22.1.8-h0d3cbff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19-19.1.7-h879f4bc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-19.1.7-hb0207f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.6-hcc0dc9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.3-hc881268_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pkg-config-0.29.2-hf7e621a_1009.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.46.4-py314h8916c15_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.14.6-h7c6738f_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/rust-1.96.1-h5655b98_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-h8c25ef7_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tapi-1600.0.11.8-h44950e2_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hb794df6_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bracex-3.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bump-my-version-1.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.3-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-arm64-19.1.7-he32a8d3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore2-2.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx2-2.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-settings-2.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.8-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-aarch64-apple-darwin-1.96.1-hf6ec828_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sdkroot_env_osx-arm64-26.0-ha3f98da_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcmatch-11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-compiler-1.11.0-h61f9b84_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools-1030.6.3-llvm19_1_hd01ab73_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_impl_osx-arm64-1030.6.3-llvm19_1_hc7668c6_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_osx-arm64-1030.6.3-llvm19_1_hd01ab73_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-19-19.1.7-default_hf3020a7_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-19.1.7-default_hf9bcbb7_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_impl_osx-arm64-19.1.7-default_hc11f16d_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_osx-arm64-19.1.7-h75f8d18_34.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt-19.1.7-h855ad52_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hc7cc350_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-956.6-llvm19_1_he86490a_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64_osx-arm64-956.6-llvm19_1_ha2625f7_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang-cpp19.1-19.1.7-default_hf3020a7_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.8-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.88.3-ha08bb59_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libllvm19-19.1.7-h8e0c9ce_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsigtool-0.1.3-h98dc951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.8-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-19-19.1.7-h91fd4e7_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-19.1.7-h855ad52_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pkg-config-0.29.2-hde07d2e_1009.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py314h54f3292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.6-h156bc91_101_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rust-1.96.1-h4ff7c5d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-codesign-0.1.3-h98dc951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1600.0.11.8-hb561403_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hd3d0363_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda build_number: 20 @@ -876,6 +1102,22 @@ packages: run_exports: {} size: 115175 timestamp: 1720805894943 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py314h2e6c369_0.conda + sha256: 802e216c39f1359aed60823b6e11d8ccd812b0ae1c81ae5ac1c81f99446409ab + md5: 0c96993dbeadf3a277cf757b9f1c9412 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + run_exports: {} + size: 1895020 + timestamp: 1778084229247 - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.6-habeac84_101_cp314.conda build_number: 101 sha256: ee8f2006e1724b1f2e9e0ccc5a7cfdcab973460faa2f63ac1f6e44fdad4c0344 @@ -994,6 +1236,65 @@ packages: - zstd >=1.5.7,<1.6.0a0 size: 601375 timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + sha256: b8fcb994134d3918d1c64a9d62f4168ff85d5bfb89e698102fe4ed679fe0df24 + md5: 108c928d2a8551832dbc762b535e90bb + depends: + - python >=3.10 + - typing-extensions >=4.0.0 + license: MIT + license_family: MIT + run_exports: {} + size: 19461 + timestamp: 1784935220549 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + sha256: e36998c5e860e26b22e5dbcd5726dd0c4eabad949c84d383cad8512757bbf6a1 + md5: fb568fbae6908ba86a090a85a089d11f + depends: + - exceptiongroup >=1.0.2 + - idna >=2.8 + - python >=3.10 + - typing_extensions >=4.5 + - python + constrains: + - trio >=0.32.0 + - uvloop >=0.22.1 + - winloop >=0.2.3 + license: MIT + license_family: MIT + run_exports: {} + size: 164465 + timestamp: 1783889660383 +- conda: https://conda.anaconda.org/conda-forge/noarch/bracex-3.0.1-pyhcf101f3_0.conda + sha256: 958861098a6985d27eb72579adf0866b3dc99076b5e21362aeb02e4773cb5bc1 + md5: 97f749ec797659e8338561149df0b76b + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 18298 + timestamp: 1784591912561 +- conda: https://conda.anaconda.org/conda-forge/noarch/bump-my-version-1.5.0-pyhd8ed1ab_0.conda + sha256: 961fadf4bf69ff62437e6032618c71b3c29a7565ec60c6741668d977cb923c96 + md5: b7c38694ce6e74e390565c23f4231ef7 + depends: + - click <8.4 + - httpx2 + - pydantic >=2.0.0 + - pydantic-settings + - python >=3.10 + - questionary + - rich + - rich-click + - tomlkit + - wcmatch >=8.5.1 + license: MIT + license_family: MIT + run_exports: {} + size: 54116 + timestamp: 1785233212974 - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda sha256: 0a0544cf95f64394fe4959286f5c71f5444ad58feb0602e53becb27448d24da6 md5: 0f51e2391ade309db462a55611263e9c @@ -1003,6 +1304,18 @@ packages: run_exports: {} size: 131780 timestamp: 1784754889428 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.3-pyhc90fa1f_0.conda + sha256: 37a5d8b10ea3516e2c42f870c9c351b9f7b31ff48c66d83490039f417e1e5228 + md5: 2266262ce8a425ecb6523d765f79b303 + depends: + - __unix + - python + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 100048 + timestamp: 1777219902525 - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-19.1.7-h138dee1_1.conda sha256: e6effe89523fc6143819f7a68372b28bf0c176af5b050fe6cf75b62e9f6c6157 md5: 32deecb68e11352deaa3235b709ddab2 @@ -1029,6 +1342,80 @@ packages: run_exports: {} size: 10490535 timestamp: 1757411851093 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + run_exports: {} + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb + md5: b8993c19b0c32a2f7b66cbb58ca27069 + depends: + - python >=3.10 + - typing_extensions + - python + license: MIT + license_family: MIT + run_exports: {} + size: 39069 + timestamp: 1767729720872 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore2-2.5.0-pyhcf101f3_0.conda + sha256: 24f4cda7df09d9f5e79bf1eaa47cfe0cf379beef13788f4cbb32bb9c29076f7a + md5: d58db3677b5352fe18bf36254b350c16 + depends: + - h11 >=0.16 + - python >=3.10 + - truststore >=0.10 + - python + constrains: + - anyio >=4.5.0,<5.0 + - h2 >=3,<5 + - socksio 1.* + - trio >=0.22.0,<1.0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 52749 + timestamp: 1782411845881 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpx2-2.5.0-pyhcf101f3_0.conda + sha256: 458d054e9641c8f5f792074bc85ce70ebc0928d1973c2c57ffb87c3c060cc62e + md5: 85b059d837508ef81872a293a563dd1a + depends: + - anyio + - httpcore2 ==2.5.0 + - idna >=3.18 + - python >=3.10 + - truststore >=0.10 + - typing_extensions >=4.5.0 + - python + constrains: + - click 8.* + - pygments 2.* + - rich >=10,<16 + - h2 >=3,<5 + - socksio 1.* + - zstandard >=0.18.0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 72841 + timestamp: 1782420230376 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda + sha256: c75632ea624aa450a394f570749420c5a2e0997d0216bc29d5d45b0f39df0426 + md5: 577b04680ae422adb86fc60d7b940659 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 163869 + timestamp: 1781620148226 - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda sha256: 41557eeadf641de6aeae49486cef30d02a6912d8da98585d687894afd65b356a md5: 86d9cba083cd041bfbf242a01a7a1999 @@ -1059,6 +1446,99 @@ packages: run_exports: {} size: 19516469 timestamp: 1785374476067 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + sha256: 0c4c35376fe920714390d46e4b8d31c876d65f18e1655899e0763ec25f2a902f + md5: 6d03368f2b2b0a5fb6839df53b2eb5e0 + depends: + - mdurl >=0.1,<1 + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 69017 + timestamp: 1778169663339 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 + md5: 592132998493b3ff25fd7479396e8351 + depends: + - python >=3.9 + license: MIT + license_family: MIT + run_exports: {} + size: 14465 + timestamp: 1733255681319 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + sha256: efe8def2c93aa34cd8d3c9af1dc4c7d312791cf769d8b2b615e32733e6df6051 + md5: 39c92a39517316e5001d645ae63d9ab9 + depends: + - python >=3.10 + - wcwidth + constrains: + - prompt_toolkit 3.0.53 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 276081 + timestamp: 1785160613307 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + sha256: 59628c765189e99ca5d3c51f0758a325bc020dfafb8fef6068045595aaae1baf + md5: 4c7171dde29a2f2b1dac681c1154a291 + depends: + - prompt-toolkit >=3.0.53,<3.0.54.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 7083 + timestamp: 1785160614678 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f + md5: 729843edafc0899b3348bd3f19525b9d + depends: + - typing-inspection >=0.4.2 + - typing_extensions >=4.14.1 + - python >=3.10 + - annotated-types >=0.6.0 + - pydantic-core ==2.46.4 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 346511 + timestamp: 1778103405862 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-settings-2.14.2-pyhcf101f3_0.conda + sha256: de1eb2cdb22a678387ec7757f10b7815c4c2a4d62f23b2a70fe4beff7e8a1fd4 + md5: a1c5305893d9956abd2079d377c5113f + depends: + - typing-inspection >=0.4.0 + - python >=3.10 + - pydantic >=2.7.0 + - python-dotenv >=0.21.0 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 52920 + timestamp: 1781884990751 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 + md5: 16c18772b340887160c79a6acc022db0 + depends: + - python >=3.10 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 893031 + timestamp: 1774796815820 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda + sha256: 74e417a768f59f02a242c25e7db0aa796627b5bc8c818863b57786072aeb85e5 + md5: 130584ad9f3a513cdd71b1fdc1244e9c + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 27848 + timestamp: 1772388605021 - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda build_number: 8 sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 @@ -1070,6 +1550,46 @@ packages: run_exports: {} size: 6989 timestamp: 1752805904792 +- conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda + sha256: 0604c6dff3af5f53e34fceb985395d08287137f220450108a795bcd1959caf14 + md5: 34fa231b5c5927684b03bb296bb94ddc + depends: + - prompt_toolkit >=2.0,<4.0 + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 31257 + timestamp: 1757356458097 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda + sha256: 3d6ba2c0fcdac3196ba2f0615b4104e532525ffa1335b50a2878be5ff488814a + md5: 0242025a3c804966bf71aa04eee82f66 + depends: + - markdown-it-py >=2.2.0 + - pygments >=2.13.0,<3.0.0 + - python >=3.10 + - typing_extensions >=4.0.0,<5.0.0 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 208577 + timestamp: 1775991661559 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.8-pyh8f84b5b_0.conda + sha256: 771b335400554d812dcdf7e40e54e47247db1f96ca0ab4413d061e6960844d9c + md5: 3251fe7203751993d1ee7ecb492b4a42 + depends: + - python >=3.10 + - rich >=12 + - click >=8 + - typing-extensions >=4 + - __unix + - python + license: MIT + license_family: MIT + run_exports: {} + size: 64412 + timestamp: 1780016631584 - conda: https://conda.anaconda.org/conda-forge/noarch/rust-std-aarch64-apple-darwin-1.96.1-hf6ec828_2.conda sha256: 1360d8d01a301379415b37a8ffc78f755302af3ee5603ba0546e4db82673af02 md5: 11b9425f6987f6fc387d98798befc850 @@ -1147,6 +1667,61 @@ packages: - __glibc >=2.28,<3.0.a0 size: 24008591 timestamp: 1765578833462 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomlkit-0.15.1-pyhcf101f3_0.conda + sha256: 5cf833b4d199688b7652fb322ecc134de9555e9444d9249750de9a153421ad0a + md5: e4942e2de7436ff28be7f5645cf3c8d6 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 49054 + timestamp: 1784287707171 +- conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.4-pyhcf101f3_0.conda + sha256: eece5be81588c39a855a0b70da84e0febb878a6d91dd27d6d21370ce9e5c5a46 + md5: c2db35b004913ec69bcac64fb0783de0 + depends: + - python >=3.10,<4 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 24279 + timestamp: 1766494826559 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + sha256: b141933ece3518f6d7b75dfb59451e2f26b405a44c18e2518a83e9a02e09315c + md5: c680b5747e8c4c8f23dca0bb7042a8fc + depends: + - typing_extensions ==4.16.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + run_exports: {} + size: 94080 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a + md5: 53f5409c5cfd6c5a66417d68e3f0a864 + depends: + - python >=3.10 + - typing_extensions >=4.12.0 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 20935 + timestamp: 1777105465795 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + sha256: 2d888f90af0686044882c74193ec80a90ec1943145d94a7b1b048958acda1848 + md5: c70ad746c22219b9700931707482992c + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + run_exports: {} + size: 52631 + timestamp: 1783002732887 - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 md5: fcb489df604d100968b737f2cb6076c6 @@ -1154,6 +1729,28 @@ packages: run_exports: {} size: 118849 timestamp: 1784250406640 +- conda: https://conda.anaconda.org/conda-forge/noarch/wcmatch-11.0-pyhcf101f3_0.conda + sha256: e77239d6a599be8b70b08fb06d1eb754288ad219a68bb28cfd3a92d226389243 + md5: c44743c4f4eb35b3078623bdff134af2 + depends: + - python >=3.10 + - bracex >=3.0 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 42758 + timestamp: 1783680391789 +- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + sha256: 4acf845da404e84cef1acccc66cc0156af1b83a5b5d7077b2ca19b705c561e57 + md5: 99f7755ec8648a042b0dbe906234f888 + depends: + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 132415 + timestamp: 1782771807703 - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h374f1ed_10.conda sha256: 4ed83961876dc8844a6f0df49c07b408efbaea275ffb0b37133e24c006990b3a md5: 9d9a39212a876e4bb751c1cc3927b678 @@ -1480,6 +2077,16 @@ packages: run_exports: {} size: 79899 timestamp: 1769482558610 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-h8c25ef7_1.conda + sha256: c3d76ff04ebed64d00a7ff5106297a55af20082a3d3df0ea7dfb235f94ba31c3 + md5: acc366a7706c83b5364f5f81e1b4857b + depends: + - __osx >=11.0 + - openssl >=3.5.7,<4.0a0 + license: MIT + run_exports: {} + size: 38559 + timestamp: 1786115361068 - conda: https://conda.anaconda.org/conda-forge/osx-64/libsigtool-0.1.3-hc0f2934_0.conda sha256: f87b743d5ab11c1a8ddd800dd9357fc0fabe47686068232ddc1d1eed0d7321ec md5: 3576aba85ce5e9ab15aa0ea376ab864b @@ -1659,6 +2266,21 @@ packages: run_exports: {} size: 239818 timestamp: 1720806136579 +- conda: https://conda.anaconda.org/conda-forge/osx-64/pydantic-core-2.46.4-py314h8916c15_0.conda + sha256: 555021648575077c62d429feb4530c724915af6f92e3d77f2accb4789c071778 + md5: a98f01ef277ca30e27a29bda4168b158 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + constrains: + - __osx >=10.13 + license: MIT + license_family: MIT + run_exports: {} + size: 1883108 + timestamp: 1778084289874 - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.14.6-h7c6738f_101_cp314.conda build_number: 101 sha256: 08c788453bdf61071e075a97684291286877536ee92f23a73b4127f1b85bdbcd @@ -1724,6 +2346,17 @@ packages: run_exports: {} size: 11567252 timestamp: 1777398362461 +- conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-h8c25ef7_1.conda + sha256: 37aac42f05e21b48a3b61b28ae624ed5a3d2acfcbaabf2cd10ef7762c4fb4c45 + md5: e7eb95a1f841fbbb8312dcd65963334f + depends: + - __osx >=11.0 + - libsigtool 0.1.3 h8c25ef7_1 + - openssl >=3.5.7,<4.0a0 + license: MIT + run_exports: {} + size: 123458 + timestamp: 1786115396108 - conda: https://conda.anaconda.org/conda-forge/osx-64/sigtool-codesign-0.1.3-hc0f2934_0.conda sha256: b89d89d0b62e0a84093205607d071932cca228d4d6982a5b073eec7e765b146d md5: 1261fc730f1d8af7eeea8a0024b23493 @@ -1933,6 +2566,18 @@ packages: run_exports: {} size: 97085 timestamp: 1757411887557 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hc7cc350_2.conda + sha256: f0b22bc30e4cc29e29ba3234cb38497fe8def2c2aae4b775d42fe5b378a018c9 + md5: 6133ddbb17ba2b50700dd88e9303ce27 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14070698 + timestamp: 1784916459058 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-956.6-llvm19_1_he86490a_5.conda sha256: 65a01b4adde1c1c7f4f6195590e90280bcb6ac34c03a81297a594a7830003c31 md5: 972af8a8dee1002cc8a9d331d273326c @@ -2151,6 +2796,29 @@ packages: run_exports: {} size: 36416 timestamp: 1767045062496 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsigtool-0.1.3-h98dc951_1.conda + sha256: fcfa03afe31f40dfabf011629d99836adbebbc3ed1b38c7e9eee9ffc7581975b + md5: c70eb797aa92a294b09ef8f41f6bd578 + depends: + - __osx >=11.0 + - openssl >=3.5.7,<4.0a0 + license: MIT + run_exports: {} + size: 36651 + timestamp: 1786115069018 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h1ae2325_0.conda + sha256: 745662565e103f290e9dc4263bbd88285082f8cf699854fe2d5f1e35a4a0d326 + md5: 0e3477c0c3e718dcf2eb74ccc8f68570 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 929203 + timestamp: 1785016131414 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h1b79a29_0.conda sha256: 9c50de03f8ff9f7e57fc5c13748736bae0538b93421eca0d3471ccb93f8b3d19 md5: 4aad9a4de332ab9ce571ef3901810b32 @@ -2163,6 +2831,22 @@ packages: - libsqlite >=3.53.4,<4.0a0 size: 925226 timestamp: 1785016124520 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_0.conda + sha256: ff75b84cdb9e8d123db2fa694a8ac2c2059516b6cbc98ac21fb68e235d0fd354 + md5: 19edaa53885fc8205614b03da2482282 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.3 + license: MIT + license_family: MIT + run_exports: {} + size: 466360 + timestamp: 1776377102261 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h6967ea9_0.conda sha256: 43895a7517c055b8893531290f9dc48bd751eb04be04f14bbce3b6c71b052be6 md5: 6c8292c2ee808aeef2406083beaa6da7 @@ -2179,6 +2863,24 @@ packages: run_exports: {} size: 465820 timestamp: 1776377317454 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_0.conda + sha256: 2fe1d8de0854342ae9cabe408b476935f82f5636e153b3b497456264dc8ff3a1 + md5: 8e037d73747d6fe34e12d7bcac10cf21 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.3 h5ef1a60_0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.3 + size: 41102 + timestamp: 1776377119495 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-heed7d32_0.conda sha256: 4d9c117b2dd222cf891710d5f6a570ebb275479979843a1477ac54ed50907b40 md5: 0c1fdc80534d8f25fd74722aba81f044 @@ -2333,6 +3035,22 @@ packages: run_exports: {} size: 49724 timestamp: 1720806128118 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py314h54f3292_0.conda + sha256: c034a7cc16f279c260d3456fcfc625838495a7f9f55aa79408a629a427769bb2 + md5: 16314d88257820013e698381af19153f + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __osx >=11.0 + - python 3.14.* *_cp314 + - python_abi 3.14.* *_cp314 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + run_exports: {} + size: 1722694 + timestamp: 1778084354332 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.6-h156bc91_101_cp314.conda build_number: 101 sha256: fc70ae73df7798bce7cac7adef7fdfb874208b2623a0e8ccb4354194b8508769 @@ -2408,6 +3126,17 @@ packages: run_exports: {} size: 114331 timestamp: 1767045086274 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-codesign-0.1.3-h98dc951_1.conda + sha256: 7efd6d7d18bf9cc5788bfe1c1e1620d9dbbe00a81c646814929a82ff31944cf3 + md5: a322c0a0d3b5be99ee11f9ccec99b60e + depends: + - __osx >=11.0 + - libsigtool 0.1.3 h98dc951_1 + - openssl >=3.5.7,<4.0a0 + license: MIT + run_exports: {} + size: 114653 + timestamp: 1786115093248 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1600.0.11.8-hb561403_3.conda sha256: 9f1cc109e6e57861110e9de6e03beca7fd2b7fbdb46124cccc07990b0844d88a md5: 0676b8719dd1e9bb1003e59a481c6c3e diff --git a/pixi.toml b/pixi.toml index 0fc9e6c..36b7112 100644 --- a/pixi.toml +++ b/pixi.toml @@ -1,62 +1,73 @@ [workspace] -authors = ["Alex Maldonado "] -channels = ["conda-forge"] name = "coursebank" +version = "26.8.0" +authors = ["Scientific Computing Studio "] +channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64"] -version = "0.1.0" - -[environments] -dev = ["dev"] -docs = ["docs"] [dependencies] rust = ">=1.96.0,<1.97" c-compiler = "*" pkg-config = "*" + [tasks] -build = "cargo build --release" -debug = "cargo build" -tests = "cargo test" -format = "cargo fmt" -lint = { cmd = "cargo clippy --all-targets -- -D warnings", depends-on = [ - "format", -] } -check-docs = "cargo test --test docs" -doc = { cmd = "cargo doc --no-deps --open", depends-on = ["check-docs"] } -doc-build = { cmd = "cargo doc --no-deps", depends-on = ["check-docs"] } -doctests = "cargo test --doc" +# --- Build and run --- +build = { cmd = "cargo build --release", description = "Compile the release binary at target/release/coursebank" } +debug = { cmd = "cargo build", description = "Compile the debug binary" } +build-lean = { cmd = "cargo build --release --no-default-features", description = "Release build without the parquet feature" } +install = { cmd = "cargo install --path . --locked", description = "Install the coursebank binary onto your PATH" } +clean = { cmd = "cargo clean", description = "Remove the target directory" } +cb = { cmd = "cargo run --release --quiet --", description = "Run the CLI, e.g., `pixi run cb validate -C path/to/course`" } -clean = "cargo clean" -install = "cargo install --path . --locked" -check = { depends-on = ["format", "lint", "tests", "check-docs", "doc-build"] } -build-lean = "cargo build --release --no-default-features" +# --- Quality gate --- +format = { cmd = "cargo fmt --all", description = "Reformat the source in place" } +fmt-check = { cmd = "cargo fmt --all --check", description = "Fail if the source is not formatted; does not rewrite" } +lint = { cmd = "cargo clippy --all-targets -- -D warnings", description = "Run clippy with warnings treated as errors" } +tests = { cmd = "cargo test", description = "Run all tests: unit, integration, and doctests" } +doctests = { cmd = "cargo test --doc", description = "Run only the documentation examples" } +check-docs = { cmd = "cargo test --test docs", description = "Check that included markdown fences declare a language" } +check = { depends-on = ["fmt-check", "lint", "tests", "doctests", "check-docs", "doc-build"], description = "Full verify-only gate: formatting, lint, tests, and doc build" } -setup-tools = "cargo install cargo-about --locked --features cli" -licenses = "cargo about generate about.hbs -o THIRD-PARTY-LICENSES.txt" +# --- Documentation --- +doc = { cmd = "cargo doc --no-deps --open", depends-on = ["check-docs"], description = "Build the API docs and open them in a browser" } +doc-build = { cmd = "cargo doc --no-deps", depends-on = ["check-docs"], description = "Build the API docs without opening a browser" } -[tasks.dist] -description = "Assemble a release bundle (binary + license notices) in dist/" -depends-on = ["build", "licenses"] -cmd = "mkdir -p dist && cp target/release/coursebank dist/ && cp LICENSE.md THIRD-PARTY-LICENSES.txt dist/" +# --- Release packaging --- +setup-tools = { cmd = "cargo install cargo-about --locked --features cli", description = "Install cargo-about, which the licenses task needs" } +licenses = { cmd = "cargo about generate about.hbs -o THIRD-PARTY-LICENSES.txt", description = "Regenerate THIRD-PARTY-LICENSES.txt" } +dist = { cmd = "mkdir -p dist && cp target/release/coursebank dist/ && cp LICENSE.md THIRD-PARTY-LICENSES.txt dist/", depends-on = ["build", "licenses"], description = "Assemble a release bundle (binary and license notices) in dist/" } +[environments] +dev = ["dev"] +docs = ["docs"] +release = ["release"] -[tasks.cb] -cmd = "cargo run --release --quiet --" -description = "Run the CLI, e.g., `pixi run cb validate --course examples/course`" - +# Developer tooling: editor support, a debugger, a faster test runner, and a +# file watcher. [feature.dev.dependencies] rust-analyzer = "*" lldb = "*" cargo-nextest = "*" [feature.dev.tasks] -watch = "cargo watch -x check -x test" -nextest = "cargo nextest run" +nextest = { cmd = "cargo nextest run", description = "Run the test suite with nextest" } +# Typst is only needed to typeset exported .typ files by hand. The exporter +# writes .typ on its own and does not require typst to be installed. [feature.docs.dependencies] typst = "*" [feature.docs.tasks] -typeset = "typst compile" +typeset = { cmd = "typst compile", description = "Compile a .typ file to PDF" } + +# Release tooling, kept in its own environment so the default and dev +# environments do not pull in Python. bump-my-version reads .bumpversion.toml, +# which encodes the YY.MM.PATCH scheme. +[feature.release.dependencies] +bump-my-version = "*" + +[feature.release.tasks] +bump = { cmd = "bump-my-version bump patch", description = "Cut the next release: rewrite the version in both manifests, commit, and tag" } +bump-show = { cmd = "bump-my-version show-bump", description = "Preview the next version without changing anything" }