#!/usr/bin/env bb ;; -*- mode: clojure -*- ;; DO NOT EDIT DIRECTLY. GENERATED FROM scr/babashka/neil.clj (require '[babashka.deps :as deps]) (deps/add-deps '{:deps {borkdude/rewrite-edn {:mvn/version "0.4.8"} org.babashka/spec.alpha {:git/url "https://github.com/babashka/spec.alpha" :git/sha "1a841c4cc1d4f6dab7505a98ed2d532dd9d56b78"} io.github.seancorfield/deps-new {:git/url "https://github.com/seancorfield/deps-new" :git/tag "v0.5.0" :git/sha "48bf01e"} org.babashka/cli {:mvn/version "0.8.58"} version-clj/version-clj {:mvn/version "2.0.2"} org.babashka/http-client {:mvn/version "0.1.4"}}}) (ns babashka.neil.meta) (def version "This def was generated by the neil build script." "0.3.65") (ns babashka.neil.utils) ;; Workaround for pmap + require which doesn't work well in bb - 2023-02-04 (def ^:private lock (Object.)) (defn- serialized-require [& args] (locking lock (apply require args))) (defn req-resolve [sym] (if (qualified-symbol? sym) (or (resolve sym) (do (-> sym namespace symbol serialized-require) (resolve sym))) (throw (IllegalArgumentException. (str "Not a qualified symbol: " sym))))) ;; End workaround (ns babashka.neil.curl {:no-doc true} (:require [babashka.http-client :as curl] [cheshire.core :as cheshire] [clojure.string :as string])) (import java.net.URLEncoder) (def unexceptional-statuses #{200 201 202 203 204 205 206 207 300 301 302 303 304 307}) (defn url-encode [s] (URLEncoder/encode s "UTF-8")) (def github-user (or (System/getenv "NEIL_GITHUB_USER") (System/getenv "BABASHKA_NEIL_DEV_GITHUB_USER"))) (def github-token (or (System/getenv "NEIL_GITHUB_TOKEN") (System/getenv "BABASHKA_NEIL_DEV_GITHUB_TOKEN"))) (def curl-opts (merge {:throw false} (when (and github-user github-token) {:basic-auth [github-user github-token]}))) (defn curl-get-json ([url] (curl-get-json url nil)) ([url opts] (let [response (curl/get url (merge curl-opts opts)) parsed-body (try (-> response :body (cheshire/parse-string true)) (catch Exception e (binding [*out* *err*] (println "Unable to parse body as JSON:") (println (ex-message e)) (println (-> response :body))) nil #_(throw e)))] (cond (and (= 403 (:status response)) (string/includes? url "api.github") (string/includes? (:message parsed-body) "rate limit")) (binding [*out* *err*] (println "You've hit the github rate-limit (60 reqs/hr). You can set an API Token to increase the limit. See neil's README for details.") nil #_(System/exit 1)) (contains? unexceptional-statuses (:status response)) parsed-body (= 404 (:status response)) nil :else (binding [*out* *err*] (println (or (not-empty (:body response)) (str url " request returned status code: " (:status response)))) nil))))) (ns babashka.neil.git {:no-doc true} (:require [babashka.fs :as fs] [babashka.neil.curl :refer [curl-get-json]] [babashka.process :refer [sh]] [clojure.string :as str])) (defn default-branch [lib] (get (curl-get-json (format "https://api.github.com/repos/%s/%s" (namespace lib) (name lib))) :default_branch)) (defn clean-github-lib [lib] (let [lib (str/replace lib "com.github." "") lib (str/replace lib "io.github." "") lib (symbol lib)] lib)) (defn latest-github-sha [lib] (let [lib (clean-github-lib lib) branch (default-branch lib)] (get (curl-get-json (format "https://api.github.com/repos/%s/%s/commits/%s" (namespace lib) (name lib) branch)) :sha))) (defn list-github-tags [lib] (let [lib (clean-github-lib lib)] (curl-get-json (format "https://api.github.com/repos/%s/%s/tags" (namespace lib) (name lib))))) (defn latest-github-tag [lib] (-> (list-github-tags lib) first)) (defn find-github-tag [lib tag] (->> (list-github-tags lib) (filter #(= (:name %) tag)) first)) (defn parse-status [line] (let [[[_ status path]] (re-seq #"^(.{2}) (.+)$" line)] {:status status :path path})) (defn status [git-opts] (let [cmd "git status --porcelain=v1" {:keys [out err]} (sh cmd git-opts)] (if-not (str/blank? err) {:err err} {:statuses (if (str/blank? out) '() (map parse-status (str/split-lines out)))}))) (defn commit [message git-opts] (let [cmd ["git" "commit" "-m" message] {:keys [err]} (sh cmd git-opts)] (when (seq err) (throw (ex-info err {}))))) (defn tag [message git-opts] (let [cmd ["git" "tag" "-a" message "-m" message] {:keys [err]} (sh cmd git-opts)] (when (seq err) (throw (ex-info err {}))))) (defn unstaged-files [status-result] (->> (:statuses status-result) (filter #(re-seq #"^ " (:status %))) (map :path))) (defn staged-files [status-result] (->> (:statuses status-result) (filter #(re-seq #"^[ARMD]" (:status %))) (map :path))) (defn resolve-repo [deps-file] (if deps-file (fs/file (fs/parent deps-file) ".git") (fs/file ".git"))) (defn repo? [deps-file] (fs/exists? (resolve-repo deps-file))) (defn commit-count [git-opts] (-> (sh "git rev-list --count HEAD" git-opts) :out str/trim Integer/parseInt)) (defn ensure-repo [git-opts] (sh "git init -b main" git-opts) (sh "git config user.name 'Neil Tests'" git-opts) (sh "git config user.email '<>'" git-opts) (sh "git add ." git-opts) (sh "git commit -m 'First commit'" git-opts)) (defn list-tags [git-opts] (let [{:keys [out]} (sh "git tag" git-opts)] (str/split-lines (str/trim out)))) (defn tag-contents [tag git-opts] (let [cmd ["git" "for-each-ref" (str "refs/tags/" tag) "--format" "%(contents)"] {:keys [out]} (sh cmd git-opts)] (not-empty (str/trim out)))) (defn add [files git-opts] (sh (concat ["git" "add"] files) git-opts)) (defn describe [git-opts] (not-empty (str/trim (:out (sh ["git" "describe"] git-opts))))) (defn show [git-opts] (not-empty (str/trim (:out (sh ["git" "show" "-s" "--format=%B"] git-opts))))) (ns babashka.neil.rewrite (:require [clojure.string :as str])) (defn indent [s n] (let [spaces (apply str (repeat n " ")) lines (str/split-lines s)] (str/join "\n" (map (fn [s] (if (str/blank? s) s (str spaces s))) lines)))) (defn clean-trailing-whitespace [s] (str/join "\n" (map str/trimr (str/split-lines s)))) (ns babashka.neil.project (:require [babashka.fs :as fs] [borkdude.rewrite-edn :as r] [clojure.edn :as edn])) (defn resolve-deps-file [dir deps-file] (if dir (fs/file dir deps-file) deps-file)) (defn ensure-neil-project [{:keys [dir deps-file]}] (let [deps-file (resolve-deps-file dir deps-file) deps-edn (slurp deps-file) edn (edn/read-string deps-edn)] (when-not (some-> edn :aliases :neil :project) (let [existing-aliases (:aliases edn) edn-nodes (r/parse-string deps-edn) edn-nodes (cond-> edn-nodes (not (:aliases edn)) (r/assoc :aliases (r/parse-string "\n {}")) (contains? existing-aliases :neil) (r/assoc-in [:aliases :neil] (r/parse-string "\n {}")) (not (some-> edn :aliases :neil :project)) (r/assoc-in [:aliases :neil :project] {}))] (spit deps-file (str edn-nodes)))))) (defn assoc-project-meta! "Updates deps-file's :neil :project `k` with `v`" [{:keys [dir deps-file k v] :as opts}] (ensure-neil-project opts) (let [deps-file (resolve-deps-file dir deps-file) deps-edn (slurp deps-file) nodes (r/parse-string deps-edn) nodes (r/assoc-in nodes [:aliases :neil :project k] v)] (spit deps-file (str nodes)))) (defn project-name [{:keys [deps-file]}] (-> (edn/read-string (slurp deps-file)) :aliases :neil :project :name)) (defn coerce-project-name [pn] (let [sym (symbol pn)] (if (qualified-symbol? sym) sym (symbol (str pn) (str pn))))) (ns babashka.neil.new {:no-doc true} (:require [babashka.neil.git :as git] [babashka.neil.project :as proj] [babashka.neil.utils :refer [req-resolve]] [clojure.edn :as edn] [clojure.set :as set] [clojure.string :as str])) (defn- built-in-template? "Returns true if the template name maps to a function in org.corfield.new." [template] (contains? (set (map name (keys (ns-publics 'org.corfield.new)))) template)) (defn- built-in-templates [] (require 'org.corfield.new) (let [non-templates #{'create}] (->> (ns-publics 'org.corfield.new) (remove (fn [[sym _]] (contains? non-templates sym))) (into {})))) (defn- github-repo-http-url [lib] (str "https://github.com/" (git/clean-github-lib lib))) (def github-repo-ssh-regex #"^git@github.com:([^/]+)/([^\.]+)\.git$") (def github-repo-http-regex #"^https://github.com/([^/]+)/([^\.]+)(\.git)?$") (defn- parse-git-url [git-url] (let [[[_ gh-user repo-name]] (or (re-seq github-repo-ssh-regex git-url) (re-seq github-repo-http-regex git-url))] (if (and gh-user repo-name) {:gh-user gh-user :repo-name repo-name} (throw (ex-info "Failed to parse :git/url" {:git/url git-url}))))) (defn- git-url->lib-sym [git-url] (when-let [{:keys [gh-user repo-name]} (parse-git-url git-url)] (symbol (str "io.github." gh-user) repo-name))) (def lib-opts->template-deps-fn "A map to define valid CLI options for deps-new template deps. - Each key is a sequence of valid combinations of CLI opts. - Each value is a function which returns a tools.deps lib map." {[#{:local/root}] (fn [lib-sym lib-opts] {lib-sym (select-keys lib-opts [:local/root])}) [#{} #{:git/url}] (fn [lib-sym lib-opts] (let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) tag (git/latest-github-tag (git-url->lib-sym url))] (if tag {lib-sym {:git/url url :git/tag (:name tag) :git/sha (-> tag :commit :sha)}} (let [sha (git/latest-github-sha (git-url->lib-sym url))] {lib-sym {:git/url url :git/sha sha}})))) [#{:git/tag} #{:git/url :git/tag}] (fn [lib-sym lib-opts] (let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) tag (:git/tag lib-opts) {:keys [commit]} (git/find-github-tag (git-url->lib-sym url) tag)] {lib-sym {:git/url url :git/tag tag :git/sha (:sha commit)}})) [#{:git/sha} #{:git/url :git/sha}] (fn [lib-sym lib-opts] (let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) sha (:git/sha lib-opts)] {lib-sym {:git/url url :git/sha sha}})) [#{:latest-sha} #{:git/url :latest-sha}] (fn [lib-sym lib-opts] (let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) sha (git/latest-github-sha (git-url->lib-sym url))] {lib-sym {:git/url url :git/sha sha}})) [#{:git/url :git/tag :git/sha}] (fn [lib-sym lib-opts] {lib-sym (select-keys lib-opts [:git/url :git/tag :git/sha])})}) (def valid-lib-opts "The set of all valid combinations of deps-new template deps opts." (into #{} cat (keys lib-opts->template-deps-fn))) (defn- deps-new-cli-opts->lib-opts "Returns parsed deps-new template deps opts from raw CLI opts." [cli-opts] (-> cli-opts (set/rename-keys {:sha :git/sha}) (select-keys (into #{} cat valid-lib-opts)))) (defn- invalid-lib-opts-error [provided-lib-opts] (ex-info (str "Provided invalid combination of CLI options for deps-new " "template deps.") {:provided-opts (set (keys provided-lib-opts)) :valid-combinations valid-lib-opts})) (defn- find-template-deps-fn "Returns a template-deps-fn given lib-opts parsed from raw CLI opts." [lib-opts] (some (fn [[k v]] (and (contains? (set k) (set (keys lib-opts))) v)) lib-opts->template-deps-fn)) (defn- template-deps "Returns a tools.deps lib map for the given CLI opts." [template cli-opts] (let [lib-opts (deps-new-cli-opts->lib-opts cli-opts) lib-sym (edn/read-string template) template-deps-fn (find-template-deps-fn lib-opts)] (if-not template-deps-fn (throw (invalid-lib-opts-error lib-opts)) (template-deps-fn lib-sym lib-opts)))) (def bb? (System/getProperty "babashka.version")) (def create-opts-deny-list [:deps-file :dry-run :git/sha :git/url :latest-sha :local/root :sha]) (defn- cli-opts->create-opts "Returns options for org.corfield.new/create based on the cli-opts. If no template is provided, the scratch template is filled in. When using the scratch template, we also provide the :scratch create-opt as a default. This changes the default scratch.clj file and namespace to match the :name option instead." [cli-opts] (let [no-template (not (:template cli-opts)) scratch-template (= (str (:template cli-opts)) "scratch")] (merge (when (or no-template scratch-template) {:template "scratch" :scratch (:name cli-opts)}) (apply dissoc cli-opts create-opts-deny-list)))) (defn- deps-new-plan "Returns a plan for calling org.corfield.new/create. :template-deps - These deps will be added with babashka.deps/add-deps before calling the create function. :create-opts - This map contains the options that will be passed to the create function." [cli-opts] (let [create-opts (cli-opts->create-opts cli-opts) tpl-deps (when (and bb? (not (built-in-template? (:template create-opts)))) (template-deps (:template create-opts) cli-opts))] (merge (when tpl-deps {:template-deps tpl-deps}) {:create-opts create-opts}))) (defn- deps-new-create [create-opts] ((req-resolve 'org.corfield.new/create) create-opts)) (defn print-new-help [] (println (str/join "\n\n" (map str/trim [" Usage: neil new [template] [name] [target-dir] [options] Runs the org.corfield.new/create function from deps-new. All of the deps-new options can be provided as CLI options: https://github.com/seancorfield/deps-new/blob/develop/doc/options.md Both built-in and external templates are supported. Built-in templates use unqualified names (e.g. scratch) whereas external templates use fully-qualified names (e.g. io.github.kit/kit-clj)." (str "The provided built-in templates are:\n\n" (str/join "\n" (for [sym (sort (keys (built-in-templates)))] (str " " sym)))) " If an external template is provided, the babashka.deps/add-deps function will be called automatically before running org.corfield.new/create. The deps for the template are inferred automatically from the template name. The following options can be used to control the add-deps behavior: --local/root Override the :deps map to use the provided :local/root path. --git/url Override the :git/url in the :deps map. If no URL is provided, a template name starting with io.github or com.github is expected and the URL will point to GitHub. --git/tag Override the :git/tag in the :deps map. If no SHA or tag is provided, the latest tag from the default branch of :git/url will be used. --sha --git/sha Override the :git/sha in the :deps map. If no SHA or tag is provided, the latest tag from the default branch of :git/url will be used. --latest-sha Override the :git/sha in the :deps map with the latest SHA from the default branch of :git/url. Examples: neil new scratch foo --overwrite neil new io.github.rads/neil-new-test-template foo2 --latest-sha"])))) (defn- deps-new-set-classpath "Sets the java.class.path property. This is required by org.corfield.new/create. In Clojure it's set by default, but in Babashka it must be set explicitly." [] (let [classpath ((req-resolve 'babashka.classpath/get-classpath))] (System/setProperty "java.class.path" classpath))) (defn- deps-new-add-template-deps "Adds template deps at runtime." [template-deps] ((req-resolve 'babashka.deps/add-deps) {:deps template-deps})) (defn run-deps-new "Runs org.corfield.new/create using the provided CLI options. To support the dry run feature, side-effects should be described in the plan provided by deps-new-plan. This function's job is to execute side-effects using the plan to provide repeatability." [{:keys [opts]}] (if (or (:help opts) (not (:name opts))) (print-new-help) (do (require 'org.corfield.new) (let [plan (deps-new-plan opts) {:keys [template-deps create-opts]} plan project-name (symbol (:name create-opts)) dir (or (:target-dir create-opts) (name project-name))] (if (:dry-run opts) (do (prn plan) nil) (do (when template-deps (deps-new-add-template-deps template-deps)) (when bb? (deps-new-set-classpath)) (deps-new-create create-opts) (proj/assoc-project-meta! (assoc opts :dir dir :k :name :v project-name)))))))) (ns babashka.neil.test (:require #?(:bb [babashka.deps :as deps]) [babashka.fs :as fs] [babashka.process :refer [shell]] [clojure.edn :as edn])) (def neil-test-spec {:dirs {:coerce []} :nses {:coerce []} :patterns {:coerce []} :vars {:coerce []} :only {:coerce :symbol} :includes {:coerce []} :excludes {:coerce []}}) (def neil-test-aliases {:dir :dirs :d :dirs :namespace :nses :n :nses :r :patterns :namespace-regex :patterns :v :vars :var :vars :include :includes :i :includes :exclude :includes :e :includes :H :test-help :help :test-help :h :test-help}) (defn clojure [& args] #?(:bb @(deps/clojure (vec args) {:inherit true}) :clj (apply shell {} "bb clojure" args))) (defn normalize-opts [opts] (if-let [only (:only opts)] (if (simple-symbol? only) (assoc opts :nses [only]) (assoc opts :vars [only])) opts)) (defn neil-test [opts] (let [opts (normalize-opts opts)] (if (:test-help opts) (do (clojure "-M:test" "--test-help") (println) (println "Additional options supported by neil:") (println " --only: a symbol denoting a var or namespace")) (let [deps-file (:deps-file opts)] (if (fs/exists? deps-file) (let [deps-edn (edn/read-string (slurp deps-file))] (if (some-> deps-edn :aliases :test) (clojure "-X:test" opts) (println "[neil] First execute: neil add test"))) (println "[neil] Not inside a deps.edn project.")))))) (ns babashka.neil.version (:require [babashka.fs :as fs] [babashka.neil.git :as git] [babashka.neil.meta :as meta] [babashka.neil.project :as proj] [clojure.edn :as edn] [clojure.string :as str])) (defn not-set-version-map? [{:keys [version-not-set]}] version-not-set) (defn raw-string-version-map? [{:keys [raw-string]}] (string? raw-string)) (defn coerce-long [x] (cond-> x (string? x) parse-long)) (defn semver-version-map? [{:keys [major minor patch pre-release build] :as m}] (and (-> major coerce-long nat-int?) (-> minor coerce-long nat-int?) (nat-int? patch) (if (contains? m :pre-release) (string? pre-release) true) (if (contains? m :build) (string? build) true))) (def semver-regex "Source: https://semver.org/" (->> ["^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)" "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" "(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"] (apply str) re-pattern)) (defn parse-semver-string [s] (when s (let [[[_ major minor patch pre-release build]] (re-seq semver-regex s)] (when (and major minor patch) (merge {:major (Integer/parseInt major) :minor (Integer/parseInt minor) :patch (Integer/parseInt patch)} (when pre-release {:pre-release pre-release}) (when build {:build build})))))) (defn str->version-map [s] (cond (#{:version-not-set} s) {:version-not-set true} (string? s) (or (parse-semver-string s) {:raw-string s}) :else (throw (ex-info "Invalid version string" {:version-string s})))) (defn semver-version-map->str [version-map str-opts] (let [{:keys [minor major patch pre-release build]} version-map] (str (when (:prefix str-opts) "v") (str/join "." [major minor patch]) (when pre-release (str "-" pre-release)) (when build (str "+" build))))) (defn version-map->str [version-map & {:as str-opts}] (cond (raw-string-version-map? version-map) (:raw-string version-map) (semver-version-map? version-map) (semver-version-map->str version-map str-opts) (not-set-version-map? version-map) :version-not-set :else (throw (ex-info "Invalid version map" {:version-map version-map})))) (defn git-opts [opts] (when (:deps-file opts) (let [root (str (fs/parent (:deps-file opts)))] (when (seq root) {:dir root})))) (defn assert-no-unstaged-files [opts] (let [unstaged-files (git/unstaged-files (git/status (git-opts opts)))] (when (and (seq unstaged-files) (not (:force opts))) (throw (ex-info "Requires all files to be staged unless --force is provided" {:unstaged-files unstaged-files}))))) (defn assert-at-least-one-staged-file [opts] (let [staged-files (git/staged-files (git/status (git-opts opts)))] (when (and (empty? staged-files) (not (:force opts))) (throw (ex-info "Requires at least one staged file unless --force is provided" {}))))) (defn assert-git-repo [{:keys [deps-file dir]}] (when-not (git/repo? deps-file) (throw (ex-info "Requires git repo" {:deps-file (str deps-file) :dir dir})))) (defn deps-edn->project-version-string [deps-edn] (get-in deps-edn [:aliases :neil :project :version] :version-not-set)) (defn project-version-string? [x] (or (string? x) (#{:version-not-set} x))) (defn assert-valid-project-version-string [project-version-string deps-file] (when-not (project-version-string? project-version-string) (throw (ex-info "Project version must be a string, e.g. \"1.0.0\"" {:deps-file (str deps-file) :project {:version project-version-string}})))) (defn- resolve-deps-file [dir deps-file] (-> (proj/resolve-deps-file dir deps-file) fs/canonicalize str)) (defn run-tag-command [{:keys [dir deps-file] :as opts}] (let [deps-file' (resolve-deps-file dir deps-file) opts' (assoc opts :deps-file deps-file') deps-edn (some-> deps-file slurp edn/read-string) project-version-string (deps-edn->project-version-string deps-edn) _ (assert-valid-project-version-string project-version-string deps-file') project-version-map (str->version-map project-version-string) prefixed-version (version-map->str project-version-map :prefix true)] (assert-git-repo opts') (assert-no-unstaged-files opts') (assert-at-least-one-staged-file opts') (git/commit prefixed-version (git-opts opts')) (git/tag prefixed-version (git-opts opts')) (println prefixed-version))) (defn run-root-command [{:keys [dir deps-file]}] (let [deps-file' (resolve-deps-file dir deps-file) deps-edn (some-> deps-file' slurp edn/read-string) project-version-string (deps-edn->project-version-string deps-edn) _ (assert-valid-project-version-string project-version-string deps-file') project-version-map (str->version-map project-version-string)] (prn {:neil meta/version :project (version-map->str project-version-map :prefix false)}))) (defn git-tag-version-enabled? [opts] (and (git/repo? (:deps-file opts)) (not (false? (:git-tag-version opts))) (not (false? (:tag opts))) (not (:no-git-tag-version opts)) (not (:no-tag opts)))) (defn- set-version! [v {:keys [deps-file] :as _opts}] (proj/assoc-project-meta! {:deps-file deps-file :k :version :v v})) (defn git-clean-working-directory? [git-status-result] (some->> (:statuses git-status-result) (remove #(re-seq #"^\?" (:status %))) empty?)) (defn assert-clean-working-directory [opts] (when (and (not (git-clean-working-directory? (git/status (git-opts opts)))) (not (:force opts))) (throw (ex-info "Requires clean working directory unless --force is provided" (git-opts opts))))) (defn run-set-command [{:keys [version dir deps-file] :as opts}] (let [deps-file' (resolve-deps-file dir deps-file) opts' (assoc opts :deps-file deps-file') git-tag-version-enabled (git-tag-version-enabled? opts')] (when git-tag-version-enabled (assert-clean-working-directory opts')) (proj/ensure-neil-project opts') (let [next-version-map (str->version-map version) next-version-string (version-map->str next-version-map :prefix false) next-prefixed-version (version-map->str next-version-map :prefix true)] (set-version! next-version-string opts') (when git-tag-version-enabled (git/add ["deps.edn"] (git-opts opts')) (git/commit next-prefixed-version (git-opts opts')) (git/tag next-prefixed-version (git-opts opts'))) (println next-prefixed-version)))) (defn- assert-semver-version [version-map] (when-not (semver-version-map? version-map) (let [provided-version (version-map->str version-map :prefix false)] (throw (ex-info "Only SemVer-style version strings can be bumped" {:provided-version provided-version}))))) (defn- initial-version-map [semver-key override] (assoc {:major 0 :minor 0 :patch 0} semver-key (or override 1))) (defn- bump-version [version-map semver-key override] (if (not-set-version-map? version-map) (initial-version-map semver-key override) (do (assert-semver-version version-map) (let [next-version (or override (inc (get version-map semver-key))) version-map' (assoc version-map semver-key next-version) {:keys [major minor patch]} version-map'] (case semver-key :major {:major major :minor 0 :patch 0} :minor {:major major :minor minor :patch 0} :patch {:major major :minor minor :patch patch}))))) (defn run-bump-command [command {:keys [dir deps-file] :as opts}] (let [deps-file' (resolve-deps-file dir deps-file) deps-edn (some-> deps-file' slurp edn/read-string) project-version-string (deps-edn->project-version-string deps-edn) _ (assert-valid-project-version-string project-version-string deps-file') project-version-map (str->version-map project-version-string) override (:version opts) bumped-version-map (bump-version project-version-map command override) bumped-version-str (version-map->str bumped-version-map :prefix false)] (run-set-command (assoc opts :version bumped-version-str)))) (defn print-help [] (println (str/trim " Usage: neil version neil version set [version] neil version [major|minor|patch] [version] neil version tag Commands for managing the :version key in the deps.edn project config. By default, these commands also create Git commits and tags in the current Git repo to help with publishing new versions. The project version is located in the deps.edn file at the current directory at the path [:aliases :neil :project :version]. Any string is a valid version, but only SemVer-style versions can be bumped using the [major|minor|patch] subcommands. ## neil version Print the current neil and project versions. Examples: neil version ## neil version set [version] Set the :version key in the project config to the provided version. - Creates a Git commit and tag (this can be bypassed with --no-tag) - Requires the working directory to be clean (this can be bypassed with --force) Examples: neil version set 0.1.0 neil version set 9.4.146.24-node.21 --force neil version set 1.1.1q+quic --no-tag ## neil version [major|minor|patch] [version] Bump the :version key in the project config. If no specific version is provided, the selected version will be incremented by one. - Creates a Git commit and tag (this can be bypassed with --no-tag) - Requires the Git working directory to be clean (this can be bypassed with --force) Examples: neil version patch neil version major 3 --force neil version minor --no-tag ## neil version tag Create a Git commit and tag for the current version. - Requires that the current directory is a Git repo - Requires that there are no unstaged files (this can be bypassed with --force) - Requires that at least one file is staged (this can be bypassed with --force) Examples: neil version tag neil version tag --force"))) (defn print-version [] (println "neil" meta/version)) (defn neil-version ([parse-args] (neil-version :root parse-args)) ([command {:keys [opts] :as _parse-args}] (if (:help opts) (print-help) (case command :root (run-root-command opts) :tag (run-tag-command opts) :set (run-set-command opts) (:major :minor :patch) (run-bump-command command opts))))) (def version-spec {:no-tag {:coerce :boolean} :no-git-tag-version {:coerce :boolean} :tag {:coerce :boolean} :git-tag-version {:coerce :boolean}}) (ns babashka.neil {:no-doc true} (:require [babashka.cli :as cli] [babashka.fs :as fs] [babashka.neil.curl :refer [curl-get-json url-encode]] [babashka.neil.git :as git] [babashka.neil.new :as new] [babashka.neil.project :as proj] [babashka.neil.utils :refer [req-resolve]] [babashka.neil.rewrite :as rw] [babashka.neil.test :as neil-test] [babashka.neil.version :as neil-version] [borkdude.rewrite-edn :as r] [clojure.edn :as edn] [clojure.string :as str] [clojure.set :as set])) (def spec {:lib {:desc "Fully qualified library name."} :version {:desc "Optional. When not provided, picks newest version from Clojars or Maven Central." :coerce :string} :sha {:desc "When provided, assumes lib refers to Github repo." :coerce :string} :latest-sha {:coerce :boolean :desc "When provided, assumes lib refers to Github repo and then picks latest SHA from it."} :tag {:desc "When provided, assumes lib refers to Github repo." :coerce :string} :latest-tag {:coerce :boolean :desc "When provided, assumes lib refers to Github repo and then picks latest tag from it."} :deps/root {:desc "Sets deps/root to give value."} :as {:desc "Use as dependency name in deps.edn" :coerce :symbol} :alias {:ref "" :desc "Add to alias ." :coerce :keyword} :deps-file {:ref "" :desc "Add to instead of deps.edn." :coerce :string :default "deps.edn"} :limit {:coerce :long} :dry-run {:coerce :boolean :desc "dep upgrade only. Prevents updates to deps.edn."} :no-aliases {:coerce :boolean :desc "Prevents updates to alias :extra-deps when upgrading."}}) (def windows? (fs/windows?)) (def bb? (System/getProperty "babashka.version")) (defn- get-clojars-artifact [qlib] (curl-get-json (format "https://clojars.org/api/artifacts/%s" qlib))) (defn stable-version? [version-str] (let [vparse (req-resolve 'version-clj.core/parse)] ; We lazy-load version-clj to improve average script startup time (empty? (set/intersection (:qualifiers (vparse version-str)) #{"rc" "alpha" "beta" "snapshot" "milestone"})))) (defn first-stable-version [versions] (->> versions (filter stable-version?) first)) (defn clojars-versions [qlib {:keys [limit] :or {limit 10}}] (let [body (get-clojars-artifact qlib)] (->> body :recent_versions (map :version) (take limit)))) (defn latest-stable-clojars-version [qlib] (first-stable-version (clojars-versions qlib {:limit 100}))) (defn latest-clojars-version [qlib] (first (clojars-versions qlib {:limit 100}))) (defn- search-mvn [qlib limit] (:response (curl-get-json (format "https://search.maven.org/solrsearch/select?q=g:%s+AND+a:%s&rows=%s&core=gav&wt=json" (namespace qlib) (name qlib) (str limit))))) (defn mvn-versions [qlib {:keys [limit] :or {limit 10}}] (let [payload (search-mvn qlib limit)] (->> payload :docs (map :v)))) (defn latest-stable-mvn-version [qlib] (first-stable-version (mvn-versions qlib {:limit 100}))) (defn latest-mvn-version [qlib] (first (mvn-versions qlib {:limit 100}))) (def deps-template (str/triml " {:deps {} :aliases {}} ")) (def bb-template (str/triml " {:deps {} :tasks { }} ")) (defn ensure-deps-file [opts] (let [target (:deps-file opts)] (when-not (fs/exists? target) (spit target (if (= "bb.edn" target) bb-template deps-template))))) (defn edn-string [opts] (slurp (:deps-file opts))) (defn edn-nodes [edn-string] (r/parse-string edn-string)) (def cognitect-test-runner-alias " {:extra-paths [\"test\"] :extra-deps {io.github.cognitect-labs/test-runner {:git/tag \"v0.5.0\" :git/sha \"b3fd0d2\"}} :main-opts [\"-m\" \"cognitect.test-runner\"] :exec-fn cognitect.test-runner.api/test}") (defn add-alias [opts alias-kw alias-contents] (ensure-deps-file opts) (let [edn-string (edn-string opts) edn-nodes (edn-nodes edn-string) edn (edn/read-string edn-string) alias (or (:alias opts) alias-kw) existing-aliases (get-in edn [:aliases]) alias-node (r/parse-string (str (when (seq existing-aliases) "\n ") alias " ;; added by neil"))] (if-not (get existing-aliases alias) (let [s (-> (if-not (seq existing-aliases) ; If there are no existing aliases, we assoc an empty map ; before updating to prevent borkdude.rewrite-edn/update ; from removing the newline preceding the :aliases key. (r/assoc edn-nodes :aliases {}) edn-nodes) (r/update :aliases (fn [aliases] (let [s (rw/indent alias-contents 1) alias-nodes (r/parse-string s) aliases' (r/assoc aliases alias-node alias-nodes)] (if-not (seq existing-aliases) ; If there are no existing aliases, add an ; explicit newline after the :aliases key. (r/parse-string (str "\n" (rw/indent (str aliases') 1))) aliases')))) str) s (rw/clean-trailing-whitespace s) s (str s "\n")] (spit (:deps-file opts) s)) (do (println (format "[neil] Project deps.edn already contains alias %s" (str alias "."))) ::update)))) (declare print-help) (defn add-cognitect-test-runner [{:keys [opts] :as cmd}] (if (:help opts) (print-help cmd) (do (add-alias opts :test cognitect-test-runner-alias) (when-let [pn (proj/project-name opts)] (let [test-ns (symbol (str (str/replace pn "/" ".") "-test")) test-path (-> (str test-ns) (str/replace "-" "_") (str/replace "." fs/file-separator) (str ".clj")) test-path (fs/file "test" test-path)] (when (or (not (fs/exists? "test")) (zero? (count (fs/list-dir "test")))) (fs/create-dirs (fs/parent test-path)) (spit test-path (format "(ns %s (:require [clojure.test :as t :refer [deftest is testing]])) (deftest %s-test (testing \"TODO: fix\" (is (= :foo :bar)))) " test-ns (name pn))))))))) (defn kaocha-alias [] (format " {:extra-deps {lambdaisland/kaocha {:mvn/version \"%s\"}} :main-opts [\"-m\" \"kaocha.runner\"]}" (latest-stable-clojars-version 'lambdaisland/kaocha))) (defn add-kaocha [{:keys [opts] :as cmd}] (if (:help opts) (print-help cmd) (do (add-alias opts :kaocha (kaocha-alias)) (println (str/trim " If you wish to create a `bin/kaocha` file, copy and run the following: mkdir -p bin && \\ echo '#!/usr/bin/env bash clojure -M:kaocha \"$@\"' > bin/kaocha && \\ chmod +x bin/kaocha "))))) (defn nrepl-alias [] (format " {:extra-deps {nrepl/nrepl {:mvn/version \"%s\"}} :main-opts [\"-m\" \"nrepl.cmdline\" \"--interactive\" \"--color\"]}" (latest-stable-clojars-version 'nrepl/nrepl))) (defn add-nrepl [{:keys [opts] :as cmd}] (if (:help opts) (print-help cmd) (add-alias opts :nrepl (nrepl-alias)))) (defn build-alias [_opts] (let [latest-tag (git/latest-github-tag 'clojure/tools.build) tag (:name latest-tag) sha (-> latest-tag :commit :sha (subs 0 7)) slipset-version (latest-stable-clojars-version 'slipset/deps-deploy) s (format " {:deps {io.github.clojure/tools.build {:git/tag \"%s\" :git/sha \"%s\"} slipset/deps-deploy {:mvn/version \"%s\"}} :ns-default build}" tag sha slipset-version)] {:s s :tag tag :sha sha})) (defn build-file [_opts] (let [base "(ns build (:require [clojure.tools.build.api :as b] [clojure.edn :as edn])) (def project (-> (edn/read-string (slurp \"deps.edn\")) :aliases :neil :project)) (def lib (or (:name project) 'my/lib1)) ;; use neil project set version 1.2.0 to update the version in deps.edn (def version (or (:version project) \"1.2.0\")) (def class-dir \"target/classes\") (def basis (b/create-basis {:project \"deps.edn\"})) (def uber-file (format \"target/%s-%s-standalone.jar\" (name lib) version)) (def jar-file (format \"target/%s-%s.jar\" (name lib) version)) (defn clean [_] (b/delete {:path \"target\"})) (defn jar [_] (b/write-pom {:class-dir class-dir :lib lib :version version :basis basis :src-dirs [\"src\"]}) (b/copy-dir {:src-dirs [\"src\" \"resources\"] :target-dir class-dir}) (b/jar {:class-dir class-dir :jar-file jar-file})) (defn install [_] (jar {}) (b/install {:basis basis :lib lib :version version :jar-file jar-file :class-dir class-dir})) (defn uber [_] (clean nil) (b/copy-dir {:src-dirs [\"src\" \"resources\"] :target-dir class-dir}) (b/compile-clj {:basis basis :src-dirs [\"src\"] :class-dir class-dir}) (b/uber {:class-dir class-dir :uber-file uber-file :basis basis})) (defn deploy [opts] (jar opts) ((requiring-resolve 'deps-deploy.deps-deploy/deploy) (merge {:installer :remote :artifact jar-file :pom-file (b/pom-path {:lib lib :class-dir class-dir})} opts)) opts) "] base)) (defn add-build [{:keys [opts] :as cmd}] (if (:help opts) (print-help cmd) (do (if-not (fs/exists? "build.clj") (spit "build.clj" (build-file opts)) (println "[neil] Project build.clj already exists.")) (ensure-deps-file opts) (let [ba (build-alias opts)] (when (= ::update (add-alias opts :build (:s ba))) (println "[neil] Updating tools build to newest git tag + sha.") (let [edn-string (edn-string opts) edn (edn/read-string edn-string) build-alias (get-in edn [:aliases :build :deps 'io.github.clojure/tools.build]) [tag-key sha-key] (cond (and (:tag build-alias) (:sha build-alias)) [:tag :sha] (and (:git/tag build-alias) (:git/sha build-alias)) [:git/tag :git/sha])] (when (and tag-key sha-key) (let [nodes (edn-nodes edn-string) nodes (r/assoc-in nodes [:aliases :build :deps 'io.github.clojure/tools.build tag-key] (:tag ba)) nodes (r/assoc-in nodes [:aliases :build :deps 'io.github.clojure/tools.build sha-key] (:sha ba)) s (str (str/trim (str nodes)) "\n")] (spit (:deps-file opts) s))))))))) (defn print-dep-add-help [] (println "Usage: neil add dep [lib] [options]") (println "Options:") (println (cli/format-opts {:spec spec :order [:lib :version :sha :latest-sha :tag :latest-tag :deps/root :as :alias :deps-file]}))) (defn log [& xs] (binding [*out* *err*] (apply prn xs))) (defn dep-add [{:keys [opts]}] (if (or (:help opts) (:h opts) (not (:lib opts))) (print-dep-add-help) (do (ensure-deps-file opts) (let [edn-string (edn-string opts) edn-nodes (edn-nodes edn-string) lib (:lib opts) lib (symbol lib) lib (symbol (or (namespace lib) (name lib)) (name lib)) alias (:alias opts) explicit-git-sha? (or (:sha opts) (:latest-sha opts)) explicit-git-tag? (or (:tag opts) (:latest-tag opts)) [version coord-type?] (cond explicit-git-tag? [(or (and (:tag opts) (git/find-github-tag lib (:tag opts))) (git/latest-github-tag lib)) :git/tag] explicit-git-sha? [(or (:sha opts) (git/latest-github-sha lib)) :git/sha] :else (or (when-let [v (:version opts)] [v :mvn]) (when-let [v (latest-stable-clojars-version lib)] [v :mvn]) (when-let [v (latest-stable-mvn-version lib)] [v :mvn]) (when-let [v (git/latest-github-sha lib)] [v :git/sha]) (when-let [v (latest-clojars-version lib)] [v :mvn]) (when-let [v (latest-mvn-version lib)] [v :mvn]))) _ (when-not version (throw (ex-info (str "Couldn't find version for lib: " lib) {:babashka/exit 1}))) missing? (nil? version) mvn? (= :mvn coord-type?) git-sha? (= :git/sha coord-type?) git-tag? (= :git/tag coord-type?) git-url (when (or git-sha? git-tag?) (or (:git/url opts) (str "https://github.com/" (git/clean-github-lib lib)))) as (or (:as opts) lib) existing-aliases (-> edn-string edn/read-string :aliases) path (if alias [:aliases alias (if (get-in existing-aliases [alias :deps]) :deps :extra-deps) as] [:deps as]) nl-path (if (and alias (not (contains? existing-aliases alias))) [:aliases alias] path) edn-nodes (if (r/get-in edn-nodes nl-path) ;; if this dep already exists, don't touch it. ;; We risk loosing :exclusions and other properties. edn-nodes ;; otherwise, force newlines! ;; force newline in ;; ;; [:deps as] if no alias ;; [:aliases alias] if alias DNE ;; [:aliases alias :deps as] if :deps present ;; [:aliases alias :extra-deps as] if alias exists (-> edn-nodes (r/assoc-in nl-path nil) str r/parse-string)) nodes (cond missing? edn-nodes mvn? (r/assoc-in edn-nodes (conj path :mvn/version) version) git-sha? ;; multiple steps to force newlines (-> edn-nodes (r/assoc-in (conj path :git/url) git-url) str r/parse-string (r/assoc-in (conj path :git/sha) version) (r/update-in path r/dissoc :sha)) git-tag? ;; multiple steps to force newlines (-> edn-nodes (r/assoc-in (conj path :git/url) git-url) str r/parse-string (r/assoc-in (conj path :git/tag) (-> version :name)) str r/parse-string (r/assoc-in (conj path :git/sha) (some-> version :commit :sha (subs 0 7))))) nodes (if-let [root (and (or git-sha? git-tag?) (:deps/root opts))] (-> nodes (r/assoc-in (conj path :deps/root) root)) nodes) s (str (str/trim (str nodes)) "\n")] (when-not missing? (spit (:deps-file opts) s)))))) (defn dep-versions [{:keys [opts]}] (when (or (:help opts) (:h opts)) (println (str/trim " Usage: neil dep versions LIB List available versions of a Clojure dependency. Only supports Clojars. $ neil dep versions http-kit/http-kit :lib http-kit/http-kit :version 2.7.0-alpha1 :lib http-kit/http-kit :version 2.7.0-SNAPSHOT :lib http-kit/http-kit :version 2.6.0 :lib http-kit/http-kit :version 2.6.0-RC1 :lib http-kit/http-kit :version 2.6.0-alpha1 :lib http-kit/http-kit :version 2.5.3 :lib http-kit/http-kit :version 2.5.3-SNAPSHOT :lib http-kit/http-kit :version 2.5.2 :lib http-kit/http-kit :version 2.5.1 :lib http-kit/http-kit :version 2.5.0")) (System/exit 0)) (let [lib (:lib opts) lib (symbol lib) versions (or (seq (clojars-versions lib opts)) (seq (mvn-versions lib opts)))] (if-not versions (binding [*out* *err*] (println "Unable to find" lib "on Clojars or Maven.") (System/exit 1)) (doseq [v versions] (println :lib lib :version v))))) (defn print-dep-search-help [] (println (str/trim " Usage: neil dep search [lib] Search Clojars for a string in any attribute of an artifact: $ neil dep search \"babashka.nrepl\" :lib babashka/babashka.nrepl :version 0.0.6 You can also search for the fully ns-qualified library: $ neil dep search \"babashka/babashka.nrepl\" :lib babashka/babashka.nrepl :version 0.0.6 To search for all artifacts in a group: $ neil dep search \"group-id:babashka\" :lib babashka/babashka :version 1.1.173 :lib babashka/fs :version 0.2.15 ... A search string can also be matched in a library's description: $ neil dep search \"test framework\" will return libraries with 'test framework' in their description. See http://github.com/clojars/clojars-web/wiki/Search-Query-Syntax for details on the search syntax."))) (defn dep-search-maven [search-term] (let [url (format "https://search.maven.org/solrsearch/select?q=%s&rows=20&wt=json" (url-encode search-term)) keys-m {:g :group_name :a :jar_name :timestamp :created :latestVersion :version} add-desc (fn [{:keys [group_name jar_name] :as m}] ;; Maven doesn't provide package description through its API, ;; but that doesn't mean we should just leave it blank (assoc m :description (format "%s/%s on Maven" group_name jar_name))) res (->> url curl-get-json :response :docs (map #(some-> % (clojure.set/rename-keys keys-m) (select-keys (vals keys-m)) add-desc)))] (if (empty? res) (binding [*out* *err*] (println "Unable to find" search-term "on Maven.")) res))) (defn dep-search-clojars [search-term] (let [url (format "https://clojars.org/search?format=json&q=%s" (url-encode search-term)) {search-results :results results-count :count} (curl-get-json url)] (if (zero? results-count) (binding [*out* *err*] (println "Unable to find" search-term "on Clojars.")) search-results))) (defn dep-search [{:keys [opts]}] (let [{:keys [search-term]} opts] (if (or (:help opts) (not (string? search-term)) (str/blank? search-term)) (print-dep-search-help) (let [search-results (->> [dep-search-maven dep-search-clojars] (map #(% search-term)) (apply concat))] (when (empty? search-results) (System/exit 1)) (doseq [search-result search-results] (prn :lib (symbol (:group_name search-result) (:jar_name search-result)) :version (:version search-result) :description (:description search-result))))))) (defn git-url->lib [git-url] (when git-url (when (str/starts-with? git-url "https://github.com/") (-> (str/replace git-url "https://github.com/" "") symbol)))) (defn dep->upgrade "Given a lib (hiccup/hiccup) and a version ({:mvn/version \"1.0.4\"}), return an upgrade ({:mvn/version \"1.0.5\"}), otherwise return nil. Supports different kinds of coordinate formats. (dep->latest-stable {:lib 'hiccup/hiccup :current {:mvn/version \"1.0.4\"}}) ;; => {:mvn/version \"1.0.5\"} (dep->upgrade {:lib 'clj-kondo/clj-kondo :current {:git/sha \"247e538\"}}) ;; => {:git/sha \"...\"} " [{:keys [lib current unstable]}] ;; for now, just upgrade to stable versions (let [current (set/rename-keys current {:sha :git/sha :tag :git/tag})] (cond ;; if there's a git tag, find the latest git tag. (:git/tag current) (let [lib (or (git-url->lib (:git/url current)) lib)] (when-let [tag (git/latest-github-tag lib)] (when (not= tag (:git/tag current)) {:git/tag (:name tag) :git/sha (-> tag :commit :sha (subs 0 7))}))) ;; if there's a git sha, find the latest git sha. (:git/sha current) (let [lib (or (git-url->lib (:git/url current)) lib)] (when-let [sha (git/latest-github-sha lib)] (when (not= sha (:git/sha current)) {:git/sha sha}))) ;; when :unstable is set, find the lastest version whatsoever unstable (when-let [version (or (first (clojars-versions lib {:limit 100})) (first (mvn-versions lib {:limit 100})))] (let [v-older? (req-resolve 'version-clj.core/older?)] (when (v-older? (:mvn/version current) version) {:mvn/version version}))) ;; if `current` is a stable maven/clojars version, find the latest stable ;; maven clojars dep (and (:mvn/version current) (stable-version? (:mvn/version current))) (when-let [version (or (first (filter stable-version? (clojars-versions lib {:limit 100}))) (first (filter stable-version? (mvn-versions lib {:limit 100}))))] ;; only upgrade to a newer version than the current. Useful when ;; developing a new version locally. (let [v-older? (req-resolve 'version-clj.core/older?)] (when (v-older? (:mvn/version current) version) {:mvn/version version}))) ;; if `current` is an unstable maven/clojars version, preferrably upgrade ;; to a more recent stable version. Otherwise, look for a more recent ;; unstable version. ;; ;; Useful when depending on an unstable version under active development. (and (:mvn/version current) (not (stable-version? (:mvn/version current)))) (let [clojars-candidates (clojars-versions lib {:limit 100}) maven-candidates (mvn-versions lib {:limit 100}) v-older? (req-resolve 'version-clj.core/older?)] (or ;; prefer a more recent stable (when-let [candidate (or (first (filter stable-version? clojars-candidates)) (first (filter stable-version? maven-candidates)))] (when (v-older? (:mvn/version current) candidate) {:mvn/version candidate})) ;; otherwise, provide a more recent unstable (when-let [candidate (or (first clojars-candidates) (first maven-candidates))] (when (v-older? (:mvn/version current) candidate) candidate)) ;; otherwise, do nothing (fall through to nil) ))))) (defn opts->specified-deps "Returns all :deps and :alias :extra-deps for the deps.edn indicated by `opts`." [opts] (let [lib (some-> opts :lib symbol) alias (some-> opts :alias) no-aliases? (:no-aliases opts) {:keys [deps aliases]} (-> (edn-string opts) edn/read-string) current-deps (->> deps (map (fn [[lib current]] (cond-> {:lib lib :current current} (:unstable opts) (assoc :unstable true))))) alias-deps (if no-aliases? [] (->> aliases (mapcat (fn [[alias def]] (->> (:extra-deps def) (map (fn [[lib current]] {:alias alias :lib lib :current current})))))))] (->> (concat current-deps alias-deps) (filter (fn [dep] (if alias (= alias (:alias dep)) true))) (filter (fn [dep] (if lib (= lib (:lib dep)) true)))))) (defn do-dep-upgrade "Updates the deps version in deps.edn for a single lib, as described in `dep-upgrade`, which is a map with `:lib`, `:current`, `:latest` (the current and latest dep coords), and optionally `:alias`. When `:current` and `:latest` do not match, `:latest` is written to deps.edn via `dep-add`. Supports `:dry-run` in the passed `opts` to instead just print the update." [opts {:keys [lib current latest alias] :as _dep-upgrade}] (let [{:keys [git/tag git/sha mvn/version]} latest log-args (concat (when alias [:alias alias]) [:lib lib] [:current-version ((some-fn :git/tag :git/sha :mvn/version) current)] (when tag [:tag tag]) (when (and (not tag) sha) [:sha sha]) (when version [:version version])) log-action (fn [action] (apply println :action (str "\"" action "\"") log-args))] (when (or (and tag (not= (:git/tag current) tag)) (and sha (not= (:git/sha current) sha)) (and version (not= (:mvn/version current) version))) (if (:dry-run opts) (log-action "upgrading (dry run)") (do (log-action "upgrading") (dep-add {:opts (cond-> opts lib (assoc :lib lib) alias (assoc :alias alias) version (assoc :version version) tag (assoc :tag tag) (and (not tag) sha) (assoc :sha sha))})))))) (defn dep-upgrade [{:keys [opts]}] (when (or (:h opts) (:help opts)) (println "Usage: neil dep upgrade [lib] [options]") (println "") (println " neil dep upgrade [options] Upgrade all libraries") (println " neil dep upgrade LIB [options] Upgrade a single library") (println " neil dep upgrade --lib LIB [options] Upgrade a single library") (println "") (println "Options:") (println "") (println (cli/format-opts {:spec spec :order [:lib :dry-run :alias :no-aliases]})) (println "") (println (str/trim " Examples: neil dep upgrade ; upgrade all deps. neil dep upgrade --dry-run ; print deps that would be upgraded. neil dep upgrade --alias lint ; update only deps for the `lint` alias. neil dep upgrade :lib clj-kondo/clj-kondo ; update a single dep. ")) (System/exit 0)) (let [lib (some-> opts :lib symbol) alias (some-> opts :alias) deps-to-check (opts->specified-deps opts) upgrades (->> deps-to-check (pmap (fn [dep] (merge dep {:latest (dep->upgrade dep)}))) ;; keep if :latest version was found (filter (fn [dep] (some? (:latest dep)))))] (when lib ;; logging and early-exit when :lib is specified (cond (not (seq deps-to-check)) (binding [*out* *err*] (if alias (println "Local dependency not found:" lib "for alias:" alias) (println "Local dependency not found:" lib)) (println "Use `neil dep add` to add dependencies.") (System/exit 1)) (not (seq upgrades)) (binding [*out* *err*] ;; note this could also mean we've hit github's rate limit (println "No remote version found for" lib) (System/exit 1)))) (doseq [dep-upgrade upgrades] (do-dep-upgrade opts dep-upgrade)))) (defn print-help [_] (println (str/trim " Usage: neil Most subcommands support the options: --alias Override alias name. --deps-file Override deps.edn file name. Subcommands: add dep Alias for `neil dep add`. test adds cognitect test runner to :test alias. build adds tools.build build.clj file and :build alias. kaocha adds kaocha test runner to :kaocha alias. nrepl adds nrepl server to :nrepl alias. dep add: Adds --lib, a fully qualified symbol, to deps.edn :deps. Run `neil dep add --help` to see all options. search: Search Clojars for a string in any attribute of an artifact Run `neil dep search --help` to see all options. upgrade: Upgrade libs in the deps.edn file. Run `neil dep upgrade --help` to see all options. versions: List available versions of a library (Clojars libraries only) Run `neil dep versions -h` to see all options. update: Alias for `upgrade`. license list Lists commonly-used licenses available to be added to project. Takes an optional search string to filter results. search Alias for `list` add Writes license text to a file Options: --license The key of the license to use (e.g. epl-1.0, mit, unlicense). --license option name may be elided when license key is provided as first argument. --file The file to write. Defaults to 'LICENSE'. new Create a project using deps-new Run `neil new --help` to see all options. version Commands for managing the :version key in the deps.edn project config. Run `neil version --help` to see all options. test Run tests. Assumes `neil add test`. Run `neil test --help` to see all options. "))) ;; licenses (def licenses-api-url "https://api.github.com/licenses") (defn license-search [{:keys [opts]}] (let [search-term (:search-term opts) license-vec (->> (str licenses-api-url "?per_page=50") curl-get-json (map #(select-keys % [:key :name]))) search-results (if search-term (filter #(str/includes? (str/lower-case (:name %)) (str/lower-case search-term)) license-vec) license-vec)] (if (empty? search-results) (binding [*out* *err*] (println "No licenses found") (System/exit 1)) (doseq [result search-results] (println :license (:key result) :name (pr-str (:name result))))))) (defn license-to-file [{:keys [opts]}] (let [license-key (:license opts) output-file (or (:file opts) "LICENSE") {:keys [message name body]} (some->> license-key url-encode (str licenses-api-url "/") curl-get-json)] (cond (not license-key) (throw (ex-info "No license key provided." {})) (= "Not Found" message) (throw (ex-info (format "License '%s' not found." license-key) {:license license-key})) (not body) (throw (ex-info (format "License '%s' has no body text." (or name license-key)) {:license license-key})) :else (spit output-file body)))) (defn add-license [opts] (try (license-to-file opts) (catch Exception e (binding [*out* *err*] (println (ex-message e)) (System/exit 1))))) (defn neil-test [{:keys [opts]}] (neil-test/neil-test opts)) (defn -main [& _args] (cli/dispatch [{:cmds ["add" "dep"] :fn dep-add :args->opts [:lib]} {:cmds ["add" "test"] :fn add-cognitect-test-runner} {:cmds ["add" "build"] :fn add-build} {:cmds ["add" "kaocha"] :fn add-kaocha} {:cmds ["add" "nrepl"] :fn add-nrepl} {:cmds ["dep" "versions"] :fn dep-versions :args->opts [:lib]} {:cmds ["dep" "add"] :fn dep-add :args->opts [:lib]} {:cmds ["dep" "search"] :fn dep-search :args->opts [:search-term]} {:cmds ["dep" "upgrade"] :fn dep-upgrade} {:cmds ["dep" "update"] :fn dep-upgrade} ;; supported as an alias {:cmds ["license" "list"] :fn license-search :args->opts [:search-term]} {:cmds ["license" "search"] :fn license-search :args->opts [:search-term]} {:cmds ["license" "add"] :fn add-license :args->opts [:license]} {:cmds ["new"] :fn new/run-deps-new :args->opts [:template :name :target-dir] :spec {:name {:coerce proj/coerce-project-name}}} {:cmds ["version" "tag"] :fn (partial neil-version/neil-version :tag) :aliases {:h :help} :spec neil-version/version-spec} {:cmds ["version" "set"] :fn (partial neil-version/neil-version :set) :args->opts [:version] :spec neil-version/version-spec :aliases {:h :help}} {:cmds ["version" "major"] :fn (partial neil-version/neil-version :major) :args->opts [:version] :spec neil-version/version-spec :aliases {:h :help}} {:cmds ["version" "minor"] :fn (partial neil-version/neil-version :minor) :args->opts [:version] :spec neil-version/version-spec :aliases {:h :help}} {:cmds ["version" "patch"] :fn (partial neil-version/neil-version :patch) :spec neil-version/version-spec :args->opts [:version] :aliases {:h :help}} {:cmds ["version"] :fn neil-version/neil-version :aliases {:h :help} :spec neil-version/version-spec} {:cmds ["help"] :fn print-help} {:cmds ["test"] :fn neil-test :spec neil-test/neil-test-spec :alias neil-test/neil-test-aliases} {:cmds [] :spec {:version {:coerce :boolean}} :fn (fn [{:keys [opts] :as m}] (if (:version opts) (neil-version/print-version) (print-help m)))}] *command-line-args* {:spec spec :exec-args {:deps-file "deps.edn"}}) nil) (when (= *file* (System/getProperty "babashka.file")) (-main))