diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..40cca84 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # covers all .github/workflows/* + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b83a0c..9fe8ad2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,41 @@ --- name: Run tests -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: jobs: Test: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + malli: ["0.14", "0.15", "0.16", "0.17", "0.18", "0.19", "latest", "head"] + continue-on-error: ${{ matrix.malli == 'head' }} steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Install clojure tools - uses: DeLaGuardo/setup-clojure@10.0 + uses: DeLaGuardo/setup-clojure@4c7a6f613e5089821bb3bb2a33a3ee115578580d # 13.6.1 with: cli: latest - - name: Run tests - run: clojure -T:build test + - name: Run tests (malli ${{ matrix.malli }}) + run: | + case "${{ matrix.malli }}" in + latest) + clojure -X:test + ;; + head) + sha=$(git ls-remote https://github.com/metosin/malli.git HEAD | cut -f1) + echo "Testing against metosin/malli@$sha" + clojure -Sdeps "{:aliases {:malli-head {:override-deps {metosin/malli {:git/url \"https://github.com/metosin/malli.git\" :git/sha \"$sha\"}}}}}" -X:test:malli-head + ;; + *) + clojure -X:test:malli-${{ matrix.malli }} + ;; + esac diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25b2ba7..49be626 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Install clojure tools - uses: DeLaGuardo/setup-clojure@10.0 + uses: DeLaGuardo/setup-clojure@4c7a6f613e5089821bb3bb2a33a3ee115578580d # 13.6.1 with: cli: latest @@ -26,4 +26,4 @@ jobs: env: CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} - run: clojure -T:build release :build/git-version $(printf '"%s"' $(git describe --tags)) :deploy/only-jar-version-type :full-and-snapshot + run: clojure -T:build release :build/git-version "$(printf '"%s"' "$(git describe --tags)")" :deploy/only-jar-version-type :full-and-snapshot diff --git a/.gitignore b/.gitignore index 1548a67..07ed09e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,10 @@ /checkouts /classes /target + +/.worktree.yml +/.claude +/bin/worktree-dev + +/tmp/* +!/tmp/.keep diff --git a/LICENSE b/LICENSE index 4dbe32a..e70fc9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Gert Goet, ThinkCreate +Copyright (c) 2026 Gert Goet, ThinkCreate Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0a46543..8a6eec5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # malli-select -[![Clojars Project](https://img.shields.io/clojars/v/dk.thinkcreate/malli-select.svg?include_prereleases)](https://clojars.org/dk.thinkcreate/malli-select) [![cljdoc badge](https://cljdoc.org/badge/dk.thinkcreate/malli-select)](https://cljdoc.org/d/dk.thinkcreate/malli-select) +[![Clojars Project](https://img.shields.io/clojars/v/dk.thinkcreate/malli-select.svg?include_prereleases)](https://clojars.org/dk.thinkcreate/malli-select) [![cljdoc badge](https://cljdoc.org/badge/dk.thinkcreate/malli-select)](https://cljdoc.org/d/dk.thinkcreate/malli-select) [![Tests](https://github.com/eval/malli-select/actions/workflows/ci.yml/badge.svg)](https://github.com/eval/malli-select/actions/workflows/ci.yml) Create subschemas of [malli](https://github.com/metosin/malli)-schemas using a spec2-inspired select notation. @@ -8,10 +8,82 @@ It's based on Rich Hickey's ideas from his talk ["Maybe Not"](https://youtu.be/Y ## Quickstart -[deps-try](https://github.com/eval/deps-try/blob/master/README.md#installation) has a built-in recipe that walks you through malli-select's features on the REPL ([recipe source](https://github.com/eval/deps-try/blob/master/recipes/malli/malli_select.clj)). -Run like so: +Follow along using [deps-try](https://github.com/eval/deps-try/blob/master/README.md#installation): + ``` clojure -$ deps-try --recipe malli/malli-select +$ deps-try io.github.eval/malli-select metosin/malli +# no install/docker +$ docker run -it --rm -v deps-try:/home/deps-try-user ghcr.io/eval/deps-try io.github.eval/malli-select metosin/malli + +user=> (require '[malli-select.core :as ms] + '[malli.core :as m :refer [form] :rename {form p}]) +user=> (def Person + [:map + [:name string?] + [:age pos-int?] + [:addresses [:vector [:map + [:street string?] [:zip string?]]]]]) + +;; require :name, everything else is optional +user=> (p (ms/select Person [:name])) +[:map + [:name string?] + [:age {:optional true} pos-int?] + [:addresses + {:optional true} + [:vector + [:map + [:street {:optional true} string?] + [:country {:optional true} string?]]]]] + +;; *if* any address is provided, it should at least have :street +user=> (p (ms/select Person [{:addresses [:street]}])) +[:map + [:name {:optional true} string?] + [:age {:optional true} pos-int?] + [:addresses + {:optional true} + [:vector + [:map [:street string?] [:country {:optional true} string?]]]]] + +;; example valid data: +;; {}, {:addresses []}, {:addresses [{:street "Main"}]} +;; +;; example invalid data: +;; {:addresses nil}, {:addresses [{}]}, {:addresses [{:street "Foo" :country :se}]} + +;; any address provided should be a full address +user=> (p (ms/select Person [{:addresses ['*]}])) +;; +;; require all attributes of a person (shallow, i.e. address attributes become optional) +user=> (p (ms/select Person ['*])) +;; example valid data: +;; {:name "Foo" :age 18 :addresses [{}]} + + +;; remove any optional attribute +user=> (p (ms/select Person [{:addresses ['*]}] {:prune-optionals true})) +;; or shorter: +user=> (p (ms/select Person ^:only [{:addresses ['*]}])) +;; example valid data: +;; {:name :not-a-string} +;; +;; Typically you'd use this to generate only specific data: +user=> (require '[malli.generator :as mg]) +user=> (mg/generate (ms/select Person ^:only [:name])) +{:name "sNeLdUI5KtPw"} + +;; selecting something not contained in the schema: +user=> (ms/select Person [:a]) +Execution error (AssertionError) at dk.thinkcreate.malli-select/select (malli_select.clj:175). +Assert failed: Selection contains unknown paths: ([:a]) + +Available: +([:addresses] [:age] [:name] [:addresses :street] [:addresses :zip]) + +(empty? invalid-selection-paths) +;; bypass this check: +user=> (ms/select Person [:a] {:verify-selection false}) ``` See [the tests](./test/malli_select/core_test.clj) for more. @@ -19,5 +91,5 @@ See [the tests](./test/malli_select/core_test.clj) for more. ## LICENSE -Copyright (c) 2023 Gert Goet, ThinkCreate. +Copyright (c) 2026 Gert Goet, ThinkCreate. Distributed under the MIT license. See [LICENSE](LICENSE). diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..93295db --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,87 @@ +# Releasing + +Releases are fully automated: pushing a git tag triggers the +[release workflow](.github/workflows/release.yml), which tests, builds and +deploys `dk.thinkcreate/malli-select` to [Clojars](https://clojars.org/dk.thinkcreate/malli-select). + +## TL;DR + +Local tags must be *signed* (`-s`): + +```sh +# snapshot release (publishes X.Y.Z-SNAPSHOT) +git tag -s vX.Y.Z-pre.1 -m "vX.Y.Z-pre.1" +git push origin vX.Y.Z-pre.1 + +# full release (publishes X.Y.Z) +git tag -s vX.Y.Z -m "vX.Y.Z" +git push origin vX.Y.Z +``` + +Watch the run with `gh run watch`. + +Alternatively create a [release on GitHub](https://github.com/eval/malli-select/releases/new) +(or `gh release create vX.Y.Z`) and let it create the tag — mark it as a +prerelease when using a `-pre` tag. Tags created this way aren't locally +signed but are attributed to your GitHub account. + +## How it works + +The workflow runs on every push to `main` and on every tag: + +```sh +clojure -T:build release :build/git-version $(printf '"%s"' $(git describe --tags)) :deploy/only-jar-version-type :full-and-snapshot +``` + +`release` (see [build.clj](build.clj)) chains `test` → `build` → `deploy`. +The output of `git describe --tags` determines the version and whether the +built jar is actually deployed: + +| `git describe --tags` | jar version | deployed? | +|-----------------------------------|-------------------|-------------------------------| +| `v1.2.3` (exact release tag) | `1.2.3` | yes — full release | +| `v1.2.3-pre.1` (pre-tag, or any commit after one) | `1.2.3-SNAPSHOT` | yes — snapshot | +| `v1.2.3-5-gabc123` (commits after a release tag) | `1.2.3-5-gabc123` | no — build only | + +Consequences: + +- A full release requires an *exact* `vX.Y.Z` tag on the commit. +- After pushing a `vX.Y.Z-pre.N` tag, every subsequent push to `main` + re-publishes `X.Y.Z-SNAPSHOT` — until the next exact release tag. +- Ordinary pushes to `main` after a release tag act as a dry run: + tests run and the jar is built, but nothing is deployed. +- Pick pre-tag versions to match the *next* intended release, e.g. after + releasing `v0.7.0` the next pre-tag should be `v0.8.0-pre.1`. +- For a full release the POM's `` is set to `vX.Y.Z`. + +## Credentials + +`deploy` authenticates with the `CLOJARS_USERNAME` and `CLOJARS_PASSWORD` +[repository secrets](https://github.com/eval/malli-select/settings/secrets/actions). + +- `CLOJARS_USERNAME`: the Clojars account name. +- `CLOJARS_PASSWORD`: a Clojars [deploy token](https://clojars.org/tokens) — + *not* the account password. Prefer a token scoped to the `dk.thinkcreate` + group. + +To rotate: create a new token on Clojars, then +`gh secret set CLOJARS_PASSWORD --repo eval/malli-select`, and delete the old +token. The cheapest end-to-end check of the credentials is publishing a +snapshot via a `-pre` tag (see TL;DR); the token's "last used" date on the +Clojars tokens page should update. + +## Local release + +The same can be done locally (e.g. when CI is down): + +```sh +CLOJARS_USERNAME=... CLOJARS_PASSWORD= \ + clojure -T:build release :build/git-version $(printf '"%s"' $(git describe --tags)) :deploy/only-jar-version-type :full-and-snapshot +``` + +## Gotchas + +- `git describe --tags` needs the full history: the checkout step uses + `fetch-depth: 0` for this — keep it when touching the workflow. +- Tests run against the `:test` alias; a test failure aborts the release + before anything is built or deployed. diff --git a/build.clj b/build.clj index 1291702..9587f67 100644 --- a/build.clj +++ b/build.clj @@ -156,9 +156,13 @@ v (not s)))] (if deploy? - (dd/deploy {:installer :remote - :artifact (b/resolve-path jar-file) - :pom-file pom-file}) + (do + ;; guards against the write-pom patch (top of this file) silently losing effect + (when-not (re-find #"provided" (slurp pom-file)) + (throw (ex-info "No provided-scoped dependency in pom.xml" {:pom-file pom-file}))) + (dd/deploy {:installer :remote + :artifact (b/resolve-path jar-file) + :pom-file pom-file})) (println (str \newline "Skipping deploy of version " version " given only-jar-release-type " only-jar-version-type))) opts)) diff --git a/deps.edn b/deps.edn index bfb456a..35b85eb 100644 --- a/deps.edn +++ b/deps.edn @@ -1,38 +1,45 @@ {:paths ["src"] - :deps {org.clojure/clojure {:mvn/version "1.11.1" :scope "provided"} - metosin/malli {:mvn/version "0.13.0" :scope "provided"}} + :deps {org.clojure/clojure {:mvn/version "1.12.0" :scope "provided"} + metosin/malli {:mvn/version "0.20.1" :scope "provided"}} :aliases - {:dev {:extra-deps {metosin/malli {:mvn/version "0.8.9"} - criterium/criterium {:mvn/version "0.4.6"} - com.clojure-goes-fast/clj-async-profiler {:mvn/version "1.0.5"}}} + {:dev {:extra-deps {criterium/criterium {:mvn/version "0.4.6"} + com.clojure-goes-fast/clj-async-profiler {:mvn/version "1.8.0"}}} :neil {:project {:name malli-select/malli-select}} - :malli-0.9 - {:override-deps {metosin/malli {:mvn/version "0.9.2"}}} + :malli-0.13 + {:override-deps {metosin/malli {:mvn/version "0.13.0"}}} - :malli-0.10 - {:override-deps {metosin/malli {:mvn/version "0.10.4"}}} + :malli-0.14 + {:override-deps {metosin/malli {:mvn/version "0.14.0"}}} - :malli-0.11 - {:override-deps {metosin/malli {:mvn/version "0.11.0"}}} + :malli-0.15 + {:override-deps {metosin/malli {:mvn/version "0.15.0"}}} - :malli-0.12 - {:override-deps {metosin/malli {:mvn/version "0.12.0"}}} + :malli-0.16 + {:override-deps {metosin/malli {:mvn/version "0.16.4"}}} + + :malli-0.17 + {:override-deps {metosin/malli {:mvn/version "0.17.0"}}} + + :malli-0.18 + {:override-deps {metosin/malli {:mvn/version "0.18.0"}}} + + :malli-0.19 + {:override-deps {metosin/malli {:mvn/version "0.19.2"}}} :test ;; added by neil {:extra-paths ["test"] - :extra-deps {metosin/malli {:mvn/version "0.8.9"} - io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}} + :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}} :exec-fn cognitect.test-runner.api/test} :perf {#_#_:extra-paths ["perf"] :extra-deps {criterium/criterium {:mvn/version "0.4.6"} - org.clojure/clojure {:mvn/version "1.11.1"} - com.clojure-goes-fast/clj-async-profiler {:mvn/version "1.0.5"}} + org.clojure/clojure {:mvn/version "1.12.0"} + com.clojure-goes-fast/clj-async-profiler {:mvn/version "1.8.0"}} :jvm-opts ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true" "-Djdk.attach.allowAttachSelf"]} :build {:deps {io.github.clojure/tools.build - {:mvn/version "0.9.6"} - slipset/deps-deploy {:mvn/version "0.2.1"}} + {:mvn/version "0.10.14"} + slipset/deps-deploy {:mvn/version "0.2.5"}} :ns-default build}}} diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000..e69de29